ds2api / security_best_practices_report.md
luckfun233
security: 修复管理员鉴权与配置导出泄露等严重漏洞
c37473d
|
Raw
History Blame Contribute Delete
17.4 kB

ds2api 安全审计报告

概述

本次安全审计针对部署在 Hugging Face Spaces 上的 ds2api(DeepSeek 反向代理)项目。背景是该用户的其他 HF Spaces 遭到猛烈的扫描攻击(探测 /.git/config/.env/wp-config.php.bak/proc/self/environ/phpinfo.php 等敏感路径),因此对当前项目进行全面审查以排除潜在隐患。

审计覆盖范围:

  • Go 后端(chi router)+ React/Vite 前端 webui + Cloudflare Worker(cron 自愈)+ Node.js chat-stream(Vercel runtime)
  • JWT (HS256) 管理员鉴权 + 可选 sha256 密码哈希
  • DeepSeek PoW + 多账号池 + SOCKS5 代理池
  • devcapture(请求/响应体内存捕获)+ rawsample(持久化到 tests/raw_stream_samples/
  • 协议适配器边界(OpenAI/Claude/Gemini/Ollama)

严重问题已在本次提交中直接修复并通过测试验证(详见"已修复问题"章节)。本报告其余章节列出尚未修复的中低风险问题,建议按优先级排期处理。


已修复问题(本次提交)

FIXED-1: 默认管理员密钥为 "admin"(CRITICAL)

位置: internal/auth/admin.go:32-45(修复前)

问题: effectiveAdminKeyDS2API_ADMIN_KEY 未设置且未配置 password_hash 时,硬编码返回 "admin"。这意味着任何未配置管理员密钥的部署(包括全新 HF Space、本地开发实例)都能用 admin 作为 admin_key 登录后台。扫描攻击日志显示攻击者正在批量探测 /admin/admin/config 等路径。

影响: 任何知道项目源码的攻击者可以直接登录未配置管理员密钥的部署,获取所有 DeepSeek 账号、代理、API key 等敏感配置。

修复: effectiveAdminKey 在未配置任何凭据时返回空字符串,禁用密码登录。VerifyAdminCredential 已有空检查会拒绝空 key。同时把警告日志从"使用不安全默认 admin"改为"管理员登录已禁用,请配置 DS2API_ADMIN_KEY 或在管理面板设置密码"。

FIXED-2: JWT secret 回退到 "admin"(CRITICAL)

位置: internal/auth/admin.go:47-57(修复前)

问题: jwtSecret 在没有 DS2API_JWT_SECRET、没有 password_hash 时回退到 effectiveAdminKey(store),也就是上面的 "admin"。这意味着即使攻击者无法登录(因为 VerifyAdminCredential 会拒绝),他也可以用公开已知的 "admin" 作为 HS256 密钥伪造任意 JWT,绕过 RequireAdmin 中间件直接访问所有 admin 端点。

影响: 与 FIXED-1 等价 —— 任何未配置 JWT secret 的部署,攻击者可伪造 JWT 直接访问所有 /admin/* 端点,包括导出全部账号密码的 /admin/config/export

修复: 新增 fallbackJWTSecret(),使用 crypto/rand 生成 32 字节进程级随机 secret(一次性生成、sync.Once 缓存),确保 JWT 签名密钥不可预测。即使攻击者知道历史默认值 "admin",也无法伪造能通过验证的 token。

FIXED-3: /admin/config/export 泄露明文密码和 live token(HIGH)

位置: internal/httpapi/admin/configmgmt/handler_config_read.go:66-78(修复前)

问题: configExport 返回的 config 字段是 h.Store.Snapshot() 的原始对象,包含:

  • accounts[].password(DeepSeek 账号明文密码)
  • accounts[].token实时有效的 DeepSeek 会话 token,可被重放)
  • proxies[].password(SOCKS5 代理密码)
  • vercel.token(Vercel API token,第三方凭据)
  • admin.password_hash(管理员密码哈希,可用于伪造 JWT)

虽然该端点要求 Bearer token 鉴权,但一旦 admin JWT 泄露(XSS、浏览器扩展、日志捕获、共享开发机器),所有密码和 live token 立刻泄露。前端实际只用 json/base64 字段(用于迁移),config 字段是冗余的展示性快照。

影响: 通过 admin token 泄露链,攻击者可一次性获取所有 DeepSeek 账号凭据,可被用于登录 DeepSeek 网站或重放 API 调用。

修复:

  1. 新增 Config.RedactSecrets() 方法,返回清除所有 token/password/vercel token/admin password_hash 的快照副本。
  2. configExportconfig 字段改用 snap.RedactSecrets(),前端展示用,永不包含敏感数据。
  3. json/base64 字段保留密码(迁移用途必需 —— 新部署需要密码才能登录 DeepSeek 获取新 token),但 accounts[].token 仍由 ExportJSONAndBase64ClearAccountTokens() 清除。
  4. 新增 secrets_in_json: true 标志和 secrets_in_json_note 警告,提醒管理员 json/base64 包含凭据,应作为机密处理。

FIXED-4: 删除可疑的 _fix.py_fix2.py(MEDIUM)

位置: /workspace/_fix.py/workspace/_fix2.py

问题:

  • _fix.py 是一次性脚本,用于修改 Dockerfile 的 CMD(已被 entrypoint.sh 取代)。
  • _fix2.py 使用 chr() 字符编码混淆访问 "Dockerfile"chr(68)+chr(111)+chr(99)+chr(107)+chr(101)+chr(114)+chr(102)+chr(105)+chr(108)+chr(101)),这种混淆在安全审计中属于可疑模式 —— 攻击者常用此技巧逃避 grep "Dockerfile" 类的静态扫描。虽然内容看起来是良性的(patch Dockerfile),但既然已经废弃,不应留在仓库里。审计期间也发现 _fix2.py 还有语法错误(old=CMD [/usr/local/bin/ds2api] 缺引号),说明它从未实际运行过。

影响: 低,但会误导后续审计、增加攻击面认知噪音。

修复: 直接删除两个文件。

FIXED-5: devcapture 默认在非 Vercel 启用,捕获完整 prompt/response(MEDIUM)

位置: internal/devcapture/store.go:75-97(修复前)

问题: NewFromEnv 的逻辑是 enabled := !isVercelRuntime(),即在 HF Spaces、本地开发、Docker 部署等所有非 Vercel 环境下默认启用 devcapture。该模块会捕获每次请求的完整 body 和响应 body(最大 5MB),存于内存并通过 /admin/dev/captures 端点暴露。对于 DeepSeek 代理来说,请求 body 包含用户的完整 prompt(可能含敏感对话内容),响应 body 包含完整 LLM 回复。

影响:

  • 内存占用:高并发下 20 条 × 5MB = 100MB,可能触发 OOM。
  • 隐私泄露:管理员 JWT 泄露后,攻击者可读取最近 20 条用户的完整 prompt/response。
  • 默认开启:大多数部署者不知道此功能存在。

修复: 改为默认禁用,必须显式设置 DS2API_DEV_PACKET_CAPTURE=1 才启用。同时移除不再使用的 isVercelRuntime() 辅助函数。


待修复问题(按优先级排序)

TODO-1: /admin/login 无速率限制(MEDIUM)

位置: internal/httpapi/admin/auth/handler_auth.gologin handler

问题: 登录端点直接调用 VerifyAdminCredential,没有任何速率限制、IP 封禁或失败计数。攻击者可对 admin key 进行暴力枚举。

影响:

  • 当 admin key 较弱(如 admin123password)时,可被字典攻击破解。
  • 即使 admin key 强,暴力请求也会消耗服务器资源。

建议修复:

  1. login handler 中维护进程级失败计数器(IP → 失败次数 + 最后失败时间)。
  2. 同一 IP 连续失败 5 次后,5 分钟内拒绝所有登录请求(返回 429)。
  3. 失败计数器在成功登录或时间窗口过后重置。
  4. 可选:使用 golang.org/x/time/rate 实现 token bucket。

注意: HF Spaces 通常有多副本,进程级限流可被绕过(每个副本独立计数)。若需更强保护,可结合 Cloudflare Worker 或外部 WAF。

TODO-2: rawsample 持久化完整 prompt/response 到磁盘(MEDIUM)

位置: internal/httpapi/admin/rawsamples/handler_raw_samples.go + internal/rawsample/rawsample.go

问题: 当 DS2API_RAW_SAMPLE_CAPTURE=1 时,系统会把请求/响应的完整原始字节流持久化到 tests/raw_stream_samples/ 目录。每个 sample 包含:

  • 完整的用户 prompt
  • DeepSeek 的完整响应
  • 账号 identifier
  • 时间戳

这些文件以 .json 形式落盘,没有加密,且在 git 提交时可能被意外加入。在生产 HF Space 上启用时,/data/ 持久化卷会一直保留这些 sample。

影响:

  • 隐私:用户 prompt 持久化到磁盘。
  • 合规:可能违反 GDPR/CCPA 等数据最小化原则。
  • 攻击面:管理员 JWT 泄露后,攻击者可下载历史 sample。

建议修复:

  1. 默认禁用(已是默认行为,确认即可)。
  2. 在 admin UI 上明确警告"启用后会持久化用户 prompt 到磁盘"。
  3. 增加自动过期机制(如 24 小时后自动删除)。
  4. .gitignore 中确认 tests/raw_stream_samples/ 已被忽略(已确认)。

TODO-3: 版本检查 handler 指向不存在的 GitHub 仓库(LOW)

位置: internal/httpapi/admin/version/handler_version.go

问题: 版本检查 handler 调用 GitHub API 比对版本,但硬编码的仓库是 CJackHwang/ds2api,而实际部署的 remote 是 luckfun233/ds2api(或 a3216/ds2api)。这导致:

  1. 版本检查永远返回"有更新可用"或"无法获取"。
  2. 误导管理员升级到无关项目的版本。
  3. 信任了一个第三方仓库的 release tag,存在供应链风险。

影响: 低,但会误导管理员。

建议修复:

  1. 把仓库 ID 改为实际部署的 a3216/ds2api(或通过环境变量配置)。
  2. 或者移除版本检查功能,让管理员自行关注 release。

TODO-4: CORS reflect origin(LOW)

位置: internal/server/router.go 的 CORS 中间件

问题: CORS 策略反射请求的 Origin header,允许任意网站发起跨域请求。虽然所有 admin 端点都要求 Bearer token(不会被 cookie 自动携带),但:

  1. 如果未来添加了基于 cookie 的鉴权,反射 origin 会变成漏洞。
  2. 反射 origin 让攻击者网站可以发起 authenticated CORS 请求(如果前端把 token 存在内存里被 XSS 偷走)。

影响: 低,因为当前 admin 鉴权用 Bearer token 而非 cookie。

建议修复:

  1. 把 CORS 限制为已知的前端域名(如 https://a3216-ds2api.hf.space)。
  2. 或者只允许同源请求(不设 Access-Control-Allow-Origin)。
  3. 如果保留 reflect origin,确保 Access-Control-Allow-Credentials: false(当前未设 credential,符合预期)。

TODO-5: account token 在内存中以明文存在(LOW)

位置: internal/config/store.gocfg.Accounts[].Token

问题: DeepSeek 账号的 session token 在内存中以明文存储。如果服务器进程内存被 dump(如通过 /proc/<pid>/mem、core dump、swap 文件),token 会泄露。

影响: 低,需要本地权限提升才能利用。

建议修复: 不建议立即修复 —— Go 进程内存加密会显著增加复杂度且无法抵御 root 攻击者。建议:

  1. 确保 HF Space 容器以非 root 用户运行(已确认 ds2api 用户)。
  2. 定期刷新 token(已有 TokenRefreshIntervalHours 配置)。
  3. 监控异常 token 使用。

TODO-6: webui 静态文件路径穿越(已防护,建议加固)

位置: internal/webui/handler.goserveFromDisk

当前状态: 已正确实现路径穿越防护 —— 使用 filepath.Clean + isPathInsideRoot 检查。

建议加固:

  1. isPathInsideRoot 失败时返回 404 而非 403(避免泄露路径存在性)。
  2. 对所有静态文件请求添加 X-Content-Type-Options: nosniff header。
  3. .json.map 等开发文件在生产环境返回 404。

TODO-7: chat_history 持久化用户对话(LOW)

位置: internal/chathistory/store.go

问题: chat_history 默认开启,把每个会话的完整消息持久化到 /data/chat_history.json。这是设计意图(用于上下文续接),但:

  1. 文件未加密。
  2. 没有 TTL 自动清理。
  3. HF Space 重启时 /data/ 保留,历史数据可能长期堆积。

影响: 隐私 —— 用户对话长期持久化。

建议修复:

  1. 添加 DS2API_CHAT_HISTORY_TTL_HOURS 环境变量,自动清理超期记录。
  2. 在 admin UI 提供"一键清空 chat history"按钮。
  3. 文档中明确告知部署者 chat_history 会持久化用户对话。

TODO-8: env writeback 把环境变量写回 config.json(LOW)

位置: internal/config/store_env_writeback.go

问题: 当 DS2API_ENV_WRITEBACK=1 时,系统会把 DS2API_CONFIG_JSON 环境变量的内容写回 /data/config.json。这意味着:

  1. 如果环境变量包含明文密码(常见于 HF Space Secrets),密码会被复制到 /data/config.json
  2. /data/config.json 的权限是 644(world-readable),同机其他用户可读。

影响: 低 —— HF Space 容器隔离,无其他用户。但本地开发或自托管场景下有风险。

建议修复:

  1. /data/config.json 权限改为 600(仅 owner 可读)。
  2. entrypoint.shchmod 600 /data/config.json
  3. 文档中说明 env writeback 会把密码写入磁盘。

TODO-9: 没有请求体大小限制(LOW)

位置: internal/server/router.go

问题: chi router 没有全局的 http.MaxBytesReader。攻击者可发送超大请求体消耗内存。

影响: 低 —— DeepSeek API 本身有请求大小限制,且 chi 默认有超时。

建议修复:

  1. 在 router 中间件添加 http.MaxBytesReader(w, r.Body, 10*1024*1024)(10MB)。
  2. /admin/config/import 等需要大 body 的端点单独放宽限制。

TODO-10: 没有 HSTS / Secure cookie(按 skill 建议不报)

security-best-practices skill 指引,HSTS 在不完全理解长期影响时不建议启用,且 HF Space 已自带 HTTPS 终结,故不作为问题报告。


渗透测试验证

针对已修复的 CRITICAL/HIGH 问题,本次审计通过单元测试模拟了以下攻击场景:

  1. 默认密钥登录TestVerifyAdminCredentialRejectsEmptyAndDefaultAdmin —— 验证未配置凭据时,"admin"、空字符串、"password""123456" 等常见弱密码全部被拒绝。

  2. JWT 伪造TestJWTForgedWithAdminSecretFailsVerification —— 模拟攻击者用历史默认 "admin" 作为 HS256 密钥伪造 JWT,验证 VerifyJWT 拒绝该 token。

  3. 配置导出泄露TestConfigExportRedactsSecretsFromConfigField —— 验证 config 字段不包含 live token、明文密码、vercel token、admin password hash。

  4. 迁移用途保留TestConfigExportJSONStillContainsPasswordsForMigration —— 验证 json/base64 字段仍保留密码(迁移必需),但绝不包含 live token。

  5. 密码哈希登录回归TestAdminLoginWithConfiguredPasswordHash —— 验证配置 password_hash 后,密码登录和 JWT 签名都正常工作。

  6. 环境变量密钥登录回归TestAdminLoginWithEnvKey —— 验证配置 DS2API_ADMIN_KEY 后,env key 登录正常。

  7. 显式 JWT secret 优先TestAdminLoginWithJWTSecretEnv —— 验证 DS2API_JWT_SECRET 优先级最高。

所有测试通过。建议在 CI 中持续运行这些回归测试,防止未来重构时 reintroduce 这些漏洞。


部署者操作建议

本次修复后,部署者应:

  1. 必须:在 HF Space Secrets 中设置 DS2API_ADMIN_KEY 为强随机字符串(至少 32 字符)。否则管理员登录将完全禁用。
  2. 强烈建议:设置 DS2API_JWT_SECRET 为另一个独立的强随机字符串(不要复用 DS2API_ADMIN_KEY)。
  3. 建议:通过 admin UI 的"修改密码"功能设置管理员密码(会写入 admin.password_hash),这样即使 DS2API_ADMIN_KEY 泄露,密码登录通道仍可用。
  4. 可选:如需调试,设置 DS2API_DEV_PACKET_CAPTURE=1 临时启用 devcapture,调试完毕后务必设回 0 或删除该环境变量。
  5. 审计:检查 /data/config.json 是否包含不再需要的账号密码,定期清理。
  6. 监控:定期查看 HF Space 日志,关注异常 admin 端点访问。

审计方法说明

本次审计采用以下方法:

  1. 静态代码审查:阅读 30+ 个关键文件,包括鉴权、配置、API 路由、前端、CF Worker、Dockerfile。
  2. 数据流追踪:从 HTTP 请求入口追踪到配置持久化,识别敏感数据(密码、token、prompt)的存储和暴露路径。
  3. 攻击者视角:假设攻击者拥有源码(项目开源),从扫描攻击日志中提取常见攻击模式,验证项目是否易受这些攻击。
  4. 依赖审查:检查 go.modpackage.json,未发现已知漏洞依赖。
  5. 配置审查:检查 Dockerfile、entrypoint.sh、CF Worker 配置,确认容器以非 root 运行、端口隔离、配置文件权限合理。

未覆盖的范围(建议后续审计):

  • DeepSeek PoW 实现的密码学正确性(pow/deepseek_pow.go
  • utls 指纹模拟的隐私影响(internal/deepseek/client/proxy.go
  • shumei 设备 ID 生成算法(internal/shumei/
  • Vercel Edge Function 的资源限制行为
  • 前端 XSS 防护(React 默认转义,但需检查 dangerouslySetInnerHTML 使用)

报告生成时间:2026-07-21 审计者:security-best-practices skill