Django密码重置系统安全风险、set_password原理及user_id篡改防范咨询
1 你的密码重置系统存在的安全风险
- 验证码无过期时间:当前你将重置验证码直接存在用户Profile中,没有设置失效时间,只要验证码没被使用,用户或拿到验证码的第三方可以永久用于重置密码,风险极高。
- 无暴力破解防护:如果你的
rand_number_mail()生成的是纯数字短验证码(常见4/6位),且没有校验验证码的错误提交次数,攻击者可以通过批量枚举验证码破解任意用户的重置凭证。 - 接口无频率限制:发送验证码接口没有做频次校验,攻击者可以批量提交邮箱地址耗尽你的邮件服务配额,或是对特定用户发起邮件轰炸。
- 无效验证码校验逻辑不完整:密码修改接口中校验验证码不存在后,仅添加了警告提示,没有中断逻辑跳转,代码仍会继续向下执行抛出异常,用户无法得到明确的错误反馈。
- 消息类型错误:邮箱不存在时你调用的是
messages.success返回错误提示,用户很难注意到这个错误反馈,应该改为messages.error。 - 异常处理过于粗糙:密码修改接口直接捕获所有异常仅在后台打印,用户端得不到任何错误提示,同时可能掩盖业务逻辑漏洞。
- 验证码冲突风险:如果多个用户同时请求重置,极端情况下
rand_number_mail()可能生成重复的验证码,导致重置逻辑混乱,建议验证码生成后校验全局唯一再存储。
2 Django set_password() 方法的逻辑
set_password() 是Django封装好的密码加密存储方法,默认自带加密机制,不会存储明文密码,具体逻辑如下:
- 接收你传入的明文密码作为输入
- 读取项目
settings.py中PASSWORD_HASHERS配置的哈希算法(默认是PBKDF2算法) - 自动生成随机加密盐,使用配置的哈希算法对「盐+明文密码」做多次迭代哈希
- 最终将「算法标识$迭代次数$盐$哈希值」拼接成的字符串赋值给user对象的
password字段
调用该方法后你需要手动执行user.save()才会将加密后的密码写入数据库,完全不需要你自己实现加密逻辑。
3 核心问题:用户是否能篡改user_id修改他人密码
你当前的实现不存在该风险
看你的密码修改逻辑:完全是通过用户提交的重置验证码forget_password_token,从数据库查询对应的UserProfile,再从UserProfile关联获取对应用户的user_id,整个流程没有从前端请求参数中读取任何用户身份相关的标识,所以即使用户想篡改user_id,也没有传入的入口,不会出现改他人密码的问题。
如果后续调整逻辑需要防范该风险,可按以下方案处理:
- 永远不要信任前端传入的用户身份标识,所有用户身份的确认都通过后端持有的凭证(重置验证码、登录态session/Token等)查询获取
- 重置验证码需要保证高熵、不可预测,且一次使用后立即失效(你当前逻辑中使用后将验证码重置为新的随机值,这部分是符合要求的)
- 如果确实需要将
user_id放到前端作为参数传递,后端校验时必须验证当前提交的重置验证码对应的user_id和前端传入的user_id完全一致,不一致直接拒绝请求。
内容的提问来源于stack exchange,提问作者boyenec
相关产品推荐
相关产品推荐

