pfSense Plus MIM API 工具包 b4acd7ca - 高危 - 远程 - 文件读取 - Snort 规则 {file} 先解码再拼接
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 agent 身份读取指定规则集之外的文件。且拼接操作并未执行。该实验环境是公开路径的“先解码后拼接”利用组件。
这是 9 个遗留问题中的第 8 个。地图:pfsense-api-nexus-mim。同一固定版本的遗留问题:fleet JWT。这不是 jaredhendrickson/pfsense-api。
| ID | 暂无 CVE |
| 类别 | 文件读取(Snort {file} 先解码后拼接) |
| 可达性 | 远程(已认证 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,针对可 GET Snort 规则集文件的 JWT |
| 产品 | Netgate/pfsense-api(pfSense Plus Nexus MIM 控制器 OpenAPI + 生成的客户端) |
| 受影响版本 | main b4acd7ca21dbca739a530403a1f62a7440f7726f(2026-05-25,Add Snort-3 API definitions) |
| 认证 | 示例在 POST /login 后发送控制器 JWT。OpenAPI 无 securitySchemes。未认证情况未经验证。 |
| 许可证 | GNU Affero GPL v3.0 |
| 实验环境 | 仅 127.0.0.1:19181。先解码后拼接利用组件。拼接操作未执行。 |
需要一个能够 GET /services/snort/rules/{name}/{file} 的调用者。官方示例在 POST /login 后使用 Authorization: Bearer,然后将该令牌作为 base_url=.../api/device/pfsense/{device_id}/api 重放,用于 GET /mim/devices 返回的每个 id。生成的操作为 AuthenticatedClient | Client 类型。此 GET 请求不涉及 CSRF。
| 方法 | 汇聚点 | 权限 | 实验室验证 vs 推断 |
|---|---|---|---|
生成的客户端 GET 请求,带 {file}=../../oracle/WITNESS(线上为 ..%2F..%2Foracle%2FWITNESS) | 解码 unquote(file) 后拼接到指定的规则集目录并读取 | 侦察: HTTP 200,filename 为 /oracle/WITNESS,响应体为 PFSENSE-API-SNORT-FILE-JOIN-WITNESS。预期是该规则集内的一个 .rules 文件。 | 实验室验证(gadget)。拼接未被执行。 |
同样的 GET 请求,带 {file}=../../../etc/passwd | 如果控制器将其映射到设备文件系统,则同样的拼接 | 侦察: 以 MIM 代理身份读取主机文件(Plus WebGUI/PHP 通常为 root)。实验室使用 /oracle/WITNESS,而非真实的 /etc/passwd。 | 推断(同样的拼接,主机路径)。 |
在 fleet JWT 之后执行同样的 GET 请求(POST /login 一次,在 /api/device/pfsense/{id}/api 上重放) | 同一未受限 {file} 的设备代理 | 侦察: 一个 helpdesk 形态的控制器令牌即可读取所有已注册设备上的文件,而不仅仅是登录所针对的那一台。 | 推断(G6 契约 + 此路径)。RBAC 未被执行。 |
同样的遗留问题出现在 {name} 上 | GET|DELETE /services/snort/rules/{name} 以及同级的 address/suppress {name} 也是未受限字符串 | 侦察 / 完整性: 重命名或删除名称是路径的规则集。 | 根据规范推断。实验室仅驱动了 {file}。 |
| 对同一线上路径的受限控制 | 解码后执行 relative_to(ruleset_dir.resolve()) | 已关闭。HTTP 400,无见证。 | 实验室验证 |
正常的 {file}=local.rules | 预期的规则集读取 | 已关闭。200,sid:1,无见证,朴素与对照均如此。 | 实验室验证 |
相邻已关闭项: 客户端对 ../ 的百分号编码(encoding=%2F)。解码后的控制拼接(control-confined=yes)。Plus 控制器拼接(plus-join-not-executed)。规则集 foo 内对 local.rules 的预期读取。未认证 GET(未证实;示例发送 Bearer)。POST /services/snort/rules 归档 / 植入 .so(预期的 IDS 管理员操作,非此遗留问题)。诊断 PHP/shell。文件写入。主机 shell。此遗留问题不是 RCE。
我固定了 Netgate/pfsense-api 的 b4acd7ca21dbca739a530403a1f62a7440f7726f,因为 2026-10-14 的 HEAD 没有标签,且最新提交是 "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。剧场:反向 shell。预言机是 /oracle/WITNESS 的 gadget 200 加上 control 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 gadget。Plus 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
../../../etc/passwd 的客户端编码为 ..%2F..%2F..%2Fetc%2Fpasswd。对 /services/snort/rules/foo/..%2F..%2Foracle%2FWITNESS 的朴素 GET 请求返回 200,文件名为 /oracle/WITNESS,响应体为 PFSENSE-API-SNORT-FILE-JOIN-WITNESS。在受限控制下,同一请求在规则集根目录下解析,返回 400 且无见证。良性的 local.rules 保持在 foo 内。
解码后在规则集根目录下解析。拒绝 .. 和绝对路径。在读取之前,先 Path.resolve() 再 relative_to(ruleset_dir.resolve()),或等效操作。在 OpenAPI 中将 {file}(以及 {name})约束为规则集安全模式,以便生成器停止发送路径形状的字符串。
b4acd7ca21dbca739a530403a1f62a7440f7726fpfapi_openapi.yml /services/snort/rules/{name}/{file}get_snort_ruleset_file.pyGNU Affero GPL v3.0。参见 LICENSE。仅限回环实验室。无担保。