188 lines
8.0 KiB
Markdown
188 lines
8.0 KiB
Markdown
# 外网攻击面安全巡检报告(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/P1:0**(无未授权 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-06,2026-06-07)** — Agent 仅接受 per-server `agent_api_key`。
|
||
- **影响**:伪造心跳需持有目标服务器的 per-server key;global `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-05(2026-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 路径 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`](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-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-01~BL-06(见 backlog 与 changelog)。
|
||
|
||
---
|
||
|
||
**下一步**:部署后重跑 `security_probe_external.sh`;新增第一台子机时走「创建 → 远程安装 Agent」验证 per-server 心跳。
|