2fauth-worker项目的安全性分析和代码审计2

26次阅读
没有评论

项目地址:项目地址:https://github.com/nap0o/2fauth-worker

看完了 features/auth/providers/* 和前端主要几块(登录、回调、vault 列表/新增、request 工具等),帮你把“明显能看出来的坑”列一下。

1. OAuth provider 代码:整体设计是靠谱的

流程大致是:

  • 前端 /login
    • 请求 GET /api/oauth/providers 拿到 provider 列表(里边有 id/name/icon/color)。
    • 点击按钮 → GET /api/oauth/authorize/:provider 拿回 { authUrl, state, codeVerifier }
    • 前端把 state / provider / codeVerifier 存到 localStorage,然后 window.location.href = authUrl
  • 回调页 /oauth/callback
    • 从 URL 取 code / state / hash 等。
    • 根据路由参数/hash/缓存判断 providerId。
    • 前端自己做一遍 state 校验
      • localStorage.oauth_state === route.query.state,否则报“State 校验失败,请求可能被篡改”。
    • 然后把 { code, state, codeVerifier } 或 Telegram 的 query 全量 JSON POST 到 /api/oauth/callback/:provider
  • 后端 AuthService.handleOAuthCallback
    • 通过 getOAuthProvider() 找到对应 provider。
    • provider.handleCallback 里:
      • GitHub / Google / Gitee / NodeLoc:标准 OAuth2 Code → Token → UserInfo;
      • CloudflareAccess:使用 PKCE(code_challenge=S256 + code_verifier);
      • Telegram:严格按官方规范校验 hash(HMAC-SHA256(bot token))和 auth_date(24h 内)。
    • 做一次白名单检查(前面已经说过)。
    • 用 generateSecureJWT 签发自己的 JWT,放进 httpOnly Cookie auth_token
    • 生成 deviceKey(基于 userId + "device_salt_offline_key" 的 HMAC),返回给前端给离线 vault 加密用。

这些 provider 本身没有明显漏洞

  • 都是固定 URL + 后端 env 里的 clientId / secret / redirectUri;
  • 没有把任何用户输入直接拼到 token/用户信息请求 URL 里;
  • Telegram 的签名+时间窗口验证是按官方推荐做的;
  • CloudflareAccess 用了 PKCE,codeVerifier 从前端带回来。

需要注意/改进的点:OAuth state 只在前端校验

  • 现在 “state” 只在前端做校验,后端完全没看 state
    • /api/oauth/authorize 只是返回 state;
    • /api/oauth/callback/:provider 里用的是 body.code,没有任何 state 验证逻辑。

这在当前架构下不算立刻致命:

  • /api/oauth/callback/* 只能从同源 JS 发 JSON 请求(fetch + Content-Type: application/json),
  • 恶意站点跑在别的域名上时,浏览器会挡住跨域 fetch;
  • 纯表单 POST 没法伪造 JSON body,所以 CSP + CORS 配合下,“第三方网站替你调用 callback 去登陆别人的账号”比较难。

但从安全审计角度,这是一个比较明显的“缺口”:

  • 推荐在后端也把 state 串起来做一次验证,例如:
    • 把 state 放在 httpOnly Cookie 里(或 server-side KV store);
    • callback 时检查 cookie_state == body.state
    • 确保就算前端被绕过,后端也不会接受没有配对 state 的授权码。

结论:不是立即爆炸级漏洞,但属于“安全 posture 不完美”,建议作者后续补。


2. Provider 里是否有明显坑?

逐个看了一遍:

  • GitHubProvider / GoogleProvider / GiteeProvider / NodeLocProvider
    • 使用固定的 token / userinfo endpoint;
    • 用的是 POST + x-www-form-urlencoded,没把 code/secret 拼到 URL 里暴露;
    • token 不写入任何日志,只在内存里用一次;
    • userInfo 中只取 id / username / email / avatar。
    • 没有用到 provider 返回的任何 HTML 片段,也没有做 v-html 之类。
  • CloudflareAccessProvider
    • getAuthorizeUrl 加了 PKCE(code_challenge=S256);
    • 回调时必须带 code_verifier,否则 CF 按理会拒绝;
    • 错误时会 console.error tokenResponse 文本,但没有把 accessToken 打到日志;
    • 用户字段直接用 OIDC userinfo,映射到 id/username/email,没额外拼接。
  • TelegramProvider
    • 严格按官方 Login Widget 标准:
      • dataCheckString 用固定 allowedKeys 排序;
      • secret key = SHA256(bot token);
      • 用 HMAC-SHA256 验证 hash;
      • 检查 auth_date 不超过 24 小时;
    • 没有对 Telegram 参数做任何 eval/HTML 注入,直接映射为 id/username/email。

总的来说:providers 这块没有找到明显逻辑漏洞或者注入点,主要问题还是前面说的“state 只在前端校验”。


3. 前端安全点:有没有明显 XSS / CSRF / 信息泄露

我重点看了这些文件:

  • frontend/src/main.js
  • frontend/src/app/app.vue
  • frontend/src/app/router.js
  • frontend/src/features/auth/views/login.vue
  • frontend/src/features/auth/views/oauthCallback.vue
  • frontend/src/features/vault/views/home.vue
  • frontend/src/features/vault/components/vaultList.vue
  • frontend/src/features/vault/components/addVaultManual.vue
  • frontend/src/shared/utils/request.js
  • frontend/src/shared/utils/crypto.js
  • frontend/src/shared/utils/totp.js
  • frontend/src/features/auth/store/userStore.js
  • frontend/src/features/vault/store/vaultStore.js

3.1 XSS 相关

唯一使用 v-html 的地方

<!-- login.vue -->
<el-icon v-if="provider.icon">
  <span v-html="provider.icon" style="display: flex;"></span>
</el-icon>
  • provider.icon 来源:GET /api/oauth/providers
  • 后端 getAvailableProviders 里 icon 全是硬编码在 provider 类里的 SVG 字符串,不是从 env 或用户输入来的;
  • 只要你自己不改代码去从配置读 SVG,这个 v-html 就不会成为 XSS 入口。

其它用户可控内容(服务名称、账号、分类、防备份文件名字等)在 UI 全部是 {{ ... }} 文本插值,没有找到 v-htmlinnerHTML 之类。

登录/回调页的错误消息

  • oauthCallback.vue<el-result v-else icon="error" title="授权失败" :sub-title="errorMsg" > errorMsg 来自 URL 的 error / error_description 或自定义文字,但这里只是普通绑定,不是 v-html,不会执行脚本。

综合看下来:

  • 没有明显的 XSS 点
  • 真正的风险还是那句:一旦前端未来改版时引入新的 v-html 或第三方脚本,整套系统因为掌握所有 TOTP secret,会马上变成高危。

3.2 CSRF 相关

  • 所有 API 请求通过 request(url, options)
    • 会从 cookie 读 csrf_token,自动在 header 加 X-CSRF-Token
    • credentials: 'include',带上同站 cookie;
    • 后端 authMiddleware 要求:
      • auth_token 存在;
      • csrf_cookie === X-CSRF-Token,否则 403;
    • 敏感路由(vault、backup 等)都挂了 authMiddleware

从前后端配合来看:CSRF 防护是到位的,除非你在别的地方再开不带 authMiddleware 的危险写接口。

3.3 离线 vault、本地加密

  • vaultStore.js
    • 从 IndexedDB 拿 device_salt(就是后端返回的 deviceKey);
    • secure_vault 存在 localStorage,但内容通过 encryptDataWithPassword() 加密:
      • PBKDF2-SHA256 + 100000 iterations;
      • AES-GCM,salt + iv + ciphertext 打包;
    • 你不拿到 deviceKey(它正常保存在浏览器 IDB 里)的话,dump localStorage 也只能得到密文。

这个算是设计上的加分项,不是漏洞。


4. 这里再帮你收个“明显风险点清单”

结合前后端,现在比较「值得你在意」的点是:

  1. OAuth state 只在前端校验(后端没校验)
    • 当前环境下不至于轻易被利用,但从 best practice 来看,是个缺口;
    • 建议:给作者提 issue,把 state 也在后端绑定到 cookie 或 KV,然后 callback 时验证。
  2. 白名单配置的“魔法值”逻辑(之前说过)
    • OAUTH_ALLOWED_USERS === JWT_SECRET 时白名单失效;
    • 你自己部署时,记得:
      • OAUTH_ALLOWED_USERS 写明确的 email/username 列表;
      • 不要乱用“等于 JWT_SECRET”这类隐式开关。
  3. ENCRYPTION_KEY 和 JWT_SECRET 的复用
    • 现在 ENCRYPTION_KEY 不配就回退到 JWT_SECRET;
    • 意味着:
      • 一把钥匙可以既解密 TOTP secret,又伪造 JWT;
    • 部署时务必单独配置一个高熵 ENCRYPTION_KEY(与 JWT_SECRET 不同)。
  4. OAuth redirectUri 的配置安全性
    • GitHub/Google/Gitee/NodeLoc/Cloudflare 的 redirectUri 都来自 env:
      • 如果你把 redirectUri 配成了攻击者控制的域名,授权码和 accessToken 会直接被对方拿走;
    • 所以部署时要确认:
      • redirectUri = 你自己 Worker 的 https 地址;
      • 不要给任何“测试域”“外部服务”共享。
  5. 整个系统的“攻击价值”非常高
    • 这一点也不是代码 bug,而是定位问题:
      • 只要 Cloudflare 账号、Worker 环境变量、前端出现 XSS 中任何一处被攻破,你所有 TOTP secret 都要视为泄露;
    • 对标密码管理器:使用/运维时要拉到那个标准去看待。

总体评价

  • features/auth/providers/* 和前端登录/回调/Vault 相关代码,没有看到那种“一眼决定不要用”的明显漏洞。
  • 最大的可以挑出来的点:
    • OAuth state 没在 server 端验证(建议改进);
    • 几个配置项的“魔法用法”(白名单关闭条件、ENCRYPTION_KEY 回退 JWT_SECRET)容易被误配置踩坑。
正文完
 0
评论(没有评论)