pfSense Plus MIM API toolkit b4acd7ca - Alta - Remoto - Leitura de arquivo - Regras do Snort {file} decode-then-join
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · pfsense-api-snort-file-join
Classe: Leitura de arquivo Alcance: Remoto
pfSense Plus MIM API (Netgate/pfsense-api b4acd7ca) - Netgate
GET /services/snort/rules/{name}/{file} recebe dois parâmetros de caminho, ambos type: string, sem pattern. Descrição: inspecionar um arquivo no conjunto de regras. SnortRuleFileContents.filename é "caminho do arquivo". O get_snort_ruleset_file._get_kwargs gerado faz quote(str(...), safe=""), então ../ vira ..%2F no fio. Um servidor que faz URL-decode e depois concatena no diretório do conjunto de regras ainda escapa.
Um chamador que consegue fazer GET nesse caminho, mais um JWT de frota, lê arquivos fora do conjunto de regras nomeado como o agente MIM em todas as máquinas inscritas. Além disso, o join não foi executado. O laboratório é um gadget de decode-then-join do caminho público.
Este é o resíduo 8 de 9. Mapa: pfsense-api-nexus-mim. Mesmo resíduo de pin: fleet JWT. Este não é o jaredhendrickson/pfsense-api.
| ID | sem CVE ainda |
| Classe | Leitura de arquivo (Snort {file} decode-then-join) |
| Alcance | Remoto (GET autenticado; o JWT de frota reutiliza o mesmo caminho em cada device_id) |
| CWE | CWE-22 |
| CVSS | Alto: 6.5 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N para um JWT que pode fazer GET no arquivo do conjunto de regras do Snort |
| Produto | Netgate/pfsense-api (controlador pfSense Plus Nexus MIM OpenAPI + cliente gerado) |
| Afetado | main b4acd7ca21dbca739a530403a1f62a7440f7726f (2026-05-25, Add Snort-3 API definitions) |
| Autenticação | Os exemplos enviam um JWT de controlador após POST /login. O OpenAPI não tem securitySchemes. Não autenticado não está comprovado. |
| Licença | GNU Affero GPL v3.0 |
| Laboratório | Apenas 127.0.0.1:19181. Gadget decode-then-join. O join adicional não foi executado. |
Precisa de um chamador que consiga GET /services/snort/rules/{name}/{file}. Os exemplos oficiais usam Authorization: Bearer após POST /login, depois reutilizam esse token como base_url=.../api/device/pfsense/{device_id}/api para cada id de GET /mim/devices. As operações geradas ainda tipam AuthenticatedClient | Client. CSRF não está em jogo neste GET.
| Método | Sink | Privilégio | Lab vs inferido |
|---|---|---|---|
GET do cliente gerado com {file}=../../oracle/WITNESS (wire ..%2F..%2Foracle%2FWITNESS) | decodifica unquote(file) e depois junta ao diretório do ruleset nomeado e lê | Recon: HTTP 200, filename /oracle/WITNESS, corpo PFSENSE-API-SNORT-FILE-JOIN-WITNESS. O pretendido é um ficheiro .rules dentro desse ruleset. | Provado em lab (gadget). Além disso, o join não foi executado. |
Mesmo GET com {file}=../../../etc/passwd | mesmo join se o controlador o mapear no sistema de ficheiros do dispositivo | Recon: lê ficheiros do host como o agente MIM (o Plus WebGUI/PHP é tipicamente root). O lab usou /oracle/WITNESS, não um /etc/passwd real. | Inferido (mesmo join, caminho do host). |
Mesmo GET após um fleet JWT (POST /login uma vez, replay em /api/device/pfsense/{id}/api) | proxy do dispositivo do mesmo {file} sem restrições | Recon: um token de controlador com forma de helpdesk lê ficheiros em todas as máquinas inscritas, não apenas naquela para a qual o login se destinava. | Inferido (contrato G6 + este caminho). Além disso, o RBAC não foi executado. |
Mesmo leftover em {name} | GET|DELETE /services/snort/rules/{name} e os irmãos address/suppress {name} também são strings sem restrições | Recon / integridade: renomear ou eliminar um ruleset cujo nome é um caminho. | Inferido a partir da spec. O lab acionou apenas {file}. |
| Controlo confinado do mesmo caminho wire | relative_to(ruleset_dir.resolve()) após decode | Fechado. HTTP 400, sem witness. | Provado em lab |
{file}=local.rules benigno | leitura pretendida do ruleset | Fechado. 200, sid:1, sem witness, tanto ingénuo como controlo. | Provado em lab |
Fechados adjacentes: percent-encoding do cliente de ../ (encoding=%2F). Join de controlo após decode (control-confined=yes). Além disso, join do controlador (plus-join-not-executed). Leitura pretendida de local.rules dentro do ruleset foo. GET não autenticado (não provado; os exemplos enviam Bearer). POST /services/snort/rules archive / .so plantado (admin IDS pretendido, não este leftover). Diagnostics PHP/shell. Escrita de ficheiros. Shell do host. Este leftover não é RCE.
Fixeio Netgate/pfsense-api b4acd7ca21dbca739a530403a1f62a7440f7726f porque o HEAD em 2026-10-14 não tinha tags e o commit mais recente é "Add Snort-3 API definitions". É assim que este caminho existe. O YAML não tem nenhuma chave pattern: em lado nenhum. {name} e {file} são strings sem restrições. quote(..., safe="") é um codificador de cliente, não uma verificação de basedir do servidor.
/services/snort/rules/{name}/{file}:
parameters:
- in: path
name: name
required: true
schema:
type: string
- in: path
name: file
required: true
schema:
type: string
get:
description: Obtain the contents of the specified file in the ruleset. This is intended for inspecting scripts provided by it.
operationId: GetSnortRulesetFile
"url": "/services/snort/rules/{name}/{file}".format(
name=quote(str(name), safe=""),
file=quote(str(file), safe=""),
),
Erros já registrados: tratar a codificação do cliente como encerramento de travessia; compor pfSense Plus; classificar o arquivo .so do Snort como este resquício; alegar GET não autenticado. Teatro: um shell reverso. O oráculo é o gadget 200 de /oracle/WITNESS mais o controle 400.
python3 pfsense-api-snort-file-join-Abraxas-Labs.py
ou
cd lab
./run.sh
Alvo apenas http://127.0.0.1:19181. Compõe o projeto pfsense-api-snort-file-join. Gadget de decodificar-e-juntar do caminho público. O join adicional não é executado. A stack é desmontada após a execução.
SUCCESS PFSENSE-API-SNORT-FILE-JOIN encoding=%2F decode-then-join=yes control-confined=yes plus-join-not-executed PFSENSE-API-SNORT-FILE-JOIN-WITNESS