
Stirling-PDF v3.2.0 - Alta - Remota - IDOR - el almacenamiento de la carpeta de procesamiento lista y sobrescribe los PDF de otros usuarios
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · stirling-storage-folder-idor
Stirling-PDF 3.2.0 - Stirling Tools
Clase: IDOR. Alcance: Remoto ROLE_USER.
POST /api/v1/processing-folders no tiene @PreAuthorize más allá de un usuario autenticado. FolderAccessGuard implica todo el basePath del almacenamiento local (./storage) incluso cuando policies.allowedFolderRoots está vacío. LocalStorageProvider escribe {ownerId}/{uuid}_{filename}. Un ROLE_USER puede establecer directory: "storage/<victimUserId>", listar los PDFs de ese propietario y sobrescribirlos in situ con replace=true.
El acceso directo a /api/v1/storage/files/{id} está parcheado (findByIdAndOwner). La petición GET de laboratorio del archivo de la víctima como el par devuelve 403. Lo que queda es la raíz implícita de la carpeta de procesamiento, no la API CRUD de almacenamiento.
Un ROLE_USER lista los nombres de los PDFs almacenados de otro usuario y sobrescribe esos blobs. La descarga de la víctima devuelve el archivo manipulado.
| ID | sin CVE todavía |
| CWE | CWE-639 / CWE-73 |
| CVSS | 7.1 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:H/A:N |
| Producto | Stirling-PDF |
| Afectado | v3.2.0 (a7e593728dd9aa81e75318a949789d3c2e9ee754), imagen stirlingtools/stirling-pdf:3.2.0-fat, login activado, storage.enabled=true |
| Autenticación | ROLE_USER en el equipo predeterminado |
| Licencia | GNU Affero GPL v3.0 |
| Laboratorio | solo 127.0.0.1:18154 |
Remanente de la serie Stirling-PDF v3.2.0 leftovers. Hermano: stirling-h2-import-leftover (H2 CREATE OR REPLACE ALIAS). Este paquete es el IDOR de almacenamiento.
Cualquier ROLE_USER humano autenticado en la versión proprietary/fat con login activado y almacenamiento local habilitado (predeterminado de la plantilla). El compose oficial tiene el login desactivado más DISABLE_ADDITIONAL_FEATURES=true; ese despliegue devuelve 403 en /api/v1/storage y omite este controlador. El despliegue HIT es la plantilla con login activado más fat.
Tabla de privilegios (exhaustiva). Mismo nivel que Signal-Server Dropwizard admin.
| Eje | Método + ruta | Otorga | Procedencia |
|---|---|---|---|
| Reconocimiento | ROLE_USER GET /api/v1/user/users | userId numérico para admin, par y STIRLING-PDF-BACKEND-API-USER | Probado en laboratorio |
| Reconocimiento | POST /api/v1/processing-folders {directory:"storage/1"} luego GET /api/v1/processing-folders/{id}/files | Nombre, tamaño, mtime del PDF almacenado del admin (5341de35-8729-49cf-92d0-c8284e05bfc5_victim-lab.pdf) | Probado en laboratorio |
| Integridad | POST /api/v1/processing-folders/{id}/sweep con /api/v1/security/add-watermark luego Range GET /api/v1/storage/files/1/download de la víctima | Reescritura in situ. El 206 / 3450 bytes de la víctima contiene STIRLING-STORAGE-FOLDER-IDOR-WITNESS | Probado en laboratorio (replace=true) |
| Confidencialidad | mismo sweep más un outputId de equipo / paso de salida que el atacante pueda leer | Copia de los bytes del PDF de la víctima | Inferido. No requerido para listar/sobrescribir |
Permanece cerrado en este pin
| Eje | Método + ruta | Por qué está cerrado |
|---|---|---|
| Recon | GET /api/v1/storage/files/{id} de un archivo no compartido | 403 Access denied. Probado en laboratorio con el id de archivo 1 |
| Recon | /api/v1/storage/** sin autenticación | 403 cuando el inicio de sesión está desactivado (ensureStorageEnabled) |
| Integridad | CRUD de UUID de carpeta entre propietarios | findByIdAndOwner |
| Integridad | LocalStorageProvider .. / clave absoluta | Path.startsWith(basePath) tras normalizar |
| Integridad | imagen ultra-lite | controlador de carpeta de procesamiento propietario ausente |
GET /api/v1/user/users listando STIRLING-PDF-BACKEND-API-USER es un IOC adicional. No lo registré como un resto propio. La descarga de la víctima sin rangos devuelve 500 tras la sobrescritura porque FileStorageController sigue anunciando el sizeBytes original de la BD. Con rangos funciona. El texto de la marca de agua es Identity-H en FlateDecode; el oráculo infla los flujos.
3.x añadió almacenamiento local y carpetas de procesamiento. El CRUD de almacenamiento está limitado al propietario. FolderAccessGuard.serverStorageRoots devuelve Path.of(basePath) para todo el árbol, no basePath/{currentUserId}. storage/<victimId> startsWith esa raíz implícita.
El laboratorio crea peeruser como ROLE_USER, sube un PDF de víctima como admin, demuestra que el GET directo al almacenamiento da 403, apunta una carpeta de procesamiento a storage/1, lista el nombre con prefijo UUID, barre una marca de agua y luego descarga con Range como la víctima.
Falsos caminos: tratar el IDOR directo de almacenamiento como aún abierto (403); tratar el compose oficial con inicio de sesión desactivado como el despliegue del HIT (almacenamiento 403, controlador ausente); afirmar una copia de bytes legible por el atacante sin un outputId.
python3 stirling-storage-folder-idor-Abraxas-Labs.py
o
cd lab
./run.sh
Dirígete solo a http://127.0.0.1:18154. Imagen stirlingtools/stirling-pdf:3.2.0-fat. Se requiere Docker. Login activado. El lanzador hace chdir a lab/ y ejecuta run.sh.
IOC direct-storage-get http=403
IOC processing-folders-save directory=storage/1 http=202
IOC listed-peer ... victim-lab.pdf
IOC victim-download attempt=1 http=206 bytes=3450 witness=1
SUCCESS stirling-storage-folder-idor direct-storage-403=yes listed-peer-pdf=yes download-has-witness=yes STIRLING-STORAGE-FOLDER-IDOR-WITNESS
Limita la raíz de almacenamiento implícita a basePath/{currentUserId} para el disco directory en servidores multiusuario. Rechaza storage/<otherId> del mismo modo en que findByIdAndOwner ya rechaza los ids de archivos ajenos. Recalcula el sizeBytes anunciado tras una reescritura in situ.
Vuelve a ejecutar el cliente de loopback contra una compilación parcheada: directory=storage/1 como peeruser debe devolver 403, y el listado de pares debe permanecer vacío.
Hasta entonces, no trates "parcheé /api/v1/storage/files/{id}" como "limité todas las API que aceptan una ruta de disco bajo ./storage".