
Verificador de prueba de concepto local para CVE-2026-100671, que reproduce la divulgación y repetición de cookies de sesión de la caché de páginas de Grav Twig contra objetivos de loopback.
Verificador mínimo del lado del atacante, acotado al objetivo, para la toma de control de sesión mediante Twig de contenido/caché de páginas de Grav corregida en Grav 2.0.25.
Avisos:
El atacante no dispone del nombre de usuario ni de la contraseña del administrador.
La cadena real es:
La acción del administrador ocurre fuera de este script. exploit.py no acepta
ningún ajuste GRAV_ADMIN_USERNAME ni GRAV_ADMIN_PASSWORD.
Loopback es el límite de seguridad predeterminado. Un objetivo que no sea loopback requiere
el flag explícito --allow-remote, destinado a una máquina HTB, TryHackMe o de laboratorio propia
y autorizada.
El cliente sigue redirecciones solo cuando permanecen en el origen del objetivo seleccionado,
utiliza clientes HTTP de la biblioteca estándar de Python, aplica tiempos de espera en las solicitudes e imprime
la cookie capturada solo cuando se proporciona explícitamente --show-cookie.
Úsalo solo contra una instancia de Grav que poseas o para la que tengas autorización explícita de probar. El script crea una página de prueba pública. Elimina esa página después de las pruebas si el objetivo conserva el contenido entre ejecuciones.
get_cookie() lee del lado del servidor la cookie de sesión del administrador visitante.La cuenta de escritura de páginas solo necesita la ruta de escritura de bajos privilegios. No necesita
api.pages.read, admin.*, acceso de superadministrador ni credenciales de administrador.
2.0.19 hasta 2.0.24: vulnerable con la configuración predeterminada de Twig de contenido
incluida.2.0.0 hasta 2.0.18, y 1.7.x: afectadas solo cuando el Twig de contenido
se habilitaba explícitamente.2.0.25: corregida.Ejecuta un objetivo Grav con:
Este repositorio de dos archivos no aprovisiona Grav, PHP, plugins, cuentas ni un bot de administrador. La visita de la víctima sigue siendo una acción externa.
Configura solo la credencial o el token del redactor con bajos privilegios:
export GRAV_TARGET=http://127.0.0.1:8080
export GRAV_WRITER_USERNAME=low
export GRAV_WRITER_PASSWORD='local-writer-password'
python3 exploit.py
El script crea una ruta pública aleatoria, imprime su URL y espera. Haga que el administrador visite esa URL una vez y luego presione Enter. El script realiza la recopilación anónima y la reproducción local de la API.
Utilice en su lugar un token de API de escritor existente:
export GRAV_WRITER_TOKEN='local-low-privilege-token'
python3 exploit.py
Útil para una terminal no interactiva o un navegador de víctima separado.
Preparar la página:
python3 exploit.py --prepare-only
Haz que un administrador visite la URL impresa. Luego recopila y reproduce usando la ruta impresa; no se necesita ninguna credencial para esta segunda fase:
python3 exploit.py --collect-only --route /cookie-proof-XXXXXXXX --show-cookie
--show-cookie imprime la cookie de sesión capturada para depuración local/autorizada en laboratorio:
captured_cookie=grav-site-<hash>=<session-value>
El valor es una credencial activa. No lo pegues en GitHub, issues, chat, capturas de pantalla ni registros. Sin --show-cookie, el script demuestra el mismo impacto a través del estado de reproducción mientras mantiene el valor fuera de la salida.
Si la recolección se ejecuta antes de la visita del administrador, la solicitud del atacante puede poblar la caché con un valor vacío. Prepara una nueva ruta aleatoria y repite.
No codifiques una IP de máquina en el repositorio. Proporciona el objetivo actual en tiempo de ejecución:
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
Después de que el administrador/bot visite la URL impresa, recopila y reproduce:
python3 exploit.py \
--target http://10.10.10.25:8080 \
--allow-remote \
--collect-only \
--route /cookie-proof-XXXXXXXX \
--show-cookie
--base-url sigue siendo un alias de compatibilidad para --target. HTTP remoto imprime
una advertencia de texto plano; use HTTPS donde la máquina lo admita.
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 línea captured_cookie aparece solo con --show-cookie.
Ejecute el mismo flujo de dos fases contra Grav 2.0.25 o posterior, luego recopile con:
python3 exploit.py --collect-only --route /cookie-proof-XXXXXXXX --expect fixed
Comportamiento fijo esperado: ningún valor de sesión visible para el atacante y ninguna reproducción de sesión exitosa. Un resultado fijo limpio sale con 0 con --expect fixed.
Actualice a Grav 2.0.25 o posterior. Para implementaciones más antiguas, el aviso enumera estos controles temporales:
security.twig_content.process_enabled: false; osystem.pages.never_cache_twig: true;No trate HttpOnly, Secure o SameSite como una solución: la lectura vulnerable de la cookie ocurre del lado del servidor durante el renderizado de Twig.
Reportado por canhieu. Este repositorio es una ayuda de reproducibilidad para el aviso ya público, no un escáner remoto ni una herramienta de recolección de credenciales.