重置接口必须绕过登录态校验,独立路由且无auth中间件;token须用random_bytes生成并password_hash存储于VARCHAR(255)字段;验证SQL需原子化三重约束;重置后须同步失效所有会话。

重置接口必须完全绕过登录态校验
ThinkPHP 默认的密码修改逻辑(比如 updatepwd)依赖 Session::get('uid'),这属于「已登录用户改密」,不能用于找回场景。一旦重置接口挂了 auth 中间件或调用了 checkLogin(),攻击者就可能伪造 session 绕过身份核验。
- 路由必须独立定义,例如
Route::post('api/password/reset', 'Index/resetPassword'),不绑定任何中间件 - 控制器方法顶部禁止出现
isLogin()、$this->checkAuth()等校验逻辑 - 命令行重置(如
tp_password_reset)天然隔离 session,适合运维紧急操作,但需限制执行权限
token 生成与存储必须用 random_bytes + password_hash
用 uniqid()、md5(time().$email) 或 rand() 生成的 token 可被批量预测,实测 1 秒内能穷举数万条有效链接。安全做法只有一种:生成强随机字节,再单向哈希后落库。
- 生成:
$token = bin2hex(random_bytes(32))(PHP 7.0+),旧版 PHP 5.6 用openssl_random_pseudo_bytes(32)并检查$crypto_strong === true - 存储:只存
password_hash($token, PASSWORD_ARGON2ID)结果到token_hash字段,绝不能存明文或 md5 - 字段长度必须是
VARCHAR(255),PASSWORD_ARGON2ID输出约 97 字符,VARCHAR(60)会截断导致password_verify()永远失败
验证 SQL 必须原子化判断状态与时效
常见错误是先查记录再用 PHP 判断 $row['expires_at'] > date('Y-m-d H:i:s')——MySQL 和 PHP 时区不一致、NTP 偏移、服务器时间未同步都会导致绕过。验证必须由数据库一次性完成。
- 查询语句必须带三重约束:
SELECT id, user_id FROM password_resets WHERE token_hash = ? AND used_at IS NULL AND expires_at > NOW() - 查到后立刻原子更新:
UPDATE password_resets SET used_at = NOW() WHERE id = ?,若影响行数为 0(并发冲突),后续全部中止并返回「令牌已使用」 -
used_at字段设为TIMESTAMP NULL,联合索引(token_hash, used_at)不仅防重放,也加速上述查询
重置成功后必须同步失效所有活跃会话
只改 users.password 是伪重置。攻击者仍可用旧 session 继续操作,甚至拿到管理员权限后横向渗透。
立即学习“PHP免费学习笔记(深入)”;
- 事务内完成两件事:更新密码 + 清空该用户在
sessions表中的全部记录(或设invalidated_at时间戳) - 若有「记住我」功能,必须删掉对应
remember_token(Redis 或数据库里) - 更可靠的做法是在用户表加
password_changed_at字段,中间件每次请求比对session_created_at < password_changed_at,自动踢出
token_hash 字段长度不够、expires_at 没进 SQL 条件、used_at 更新没做原子判断——这些细节一漏,整套流程就形同虚设。



















