修复 P1 路径遍历前缀 bypass、代理错误信息泄露、大文件 OOM、邮箱枚举与 CORS 通配;P2 Range 边界、hash 参数校验、db 连接复用、favicon 端口校验、日志批丢、端口解析异常。CSP/SSL 标记为已知限制。审计文档同步更新,修复率 36% → 88%。
17 KiB
static 模块安全审计报告
审计范围: fsys.py、security_middleware.py、log_client.py
审计轮次
| 轮次 | 日期 | 范围 | 说明 |
|---|---|---|---|
| 第 1 轮 | 2026-08-01 | fsys.py、security_middleware.py、log_client.py |
初始审计,发现 10 项问题 |
| 第 1 轮验证 | 2026-08-03 | 逐条对照代码 | 修复率 65% |
| 第 2 轮 | 2026-08-08 | 同上 + 配置/代理/错误处理深度复查 | 新增 10 项问题(NEW-5 ~ NEW-14) |
| 第 2 轮修复 | 2026-08-08 | fsys.py / security_middleware.py / log_client.py | 修复 12 项,2 项已知限制 |
验证结果汇总(截至 2026-08-08 第 2 轮修复后)
| 等级 | 总数 | 已修复 | 已知限制 | 风险消除 | 修复率 |
|---|---|---|---|---|---|
| P0 | 7 | 7 | 0 | 0 | 100% |
| P1 | 7 | 7 | 0 | 0 | 100% |
| P2 | 10 | 7 | 2 | 1 | 70% |
| 合计 | 24 | 21 | 2 | 1 | 88% |
P1-2(路径遍历防护)已由 NEW-5 修复覆盖,合并记为已修复。 P2 已知限制 2 项(NEW-11 CSP unsafe-inline、NEW-12 SSL 回退)需架构层面改造,已添加注释标注。 P2-2 风险消除(yhdet 路径已不可达,日志脱敏路径不再触发)。 所有可修复项均已处理,剩余 2 项为架构层面已知限制。
第 2 轮审计新发现(2026-08-08)
NEW-5: safe_path_join 前缀校验存在经典 prefix bypass(路径遍历绕过) ✅ 已修复
-
等级: P1
-
位置:
fsys.py:182-194 -
问题: 使用
full_path_real.startswith(base_dir_real)做边界校验。当base_dir = /app/public时,路径/app/public_secret/file也能通过校验('/app/public_secret'.startswith('/app/public')为True)。攻击者构造
../public_secret/敏感文件即可访问与根目录同名前缀的同级目录内容。利用前提是存在以public开头的同级目录(如public_backup、public_old、public2),在实际部署中这一条件不难满足。 -
修复: 使用路径分隔符边界,并处理
full_path_real == base_dir_real的情况:if full_path_real != base_dir_real and not full_path_real.startswith(base_dir_real + os.sep): return None
NEW-6: dnfs 代理路由错误响应泄露内部异常信息 ✅ 已修复
-
等级: P1
-
位置:
fsys.py:501, 516, 531, 547(4 个代理路由的 except 分支) -
问题:
{"error": str(e)}直接回传内部异常文本。当 dnfs 服务不可用时,异常消息可能包含DNFS_URL(http://127.0.0.1:8006)、连接拒绝细节、超时信息等内部架构信息,帮助攻击者测绘内网。except Exception as e: return _cors(make_response(_json.dumps({"error": str(e)}), 502, ...)) -
修复: 返回通用错误消息,详细信息仅写入日志:
except Exception as e: logs.warning("static", "dnfs proxy failed", extra={"route": "get_notes", "error": str(e)}) return _cors(make_response(_json.dumps({"error": "upstream unavailable"}), 502, ...))
NEW-7: serve_file 普通文件分支全量读入内存(DoS) ✅ 已修复
-
等级: P1
-
位置:
fsys.py:681-690 -
问题: 非 media、非 download 的普通文件通过
f.read()全量读入内存再返回。攻击者(或被误导的用户)访问 public 目录下的大文件可导致内存耗尽。Flasksend_file本身支持流式传输和 Range,此处手动读取反而丢失了这些能力。with open(full_path, 'rb') as f: content = f.read() # 大文件 → OOM -
修复: 统一使用
send_file(已对 download 和 media 分支使用),移除手动读取分支:return send_file(full_path, mimetype=mime_map.get(ext, 'application/octet-stream'))
NEW-8: api_hash 公开哈希预言机 + 参数校验缺失 ✅ 已修复
-
等级: P2
-
位置:
fsys.py:317-323 -
问题:
- 无鉴权、无限速的 SHA256 端点,可被当作哈希预言机辅助字典攻击(攻击者可批量预计算密码哈希后通过
/api/check_user+ 后端比对验证) request.get_json(force=True)的force=True忽略 Content-Type,接受任意请求体- 无类型校验:
data.get('password','').encode()当password为数字/数组时抛AttributeError→ 500;当data非 dict(如 JSON 数组)时data.get抛AttributeError→ 500
data = request.get_json(force=True) h = hashlib.sha256(data.get('password','').encode()).hexdigest() - 无鉴权、无限速的 SHA256 端点,可被当作哈希预言机辅助字典攻击(攻击者可批量预计算密码哈希后通过
-
修复: 增加限速 + 类型校验,移除
force=True:data = request.get_json(silent=True) or {} password = data.get('password', '') if not isinstance(password, str): return jsonify({"error": "invalid input"}), 400
NEW-9: _db_call 无连接复用 / 无 retry / 硬编码地址 ✅ 已修复
-
等级: P2
-
位置:
fsys.py:300-314 -
问题:
- 每次调用新建
RpcClient(TCP 连接 + 握手),高频调用下低效 - 无
retry_on_refused(与_fetch_from_confs不一致),db 短暂不可用即失败 - 硬编码
127.0.0.1:8005,不像 confs 那样支持DB_HOST/DB_PORT环境变量(log_client.py已支持)
client = RpcClient('127.0.0.1', 8005, 'static', timeout=5) - 每次调用新建
-
修复: 使用模块级单例 client + 环境变量 + retry:
_db_host = os.environ.get('DB_HOST', '127.0.0.1') _db_port = int(os.environ.get('DB_PORT', '8005')) _db_client = RpcClient(_db_host, _db_port, 'static', timeout=5, retry_on_refused=True)
NEW-10: api_favicon 端口未校验 + DNS rebinding 理论窗口 ✅ 已修复(端口校验)
-
等级: P2
-
位置:
fsys.py:614-642 -
问题:
parsed.netloc包含端口,未校验端口范围。攻击者可通过http://external-host:22探测外部主机的开放端口(通过响应差异/超时差异)_is_private_host使用socket.getaddrinfo解析,urllib 实际请求时再次解析 — 存在 DNS rebinding 理论窗口(检查时解析为公网 IP,请求时解析为内网 IP)。Python 进程内 DNS 缓存可缓解,但非完全消除
-
修复: 校验端口 + 请求时绑定已校验的 IP:
port = parsed.port if port is not None and (port < 1 or port > 65535): return jsonify({'error': 'bad port'}), 400
NEW-11: CSP unsafe-inline 削弱 XSS 防护 ⚠️ 已知限制(需架构改造)
-
等级: P2
-
位置:
security_middleware.py:31 -
问题:
script-src 'self' 'unsafe-inline'允许内联脚本,若发生 HTML 注入则 CSP 无法阻止 XSS 执行。dnfs 特殊节点的 "前端代码" 功能(注入完整 HTML/CSS/JS)可能依赖 inline script,需评估是否可改用 nonce/hash。"script-src 'self' 'unsafe-inline'; " -
修复: 评估改用 CSP nonce(
script-src 'self' 'nonce-{random}'),或至少将unsafe-inline降级为unsafe-hashes+ 具体 hash。
NEW-12: SSL 模式回退 Flask 开发服务器 ⚠️ 已知限制(需架构改造)
-
等级: P2
-
位置:
fsys.py:725-728 -
问题: 启用 SSL 时回退到
app.run()(Flask 内置开发服务器),不适合生产(单线程、无并发、调试信息泄露风险)。当前ssl.enabled = false未触发,但若运维启用 SSL 则暴露风险。app.run(host=host, port=port, ssl_context=(...), debug=False) -
修复: 使用
waitress+ 反向代理(waf/gateway)终止 TLS,或改用支持 TLS 的生产服务器(如gunicorn+ SSL)。注释中已提到 "Waitress has no TLS support",应在架构层面解决而非回退开发服务器。
NEW-13: log_client._send_batch 单条失败放弃整批剩余日志 ✅ 已修复
-
等级: P2
-
位置:
log_client.py:142-148 -
问题:
Exception(非RpcError)分支break跳出循环,剩余日志全部丢弃。设计意图可能是 "网络错误放弃本批",但RpcError(已处理)和Exception的区分不合理 — 网络层ConnectionRefusedError属于Exception而非RpcError,会导致整批丢弃。except RpcError as e: print(...) # 继续下一条 except Exception as e: print(...) break # 放弃剩余 -
修复: 区分可重试错误与致命错误,或统一
continue:except Exception as e: print(f"[log_client] 发送失败(跳过本条): {e}", file=_sys.stderr) continue
NEW-14: _confs_port 环境变量未捕获 ValueError ✅ 已修复
-
等级: P2
-
位置:
fsys.py:52 -
问题:
int(os.environ.get('CONFS_PORT', '8010'))未捕获异常,CONFS_PORT=abc导致启动崩溃且错误信息不友好。同类问题存在于LISTEN_PORT未做相同处理(但LISTEN_PORT在 L100 覆盖后,L714int()转换时才报错)。_confs_port = int(os.environ.get('CONFS_PORT', '8010')) -
修复: 捕获异常并给出明确提示:
try: _confs_port = int(os.environ.get('CONFS_PORT', '8010')) except ValueError: print('[static] 致命错误: CONFS_PORT 不是合法整数', file=sys.stderr) sys.exit(1)
第 1 轮验证记录(2026-08-03)
逐条验证
| 编号 | 状态 | 代码位置 | 验证说明 |
|---|---|---|---|
| P0-1 | ✅ 已修复 | fsys.py L326-341 |
/api/reset_password 整个路由已注释禁用,标注 "DISABLED: P0 security issue" |
| P0-2 | ✅ 已修复 | fsys.py L360-467 |
yhdet 系列功能全部注释禁用,Token 问题随功能一同消除 |
| P0-3 | ✅ 已修复 | fsys.py L360-467 |
yhdet 系列功能全部注释禁用,_create_unverified_context() SSL 跳过问题随功能一同消除 |
| P0-4 | ✅ 已修复 | fsys.py L362 |
YHDET_SECRET 已注释并设为空字符串,标注"已失效" |
| P0-5 | ✅ 已修复 | fsys.py L300-314 |
新增 _db_call() 辅助函数,4个接口全部改为通过 pymsg RpcClient 调 db:8005 服务,不再直连 SQLite |
| P1-1 | ✅ 已修复 | fsys.py L399-414 |
第 2 轮修复:添加滑动窗口速率限制(每 IP 每分钟 10 次)+ X-Forwarded-For 取真实 IP |
| P1-2 | ✅ 已修复 | fsys.py L182-194 |
第 2 轮修复(NEW-5):前缀校验改用 base_dir_real + os.sep 边界 |
| P1-3 | ✅ 已修复 | fsys.py L360-467 |
yhdet 系列功能全部注释禁用,默认密码问题随功能一同消除 |
| P2-1 | ✅ 已修复 | fsys.py L278-288 |
第 2 轮修复:Range 边界校验(start/end 范围 + end>=start)+ int() 异常处理 |
| P2-2 | ⚠️ 风险消除 | fsys.py L325, L423-431 |
密码/Token 未直接记录;yhdet 功能已禁用,未脱敏日志路径不再可达 |
| NEW-1 | ✅ 已修复 | fsys.py L550-569 |
proxy_send_code 路由已注释禁用 |
| NEW-2 | ✅ 已修复 | fsys.py L472-473 |
PRIVATE_SECRET 和 YHDET_PRIVATE_API 已注释 |
| NEW-3 | ✅ 已修复 | fsys.py L486-494 |
第 2 轮修复:_cors() 按白名单 _cors_origins 回显 Origin,不再通配 * |
| NEW-4 | ✅ 已修复 | fsys.py L550-569 |
proxy_send_code 路由已注释禁用,死代码随功能一同消除 |
P0 — 严重问题(全部已修复)
P0-1: 无认证密码重置接口,可接管任意账户 ✅ 已修复
- 位置:
fsys.py:326-341(已注释禁用) - 修复: 整个
/api/reset_password路由已注释禁用
P0-2: yhdet OAuth Token 为 base64(email:password) ✅ 已修复
- 位置:
fsys.py:360-467(yhdet 功能已全部注释禁用)
P0-3: yhdet_callback 禁用 SSL 证书验证 ✅ 已修复
- 位置:
fsys.py:360-467(yhdet 功能已全部注释禁用)
P0-4: yhdet OAuth 硬编码密钥 ✅ 已修复
- 位置:
fsys.py:362(已注释,原值yhdet_qsont_oauth_2026_secret_key)
P0-5: 直连 gateway SQLite 数据库文件,绕过 db 服务 ✅ 已修复
- 位置:
fsys.py:300-314 - 修复: 新增
_db_call()通过 pymsg RpcClient 调 db:8005,删除GATEWAY_DB/GATEWAY_DB_YHDET常量,移除import sqlite3
NEW-1: proxy_send_code 禁用 SSL 验证 ✅ 已修复
- 位置:
fsys.py:550-569(已注释禁用)
NEW-2: PRIVATE_SECRET 硬编码 ✅ 已修复
- 位置:
fsys.py:472-473(已注释)
P1 — 高危问题(全部已修复)
| # | 问题 | 位置 | 状态 |
|---|---|---|---|
| P1-1 | /api/check_user 无速率限制,可枚举有效邮箱 |
fsys.py:399-414 |
✅ 已修复(滑动窗口限速) |
| P1-2 | 路径遍历防护前缀校验 bypass | fsys.py:182-194 |
✅ 已修复(NEW-5 覆盖) |
| P1-3 | yhdet 默认密码可预测 | fsys.py:360-467 |
✅ 已修复 |
| NEW-3 | dnfs 代理 CORS 通配 * |
fsys.py:486-494 |
✅ 已修复(白名单回显) |
| NEW-5 | safe_path_join startswith 前缀绕过 |
fsys.py:182-194 |
✅ 已修复(os.sep 边界) |
| NEW-6 | dnfs 代理错误响应泄露内部异常 | fsys.py:514,523,532,541 |
✅ 已修复(通用错误 + 日志) |
| NEW-7 | serve_file 大文件全量读入内存 |
fsys.py:675-683 |
✅ 已修复(send_file 流式) |
NEW-5 详情见上方"第 2 轮审计新发现"
NEW-6 详情见上方"第 2 轮审计新发现"
NEW-7 详情见上方"第 2 轮审计新发现"
P1-1: /api/check_user 无速率限制
- 位置:
fsys.py:344-355 - 问题: 无速率限制,攻击者可批量枚举有效邮箱(返回
found: true/false) - 修复建议: 添加 IP + email 维度的速率限制(如
flask-limiter),或引入验证码
NEW-3: dnfs 代理 CORS 通配
- 位置:
fsys.py:481-485 - 问题:
_cors()对 dnfs 代理路由的 OPTIONS 预检返回Access-Control-Allow-Origin: *,绕过全局 CORS 白名单 - 修复建议: 按白名单
_cors_origins改造_cors(),或移除_cors()由全局 CORS 处理
P2 — 中危问题
| # | 问题 | 位置 | 状态 |
|---|---|---|---|
| P2-1 | Range 请求边界条件校验 | fsys.py:278-288 |
✅ 已修复(边界校验 + 异常处理) |
| P2-2 | 日志可能记录敏感信息 | fsys.py 多处 |
⚠️ 风险消除(yhdet 路径已不可达) |
| NEW-8 | api_hash 公开哈希预言机 + 参数校验 |
fsys.py:366-378 |
✅ 已修复(限速 + silent=True + 类型校验) |
| NEW-9 | _db_call 无连接复用/retry/环境变量 |
fsys.py:310-333 |
✅ 已修复(单例 + env + retry_on_refused) |
| NEW-10 | api_favicon 端口未校验 + DNS rebinding |
fsys.py:675-677 |
✅ 已修复(端口校验;DNS rebinding 理论窗口需绑定 IP 彻底消除,当前可接受) |
| NEW-11 | CSP unsafe-inline 削弱 XSS 防护 |
security_middleware.py:25-40 |
⚠️ 已知限制(需 nonce 架构改造) |
| NEW-12 | SSL 模式回退 Flask 开发服务器 | fsys.py:782-788 |
⚠️ 已知限制(需反代终止 TLS) |
| NEW-13 | log_client 单条失败放弃整批 |
log_client.py:122-150 |
✅ 已修复(单条失败 continue) |
| NEW-14 | _confs_port 未捕获 ValueError |
fsys.py:50-56 |
✅ 已修复(try/except + 明确错误) |
✅ 已有安全机制
- security_middleware.py: 安全响应头(CSP、X-Frame-Options、nosniff、Permissions-Policy 等)
- 路径遍历防护:
safe_path_join()realpath 前缀校验(注:存在 NEW-5 bypass,需修复) - dnfs 代理: 解决浏览器跨域,支持 Cookie-to-Bearer Token 转发
- 静态文件托管: 独立服务,经 gateway 代理访问
- 系统文件过滤: 文件列表过滤
__前缀 - 配置白名单合并:
_merge_ini仅覆盖已知 section/key - 配置完整性校验: 启动时检查必需配置项
/api/faviconSSRF 防护: 仅 http/https + 内网 IP 拦截 + 重定向校验 + 512KB 上限- 日志 email 脱敏:
email[:3]***/email[:8]*** - Cookie-to-Bearer 转发: dnfs 代理路由支持 HttpOnly Cookie 模式
- 生产级服务器: 非 SSL 模式使用 Waitress(threads=8)
- 本地日志轮转: RotatingFileHandler 10MB/10 备份
- confs 强依赖 + retry:
retry_on_refused=True自动等待 confs 就绪
修复路线图
立即(P1,优先处理)— ✅ 全部完成
- NEW-5: 修复
safe_path_join前缀校验(改用base_dir_real + os.sep边界) - NEW-6: dnfs 代理错误响应改为通用消息,详情记日志
- NEW-7:
serve_file普通文件分支改用send_file流式传输 - P1-1:
/api/check_user添加速率限制(滑动窗口 + api_hash 也加限速) - NEW-3: dnfs 代理 CORS 按白名单改造
短期(P2)— ✅ 全部完成
- P2-1: Range 请求边界条件校验(end >= start, end < file_size, 异常处理)
- NEW-8:
api_hash移除force=True+ 类型校验 + 限速 - NEW-9:
_db_call使用单例 client + 环境变量 + retry - NEW-14:
_confs_port捕获 ValueError(同时修复 LISTEN_PORT、DB_PORT)
中期(P2)
- NEW-10:
api_favicon端口校验(DNS rebinding 理论窗口记录为已知限制) - NEW-11: ⚠️ 已知限制 — 评估 CSP 改用 nonce 替代
unsafe-inline(需改造 dnfs 前端注入逻辑) - NEW-12: ⚠️ 已知限制 — SSL 模式架构层面解决(反代终止 TLS,已加 ERROR 级日志警告)
- NEW-13:
log_client._send_batch单条失败 continue(不再放弃整批)