abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · pfsense-api-fleet-jwt
类别: IDOR 可达性: 远程
pfSense Plus MIM API(Netgate)固定于 b4acd7ca - github.com/Netgate/pfsense-api
一个 POST /login Bearer 令牌被复用于每一个已注册的 device_id。官方示例将该令牌复制到 /api/device/pfsense/{device_id}/api,用于 GET /mim/devices 返回的每一个 id。LoginResponse 字段仅有 token、user、version 和 alerts。LDAP MIM 角色是全局的,它们不绑定到设备。一个能够列出设备的控制器登录,使用为原本用于防火墙 A 的用户签发的令牌,与防火墙 B 通信。
这是单一客户租户,而非 SaaS 跨租户遗留问题。未认证调用仍返回 401。Plus RBAC 未被执行。系列中心:pfsense-api-nexus-mim(这是遗留问题 6)。从遗留问题 9(verify_ssl=False 示例)窃取的 JWT 就是这个 fleet 令牌。
| ID | 暂无 CVE |
| 类别 | IDOR |
| 可达性 | 远程(已认证的控制器 JWT) |
| CWE | CWE-639 |
| CVSS | 高危:8.1 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N |
| 产品 | Netgate/pfsense-api(pfSense Plus Nexus 多实例管理控制器 OpenAPI + 生成的客户端) |
| 受影响版本 | 固定于 b4acd7ca21dbca739a530403a1f62a7440f7726f(2026-05-25)。无标签。服务器仅限 Plus(付费)。不是 CE REST 包。 |
| 认证 | 来自 POST /login 的控制器 JWT。如果允许 GET /mim/devices,则无需猜测 device_id。 |
| 许可证 | GNU Affero GPL v3.0 |
| 实验环境 | 仅 127.0.0.1:19161。已发布客户端契约的 Python mock。无 Plus 镜像。 |
我固定了 github.com/Netgate/pfsense-api main b4acd7ca21dbca739a530403a1f62a7440f7726f(2026-05-25,Add Snort-3 API definitions)。这不是 jaredhendrickson/pfsense-api / pfrest.org。
一个能够列出设备的控制器登录,使用同一个 JWT 与每一个已注册实例通信。CSRF 不适用(Bearer API)。一旦代理接受 fleet 令牌,每台机器上的 PHP / shell / export-key 就是预期的管理员权限。遗留问题是缺少按设备绑定,而不是 Diagnostics: Command。
| 权限 | 方法 / 路径 | 实验室验证或推断 |
|---|---|---|
列出所有已注册的 device_id(名称、地址、状态) | 使用登录 Bearer 的 GET /mim/devices | 侦察(实验室验证)。 Mock 返回了 site-a 和 site-b。未认证为 401。 |
| 使用一个令牌读取防火墙 A 和防火墙 B 的系统状态 | GET /api/device/pfsense/{device_id}/api/system | 侦察(实验室验证)。 device-a=200,device-b=200。仅在 B 上见证。 |
| 在两个 id 上 POST 诊断 PHP 包装器 | POST /api/device/pfsense/{device_id}/api/diag/cmd/php | 完整性(仅在 mock 上实验室验证)。 两者均为 200。此外 RBAC 未执行,因此实时角色门控未经证实。 |
如果登录包含 console 或 system,则在每个已注册的机器上运行 PHP / shell / 文件编辑 | POST /diag/cmd/php、POST /diag/cmd/shell、GET/POST /diag/editfile、下载/上传 | 完整性(从 OAS + 示例推断)。 一旦代理接受集群令牌,即为预期的管理员权限。典型的 pfSense PHP-FPM 以 root 运行。 |
| 在每个列出的实例上导出 CA / 叶子私钥和 PKCS12 | GET /system/certificates/{refid}/export-key、GET /system/certauth/{refid}/export-key、GET /system/certificates/{refid}/export-p12 | 侦察(推断)。 相同的 JWT,与遗留 4 相同的 schema。此外 RBAC 未执行。 |
| 在每个列出的实例上转储 ACME 账户和已签发的密钥 | GET /services/acme 及相关账户/证书 GET | 侦察(推断)。 相同的 JWT,遗留 5 schema。 |
| 在每个列出的实例上更改别名、防火墙、接口、VPN、服务 | 相同的每设备前缀,其余 700+ 操作 | 完整性(推断)。 相同的 JWT,与本地实例相同的 schema。 |
| 在每个列出的实例上重启 / 关机 / 恢复配置 | POST /diag/control、POST /diag/backup/restore | 可用性 + 完整性(推断)。 相同的代理。未实际执行。 |
| 跨客户 SaaS IDOR | 不适用 | 已关闭。 单一客户租户。Netgate 在该租户中运行控制器。 |
| 未认证的 Internet 到 :8443 | 无 Bearer 的 GET /mim/devices | 本实验室中已关闭。 401。错误的 Bearer 也是 401。 |
| 离线设备 | 样本跳过 state != online | 由样本关闭。 不是认证边界。 |
对 device_id 的盲数字遍历 | GET /api/device/pfsense/1/api/system | 作为猜测要求已关闭。 Id 是长字符串(ED25519 形状)。列举消除了猜测。 |
需要: 一个被允许访问设备代理的控制器登录。到 MIM 控制器的网络路径(产品中为 :8443,本实验室中为回环 :19161)。不是未认证。不是 CSRF。
| 攻击面 | 原因 |
|---|---|
| 未认证列举 / php / shell | Mock 在无 Bearer 和错误 Bearer 时均为 401。规范对 securitySchemes 的沉默不是服务器 200。 |
| SaaS 跨租户 | 单一客户租户。营销页面上仍计划了租户集群。 |
mim_user / 无 Nexus 组 | 如果登录门控成立,则记录为已关闭。 |
功能性 mim_* 角色名称 | 它们存在(view、logs、usermgmt、services、firewall、interfaces、devices、system、superuser)。它们是全局的,不是按 device_id 的。实时 Plus 检查未执行(plus-rbac=not-executed)。 |
| 注册 SSH JSON 插值 | 遗留 1。不同的包。 |
注册 AutoAddPolicy | 遗留 2。不同的包。 |
| 无 JWT 的设备 unix socket | 遗留 3。本地,不同的包。 |
| CE REST 包 | jaredhendrickson/pfsense-api 是不同的服务器。 |
| CE WebGUI QinQ / OpenVPN 向导 / CARP / 限速器 / IPsec 横幅 | 已作为 CE 遗留问题发送邮件。此 OAS 中的字段名是相同的接收器,不是新的 CVE。 |
预期的 admins / mim_superuser 集群控制台 | 控制器所有者的产品模型。遗留问题是其他所有人缺失的对象 ACL。 |
我固定了 b4acd7ca 并对照 OpenAPI 阅读了官方示例。每个与受管防火墙通信的样本都这样做:
devApi = AuthenticatedClient(
base_url=CONTROLLER_URL+"/api/device/pfsense/{}/api".format(device.device_id),
verify_ssl=False,
headers={"Content-Type": "application/json"},
cookies=cookies,
token=token,
)
helper_funcs.py 中的 createDeviceApiChild 接收任意 device_id 字符串并复制 self.token。example.js 执行 newClient("device/pfsense/" + device.device_id + "/api") 然后 setBearer(client.getBearer())。example-upgrade-devices.py 中的 newDevApiClient 是同样的复制。GET /mim/devices 是列表预言机(仅标签过滤)。
pfapi_openapi.yml 和 py/pfapi/models/login_response.py 中的 LoginResponse 包含 token / user / version / alerts。LDAP 的 nexus_group_prefix 记录了角色 view, logs, usermgmt, services, firewall, interfaces, devices, system, superuser,并可选 read/modify/delete。这些名称中没有一个接受设备列表。
错误方向:将其视为 SaaS 跨租户 IDOR;将 OpenAPI 对 securitySchemes 的沉默视为未认证 RCE(mock 在没有 Bearer 时仍然返回 401);在没有运行 Plus 的情况下假设存在实时的 mim_view 与 mim_system 分离;将 POST /diag/cmd/php 评为未公开的 Critical(Netgate 将 Diagnostics: Command 视为预期的管理员权限)。表演性质:反向 shell。预言机是一个 token、两个设备 id、两者都返回 200,仅在 B 上有见证。
python3 pfsense-api-fleet-jwt-Abraxas-Labs.py
或
cd lab
./run.sh
Loopback 127.0.0.1:19161。Compose 项目 pfsense-api-fleet-jwt。已发布的登录和每设备代理契约的 Python mock。运行结束后栈会拆除。Pin YAML 摘录已打包在 poc.py 中;本地克隆仓库是可选的。无 Plus 镜像。见证 PFSENSE-API-FLEET-JWT-WITNESS。最后一行:
SUCCESS PFSENSE-API-FLEET-JWT one-token=yes device-a=200 device-b=200 schema-no-device-acl=yes plus-rbac=not-executed PFSENSE-API-FLEET-JWT-WITNESS
将 JWT 绑定到设备标签或允许列表,这样原本为防火墙 A 签发的登录令牌就无法调用 /api/device/pfsense/{B}/api。将控制器管理员与实例控制台分离,这样帮助台角色就无法对每一台已注册的设备执行 POST /diag/cmd/php。在 LoginResponse 中返回角色和允许的设备。将 GET /mim/devices 视为授权边界,而不是显示过滤器。