直接用(免责:会明文推送到我的TG。。建议绑定自己的cloudflare)
🔐 Cloudflare 2FA Generator - 免费的双因素验证码生成器
基于 Cloudflare Workers,支持云端同步、WebDAV 备份、Telegram 推送 本
GitHub: https://github.com/soga11/Cloudflare-2FA-Generator
特点:
✨ 3 分钟快速部署
🔒 SHA-256 端到端加密
☁️ 多设备云端同步
📱 支持摄像头/截图扫码
💾 三重备份(本地 + WebDAV + Telegram)
⚡ 全球 CDN 加速
完全免费,开源 MIT 协议
再次免责:此项目安全性为0 Ai说很安全¿ 没任何鉴权。不建议使用
这帖子也是AI写的。。。我就复制粘贴。。 有问题丢给AI 哪位大佬在改下 我在fork回来

🧠 GPT-5.2 Thinking
先给结论:这份代码“能跑出 TOTP 验证码”,但作为可公开部署的 2FA 管理/保险库应用,存在多处“高危级别”的漏洞与设计缺陷——其中最严重的是:会话 Token 可伪造导致任意账号接管(IDOR/认证绕过)、密码与保险库密码用 SHA-256 直哈希(不符合业界最佳实践)、所谓 encrypted_secret 实际明文存储 2FA 密钥、以及把 2FA 密钥写入云端日志/Telegram(极高敏感数据外泄面)。下面我按“系统数据流 → 关键安全/功能 Bug → 具体修复路径”循序渐进拆开讲。
⸻
你这份 Worker 同时提供了:前端单页(HTML_CONTENT)+ 一组 /api/* JSON API。整体链路可以分成四条主线:
(A) 账号体系(注册/登录/会话)
• 注册:/api/register 把 username 与 sha256(password) 写入 users.password_hash。
• 登录:/api/login 校验同样的 SHA-256 哈希匹配后,返回 sessionToken = "${id}-${expiry}-${random}"。这里的 expiry 是当前时间 + 2 小时,random 是 crypto.getRandomValues 生成的 32 字节十六进制串。
• 鉴权:所有需要登录的 API 通过 Authorization: Bearer <sessionToken>,在 Worker 里 getUserIdFromRequest() 仅做三件事:
1. split('-') 取 userId 与 expiry;
2. 判断是否过期;
3. 直接返回 userId。
注意:这里没有任何“签名校验/服务端存储校验”,这会成为最大漏洞根源。
(B) 保险库(vault password)
• users.vault_password_hash 用于区分“是否设置过保险库密码”。
• 前端解锁逻辑:浏览器把 vaultPassword 放在 sessionStorage,解锁时调用 /api/verify-vault-password 比对 sha256(vaultPassword) 是否等于 DB 中的 vault_password_hash。
• 但关键点:服务端并没有把“保险库解锁状态”作为权限条件。也就是说,“保险库密码”目前只是前端 UX 上的门槛,而不是一个真正的加密/授权边界。
(C) 2FA 密钥与账号保存
• 常用账号保存:/api/save-account 把 secret 规整成 Base32(去空格、去 -_、大写)后写入 saved_accounts.encrypted_secret。
• 名字叫 encrypted_secret,但并没有任何加密步骤。这意味着数据库一旦泄露,所有 2FA 密钥会被直接还原;同时任何能拿到会话的人也能直接拉取密钥。
(D) TOTP 生成与日志
• TOTP 算法在浏览器执行:Base32 解码 → 时间步(30 秒)→ HMAC-SHA1 → 动态截断 → %1000000 得 6 位码。这个算法流程与 RFC 6238 的标准做法一致(HMAC + 动态截断 + 30 秒步长是典型组合)。 
• “云端记录”:/api/save-totp-log 会把 secret(密钥本体)、IP、UA 写到 totp_logs;并且如果配置了 Telegram Bot,还会把密钥与验证码推送到 Telegram。
从这四条链路你应该能看出:你现在的系统把“2FA 的最高价值资产”(共享密钥 secret)放在了多个高风险位置:数据库字段、日志表、Telegram 消息、WebDAV 备份文件、前端 Local/Session Storage。只要其中任一环被攻破,2FA 就等价失效。
⸻
你当前的 sessionToken 结构是:<userId>-<expiry>-<random>,并且 getUserIdFromRequest() 只解析与检查 expiry,不校验 token 是否由服务端签发。换句话说:
• 攻击者无需知道任何密码;
• 只要猜到/枚举一个用户的 userId(自增整数非常好猜),就能自己构造:
victimUserId-(未来很久的时间戳)-随便填点字符串
• 你的服务端会把它当成合法登录态,允许访问:
• /api/saved-accounts(直接拉取该用户的全部 2FA 密钥明文)
• /api/cloud-history(拉取日志里的密钥)
• /api/update-account、/api/delete-account(篡改/删除对方保存项)
• 以及所有以 user_id 为条件的 D1 查询接口
在 OWASP 的会话管理视角里:Session ID/Token 一旦建立,就等价于最强认证方式本身,因此必须具备不可预测性与不可伪造性,并能被正确失效与轮换。 
你现在做到了“随机串看起来很随机”,但没做到“服务端验证 token 的真实性”,所以随机串再强也没有用——攻击者完全可以绕过。
最佳实践修复路线(按优先级,从最稳妥到较轻量):
1. 服务端存储会话(推荐)
• 新建 sessions 表:session_id (随机), user_id, expires_at, created_at, revoked_at。
• 登录时生成高熵 session_id(32 bytes 随机),写入 D1;返回给前端。
• 每次请求:从 header/cookie 取 session_id,查表确认存在且未过期/未撤销。
• 登出时:把该 session 标记 revoked_at 或直接删行。这样“登出”才真正有效。
这条路线与 OWASP “可终止会话、会话失效”等要求高度一致,也更容易做“全端登出/改密后踢下线”等能力。 
2. 使用签名 Token(JWT/自定义 HMAC)但必须校验签名
• 你可以保留 userId、exp 这样的声明,但必须对 payload 做 HMAC-SHA256 签名(用 Worker 的 secret env 变量),服务端收到后验证签名一致才放行。
• 否则依旧可伪造。
• 需要注意:签名 token 仍然难以“即时登出”(除非引入黑名单/版本号),所以纯 JWT 往往需要配合服务端状态(例如 token version、revocation list)才能达到安全登出。
3. 把 token 放到 HttpOnly Cookie(推荐配合方案 1 或 2)
• 你现在把 token 放 sessionStorage,XSS 一旦发生就会被直接读走。
• 用 Set-Cookie 下发 HttpOnly; Secure; SameSite=Lax/Strict 可以显著降低被脚本窃取的概率(仍需防 CSRF,但 SameSite 能缓解)。这也是业界常见组合。 
这一条是“是否有 bug”的分水岭:只要不修,你的系统不是“可能被攻破”,而是“默认可被接管”。
⸻
3.1 密码哈希:SHA-256 直哈希的问题是什么?
SHA-256 是“通用摘要函数”,设计目标是快,而密码存储恰恰需要慢(让离线爆破成本变高)。OWASP 的 Password Storage Cheat Sheet 明确给出:应使用专用密码哈希(如 Argon2id / bcrypt / scrypt),在需要 PBKDF2 时也应设置足够的工作因子。 
你当前 sha256(password) 没有盐(salt)、没有工作因子(iterations),数据库一旦泄露,攻击者可以用 GPU 极高速爆破并复用到其它站点(撞库风险)。
Cloudflare Workers 环境里很多人会用 WebCrypto PBKDF2,但还要注意 workerd 对 PBKDF2 迭代次数存在安全争议/限制讨论(社区里就有“迭代数不够安全”的 issue)。 
这意味着你如果要“自己造密码学轮子”,必须非常谨慎地选择参数,并进行基准测试与后续参数升级策略。
3.2 保险库密码:即使你把 vault_password_hash 存得更安全,也挡不住“密钥明文存储”
现在的“保险库”只是:前端解锁后显示常用账号。但密钥 secret 是明文存到 D1 的,所以:
• 数据库泄露:保险库密码无意义;
• 会话被盗/伪造:攻击者直接调用 /api/saved-accounts 取走全部 secret;
• 甚至你自己在 /api/save-totp-log 与 Telegram 里也在外发 secret(后面会讲)。
3.3 最佳实践:把“保险库密码”变成真正的密钥保护机制(两条可选架构)
架构 A:客户端加密(更贴合“零信任保险库”)
• 服务器永远只存密文:ciphertext + salt + iv + kdf_params;
• 解密只在浏览器进行:用户输入 vaultPassword → KDF 派生 AES-GCM key → 解密显示;
• 服务器不需要知道 vaultPassword,也无法解密用户密钥。
这种设计对“服务器被拖库”的韧性最强,符合“保险库”产品的常见安全边界模型(服务端即便被攻破也拿不到明文 secret)。
架构 B:服务端加密(实现简单但边界弱一些)
• 服务器用 vaultPassword 派生 key 或用服务端主密钥加密;
• 但这样服务器理论上能解密,风险比架构 A 高。
如果你只是自用、且信任服务端环境,这条路也可行,但“保险库”这个词会被削弱。
无论 A/B,你都需要参考 OWASP 的加密存储原则:为敏感数据“静态加密”、正确管理密钥与随机数、不要把“可逆加密”当密码存储手段等。 
⸻
你的 /api/save-totp-log 做了两件非常危险的事:
1. 往 totp_logs 写入 secret(2FA 共享密钥)
2. 可选地把 username + secret + code + IP 发送到 Telegram
在 2FA/TOTP 体系里,secret 是长期共享密钥,一旦泄露,攻击者就可以在自己的设备上生成与你完全同步的 TOTP,从此“验证码”再也不是第二因素,而是“攻击者也有的第一因素”。RFC 6238 的安全性基础就是 secret 的机密性。 
从威胁模型角度看,日志系统/消息系统往往比主数据库更容易“旁路泄露”:
• 日志可能被开发/运维/第三方平台访问;
• Telegram Bot token 一旦泄露,所有历史消息可被导出;
• 即使 token 不泄露,消息在多个设备、云端同步,也扩大暴露面;
• 你还把 IP 与时间一起记录,相当于做了可审计的“2FA 使用轨迹”,隐私风险更高。
最佳实践建议:
• 绝不记录明文 secret。如果你需要“去重/统计”,可以记录 HMAC(server_secret, secret) 的短指纹(不可逆地标识同一 secret),而不是 secret 本体。
• Telegram 通知只发送“事件发生了”即可,比如:账号名(或匿名标识)、时间、IP(可选),不要带 secret,不要带验证码 code。
• 如果你想做“异常检测”,更应该记录:请求来源、设备指纹、失败次数、频率等,而不是把密钥本体放进任何日志链路。
• 这也与 OWASP 对认证与会话安全的常见建议一致:敏感凭据不应出现在日志与外部通道中。 
就“有没有 bug”而言:这不一定是“代码运行 bug”,但在安全语义上是功能性灾难——你部署出去后,任何能访问日志/Telegram 的人都能克隆你的 2FA。
⸻
5.1 可用性层面的 bug / 不稳定点
你用 PROPFIND Depth:1 拉目录,然后用正则:
xmlText.match(/<D:href>([^<]2fa_backup[^<].json)</D:href>/i)
只取第一个匹配项。这里至少有四个现实问题:
1. 拿到的不一定是“最新备份”
WebDAV 的 PROPFIND 返回通常包含多个 <D:response>,并带 <getlastmodified>。你虽然在请求体里要了 <getlastmodified/>,但却没有解析它来排序。结果可能恢复的是旧备份,甚至是列表里第一个出现的随机项(不同服务实现返回顺序不一致)。
2. href 可能是 URL 编码/包含基路径
不同 WebDAV 服务会返回绝对路径、相对路径、带编码字符的路径。你现在把 fileUrl 拼接为:
• 若 filePath 不以 http 开头:url.replace(//$/, '') + (filePath...) + filePath
这个拼接在遇到“服务端返回的是完整路径但不含域名”或“返回路径已含 folder 前缀”时,容易得到重复路径或缺斜杠的 URL,导致 404。
3. 只用正则解析 XML 很脆弱
WebDAV XML 命名空间前缀不一定是 D:,可能是 d: 或无前缀;有的返回还会换行缩进、实体编码。正则会在这些情况下失效。稳妥做法是用 XML 解析器(或至少更健壮的匹配逻辑),然后按 displayname/getlastmodified 选择目标文件。
4. 错误处理对 207 Multi-Status 没有精确区分
response.ok 对 207 是 true(因为 2xx),所以你的 “连接成功” 分支大体能跑,但如果需要进一步判断哪些条目失败,你现在没有解析 Multi-Status 的细节。
5.2 安全层面的 bug / 可被滥用点
1. 你把 WebDAV 凭据明文存到 localStorage
webdavPassword 被 localStorage.setItem('webdavPassword', password) 保存,这意味着:
• 任意同源脚本(包括被注入的 XSS、被劫持的第三方 CDN 脚本)都能读走;
• 浏览器扩展/调试工具也更容易导出。
这在安全上非常危险,尤其你还引入了外部 CDN 脚本(jsQR)。供应链风险并不是理论问题。
2. Worker 端作为“任意 URL 的代理”,存在 SSRF/滥用风险
你的 /api/webdav-test|upload|download 会对用户提供的 url 发起 fetch(),并带 Basic Auth。虽然 Workers 没有传统意义的内网,但攻击者可以把你的 Worker 当作“边缘代理”去扫描/请求任意公网地址,带宽与请求额度可能被打爆,甚至触发平台风控。
最佳实践是:
• 至少限制为 https://;
• 加 allowlist(例如只允许已知 WebDAV 域名)或用户自定义域名但需手动验证;
• 加速率限制与审计;
• 上传/下载大小限制,避免被当作数据中转站。
3. btoa 在 Workers 里一般可用,但仍建议对非 ASCII 用户名/密码做 UTF-8 处理
你现在 btoa(${username}😒{password}) 在遇到非 ASCII 字符可能出错。一般 WebDAV 凭据多为 ASCII,但不排除国际化账号。关于 btoa 在 Workers 可用性,常见环境确实支持,但仍建议编码稳妥处理。 
⸻
这部分不一定导致“立刻报错”,但会导致维护困难或在某些部署模式下行为混乱。
1. 混用 addEventListener('fetch') 与 export default { fetch() }
Cloudflare Workers 有两种常见写法:传统 Service Worker 风格(addEventListener)与 ES Modules 风格(export default)。官方在介绍 Modules 时明确强调“用 export 语义替代旧的 addEventListener 语法”,实际项目中通常二选一以避免歧义。 
你现在两套都写了:
虽然你当前两个入口都调用 handleRequest(),多数情况下看起来“能用”,但这会让后续迁移/调试非常痛苦(尤其当你开始用 import、拆文件、或引入框架时)。
Cloudflare 文档与示例通常是从 context.env 或 env.DB 取 binding 来执行查询。 
你把 env.DB 写到 globalThis.DB,短期可行,但会引入两个问题:
你对 saved_accounts.user_id 做了外键与 ON DELETE CASCADE,这在 D1 是支持且默认按 SQLite 外键启用语义执行的。 
需要注意的反而是:未来做迁移/改表时,D1 对外键 defer 的行为与 SQLite 的一些细节有关(例如 defer_foreign_keys 并不会阻止 ON DELETE CASCADE 的级联执行),这会影响“重建表”式迁移策略。 
这不是你当前的运行 bug,但如果你后面用 ORM 自动迁移,很容易踩坑导致级联删除误伤数据。
⸻
下一步行动建议(按“先止血、再升级”的顺序)
1. 立刻修会话鉴权:引入 sessions 表做服务端会话校验 + 登出可撤销;同时把 token 改为 HttpOnly Cookie(或至少签名校验)。 
2. 立刻停止记录/外发 secret:totp_logs 不存 secret;Telegram 不发 secret/code,只发事件摘要。 
3. 把 encrypted_secret 变成真加密:优先走“客户端加密、服务端存密文”的保险库模型,并参考 OWASP 的加密存储原则。 
4. 升级密码存储:改用 Argon2id/bcrypt/scrypt 或(受限时)PBKDF2+salt+高工作因子,并制定参数升级策略,遵循 OWASP Password Storage 建议。 
5. WebDAV 恢复逻辑改为解析 XML + 选最新文件,并限制 URL/速率/大小,避免被当代理滥用;同时不要把 WebDAV 密码放 localStorage。
6. 清理 Worker 入口写法:只保留一种(推荐 ES Modules export default { fetch }),并把 env 作为依赖显式传递。 
如果你愿意,我可以在你现有结构上,直接给出一版“最小改动但能达标”的补丁方案:包括 sessions 表 SQL、鉴权中间件、以及把 saved_accounts.encrypted_secret 改为 AES-GCM 密文存储的字段设计(salt/iv/ciphertext 组织方式)。
@soga #13 最好提醒大伙儿一下
目前这个安全性上不了生成环境的...
牛。
已点star
@rainyfall #2
应该没泄露吧
@agent #3 感谢 第一个星

@soga #4 泄露是泄露,明文是明文
泄露确实没招,但是不要明文暴露
挺好,我一会儿让 AI 分析一下你的代码有没有 bug
@rainyfall #6 哦哦 TG那边还特意明文的。。个人习惯吧

@soga #8
改成仅名称好点,明文我觉得不太好
@dajiji666 #7 多丢给几个Ai 打起来 bug还很多