pfSense Plus MIM API toolkit b4acd7ca - Élevé - À distance - Lecture de fichier - Règles Snort {file} decode-then-join
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · pfsense-api-snort-file-join
Classe : Lecture de fichier Portée : Distante
pfSense Plus MIM API (Netgate/pfsense-api b4acd7ca) - Netgate
GET /services/snort/rules/{name}/{file} prend deux paramètres de chemin, tous deux type: string, sans pattern. Description : inspecter un fichier dans le jeu de règles. SnortRuleFileContents.filename est « chemin du fichier ». Le get_snort_ruleset_file._get_kwargs généré effectue quote(str(...), safe=""), donc ../ devient ..%2F sur le réseau. Un serveur qui décode l'URL puis concatène dans le répertoire du jeu de règles sort quand même du répertoire.
Un appelant qui peut faire un GET sur ce chemin, muni d'un JWT de flotte, lit des fichiers en dehors du jeu de règles nommé en tant qu'agent MIM sur chaque machine enrôlée. De plus, la concaténation n'a pas été exécutée. Le laboratoire est un gadget de décodage-puis-concaténation du chemin public.
Ceci est le résidu 8 sur 9. Carte : pfsense-api-nexus-mim. Même résidu épinglé : fleet JWT. Il ne s'agit pas de jaredhendrickson/pfsense-api.
| ID | pas encore de CVE |
| Classe | Lecture de fichier (Snort {file} décodage-puis-concaténation) |
| Portée | Distante (GET authentifié ; le JWT de flotte réutilise le même chemin sur chaque device_id) |
| CWE | CWE-22 |
| CVSS | Élevé : 6.5 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N pour un JWT qui peut faire un GET sur le fichier du jeu de règles Snort |
| Produit | Netgate/pfsense-api (contrôleur pfSense Plus Nexus MIM OpenAPI + client généré) |
| Affecté | main b4acd7ca21dbca739a530403a1f62a7440f7726f (2026-05-25, Add Snort-3 API definitions) |
| Auth | Les exemples envoient un JWT de contrôleur après POST /login. OpenAPI n'a pas de securitySchemes. L'accès non authentifié n'est pas prouvé. |
| Licence | GNU Affero GPL v3.0 |
| Lab | 127.0.0.1:19181 uniquement. Gadget de décodage-puis-concaténation. La concaténation n'a pas été exécutée. |
Nécessite un appelant qui peut faire GET /services/snort/rules/{name}/{file}. Les exemples officiels utilisent Authorization: Bearer après POST /login, puis rejouent ce jeton avec base_url=.../api/device/pfsense/{device_id}/api pour chaque id issu de GET /mim/devices. Les opérations générées typent toujours AuthenticatedClient | Client. Le CSRF n'entre pas en jeu sur ce GET.
| Méthode | Sink | Privilège | Labo vs déduit |
|---|---|---|---|
GET du client généré avec {file}=../../oracle/WITNESS (wire ..%2F..%2Foracle%2FWITNESS) | décoder unquote(file) puis joindre sur le répertoire du ruleset nommé et lire | Recon : HTTP 200, filename /oracle/WITNESS, corps PFSENSE-API-SNORT-FILE-JOIN-WITNESS. L'intention est un fichier .rules à l'intérieur de ce ruleset. | Prouvé en labo (gadget). De plus, la jointure n'a pas été exécutée. |
Même GET avec {file}=../../../etc/passwd | même jointure si le contrôleur la mappe sur le système de fichiers de l'appareil | Recon : lire les fichiers de l'hôte en tant qu'agent MIM (Plus WebGUI/PHP est généralement root). Le labo a utilisé /oracle/WITNESS, pas un /etc/passwd réel. | Déduit (même jointure, chemin hôte). |
Même GET après un fleet JWT (POST /login une fois, rejouer sur /api/device/pfsense/{id}/api) | proxy d'appareil du même {file} non contraint | Recon : un jeton de contrôleur de type helpdesk lit les fichiers sur chaque machine enrôlée, pas seulement celle pour laquelle la connexion était prévue. | Déduit (contrat G6 + ce chemin). De plus, le RBAC n'a pas été exécuté. |
Même reliquat sur {name} | GET|DELETE /services/snort/rules/{name} et les {name} frères address/suppress sont aussi des chaînes non contraintes | Recon / intégrité : renommer ou supprimer un ruleset dont le nom est un chemin. | Déduit de la spécification. Le labo n'a piloté que {file}. |
| Contrôle confiné du même chemin wire | relative_to(ruleset_dir.resolve()) après décodage | Fermé. HTTP 400, aucun témoin. | Prouvé en labo |
{file}=local.rules bénin | lecture du ruleset prévue | Fermé. 200, sid:1, aucun témoin, à la fois naïf et contrôle. | Prouvé en labo |
Fermé adjacent : encodage en pourcentage côté client de ../ (encoding=%2F). Jointure de contrôle après décodage (control-confined=yes). De plus, jointure du contrôleur (plus-join-not-executed). Lecture prévue de local.rules à l'intérieur du ruleset foo. GET non authentifié (non prouvé ; les exemples envoient Bearer). POST /services/snort/rules archive / .so planté (admin IDS prévu, pas ce reliquat). Diagnostics PHP/shell. Écriture de fichier. Shell hôte. Ce reliquat n'est pas une RCE.
J'ai épinglé Netgate/pfsense-api b4acd7ca21dbca739a530403a1f62a7440f7726f car HEAD au 2026-10-14 n'avait pas de tags et le dernier commit est « Add Snort-3 API definitions ». C'est ainsi que ce chemin existe. Le YAML n'a aucune clé pattern: nulle part. {name} et {file} sont des chaînes non contraintes. quote(..., safe="") est un encodeur côté client, pas une vérification de basedir côté serveur.
/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=""),
),
Mauvais chemins déjà enregistrés : traiter l'encodage client comme une traversée de fermeture ; composer pfSense Plus ; classer l'archive Snort .so comme ce résidu ; prétendre à un GET non authentifié. Théâtre : un reverse shell. L'oracle est le gadget 200 de /oracle/WITNESS plus le contrôle 400.
python3 pfsense-api-snort-file-join-Abraxas-Labs.py
ou
cd lab
./run.sh
Ciblez uniquement http://127.0.0.1:19181. Composez le projet pfsense-api-snort-file-join. Gadget decode-then-join du chemin public. Le join plus n'est pas exécuté. La pile se démonte après l'exécution.
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