密码重置令牌七大攻击手法:从弱加密到 JWT 泄漏拿下任意账号

导语:密码重置是 Web 应用对公网暴露最彻底的入口之一,依赖的那串”看似安全”的令牌,往往就是整条防线上最薄的那一块。从弱随机、IDOR 越权到 JWT 过度信息泄漏,七种常见手法几乎每一种都能直接打通到 ATO(Account Takeover,账号接管)。本文按红队视角拆解这些手法的识别方式、利用路径和防御要点。

密码重置令牌攻击面总览

一、为什么密码重置流程特别脆弱

重置流程核心凭证就是那串 token——这串东西一旦出问题,整个用户体系就会被穿透。问题在于,大多数团队把防御精力集中在”主登录流程”上,反而把重置流程当成次要功能,代码仓促、测试草率——这正是红队最喜欢的入口。下面的七种手法,几乎覆盖了真实世界里常见的每一类缺陷。


二、七大攻击手法详解

2.1 弱加密的密码重置令牌

最常见的实现方式是用 base64(user_id + timestamp) 或 md5(user_id + email) 这种”看似加密”的方案生成。这类算法根本没有密钥空间可言,攻击者拿到自己账号的令牌,反推生成规则,就能批量预测其他用户的令牌。

# 常见的"伪随机"生成代码(漏洞示例)
import hashlib, time, base64

def weak_token(user_id):
    raw = f"{user_id}:{int(time.time())}".encode()
    return base64.b64encode(raw).decode()

# 攻击者拿到自己 token(如 c2VjcmV0OjE3MzU4OTAwMA==)
# base64 -d 后是 "secret:1735890000",瞬间掌握规则

红队识别技巧:申请两次重置看 token 长度,再用 base64 -d 试一下,肉眼能看出结构的就基本废了。

2.2 可重用的密码重置令牌

理想状态:用户点链接、设新密码、令牌立即失效。但很多实现里,令牌存进数据库后没标记”已使用”,于是同一个 token 可以反复提交。

测试四步:申请令牌 A → 用 A 改密成 P1 → 再用 A 改密成 P2 → 看哪个生效。P2 生效就说明令牌可重用,攻击者可以反复改受害者密码,受害者全程无感。

2.3 IDOR 越权改密

很多重置链接里除了 token,还会带用户标识符,比如 https://target.com/reset?token=xxx&user=victim。后端逻辑有时写成”先看 token 是否合法 → 再用 user 参数定位账号 → 改密码”。

攻击者只要把自己的合法 token 配上受害者 user 标识符,就能把受害者密码改掉:

GET /reset?token=ATTACKER_VALID_TOKEN&user=victim HTTP/1.1
Host: target.com

token 鉴权、user 改写,绕过账户隔离。修复点:user 标识符必须从 session 或 token payload 里取,绝不信任 URL 参数。

2.4 缺乏随机生成——连续请求令牌不变

正经 token 应该是 secrets.token_urlsafe(32) 这种级别,至少 32 字节、基于密码学安全 RNG。但有些开发者用 randint(100000, 999999) 或自增 ID 加密。

快速识别:连续点 5 次”忘记密码”,对比 5 个 token。5 个相同是固定值,递增是自增 ID,完全不一样可能是 md5(user_id + counter)。一旦发现规则,可以批量申请 token 攻击改密接口。

2.5 弱/短令牌可暴力破解

有些应用用 4 位、6 位数字验证码作”重置码”,理由是”用户友好”。4 位数字只有 10000 种可能,单线程几秒就能穷举完;6 位数字是百万级,配合代理池几分钟就能跑完:

import requests

# 暴力破解 6 位数字验证码
for code in range(0, 1000000):
    code_str = f"{code:06d}"
    r = requests.post(
        "https://target.com/api/reset/verify",
        json={"email": "victim@target.com", "code": code_str},
        timeout=2
    )
    if "success" in r.text or r.status_code == 200:
        print(f"FOUND: {code_str}")
        break

防御要点:验证码至少 128 bit,过期时间 15 分钟以内,必须一次性。

2.6 密码重置令牌未校验

有些开发者的逻辑写成”打开链接 → 直接进改密页面”,根本没在后端验证 token。前端页面有 token 字段就算”已验证”。

测试四步:拿真实重置链接 → 删掉 token 参数(URL 只剩 ?user=victim)→ 提交改密请求 → 看页面是否照常工作、密码真被改了。零认证漏洞在老旧 PHP/Java 应用里特别常见。

2.7 JWT 重置令牌的过度信息泄露

如果重置链接用 JWT,攻击者拿到 token 第一件事就是 base64 解码 payload 看里面塞了什么。常见的信息泄露:

  • 老密码哈希:极少数开发者在 payload 里塞 old_password_hash,”方便用户改密后自动迁移数据”,结果把密码哈希直接送给攻击者
  • 自增 user ID:JWT 的 sub 字段是自增数字,配合 alg=none 攻击或密钥爆破可以批量遍历
  • 角色字段:payload 里同时放 is_admin: true,就成了越权后门
# JWT 过度信息检测的标准动作
echo "eyJhbGci...xxx" | cut -d. -f2 | base64 -d
# {"sub":"12345","email":"victim@target.com","is_admin":true}

修复要点:payload 里只放最小必要信息;签名算法必须 RS256/ES256,绝不接受 alg: none。


三、红队七步测试 checklist

把这七种手法串成一条测试流程,可以在一次 SRC(安全响应中心)报告里覆盖整个密码重置面:

  1. 申请两次重置,对比 token 随机性、长度、是否可预测
  2. 用同一 token 改两次密码,验证一次性
  3. 改 URL 的 user 参数 + 自己的合法 token,测 IDOR
  4. 连续 5 次申请 token,看是否每次相同
  5. 短数字验证码的话,10 行 Python 跑穷举
  6. 删除 token 参数只留 user,看后端是否仍改密
  7. JWT 方案:base64 解 payload 看信息泄露,再试 alg=none 和密钥爆破

任何一条命中就是 Critical 级别的 ATO 漏洞。


四、防御建议

这七种手法背后其实只有三条原则:

令牌必须够强。长度 128 bit 以上、用 secrets 模块或等价库、绝不基于时间戳或自增 ID 衍生。

令牌必须一次性。数据库里加 used_at 字段,使用即标记;JWT 方案则用 jti(JWT ID)+ 服务端黑名单。

令牌和用户标识必须强绑定。token payload 里包含 user_id(加密或签名),后端只信任 token 里的标识,绝不读 URL 参数。

JWT 方案:签名算法强制 RS256/ES256,禁用 alg: none、禁用 HS256 配合弱密钥;payload 里绝不放过期老密码、角色字段这类敏感信息。

把这七种手法挡在门外,相当于把账号接管这条最常见的入侵路径直接关掉。


五、素材出处

  • 原文:Harsh Bothra「Password Reset Token Issues」 – https://raw.githubusercontent.com/harsh-bothra/learn365/main/days/day23.md
  • 弱加密案例:medium.com – https://medium.com/bugbountywriteup/weak-cryptography-in-password-reset-to-full-account-takeover-fc61c75b36b9
  • 演讲:Harsh Bothra「Broken Cryptography and Account Takeovers」 – https://www.youtube.com/watch?v=mLi3qxhLIA0&list=PLYn5_MxRvV-fxPL90I-uebXQzQBXfIaY0&index=10
  • 演讲 Slides:speakerdeck.com – https://speakerdeck.com/harshbothra/broken-cryptography-and-account-takeovers

© 版权声明
THE END
喜欢就支持一下吧
点赞12 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容