收紧 PUBLIC 路径误匹配、强制 health/detail 与壁纸 sync 鉴权、默认关闭 OpenAPI; 安装锁定后 /api/install/* 统一 404;附探测脚本、测试与审计文档。 Co-authored-by: Cursor <cursoragent@cursor.com>
8.3 KiB
外网攻击面安全巡检报告(E1–E6)
日期:2026-06-06
范围:生产 https://api.synaglobal.vip 无 JWT / 弱认证暴露面
信任边界:已登录管理员视为可信;本轮不审计持 JWT 后的管理操作
模式:audit_only(仅报告与 backlog,本轮不修改应用代码)
黑盒证据:2026-06-06-external-attack-probe-production.md
1. 攻击面资产表(E1)
来源:server/api/auth_jwt.py PUBLIC_PREFIXES、server/main.py 路由与静态挂载、OpenAPI 交叉核对。
1.1 JWT 中间件跳过路径(PUBLIC_PREFIXES)
| 前缀/路径 | 方法 | 二次鉴权 | 说明 |
|---|---|---|---|
/api/auth/login |
POST | 无 | 暴破/枚举面;15min 锁定 |
/api/auth/refresh |
POST | HttpOnly cookie | 刷新令牌 |
/api/auth/logout |
POST | body refresh | 吊销 refresh |
/api/agent/* |
POST | X-API-Key + per-server |
heartbeat、script-callback |
/api/install/* |
GET/POST | 安装锁 + install_token | 已锁定除 status 外 403 |
/api/settings/bing-wallpaper(s) |
GET | 无 | 只读壁纸 URL |
/api/settings/bing-wallpapers/sync |
POST | 应为 JWT;见 §2 FINDING | 前缀匹配导致误放行 |
/health |
GET | 无 | 纯文本 ok |
/health/detail |
GET | 应为 JWT;见 §2 FINDING | 前缀 /health 误匹配 |
/ws/* |
WS | Query JWT / WebSSH token | ADR-011 |
/openapi.json /docs /redoc |
GET | 无 | FastAPI 默认文档 |
其余 /api/* |
* | JwtAuthMiddleware Bearer | 401 无 token |
注意:JwtAuthMiddleware 仅拦截 path.startswith("/api/");/health/detail 不走中间件,完全依赖 Depends(get_current_admin),与 PUBLIC_PREFIXES 前缀匹配叠加后失效。
1.2 静态与前端壳
| 路径 | 认证 | 文件 |
|---|---|---|
/app/* |
无(SPA);AppAuth 仅校验 refresh cookie 格式 |
web/app/ |
/agent/install.sh agent.py agent.sh uninstall.sh |
无 | web/agent/ |
/app/install.html |
安装模式;锁定后归档为 .bak |
安装向导 |
1.3 Install API 端点(prefix=/api/install)
| 端点 | 方法 | 已锁定生产实测 |
|---|---|---|
/status |
GET | 404(_is_locked()) |
/env-check |
GET | 403 |
/connection-check |
GET | 403 |
/state |
GET | 403 |
/test-credentials |
POST | 403(合法 body) |
/init-db |
POST | 403 |
/create-admin |
POST | 403 + install_token |
/lock |
POST | 403 |
1.4 Agent API
| 端点 | 认证链 |
|---|---|
POST /api/agent/heartbeat |
Header X-API-Key → _verify_server_api_key(server_id) |
POST /api/agent/script-callback |
同上 + job_id/secret + Redis 限速 |
控制面无 /api/agent/exec(P0 RCE 已移除,注释见 agent.py:378)。
2. 生产黑盒探测(E2)
见 2026-06-06-external-attack-probe-production.md。
FINDING 汇总
| ID | 级别 | 问题 | 根因(代码) |
|---|---|---|---|
| EXT-01 | P2 | GET /health/detail 无 JWT 返回组件状态 |
PUBLIC_PREFIXES 含 /health,_is_public_path 使 get_current_admin 无凭证返回 None;handler 未校验 admin |
| EXT-02 | P2 | POST /api/settings/bing-wallpapers/sync 无 JWT 可触发 Bing 下载 |
同上:/api/settings/bing-wallpapers 前缀匹配 /sync 子路径 |
| EXT-03 | H | /openapi.json /docs /redoc 公网可访问 |
main.py 未设 docs_url=None;前缀在 PUBLIC |
| EXT-04 | H | 外网 WS 握手 HTTP 403,无法验证 4001/4401 | 反代/Nginx 规则;应用层逻辑见 websocket.py |
| — | PASS | 管理 API、假 Agent Key、锁定 install | JwtAuthMiddleware / _reject_if_locked |
P0/P1:0(无未授权 RCE、写库、读密文、重装成功)。
3. Agent M2M 滥用链(E3)
3.1 认证逻辑
sequenceDiagram
participant Attacker
participant API as Nexus_API
participant Redis
participant WS as WebSocket_clients
Note over Attacker: 持有泄露的 global API_KEY
Attacker->>API: POST /api/agent/heartbeat<br/>X-API-Key + server_id=N
API->>API: server 存在且无 agent_api_key?
alt legacy 服务器
API->>Redis: HSET heartbeat:N
API->>WS: broadcast_alert 若超阈值
API-->>Attacker: 200 ok
else 已有 per-server key
API-->>Attacker: 401
end
- 全局 Key 回退:
server/api/agent.py:84-92— 仅当服务器未设置agent_api_key时接受settings.API_KEY。 - 影响:伪造心跳 → 污染在线状态/告警/Telegram;非控制面 RCE(exec 在子机
web/agent/agent.py)。 - script-callback:需有效
job_id+secret(一次性);check_script_callback_rate每 job 10/min、每 IP 60/min(script_callback_rate.py)。 - 生产探测:假 Key → 401。
与 risk-acceptance 关系:global Key legacy 仍为接受项;外网视角下 Key 泄露 + 未迁移服务器 = P2 伪造状态,维持文档化接受,见 risk-acceptance-single-operator.md §接受项 2。
4. 安装向导边界(E4)
4.1 令牌逻辑(代码)
_verify_install_token:secrets.compare_digest+ 状态文件install_token(install.py:933-946)。- 无状态文件 → 403;暴力需猜 256-bit 级 token,不可行。
create-admin/lock需 token 或已完成步骤。
4.2 已安装 + 锁定(生产)
- BL-05(2026-06-07):全部
/api/install/*→ 404Not found(含 status、env-check、init-db、lock 等),无中文指纹。 - 无法外网重装(init-db/create-admin 被锁)。
4.3 残余 H
- 未锁定环境下
/status会返回installed/locked/configured(仅安装模式相关)。 - OpenAPI 仍暴露 install 路径 schema(EXT-03)。
5. 认证与会话(E5)
| 项 | 结论 |
|---|---|
| 登录暴破 | MAX_LOGIN_FAILURES + 15min 锁定(auth_service.py:122-130);本轮未跑循环暴破 |
| IP allowlist | 仅 login()(auth_service.py:98-120);拿到 JWT 后不受 allowlist 约束 |
| 用户名枚举 | 生产单次错误登录:用户名或密码错误(不存在与密码错误文案统一)— PASS |
| Refresh | HttpOnly + Secure + SameSite=Lax;重用检测见 tests/test_auth_refresh_reuse.py |
| Logout CSRF | SameSite=Lax 降低跨站 POST 风险 |
外网主要面:/api/auth/login 暴破(有锁定)、refresh cookie 窃取(需 XSS/本机恶意软件)。
6. 总结与验收
| 验收项 | 状态 |
|---|---|
| 生产黑盒脚本 + 证据 | ✅ scripts/security_probe_external.sh + probe 报告 |
| PUBLIC_PREFIXES + static 覆盖 | ✅ §1 |
| 0 未解释 P0/P1 | ✅ |
| 加固 backlog 独立 | ✅ 2026-06-06-external-attack-hardening-backlog.md |
分级统计
- P0:0
- P1:0
- P2:2(EXT-01、EXT-02)
- H:2+(EXT-03、EXT-04、OpenAPI 指纹、Agent legacy 已接受)
与 B1–B6 API 巡检关系
| 已完成 B 轮 | 本轮 E 轮 |
|---|---|
| 鉴权后逻辑、输入上限 | 无 JWT 暴露面 |
| 当场修复若干 schema 上限 | audit_only,修复进 backlog |
7. 待定加固项(不排期)
以下项已记入 2026-06-06-external-attack-hardening-backlog.md,本轮不实施:
| ID | 项 | 说明 |
|---|---|---|
| BL-06 | Agent per-server key 迁移 | 存量子机逐步分配 agent_api_key,监控 global API_KEY legacy 回退日志;见 risk-acceptance-single-operator.md §接受项 2 |
| BL-07 | WebSocket 反代核对 | 生产 Nginx /ws/ 的 Upgrade/Connection 配置;外网探测曾得 HTTP 403 而非应用层 4001/4401 |
| BL-08 | 全站 IP 白名单(可选) | Nginx 层限制 /api/*;须为 /api/agent/* 单独放行(2000+ 子机心跳) |
已实施加固:BL-01~BL-05(见 backlog 与 changelog)。
下一步:部署 BL-05 后重跑 security_probe_external.sh;BL-06~08 按运维需要再排期。