pfSense Plus MIM API toolkit b4acd7ca — высокий — удалённый — чтение файлов — правила Snort {file} decode-then-join
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · pfsense-api-snort-file-join
Класс: Чтение файлов Доступ: Удалённый
pfSense Plus MIM API (Netgate/pfsense-api b4acd7ca) - Netgate
GET /services/snort/rules/{name}/{file} принимает два параметра пути, оба type: string, без pattern. Описание: просмотр файла в наборе правил. SnortRuleFileContents.filename — это "путь к файлу". Сгенерированный get_snort_ruleset_file._get_kwargs выполняет quote(str(...), safe=""), поэтому ../ превращается в ..%2F на уровне передачи. Сервер, который выполняет URL-декодирование, а затем присоединяет путь к каталогу набора правил, всё равно выходит за его пределы.
Вызывающий, способный выполнить GET по этому пути, вместе с fleet JWT, читает файлы за пределами указанного набора правил от имени агента MIM на каждом зарегистрированном устройстве. При этом join не был выполнен. Лаборатория представляет собой гаджет decode-then-join публичного пути.
Это остаток 8 из 9. Карта: pfsense-api-nexus-mim. Тот же остаток с привязкой: fleet JWT. Это не jaredhendrickson/pfsense-api.
| ID | CVE пока нет |
| Класс | Чтение файлов (Snort {file} decode-then-join) |
| Доступ | Удалённый (аутентифицированный GET; fleet JWT повторно использует тот же путь для каждого device_id) |
| CWE | CWE-22 |
| CVSS | Высокий: 6.5 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N для JWT, который может выполнить GET файла набора правил Snort |
| Продукт | Netgate/pfsense-api (контроллер pfSense Plus Nexus MIM OpenAPI + сгенерированный клиент) |
| Затронуто | main b4acd7ca21dbca739a530403a1f62a7440f7726f (2026-05-25, Add Snort-3 API definitions) |
| Аутентификация | Примеры отправляют JWT контроллера после POST /login. В OpenAPI нет securitySchemes. Неаутентифицированный доступ не доказан. |
| Лицензия | GNU Affero GPL v3.0 |
| Лаборатория | Только 127.0.0.1:19181. Гаджет decode-then-join. Join не выполнен. |
Требуется вызывающий, способный выполнить GET /services/snort/rules/{name}/{file}. Официальные примеры используют Authorization: Bearer после POST /login, затем повторно применяют этот токен как base_url=.../api/device/pfsense/{device_id}/api для каждого id из GET /mim/devices. Сгенерированные операции по-прежнему типизируются как AuthenticatedClient | Client. CSRF здесь не задействован при этом GET.
| Метод | Sink | Привилегия | Лаборатория vs предположение |
|---|---|---|---|
GET сгенерированного клиента с {file}=../../oracle/WITNESS (wire ..%2F..%2Foracle%2FWITNESS) | decode unquote(file), затем join к каталогу именованного ruleset и чтение | Recon: HTTP 200, filename /oracle/WITNESS, тело PFSENSE-API-SNORT-FILE-JOIN-WITNESS. Предполагался файл .rules внутри этого ruleset. | Доказано в лаборатории (gadget). Плюс join не был выполнен. |
Тот же GET с {file}=../../../etc/passwd | тот же join, если контроллер отображает его на файловую систему устройства | Recon: чтение файлов хоста от имени агента MIM (Plus WebGUI/PHP обычно работает как root). В лаборатории использовался /oracle/WITNESS, а не живой /etc/passwd. | Предположение (тот же join, путь хоста). |
Тот же GET после fleet JWT (POST /login один раз, повтор на /api/device/pfsense/{id}/api) | прокси устройства для того же неограниченного {file} | Recon: один токен контроллера в стиле helpdesk читает файлы на каждом зарегистрированном устройстве, а не только на том, для которого предназначался вход. | Предположение (контракт G6 + этот путь). Плюс RBAC не выполнялся. |
Тот же остаток на {name} | GET|DELETE /services/snort/rules/{name} и соседние address/suppress {name} также являются неограниченными строками | Recon / целостность: переименование или удаление ruleset, имя которого является путём. | Предположение из спецификации. В лаборатории использовался только {file}. |
| Ограниченный контроль того же wire-пути | relative_to(ruleset_dir.resolve()) после decode | Закрыто. HTTP 400, без witness. | Доказано в лаборатории |
Безобидный {file}=local.rules | предполагаемое чтение ruleset | Закрыто. 200, sid:1, без witness, как при наивном, так и при контрольном варианте. | Доказано в лаборатории |
Смежные закрытые: клиентское percent-кодирование ../ (encoding=%2F). Контрольный join после decode (control-confined=yes). Плюс join контроллера (plus-join-not-executed). Предполагаемое чтение local.rules внутри ruleset foo. Неаутентифицированный GET (не доказан; примеры отправляют Bearer). POST /services/snort/rules archive / подложенный .so (предполагаемый IDS-администратор, а не этот остаток). Diagnostics PHP/shell. Запись файлов. Shell хоста. Этот остаток не является RCE.
Я зафиксировал Netgate/pfsense-api b4acd7ca21dbca739a530403a1f62a7440f7726f, потому что HEAD на 2026-10-14 не имел тегов, а последний коммит — "Add Snort-3 API definitions". Именно так этот путь и существует. В YAML нигде нет ни одного ключа pattern:. {name} и {file} — неограниченные строки. quote(..., safe="") — это клиентский энкодер, а не серверная проверка basedir.
/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=""),
),
Ошибочные повороты, уже зафиксированные: трактовка клиентской кодировки как закрывающего обхода; компоновка pfSense Plus; ранжирование архива Snort .so как этого остатка; утверждение о неаутентифицированном GET. Театр: обратная оболочка. Оракул — гаджет 200 из /oracle/WITNESS плюс контроль 400.
python3 pfsense-api-snort-file-join-Abraxas-Labs.py
или
cd lab
./run.sh
Цель — только http://127.0.0.1:19181. Проект Compose pfsense-api-snort-file-join. Гаджет decode-then-join публичного пути. Плюс join не выполняется. Стек сворачивается после запуска.
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