Requête GET modifiant l'état déclenchée par une balise img ou un lien, exploitant les endpoints qui acceptent incorrectement GET pour les mutations.
TL;DR
<img>, <a href>, window.location, meta refresh — aucun clic requis pour les balises <img>window.location et <a> réussissentLe CSRF basé sur GET classique (CWE-352) exploite les endpoints web qui effectuent des opérations modifiant l'état en réponse à des requêtes GET. RFC 9110 §9.2 définit GET comme une méthode « sûre » qui NE DOIT PAS modifier l'état du serveur. Quand les développeurs implémentent des fonctionnalités destructives ou modifiant l'état sur des endpoints GET — panneaux admin PHP hérités, boutons d'action rapide, liens de déconnexion — ils exposent chaque visiteur authentifié à une attaque en un clic via n'importe quelle page qu'il visite.
L'attaque ne nécessite aucune interaction utilisateur au-delà de la visite de la page de l'attaquant. Une balise <img> avec un src pointant vers l'endpoint cible fait émettre par le navigateur une requête GET authentifiée immédiatement au chargement de la page. La victime ne voit rien de visuel — une image d'un pixel zéro est invisible. Cela fait du CSRF basé sur GET la variante CSRF la plus simple et la plus fiable : aucun JavaScript requis, aucune soumission de formulaire nécessaire, et aucune protection SameSite=Lax pour les attaques de navigation GET de premier niveau.
La distinction moderne est importante : <img> déclenche un GET de sous-ressource (bloqué par SameSite=Lax), tandis que <a href> ou window.location déclenche un GET de navigation de premier niveau (les cookies Lax SONT envoyés). Les deux vecteurs fonctionnent contre différentes configurations de cookies. Contre SameSite=None ou les cookies sans attribut, les deux fonctionnent. Contre SameSite=Lax, seules les variantes de navigation réussissent.
La chaîne d'attaque exploite l'envoi automatique de cookies par le navigateur sur toute requête sortante vers un domaine pour lequel la victime détient une session valide.
Étapes de l'attaque :
target.com (cookie de session présent dans le navigateur).evil.com — via phishing, publicité malveillante ou page compromise.<img>, une redirection de script ou un lien cliquable ciblant un endpoint GET modifiant l'état.Payloads d'attaque — trois vecteurs de livraison :
<!-- Vecteur 1 : Balise image — zéro clic, se déclenche au chargement de la page -->
<!-- Fonctionne contre SameSite=None/Absent ; BLOQUÉ par SameSite=Lax (sous-ressource) -->
<img src="https://banque.com/virement?vers=attaquant&montant=5000" width="1" height="1">
<!-- Vecteur 2 : Redirection de navigation de premier niveau — contourne SameSite=Lax -->
<!-- Les cookies Lax SONT envoyés lors des navigations GET de premier niveau -->
<script>window.location = 'https://banque.com/virement?vers=attaquant&montant=5000';</script>
<!-- Vecteur 3 : Lien cliquable — ingénierie sociale, contourne SameSite=Lax -->
<a href="https://target.com/admin/users/42/delete">Cliquez ici pour réclamer votre prix</a>La distinction critique SameSite=Lax :
Le navigateur envoie le cookie SameSite=Lax sur : navigation GET de premier niveau (window.location, <a href>, barre d'adresse)
Le navigateur BLOQUE le cookie SameSite=Lax sur : <img src>, <script src>, <iframe src>, fetch(), XMLHttpRequestLes attaquants ciblant les applications protégées par Lax utilisent window.location ou des chaînes de redirection plutôt que des balises <img>. La différence pratique est que le vecteur de navigation nécessite un déclencheur de visite de page (facilement réalisé avec une redirection automatique) plutôt que le chargement de page.
| Variante | Technique | SameSite=Lax bloqué ? | Impact |
|---|---|---|---|
Balise image <img src> | Zéro clic, se déclenche au chargement de page | Oui (sous-ressource) | Action de compte sur SameSite=None |
Redirection script window.location | Zéro clic via JS au chargement de page | Non (nav de premier niveau) | Action de compte malgré Lax |
Lien ancre <a href> | Ingénierie sociale un clic | Non (nav de premier niveau) | Action de compte malgré Lax |
Meta refresh <meta http-equiv="refresh"> | Zéro clic, aucun JS requis | Non (nav de premier niveau) | CSRF dans les environnements CSP stricts |
Remplacement de méthode ?_method=POST | GET traité comme POST par le framework | Non (GET de premier niveau) | CSRF d'endpoint POST via Lax |
| CSRF mis en cache | L'attaquant met en cache l'URL de changement d'état dans CDN | Non | Déclencheur CSRF persistant |
Le vecteur de remplacement de méthode mérite une attention particulière. Symfony, Rails (_method), Laravel et certains middleware Express acceptent un paramètre de requête _method=POST sur les requêtes GET. Un attaquant envoie un GET avec ?_method=POST — le navigateur envoie les cookies SameSite=Lax car c'est un GET — et le framework le traite comme un POST, exécutant le changement d'état.
GET /compte/email?_method=POST&email=attaquant@evil.com HTTP/1.1
Host: target.com
Cookie: session=token_session_victimeCVE-2024-23902 — Jenkins GitLab Branch Source Plugin : La validation de formulaire sur cet endpoint de plugin ne nécessitait pas de POST — l'endpoint acceptait des requêtes GET pour des opérations modifiant l'état incluant la connexion à des URLs spécifiées par l'attaquant. CVSS Moyen, vecteur AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:N. Corrigé dans la version 688.v5fa_356ee8520. Publié le 24 janvier 2024.
CVE-2024-20254 — Cisco Expressway CSRF (CVSS 9.6) : L'interface de gestion API Cisco Expressway contenait des protections CSRF insuffisantes sur les endpoints contrôlant la configuration de l'appareil. Un attaquant distant non authentifié pouvait tromper un administrateur authentifié pour qu'il visite un lien forgé, déclenchant des modifications de configuration arbitraires au niveau de privilège administrateur. CVE-2024-20254 était exploitable dans la configuration Expressway par défaut, sans prérequis d'authentification pour l'attaquant.
CVE-2023-47640 — Grails Framework (CVSS 8.8) : La protection CSRF intégrée de Grails était contournée en omettant complètement le champ token des requêtes GET vers des endpoints que le framework aurait dû protéger. L'omission de token — accepté quand absent — est le fondement des attaques CSRF GET et POST. Affectait Grails < 5.3.4, nécessitant une mise à jour du framework.
HackerOne #2326194 — Argo CD / Kubernetes (4 660 $) : Une validation de requête insuffisante dans l'interface de gestion GitOps d'Argo CD permettait au CSRF d'affecter toute la configuration du cluster Kubernetes — reconfiguration arbitraire du cluster via CSRF sur un endpoint de gestion.
delete, remove, destroy, transfer, update, promote, ban, revoke, approve, confirm, reset. Les endpoints GET contenant ces mots sont des cibles haute priorité.Set-Cookie pour l'attribut SameSite. Absent ou SameSite=None signifie que les attaques <img> fonctionnent. SameSite=Lax signifie que les vecteurs de navigation fonctionnent. SameSite=Strict bloque la plupart des vecteurs GET (tester la chaîne de redirection ouverte pour le contournement).BreachVex détecte le CSRF basé sur GET en utilisant une différentielle à deux sondes : GET authentifié (enregistre le statut) vs. GET non authentifié (jar de cookies vidé). Les endpoints retournant HTTP 2xx avec auth et HTTP 401/403 sans auth sont confirmés protégés par authentification. La sonde ignore les endpoints destructifs irréversibles en mode sûr (chemins contenant delete, destroy, kill) pour éviter les effets secondaires lors du scan.
Ne jamais effectuer de changements d'état sur des requêtes GET. Suivre RFC 9110 §9.2 : GET, HEAD et OPTIONS doivent être idempotents et sans effets secondaires.
# FastAPI — imposer POST pour toutes les opérations modifiant l'état
from fastapi import APIRouter, Request, HTTPException, Depends
router = APIRouter()
# VULNÉRABLE : changement d'état sur GET
@router.get("/admin/utilisateurs/{user_id}/delete")
async def delete_user_bad(user_id: int):
await db.delete_user(user_id) # NE PAS FAIRE CELA
return {"ok": True}
# SÛRE : changement d'état sur POST avec validation du token CSRF
@router.post("/admin/utilisateurs/{user_id}/delete")
async def delete_user_safe(
user_id: int,
request: Request,
csrf_token: str = Form(...),
current_user = Depends(require_admin)
):
validate_csrf_token(request, csrf_token)
await db.delete_user(user_id)
return {"ok": True}<!-- Chaque formulaire modifiant l'état doit inclure le token CSRF -->
<form action="/admin/utilisateurs/42/delete" method="POST">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button type="submit">Supprimer l'utilisateur</button>
</form>// Pour les requêtes AJAX : lire le token depuis la balise meta et l'inclure dans les en-têtes
const csrfToken = document.querySelector('meta[name="csrf-token"]').content;
fetch('/admin/utilisateurs/42/delete', {
method: 'POST',
headers: { 'X-CSRF-Token': csrfToken, 'Content-Type': 'application/json' },
credentials: 'same-origin',
body: JSON.stringify({ confirm: true }),
});Set-Cookie: __Host-SID=<token>; Path=/; Secure; HttpOnly; SameSite=StrictUtiliser SameSite=Strict plutôt que Lax pour bloquer toutes les requêtes intersites y compris la navigation de premier niveau. Le préfixe __Host- empêche les sous-domaines d'injecter des cookies de remplacement.
SameSite=Lax ne protège pas les endpoints GET modifiant l'état. Quand le navigateur suit un lien ou une redirection vers un endpoint GET, les cookies Lax sont inclus. Si cet endpoint GET modifie des données, l'attaque réussit. La seule défense contre le CSRF GET via navigation de premier niveau est de ne pas accepter les changements d'état sur GET du tout.
Oui. Le CSRF basé sur GET survient quand une application effectue une action modifiant l'état en réponse à une requête GET, violant RFC 9110 §9.2. Un attaquant embarque une balise <img>, <a> ou <script> pointant vers l'URL modifiant l'état. Le navigateur envoie automatiquement les cookies de session de la victime avec la requête.
Partiellement. SameSite=Lax bloque les requêtes GET depuis des balises de sous-ressource (<img>, <script>) car celles-ci ne sont pas des navigations de premier niveau. Cependant, SameSite=Lax envoie explicitement les cookies lors des navigations GET de premier niveau — ce qui signifie qu'un attaquant utilisant une redirection window.location ou un lien cliquable peut toujours déclencher un CSRF basé sur GET.
Une navigation de premier niveau change l'URL dans la barre d'adresse du navigateur. Cela inclut les clics sur des liens, les assignations window.location, le meta refresh et les soumissions de formulaires naviguant dans le frame principal. Les requêtes de sous-ressources (img, script, iframe, fetch) ne sont pas des navigations de premier niveau et sont bloquées par SameSite=Lax.
RFC 9110 §9.2 définit les méthodes HTTP sûres. Les méthodes sûres NE DOIVENT PAS modifier l'état du serveur — elles doivent être purement informationnelles. GET, HEAD et OPTIONS sont sûres par cette définition. Tout serveur qui modifie l'état sur une requête GET viole RFC 9110 et crée une surface CSRF basée sur GET.
Proxifier tout le trafic via Burp Suite et filtrer les requêtes GET qui retournent 2xx. Chercher des segments de chemin contenant : delete, remove, destroy, transfer, update, promote, ban, revoke, approve, confirm. Tester chaque requête en la rejouant sans cookie de session — si elle retourne 401/403, l'endpoint est protégé par authentification et vaut la peine d'être testé pour le CSRF.
Le CSRF de déconnexion force la victime à se déconnecter en déclenchant l'endpoint /logout en intersite. L'impact est un déni de service (perte de session) plutôt qu'une prise de contrôle de compte. Il est évalué informatif à faible sévérité. Contrairement au CSRF d'action de compte, le CSRF de déconnexion ne peut pas être combiné avec le vol de données sauf s'il est associé à une attaque de fixation de session.
Les liens d'action rapide des panneaux d'administration sont le scénario CSRF GET le plus impactant. Si un CMS, un forum ou un panneau admin SaaS utilise GET pour la suppression d'utilisateurs, la promotion de rôle ou la publication de contenu, un attaquant qui hameçonne un administrateur obtient un CSRF de niveau admin complet. CVE-2024-20254 (Cisco Expressway, CVSS 9.6) exploitait un déclencheur d'action admin sans authentification de l'attaquant.
BreachVex utilise une sonde différentielle : il envoie la requête GET avec des en-têtes d'authentification, enregistre le code de statut, puis vide le jar de cookies et renvoie sans cookies. Si la requête authentifiée retourne 2xx et la requête non authentifiée retourne 401/403, l'endpoint est protégé par authentification et modifie l'état — CSRF GET confirmé.