Files
Nexus/docs/reports/2026-06-06-external-attack-surface-audit.md
T
Nexus Agent eab35a35be fix(security): BL-06 强制 Agent per-server key 并合并心跳单次查库
移除 Agent 认证与安装链路的 global API_KEY 回退,收口 risk-acceptance 接受项 2;巡检补修心跳重复 get_server 并加 assert_called_once 回归测试。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 11:17:41 +08:00

8.0 KiB
Raw Blame History

外网攻击面安全巡检报告(E1E6

日期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_PREFIXESserver/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/execP0 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 无凭证返回 Nonehandler 未校验 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/P10(无未授权 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 已移除(BL-062026-06-07 — Agent 仅接受 per-server agent_api_key
  • 影响:伪造心跳需持有目标服务器的 per-server keyglobal API_KEY 不再通行。
  • script-callback:需有效 job_id + secret(一次性)+ per-server keycheck_script_callback_rate 每 job 10/min、每 IP 60/minscript_callback_rate.py)。
  • 生产探测:假 Key 或 global Key → 401

与 risk-acceptance 关系:接受项 2 已于 2026-06-07 收口,见 risk-acceptance-single-operator.md §接受项 2。


4. 安装向导边界(E4

4.1 令牌逻辑(代码)

  • _verify_install_tokensecrets.compare_digest + 状态文件 install_tokeninstall.py:933-946)。
  • 无状态文件 → 403;暴力需猜 256-bit 级 token不可行
  • create-admin / lock 需 token 或已完成步骤。

4.2 已安装 + 锁定(生产)

  • BL-052026-06-07:全部 /api/install/*404 Not found(含 status、env-check、init-db、lock 等),无中文指纹。
  • 无法外网重装(init-db/create-admin 被锁)。

4.3 残余 H

  • 未锁定环境下 /status 会返回 installed/locked/configured(仅安装模式相关)。
  • OpenAPI 仍暴露 install 路径 schemaEXT-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

分级统计

  • P00
  • P10
  • P22EXT-01、EXT-02
  • H2+EXT-03、EXT-04、OpenAPI 指纹、Agent legacy 已接受)

与 B1B6 API 巡检关系

已完成 B 轮 本轮 E 轮
鉴权后逻辑、输入上限 无 JWT 暴露面
当场修复若干 schema 上限 audit_only,修复进 backlog

7. 加固项状态(2026-06-07 更新)

ID 状态
BL-06 Agent per-server key 迁移 已实施2026-06-07):移除 global API_KEY 回退,强制 per-server agent_api_key;见 changelog 2026-06-07-agent-per-server-key-enforced.md
BL-07 WebSocket 探测口径 已接受:无 token 外网 HTTP 403 为应用拒绝握手;登录后 WS 正常;见 deploy-verification §WebSocket
BL-08 全站 IP 白名单 不需要(运维确认不实施;维持仅 login allowlist

已实施加固BL-01BL-06(见 backlog 与 changelog)。


下一步:部署后重跑 security_probe_external.sh;新增第一台子机时走「创建 → 远程安装 Agent」验证 per-server 心跳。