pfSense Plus MIM API toolkit b4acd7ca - Alta - Remoto - Lettura file - Regole Snort {file} decode-then-join
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · pfsense-api-snort-file-join
Classe: Lettura di file Portata: Remota
pfSense Plus MIM API (Netgate/pfsense-api b4acd7ca) - Netgate
GET /services/snort/rules/{name}/{file} accetta due parametri di percorso, entrambi type: string, senza pattern. Descrizione: ispeziona un file nel ruleset. SnortRuleFileContents.filename è "percorso del file". Il metodo generato get_snort_ruleset_file._get_kwargs esegue quote(str(...), safe=""), quindi ../ diventa ..%2F sul filo. Un server che decodifica l'URL e poi esegue il join nella directory del ruleset riesce comunque a uscirne.
Un chiamante che può eseguire GET su quel percorso, insieme a un JWT di flotta, legge file al di fuori del ruleset indicato come agente MIM su ogni macchina registrata. Inoltre il join non è stato eseguito. Il laboratorio è un gadget decode-then-join del percorso pubblico.
Questo è il residuo 8 di 9. Mappa: pfsense-api-nexus-mim. Stesso pin residuo: fleet JWT. Questo non è jaredhendrickson/pfsense-api.
| ID | nessuna CVE al momento |
| Classe | Lettura di file (Snort {file} decode-then-join) |
| Portata | Remota (GET autenticata; il JWT di flotta riutilizza lo stesso percorso su ogni 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 per un JWT che può eseguire GET sul file del ruleset Snort |
| Prodotto | Netgate/pfsense-api (controller OpenAPI pfSense Plus Nexus MIM + client generato) |
| Affetto | main b4acd7ca21dbca739a530403a1f62a7440f7726f (2026-05-25, Add Snort-3 API definitions) |
| Auth | Gli esempi inviano un JWT del controller dopo POST /login. OpenAPI non ha securitySchemes. L'accesso non autenticato non è dimostrato. |
| Licenza | GNU Affero GPL v3.0 |
| Lab | solo 127.0.0.1:19181. Gadget decode-then-join. Inoltre il join non è stato eseguito. |
Richiede un chiamante che possa eseguire GET /services/snort/rules/{name}/{file}. Gli esempi ufficiali usano Authorization: Bearer dopo POST /login, poi riproducono quel token come base_url=.../api/device/pfsense/{device_id}/api per ogni id da GET /mim/devices. Le operazioni generate continuano a tipizzare AuthenticatedClient | Client. Il CSRF non è in gioco su questa GET.
| Metodo | Sink | Privilegio | Lab vs dedotto |
|---|---|---|---|
GET del client generato con {file}=../../oracle/WITNESS (wire ..%2F..%2Foracle%2FWITNESS) | decodifica unquote(file) poi unisce alla directory del ruleset denominato e legge | Recon: HTTP 200, filename /oracle/WITNESS, body PFSENSE-API-SNORT-FILE-JOIN-WITNESS. L'intento è un file .rules all'interno di quel ruleset. | Provato in lab (gadget). Inoltre il join non è stato eseguito. |
Stesso GET con {file}=../../../etc/passwd | stesso join se il controller lo mappa sul filesystem del dispositivo | Recon: legge file host come agente MIM (Plus WebGUI/PHP è tipicamente root). Il lab ha usato /oracle/WITNESS, non un /etc/passwd reale. | Dedotto (stesso join, percorso host). |
Stesso GET dopo un fleet JWT (POST /login una volta, replay su /api/device/pfsense/{id}/api) | proxy del dispositivo dello stesso {file} non vincolato | Recon: un token controller di tipo helpdesk legge file su ogni box registrato, non solo quello per cui il login era previsto. | Dedotto (contratto G6 + questo percorso). Inoltre RBAC non eseguito. |
Stesso residuo su {name} | GET|DELETE /services/snort/rules/{name} e i sibling address/suppress {name} sono anch'essi stringhe non vincolate | Recon / integrità: rinomina o elimina un ruleset il cui nome è un percorso. | Dedotto dalla specifica. Il lab ha pilotato solo {file}. |
| Controllo confinato dello stesso percorso wire | relative_to(ruleset_dir.resolve()) dopo la decodifica | Chiuso. HTTP 400, nessun witness. | Provato in lab |
{file}=local.rules benigno | lettura prevista del ruleset | Chiuso. 200, sid:1, nessun witness, sia naive che control. | Provato in lab |
Adiacenti chiusi: percent-encoding lato client di ../ (encoding=%2F). Join di controllo dopo la decodifica (control-confined=yes). Inoltre join del controller (plus-join-not-executed). Lettura prevista di local.rules all'interno del ruleset foo. GET non autenticato (non provato; gli esempi inviano Bearer). POST /services/snort/rules archive / .so piantato (admin IDS previsto, non questo residuo). Diagnostica PHP/shell. Scrittura file. Shell host. Questo residuo non è RCE.
Ho fissato Netgate/pfsense-api b4acd7ca21dbca739a530403a1f62a7440f7726f perché HEAD il 2026-10-14 non aveva tag e l'ultimo commit è "Add Snort-3 API definitions". È così che questo percorso esiste. Lo YAML non ha alcuna chiave pattern: da nessuna parte. {name} e {file} sono stringhe non vincolate. quote(..., safe="") è un encoder lato client, non un controllo basedir lato server.
/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=""),
),
Turni errati già registrati: trattare la codifica del client come chiusura della traversata; comporre pfSense Plus; classificare l'archivio .so di Snort come questo residuo; affermare una GET non autenticata. Teatro: una reverse shell. L'oracolo è il gadget 200 di /oracle/WITNESS più il controllo 400.
python3 pfsense-api-snort-file-join-Abraxas-Labs.py
o
cd lab
./run.sh
Prendi di mira solo http://127.0.0.1:19181. Componi il progetto pfsense-api-snort-file-join. Gadget decode-then-join del percorso pubblico. Inoltre il join non viene eseguito. Lo stack si smonta dopo l'esecuzione.
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