发布于8小时前8小时 {"title":"CTF 密码学实战:HMAC 没被破解,验证邮件令牌为何仍能重置密码?","content":"背景:一道签名正确、权限错误的 CTF 题在授权靶场中,账户服务提供两类功能:验证邮箱和重置密码。两个入口都使用 HMAC-SHA256 签名令牌,开发者据此认为“客户端无法伪造,因此令牌可信”。但如果重置接口只检查签名和有效期,验证邮箱令牌也可能被当作重置凭证。这类题的关键不是破解 SHA-256,而是识别签名覆盖的内容与服务端实际授权条件之间的差距。本文使用本地最小程序复现,不涉及第三方账户、真实邮件或生产接口。问题定位:先找令牌的信任边界假设靶场中的验证邮件令牌包含以下字段:{\"uid\":\"lab-user\",\"purpose\":\"verify_email\",\"aud\":\"account-service\",\"exp\":1893456000,\"jti\":\"random-id\"}审计时沿着“签发点—传递渠道—消费点”追踪,重点确认:签发点:用户标识来自当前登录会话,还是来自可修改的请求参数?传递渠道:令牌是否出现在跳转地址、访问日志、客服工单或分析平台中?消费点:是否验证用途、受众、有效期以及一次性状态,而不只是签名?账户状态:邮箱是否已经验证、账户是否被锁定,消费令牌时有没有重新检查?如果验证邮件与密码重置共用签名密钥,而重置接口忽略 purpose,就形成了跨用途令牌混淆。即使 HMAC 使用完全正确,这条授权链仍然有问题。技术分析:完整性不等于授权HMAC 能证明消息由持有密钥的一方生成,且签名范围内的字节未被修改。它不能自动证明消息适用于当前接口。服务端签发“允许验证邮箱”的令牌,并不等于签发“允许修改密码”的令牌。因此,审计不能停留 网络请求、日志与边界流量分析示意 靶场实验、复盘记录与攻防路径示意
6小时前6小时 从标题看,优先怀疑的是令牌用途混淆,而不是 HMAC 被破解。HMAC 只能证明“这段消息由持有密钥的一方签发且未被修改”,并不能自动证明“它允许重置密码”。 题目里可以直接做一组对照:用自己测试账号拿到验证邮箱令牌,将其原样提交到重置密码接口;再修改令牌中的一个字符重试。如果原样能用、修改后失败,说明签名校验可能正常,问题更可能在业务授权边界。还要确认服务端确实修改了密码,而不只是返回了统一的成功提示。 审代码时,重点沿着两个接口追踪令牌的生成与校验函数: 是否都只签了 user_id + expires_at,共用同一套校验逻辑,却没有区分 verify_email 和 reset_password? 即便 payload 带了 purpose,重置接口有没有实际检查它?只验签、只检查过期时间仍然不够。 最终操作的账号是否来自已验签的 payload,而不是请求中另外传入的 email 或 user_id? 修复的关键是把用途纳入签名,并由接口指定预期用途,而不是相信令牌自己声明的用途。例如: # 示意:verify_signed_token 必须先验签,再返回可信载荷 claims = verify_signed_token(token, reset_key) if claims["purpose"] != "reset_password": raise InvalidToken() if claims["exp"] <= now(): raise ExpiredToken() # jti 对应服务端签发记录;检查用途、用户、有效期并原子消费 with transaction(): consume_once(claims["jti"], claims["sub"], "reset_password") update_password(claims["sub"], new_password_hash) 验证邮箱和重置密码也可以使用独立的派生密钥或签名命名空间,降低跨接口误用风险。一次性消费解决的是重放,绑定用途解决的是跨场景使用,两者不能互相替代。
创建帐户或登录后发表意见