
Stirling-PDF v3.2.0 - Alta - Remota - IDOR - a raiz de armazenamento da pasta de processamento lista e sobrescreve PDFs de pares
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · stirling-storage-folder-idor
Stirling-PDF 3.2.0 - Stirling Tools
Classe: IDOR. Alcance: Remoto ROLE_USER.
POST /api/v1/processing-folders não tem @PreAuthorize além de um usuário autenticado. FolderAccessGuard implica todo o basePath do armazenamento local (./storage) mesmo quando policies.allowedFolderRoots está vazio. LocalStorageProvider grava {ownerId}/{uuid}_{filename}. Um ROLE_USER pode definir directory: "storage/<victimUserId>", listar os PDFs daquele proprietário e sobrescrevê-los no local com replace=true.
O acesso direto a /api/v1/storage/files/{id} está corrigido (findByIdAndOwner). O GET de laboratório do arquivo da vítima como o par retorna 403. O que resta é a raiz implícita da pasta de processamento, não a API CRUD de armazenamento.
Um ROLE_USER lista os nomes dos PDFs armazenados de outro usuário e sobrescreve esses blobs. O download da vítima retorna o arquivo adulterado.
| ID | sem CVE ainda |
| 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 |
| Produto | Stirling-PDF |
| Afetado | v3.2.0 (a7e593728dd9aa81e75318a949789d3c2e9ee754), imagem stirlingtools/stirling-pdf:3.2.0-fat, login-on, storage.enabled=true |
| Autenticação | ROLE_USER na equipe padrão |
| Licença | GNU Affero GPL v3.0 |
| Laboratório | somente 127.0.0.1:18154 |
Resíduo da série Stirling-PDF v3.2.0 leftovers. Irmão: stirling-h2-import-leftover (H2 CREATE OR REPLACE ALIAS). Este pacote é o IDOR de armazenamento.
Qualquer ROLE_USER humano autenticado em login-on proprietary/fat com armazenamento local habilitado (padrão do template). O compose oficial é login-off mais DISABLE_ADDITIONAL_FEATURES=true; essa implantação retorna 403 em /api/v1/storage e omite este controller. A implantação HIT é o template login-on mais fat.
Tabela de privilégios (exaustiva). Mesmo critério do Signal-Server Dropwizard admin.
| Eixo | Método + caminho | Concede | Proveniência |
|---|---|---|---|
| Reconhecimento | ROLE_USER GET /api/v1/user/users | userId numérico para admin, par e STIRLING-PDF-BACKEND-API-USER | Comprovado em laboratório |
| Reconhecimento | POST /api/v1/processing-folders {directory:"storage/1"} depois GET /api/v1/processing-folders/{id}/files | Nome, tamanho, mtime do PDF armazenado do admin (5341de35-8729-49cf-92d0-c8284e05bfc5_victim-lab.pdf) | Comprovado em laboratório |
| Integridade | POST /api/v1/processing-folders/{id}/sweep com /api/v1/security/add-watermark depois Range GET /api/v1/storage/files/1/download da vítima | Reescrita no local. A vítima 206 / 3450 bytes contém STIRLING-STORAGE-FOLDER-IDOR-WITNESS | Comprovado em laboratório (replace=true) |
| Confidencialidade | mesmo sweep mais um outputId de equipe / etapa de saída que o atacante consegue ler | Cópia dos bytes do PDF da vítima | Inferido. Não necessário para listagem/sobrescrita |
Permanece fechado neste pin
| Eixo | Método + caminho | Por que está fechado |
|---|---|---|
| Recon | GET /api/v1/storage/files/{id} de um arquivo não compartilhado | 403 Access denied. Comprovado em laboratório no id de arquivo 1 |
| Recon | /api/v1/storage/** sem autenticação | 403 quando o login está desativado (ensureStorageEnabled) |
| Integridade | CRUD de UUID de pasta entre proprietários | findByIdAndOwner |
| Integridade | LocalStorageProvider .. / chave absoluta | Path.startsWith(basePath) após normalize |
| Integridade | imagem ultra-lite | controlador proprietário de pasta de processamento ausente |
GET /api/v1/user/users listando STIRLING-PDF-BACKEND-API-USER é um IOC adicional. Não o registrei como um leftover próprio. O download da vítima sem Range retorna 500 após a sobrescrita porque FileStorageController ainda anuncia o sizeBytes original do BD. Range funciona. O texto da marca d'água é Identity-H em FlateDecode; o oráculo infla os streams.
A versão 3.x adicionou armazenamento local e pastas de processamento. O CRUD de armazenamento é restrito ao proprietário. FolderAccessGuard.serverStorageRoots retorna Path.of(basePath) para toda a árvore, não basePath/{currentUserId}. storage/<victimId> startsWith essa raiz implícita.
O laboratório cria peeruser como ROLE_USER, faz upload de um PDF da vítima como admin, comprova GET direto no armazenamento 403, aponta uma pasta de processamento para storage/1, lista o nome com prefixo UUID, varre uma marca d'água e então faz download via Range como a vítima.
Caminhos errados: tratar o IDOR direto de armazenamento como ainda aberto (403); tratar o compose oficial com login desativado como o deploy do HIT (armazenamento 403, controlador ausente); alegar cópia de bytes legível pelo atacante sem um outputId.
python3 stirling-storage-folder-idor-Abraxas-Labs.py
ou
cd lab
./run.sh
Aponte apenas para http://127.0.0.1:18154. Imagem stirlingtools/stirling-pdf:3.2.0-fat. Docker necessário. Login ativado. O launcher faz chdir para lab/ e executa 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
Restrinja a raiz de armazenamento implícita a basePath/{currentUserId} para o disco directory em servidores multiusuário. Recuse storage/<otherId> da mesma forma que findByIdAndOwner já recusa ids de arquivos de terceiros. Recalcule o sizeBytes anunciado após uma reescrita no local.
Execute novamente o cliente de loopback contra uma build corrigida: directory=storage/1 como peeruser deve retornar 403, e o peer listado deve permanecer vazio.
Até então, não trate "eu corrigi /api/v1/storage/files/{id}" como "eu restrinji todas as APIs que recebem um caminho de disco em ./storage".