pfSense Plus MIM API toolkit b4acd7ca - Hoch - Remote - Datei lesen - Snort-Regeln {file} decode-then-join
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · pfsense-api-snort-file-join
Klasse: Datei lesen Reichweite: Remote
pfSense Plus MIM API (Netgate/pfsense-api b4acd7ca) - Netgate
GET /services/snort/rules/{name}/{file} nimmt zwei Pfadparameter entgegen, beide type: string, kein pattern. Beschreibung: eine Datei im Ruleset inspizieren. SnortRuleFileContents.filename ist "path of file". Das generierte get_snort_ruleset_file._get_kwargs führt quote(str(...), safe="") aus, sodass ../ auf der Leitung zu ..%2F wird. Ein Server, der URL-dekodiert und dann in das Ruleset-Verzeichnis joint, läuft dennoch heraus.
Ein Aufrufer, der diesen Pfad GETten kann, plus ein Fleet-JWT, liest Dateien außerhalb des benannten Rulesets als der MIM-Agent auf jeder eingetragenen Box. Zudem wurde der Join nicht ausgeführt. Das Lab ist ein Decode-then-Join-Gadget des öffentlichen Pfads.
Dies ist der 8. von 9 Überresten. Karte: pfsense-api-nexus-mim. Gleicher Pin-Überrest: Fleet-JWT. Dies ist nicht jaredhendrickson/pfsense-api.
| ID | noch keine CVE |
| Klasse | Datei lesen (Snort {file} Decode-then-Join) |
| Reichweite | Remote (authentifizierter GET; Fleet-JWT verwendet denselben Pfad auf jeder device_id erneut) |
| CWE | CWE-22 |
| CVSS | Hoch: 6.5 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N für ein JWT, das die Snort-Ruleset-Datei GETten kann |
| Produkt | Netgate/pfsense-api (pfSense Plus Nexus MIM Controller OpenAPI + generierter Client) |
| Betroffen | main b4acd7ca21dbca739a530403a1f62a7440f7726f (2026-05-25, Add Snort-3 API definitions) |
| Auth | Beispiele senden ein Controller-JWT nach POST /login. OpenAPI hat keine securitySchemes. Unauth ist nicht bewiesen. |
| Lizenz | GNU Affero GPL v3.0 |
| Lab | nur 127.0.0.1:19181. Decode-then-Join-Gadget. Zudem wurde der Join nicht ausgeführt. |
Benötigt einen Aufrufer, der GET /services/snort/rules/{name}/{file} ausführen kann. Offizielle Beispiele verwenden Authorization: Bearer nach POST /login und spielen dann dieses Token als base_url=.../api/device/pfsense/{device_id}/api für jede ID aus GET /mim/devices erneut ab. Generierte Ops typisieren weiterhin AuthenticatedClient | Client. CSRF ist bei diesem GET nicht im Spiel.
| Methode | Sink | Berechtigung | Lab vs. abgeleitet |
|---|---|---|---|
Generated-client GET mit {file}=../../oracle/WITNESS (Wire ..%2F..%2Foracle%2FWITNESS) | unquote(file) dekodieren, dann auf das benannte Ruleset-Verzeichnis joinen und lesen | Recon: HTTP 200, filename /oracle/WITNESS, Body PFSENSE-API-SNORT-FILE-JOIN-WITNESS. Beabsichtigt ist eine .rules-Datei innerhalb dieses Rulesets. | Lab-bestätigt (Gadget). Plus-Join wurde nicht ausgeführt. |
Derselbe GET mit {file}=../../../etc/passwd | derselbe Join, wenn der Controller ihn auf das Gerätedateisystem abbildet | Recon: Host-Dateien als MIM-Agent lesen (Plus WebGUI/PHP ist typischerweise root). Lab verwendete /oracle/WITNESS, nicht ein live /etc/passwd. | Abgeleitet (derselbe Join, Host-Pfad). |
Derselbe GET nach einem Fleet-JWT (POST /login einmal, Replay auf /api/device/pfsense/{id}/api) | Geräte-Proxy desselben uneingeschränkten {file} | Recon: ein helpdesk-förmiges Controller-Token liest Dateien auf jeder registrierten Box, nicht nur auf der, für die der Login gedacht war. | Abgeleitet (G6-Vertrag + dieser Pfad). Plus RBAC wurde nicht ausgeführt. |
Derselbe Leftover auf {name} | GET|DELETE /services/snort/rules/{name} und benachbarte address/suppress {name} sind ebenfalls uneingeschränkte Strings | Recon / Integrität: ein Ruleset umbenennen oder löschen, dessen Name ein Pfad ist. | Aus der Spezifikation abgeleitet. Lab steuerte nur {file} an. |
| Eingeschränkte Kontrolle desselben Wire-Pfads | relative_to(ruleset_dir.resolve()) nach dem Dekodieren | Geschlossen. HTTP 400, kein Witness. | Lab-bestätigt |
Gutartiges {file}=local.rules | beabsichtigtes Ruleset-Lesen | Geschlossen. 200, sid:1, kein Witness, sowohl naiv als auch Kontrolle. | Lab-bestätigt |
Angrenzend geschlossen: Client-Prozentkodierung von ../ (encoding=%2F). Kontroll-Join nach dem Dekodieren (control-confined=yes). Plus-Controller-Join (plus-join-not-executed). Beabsichtigtes Lesen von local.rules innerhalb Ruleset foo. Unauthentifizierter GET (nicht bewiesen; Beispiele senden Bearer). POST /services/snort/rules Archiv / platziertes .so (beabsichtigter IDS-Admin, nicht dieser Leftover). Diagnostics PHP/Shell. Dateischreiben. Host-Shell. Dieser Leftover ist kein RCE.
Ich habe Netgate/pfsense-api b4acd7ca21dbca739a530403a1f62a7440f7726f gepinnt, weil HEAD am 2026-10-14 keine Tags hatte und der letzte Commit "Add Snort-3 API definitions" ist. So existiert dieser Pfad. Die YAML hat nirgendwo pattern:-Schlüssel. {name} und {file} sind uneingeschränkte Strings. quote(..., safe="") ist ein Client-Encoder, keine serverseitige basedir-Prüfung.
/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=""),
),
Falsche Wege, die bereits dokumentiert wurden: Client-Encoding als schließende Traversierung behandeln; pfSense Plus komponieren; Snort-Archiv .so als diesen Überrest einordnen; unauth GET behaupten. Theater: eine Reverse Shell. Das Orakel ist Gadget 200 von /oracle/WITNESS plus Kontrolle 400.
python3 pfsense-api-snort-file-join-Abraxas-Labs.py
oder
cd lab
./run.sh
Ziel nur http://127.0.0.1:19181. Compose-Projekt pfsense-api-snort-file-join. Decode-dann-Join-Gadget des öffentlichen Pfads. Plus Join wird nicht ausgeführt. Stack wird nach dem Lauf abgebaut.
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