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

188 lines
8.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 外网攻击面安全巡检报告(E1–E6)
**日期**2026-06-06
**范围**:生产 `https://api.synaglobal.vip` 无 JWT / 弱认证暴露面
**信任边界**:已登录管理员视为可信;本轮不审计持 JWT 后的管理操作
**模式**audit_only(仅报告与 backlog**本轮不修改应用代码**)
**黑盒证据**[`2026-06-06-external-attack-probe-production.md`](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`](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/P10**(无未授权 RCE、写库、读密文、重装成功)。
---
## 3. Agent M2M 滥用链(E3
### 3.1 认证逻辑
```mermaid
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 key`check_script_callback_rate` 每 job 10/min、每 IP 60/min`script_callback_rate.py`)。
- **生产探测**:假 Key 或 global Key → **401**
**与 risk-acceptance 关系**:接受项 2 已于 2026-06-07 收口,见 [`risk-acceptance-single-operator.md`](../project/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-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`](2026-06-06-external-attack-hardening-backlog.md) |
### 分级统计
- **P0**0
- **P1**0
- **P2**2EXT-01、EXT-02
- **H**2+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 心跳。