一次CSRF漏洞挖掘与审计
字数 1427 2025-08-22 12:23:36

CSRF漏洞挖掘与审计教学文档

一、漏洞概述

CSRF(Cross-Site Request Forgery,跨站请求伪造)是一种常见的Web安全漏洞,攻击者可以利用该漏洞在用户不知情的情况下,以用户的身份执行未经授权的操作。

漏洞危害

  • 在修改他人密码的场景中尤为严重
  • 攻击者可以完全控制目标用户账户
  • 可能导致数据泄露、权限提升等严重后果

二、漏洞原理

核心机制

  1. Web应用依赖用户身份验证(通常通过会话信息如Cookie)
  2. 用户登录后,浏览器会保存会话信息
  3. 用户访问其他页面时,浏览器自动携带这些会话信息
  4. 服务器通过验证会话信息判断用户身份

攻击流程

  1. 攻击者构造恶意请求
  2. 诱使用户在已登录目标网站的情况下访问恶意页面
  3. 浏览器自动携带用户的会话信息向目标网站发起请求
  4. 服务器误认为是用户本人发起的合法请求
  5. 执行未经授权的操作(如修改密码)

三、漏洞复现

环境准备

  • 开发环境:idea2024、jdk1.8、tomcat+maven
  • 目标系统:超市管理系统
  • 测试工具:Burp Suite

复现步骤

  1. 定位漏洞点:修改密码功能

  2. 分析请求流程:

    • 第一个请求包:验证旧密码(oldpassword参数)
    • 第二个请求包:实际修改密码请求
  3. 关键请求包分析:

POST /smbms/jsp/user.do HTTP/1.1
Host: 192.168.32.142:8080
Content-Type: application/x-www-form-urlencoded
Cookie: JSESSIONID=386FCEF330712CC65F793089E7377DC0

method=savepwd&oldpassword=123456789&newpassword=12345678%2B&rnewpassword=12345678%2B
  1. 使用Burp Suite生成CSRF PoC
  2. 在另一个浏览器中登录其他账户测试PoC
  3. 验证密码是否被修改

四、代码审计分析

1. Web.xml配置分析

<servlet>
    <servlet-name>UserServlet</servlet-name>
    <servlet-class>org.example.servlet.user.UserServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>UserServlet</servlet-name>
    <url-pattern>/jsp/user.do</url-pattern>
</servlet-mapping>

2. 关键代码分析

修改密码功能(updatePwd方法)

public void updatePwd(HttpServletRequest req, HttpServletResponse resp){
    // 从session中获取用户id
    Object attribute = req.getSession().getAttribute(Constansts.USER_SESSION);
    String newpassword = req.getParameter("newpassword");
    boolean flag = false;
    
    if(attribute !=null && !StringUtils.isNullOrEmpty(newpassword)){
        UserService userService = new UserServiceImpl();
        flag = userService.updatePwd(((User)attribute).getId(),newpassword);
        
        if(flag){
            req.setAttribute("message","修改密码成功!请重新登录!");
            req.getSession().removeAttribute(Constansts.USER_SESSION);
        }else {
            req.setAttribute("message","修改密码失败!");
        }
    }else{
        req.setAttribute("message","新密码有问题!");
    }
    
    try {
        req.getRequestDispatcher("pwdmodify.jsp").forward(req, resp);
    } catch (ServletException e) {
        throw new RuntimeException(e);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

漏洞点:

  • 虽然前端传递了oldpassword参数,但后端代码中完全没有使用和校验
  • 没有CSRF Token等防护机制
  • 仅依赖session验证用户身份

旧密码验证功能(pwdModify方法)

public void pwdModify(HttpServletRequest req, HttpServletResponse resp){
    Object attribute = req.getSession().getAttribute(Constansts.USER_SESSION);
    String oldpassword = req.getParameter("oldpassword");
    Map<String,String> ressltMap = new HashMap<String,String>();
    
    if(attribute == null){
        ressltMap.put("result","sessionor");
    } else if (StringUtils.isNullOrEmpty(oldpassword)) {
        ressltMap.put("result","error");
    }else {
        String userPassword = ((User)attribute).getUserPassword();
        if(userPassword.equals(oldpassword)){
            ressltMap.put("result","true");
        }else{
            ressltMap.put("result","false");
        }
    }
    
    try {
        resp.setContentType("application/json");
        PrintWriter writer = resp.getWriter();
        writer.write(JSONArray.toJSONString(ressltMap));
        writer.flush();
        writer.close();
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

漏洞点:

  • 验证结果仅通过前端AJAX处理
  • 攻击者可拦截响应包修改result值为true绕过验证
  • 后端验证与修改密码操作分离,没有关联性

3. 前端实现分析(pwdmodify.jsp)

<form id="userForm" name="userForm" method="post" action="${pageContext.request.contextPath }/jsp/user.do">
    <input type="hidden" name="method" value="savepwd">
    <div class="">
        <label for="oldPassword">旧密码:</label>
        <input type="password" name="oldpassword" id="oldpassword" value="">
    </div>
    <div>
        <label for="newPassword">新密码:</label>
        <input type="password" name="newpassword" id="newpassword" value="">
    </div>
    <div>
        <label for="reNewPassword">确认新密码:</label>
        <input type="password" name="rnewpassword" id="rnewpassword" value="">
    </div>
    <div class="providerAddBtn">
        <input type="button" name="save" id="save" value="保存" class="input-button">
    </div>
</form>

AJAX验证代码:

oldpassword.on("blur",function(){
    $.ajax({
        type:"GET",
        url:path+"/jsp/user.do",
        data:{method:"pwdmodify",oldpassword:oldpassword.val()},
        dataType:"json",
        success:function(data){
            if(data.result == "true"){
                validateTip(oldpassword.next(),{"color":"green"},imgYes,true);
            }else if(data.result == "false"){
                validateTip(oldpassword.next(),{"color":"red"},imgNo + " 原密码输入不正确",false);
            }else if(data.result == "sessionerror"){
                validateTip(oldpassword.next(),{"color":"red"},imgNo + " 当前用户session过期,请重新登录",false);
            }else if(data.result == "error"){
                validateTip(oldpassword.next(),{"color":"red"},imgNo + " 请输入旧密码",false);
            }
        },
        error:function(data){
            validateTip(oldpassword.next(),{"color":"red"},imgNo + " 请求错误",false)
        }
    });
});

五、漏洞利用分析

1. 直接利用方式

  • 构造恶意HTML表单,诱导用户点击
  • 绕过旧密码验证:
    • 直接发送修改密码请求(method=savepwd
    • 拦截验证响应,修改resulttrue

2. 利用难点

  • 需要用户已登录系统
  • 需要诱使用户访问恶意页面

3. 利用效果

  • 成功修改用户密码
  • 用户被强制退出登录(session被清除)

六、修复建议

1. 添加CSRF Token

  • 在表单中添加随机生成的Token
  • 服务器端验证Token有效性
// 生成Token
String csrfToken = UUID.randomUUID().toString();
session.setAttribute("csrfToken", csrfToken);

// 在表单中添加
<input type="hidden" name="csrfToken" value="${csrfToken}">

// 服务器端验证
if(!request.getParameter("csrfToken").equals(session.getAttribute("csrfToken"))){
    // 验证失败处理
}

2. 加强旧密码验证

  • 将旧密码验证与修改操作合并
  • 在修改密码方法中验证旧密码
public void updatePwd(HttpServletRequest req, HttpServletResponse resp){
    Object attribute = req.getSession().getAttribute(Constansts.USER_SESSION);
    String oldpassword = req.getParameter("oldpassword");
    String newpassword = req.getParameter("newpassword");
    
    if(attribute == null){
        // 处理未登录情况
        return;
    }
    
    User user = (User)attribute;
    if(!user.getUserPassword().equals(oldpassword)){
        // 旧密码不匹配
        return;
    }
    
    // 继续修改密码逻辑
}

3. 使用SameSite Cookie属性

<session-config>
    <cookie-config>
        <http-only>true</http-only>
        <secure>true</secure>
        <same-site>strict</same-site>
    </cookie-config>
    <session-timeout>30</session-timeout>
</session-config>

4. 验证HTTP Referer头

String referer = request.getHeader("Referer");
if(referer == null || !referer.startsWith("https://yourdomain.com")){
    // 非法请求
    return;
}

5. 关键操作使用POST请求

  • 避免使用GET请求进行状态修改操作

七、经验总结

  1. 常见误区:认为有旧密码验证就能防御CSRF

    • 实际:如果验证与操作分离,仍可能被绕过
  2. 审计要点

    • 检查关键操作是否有CSRF防护机制
    • 验证前端验证是否可被绕过
    • 检查操作是否与验证相关联
  3. 开发建议

    • 关键操作必须添加CSRF Token
    • 前后端验证要紧密结合
    • 遵循"不信任用户输入"原则
  4. 测试方法

    • 使用Burp Suite生成CSRF PoC测试
    • 尝试绕过前端验证
    • 检查是否依赖单一验证机制

安全箴言:漏洞虐我千百遍,我待漏洞如初恋。保持学习心态,持续提升安全意识和技能。

CSRF漏洞挖掘与审计教学文档 一、漏洞概述 CSRF(Cross-Site Request Forgery,跨站请求伪造)是一种常见的Web安全漏洞,攻击者可以利用该漏洞在用户不知情的情况下,以用户的身份执行未经授权的操作。 漏洞危害 在修改他人密码的场景中尤为严重 攻击者可以完全控制目标用户账户 可能导致数据泄露、权限提升等严重后果 二、漏洞原理 核心机制 Web应用依赖用户身份验证(通常通过会话信息如Cookie) 用户登录后,浏览器会保存会话信息 用户访问其他页面时,浏览器自动携带这些会话信息 服务器通过验证会话信息判断用户身份 攻击流程 攻击者构造恶意请求 诱使用户在已登录目标网站的情况下访问恶意页面 浏览器自动携带用户的会话信息向目标网站发起请求 服务器误认为是用户本人发起的合法请求 执行未经授权的操作(如修改密码) 三、漏洞复现 环境准备 开发环境:idea2024、jdk1.8、tomcat+maven 目标系统:超市管理系统 测试工具:Burp Suite 复现步骤 定位漏洞点:修改密码功能 分析请求流程: 第一个请求包:验证旧密码( oldpassword 参数) 第二个请求包:实际修改密码请求 关键请求包分析: 使用Burp Suite生成CSRF PoC 在另一个浏览器中登录其他账户测试PoC 验证密码是否被修改 四、代码审计分析 1. Web.xml配置分析 2. 关键代码分析 修改密码功能(updatePwd方法) 漏洞点: 虽然前端传递了 oldpassword 参数,但后端代码中完全没有使用和校验 没有CSRF Token等防护机制 仅依赖session验证用户身份 旧密码验证功能(pwdModify方法) 漏洞点: 验证结果仅通过前端AJAX处理 攻击者可拦截响应包修改 result 值为 true 绕过验证 后端验证与修改密码操作分离,没有关联性 3. 前端实现分析(pwdmodify.jsp) AJAX验证代码: 五、漏洞利用分析 1. 直接利用方式 构造恶意HTML表单,诱导用户点击 绕过旧密码验证: 直接发送修改密码请求( method=savepwd ) 拦截验证响应,修改 result 为 true 2. 利用难点 需要用户已登录系统 需要诱使用户访问恶意页面 3. 利用效果 成功修改用户密码 用户被强制退出登录(session被清除) 六、修复建议 1. 添加CSRF Token 在表单中添加随机生成的Token 服务器端验证Token有效性 2. 加强旧密码验证 将旧密码验证与修改操作合并 在修改密码方法中验证旧密码 3. 使用SameSite Cookie属性 4. 验证HTTP Referer头 5. 关键操作使用POST请求 避免使用GET请求进行状态修改操作 七、经验总结 常见误区 :认为有旧密码验证就能防御CSRF 实际:如果验证与操作分离,仍可能被绕过 审计要点 : 检查关键操作是否有CSRF防护机制 验证前端验证是否可被绕过 检查操作是否与验证相关联 开发建议 : 关键操作必须添加CSRF Token 前后端验证要紧密结合 遵循"不信任用户输入"原则 测试方法 : 使用Burp Suite生成CSRF PoC测试 尝试绕过前端验证 检查是否依赖单一验证机制 安全箴言 :漏洞虐我千百遍,我待漏洞如初恋。保持学习心态,持续提升安全意识和技能。