You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ASP.NET忘记密码控制器权限漏洞修复咨询及代码排查

ASP.NET ForgotPassword控制器权限漏洞修复方案

这个漏洞的核心问题很明确:你的接口直接把<user_id>暴露在路径里,且没有任何身份校验逻辑,导致攻击者可以随意传入任意user_id,触发对应账号的密码重置流程——完全跳过了身份验证环节。下面是针对性的修复方案,从根源解决到临时补丁都有:

一、彻底重构接口逻辑(推荐方案)

ForgotPassword的正确逻辑应该是用户主动提供自身身份标识(邮箱/手机号),系统通过验证凭证确认用户身份后再允许重置,而非直接让用户指定user_id。具体步骤:

  1. 修改接口设计

    • 把原来的POST /ForgotPassword/<user_id>调整为POST /ForgotPassword,请求体仅接收用户的邮箱或手机号(比如{"email": "xxx@xx.com"}),彻底移除路径中的user_id参数。
    • 后端通过邮箱/手机号查询对应user_id,而非直接信任前端传入的数值,从根源堵死攻击者指定任意账号的可能。
  2. 增加身份验证环节

    • 用户提交邮箱后,系统生成一次性、带过期时间的重置令牌(可使用JWT或随机字符串),将包含令牌和user_id的重置链接发送到用户的注册邮箱/手机号。
    • 新增POST /ResetPassword接口,要求前端传入user_id、令牌和新密码,后端先验证令牌的有效性、过期时间,以及令牌与该user_id的绑定关系,全部通过后再执行密码重置操作。
  3. 添加请求防护机制

    • 给ForgotPassword接口添加频率限制:同一IP或同一邮箱1小时内最多请求5次,防止攻击者批量枚举用户或刷请求。
    • 对请求参数做格式校验,比如验证邮箱格式、手机号格式,过滤非法输入。

二、临时修复方案(无法立即重构时使用)

如果暂时不能调整接口结构,至少要给现有接口加验证逻辑,确保只有合法用户才能操作对应的user_id:

  • 要求请求额外携带该user_id对应的邮箱/手机号,后端先校验邮箱/手机号是否与user_id匹配,匹配成功后再执行后续流程。
  • 注意:这个方案只是临时补丁,攻击者仍可能通过枚举邮箱+user_id尝试攻击,不如重构方案彻底。

修复后的代码示例

[HttpPost("ForgotPassword")]
public async Task<IActionResult> ForgotPassword([FromBody] ForgotPasswordRequest model)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    // 校验邮箱格式
    if (!new EmailAddressAttribute().IsValid(model.Email))
    {
        return BadRequest("无效的邮箱格式");
    }

    // 通过邮箱找用户,找不到也不泄露信息
    var user = await _userManager.FindByEmailAsync(model.Email);
    if (user != null)
    {
        // 生成重置令牌
        var resetToken = await _userManager.GeneratePasswordResetTokenAsync(user);
        var resetLink = Url.Action("ResetPassword", "Account", 
            new { userId = user.Id, token = resetToken }, Request.Scheme);

        // 发送重置邮件(替换为你的邮件发送逻辑)
        await _emailSender.SendEmailAsync(model.Email, "重置密码", $"点击链接重置密码:{resetLink}");
    }

    // 返回通用提示,避免泄露用户是否存在
    return Ok("如果该邮箱已注册,我们会发送重置密码的邮件");
}

[HttpPost("ResetPassword")]
public async Task<IActionResult> ResetPassword([FromBody] ResetPasswordRequest model)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    var user = await _userManager.FindByIdAsync(model.UserId);
    if (user == null)
    {
        return BadRequest("用户不存在");
    }

    // 验证令牌有效性
    var resetResult = await _userManager.ResetPasswordAsync(user, model.Token, model.NewPassword);
    if (resetResult.Succeeded)
    {
        return Ok("密码重置成功");
    }

    return BadRequest(resetResult.Errors.Select(e => e.Description));
}

// 请求模型
public class ForgotPasswordRequest
{
    [Required]
    public string Email { get; set; }
}

public class ResetPasswordRequest
{
    [Required]
    public string UserId { get; set; }
    [Required]
    public string Token { get; set; }
    [Required]
    [DataType(DataType.Password)]
    public string NewPassword { get; set; }
}

额外注意点

  • 永远不要返回"该用户不存在"这类明确提示,用"如果该邮箱已注册,我们会发送邮件"的通用话术,防止攻击者枚举你的用户列表。
  • 重置令牌的过期时间设短一点(比如15分钟),就算泄露也难以被滥用。
  • 重置链接一定要使用HTTPS,避免令牌被中间人截获。

内容的提问来源于stack exchange,提问作者Ahmed Albarwani

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 03:50:32