修复J2EE漏洞:4. CSRF漏洞
字数 1041 2025-08-29 08:31:53

J2EE CSRF漏洞防护详解

1. CSRF漏洞概述

CSRF(Cross-Site Request Forgery)跨站请求伪造是一种常见的Web安全漏洞,攻击者诱导受害者进入第三方网站,在受害者已登录目标网站的情况下,利用受害者的身份凭证执行非预期的操作。

2. Struts框架CSRF防护

2.1 使用<s:token/>标签

在Struts框架中,可以通过<s:token/>标签生成并验证CSRF令牌:

<s:form action="reg" theme="simple">
    username:<s:textfield name="username"></s:textfield><br />
    password:<s:password name="password"></s:password><br />
    <s:token></s:token>
    <s:submit value="注册"></s:submit>
</s:form>

2.2 配置token拦截器

struts.xml中配置token拦截器:

<package name="test" namespace="/test" extends="struts-default">
    <interceptors>
        <!-- 配置拦截器 -->
        <interceptor-stack name="tokenStack">
            <!-- 命名拦截器栈,名字随便 -->
            <interceptor-ref name="token"/>
            <!-- 此拦截器为token拦截器,struts已经实现 -->
            <interceptor-ref name="defaultStack" />
            <!-- 默认拦截器,注意顺序,默认拦截器放在最下面 -->
        </interceptor-stack>
    </interceptors>
    <default-interceptor-ref name="tokenStack" />
    <!-- 让该包中所有action都是用我们配置的拦截器栈,名字和上面的对应 -->
</package>

2.3 流程说明

  1. 客户端申请token
  2. 服务器端生成token,并存放在session中,同时将token发送到客户端
  3. 客户端存储token,在请求提交时,同时发送token信息
  4. 服务器端统一拦截同一个用户的所有请求,验证当前请求是否需要被验证
  5. 验证session中token是否和用户请求中的token一致,如果一致则放行
  6. session清除会话中的token,为下一次的token生成作准备
  7. 并发重复请求到来,验证token和请求token不一致,请求被拒绝

3. SpringMVC框架CSRF防护

3.1 基本思路

  1. 跳转页面前生成随机token,并存放在session中
  2. form中将token放在隐藏域中,保存时将token放头部一起提交
  3. 获取头部token,与session中的token比较,一致则通过
  4. 生成新的token,并传给前端

3.2 配置拦截器

<mvc:interceptors>
    <!-- csrf攻击防御 -->
    <mvc:interceptor>
        <!-- 需拦截的地址 -->
        <mvc:mapping path="/**"/>
        <!-- 需排除拦截的地址 -->
        <mvc:exclude-mapping path="/resources/**"/>
        <bean class="com.cnpc.framework.interceptor.CSRFInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>

3.3 拦截器实现

public class CSRFInterceptor extends HandlerInterceptorAdapter {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();
        VerifyCSRFToken verifyCSRFToken = method.getAnnotation(VerifyCSRFToken.class);
        
        // 如果配置了校验csrf token校验,则校验
        if (verifyCSRFToken != null) {
            // 是否为Ajax标志
            String xrq = request.getHeader("X-Requested-With");
            
            // 非法的跨站请求校验
            if (verifyCSRFToken.verify() && !verifyCSRFToken(request)) {
                if (StrUtil.isEmpty(xrq)) {
                    // form表单提交,url get方式,刷新csrftoken并跳转提示页面
                    String csrftoken = CSRFTokenUtil.generate(request);
                    request.getSession().setAttribute("CSRFToken", csrftoken);
                    response.setContentType("application/json;charset=UTF-8");
                    PrintWriter out = response.getWriter();
                    out.print("非法请求");
                    response.flushBuffer();
                    return false;
                } else {
                    // 刷新CSRFToken,返回错误码,用于ajax处理,可自定义
                    String csrftoken = CSRFTokenUtil.generate(request);
                    request.getSession().setAttribute("CSRFToken", csrftoken);
                    ResultCode rc = CodeConstant.CSRF_ERROR;
                    response.setContentType("application/json;charset=UTF-8");
                    PrintWriter out = response.getWriter();
                    out.print(JSONObject.toJSONString(rc));
                    response.flushBuffer();
                    return false;
                }
            }
        }
        return true;
    }
    
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        // 第一次生成token
        if (modelAndView != null) {
            if (request.getSession(false) == null || StrUtil.isEmpty((String) request.getSession(false).getAttribute("CSRFToken"))) {
                request.getSession().setAttribute("CSRFToken", CSRFTokenUtil.generate(request));
                return;
            }
        }
        
        // 刷新token
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();
        RefreshCSRFToken refreshAnnotation = method.getAnnotation(RefreshCSRFToken.class);
        
        // 跳转到一个新页面 刷新token
        String xrq = request.getHeader("X-Requested-With");
        if (refreshAnnotation != null && refreshAnnotation.refresh() && StrUtil.isEmpty(xrq)) {
            request.getSession().setAttribute("CSRFToken", CSRFTokenUtil.generate(request));
            return;
        }
        
        // 校验成功 刷新token 可以防止重复提交
        VerifyCSRFToken verifyAnnotation = method.getAnnotation(VerifyCSRFToken.class);
        if (verifyAnnotation != null) {
            if (verifyAnnotation.verify()) {
                if (StrUtil.isEmpty(xrq)) {
                    request.getSession().setAttribute("CSRFToken", CSRFTokenUtil.generate(request));
                } else {
                    Map<String, String> map = new HashMap<String, String>();
                    map.put("CSRFToken", CSRFTokenUtil.generate(request));
                    response.setContentType("application/json;charset=UTF-8");
                    OutputStream out = response.getOutputStream();
                    out.write((",'csrf':" + JSONObject.toJSONString(map)).getBytes("UTF-8"));
                }
            }
        }
    }
    
    /**
     * 处理跨站请求伪造 针对需要登录后才能处理的请求,验证CSRFToken校验
     */
    protected boolean verifyCSRFToken(HttpServletRequest request) {
        // 请求中的CSRFToken
        String requstCSRFToken = request.getHeader("CSRFToken");
        if (StrUtil.isEmpty(requstCSRFToken)) {
            return false;
        }
        
        String sessionCSRFToken = (String) request.getSession().getAttribute("CSRFToken");
        if (StrUtil.isEmpty(sessionCSRFToken)) {
            return false;
        }
        
        return requstCSRFToken.equals(sessionCSRFToken);
    }
}

4. ESAPI实现CSRF防护

4.1 生成CSRF Token

private String csrfToken = resetCSRFToken();

private String resetCSRFToken() {
    String csrfToken = ESAPI.randomizer().getRandomString(8, DefaultEncoder.CHAR_ALPHANUMERICS);
    return csrfToken;
}

4.2 在表单中添加CSRF Token

对于需要保护的表单,增加一个隐藏的csrfToken字段:

<!-- URL上 -->
<a href='<%=ESAPI.httpUtilities().addCSRFToken("/zhutougg/main?function=listUser")%>'>查询用户</a>

<!-- 表单中 -->
<input type="hidden" name="ctoken" value="<%=ESAPI.DefaultHTTPUtilities.getCSRFToken() %>">

4.3 服务器端验证

@RequestMapping(value="/admin/login",method = RequestMethod.POST)
public String listUser(HttpServletRequest request, HttpServletResponse response, Model model) {
    ESAPI.httpUtilities().verifyCSRFToken(request);
    // 其他业务逻辑
}

4.4 用户退出时移除token

ESAPI.authenticator().getCurrentUser().logout();

5. 最佳实践总结

  1. 重要操作必须使用POST请求:GET请求容易被利用进行CSRF攻击
  2. 使用CSRF Token:为每个表单生成唯一的token,并在服务器端验证
  3. 验证Referer头:检查请求来源是否合法
  4. 设置SameSite Cookie属性:限制第三方网站使用Cookie
  5. 双重提交Cookie:将token同时放在Cookie和请求参数中,服务器验证两者是否一致
  6. 定期更换Token:防止token被窃取后长期有效
  7. 敏感操作二次验证:如密码修改等操作要求用户重新输入密码

通过以上措施,可以有效防止CSRF攻击,保护Web应用的安全。

J2EE CSRF漏洞防护详解 1. CSRF漏洞概述 CSRF(Cross-Site Request Forgery)跨站请求伪造是一种常见的Web安全漏洞,攻击者诱导受害者进入第三方网站,在受害者已登录目标网站的情况下,利用受害者的身份凭证执行非预期的操作。 2. Struts框架CSRF防护 2.1 使用 <s:token/> 标签 在Struts框架中,可以通过 <s:token/> 标签生成并验证CSRF令牌: 2.2 配置token拦截器 在 struts.xml 中配置token拦截器: 2.3 流程说明 客户端申请token 服务器端生成token,并存放在session中,同时将token发送到客户端 客户端存储token,在请求提交时,同时发送token信息 服务器端统一拦截同一个用户的所有请求,验证当前请求是否需要被验证 验证session中token是否和用户请求中的token一致,如果一致则放行 session清除会话中的token,为下一次的token生成作准备 并发重复请求到来,验证token和请求token不一致,请求被拒绝 3. SpringMVC框架CSRF防护 3.1 基本思路 跳转页面前生成随机token,并存放在session中 form中将token放在隐藏域中,保存时将token放头部一起提交 获取头部token,与session中的token比较,一致则通过 生成新的token,并传给前端 3.2 配置拦截器 3.3 拦截器实现 4. ESAPI实现CSRF防护 4.1 生成CSRF Token 4.2 在表单中添加CSRF Token 对于需要保护的表单,增加一个隐藏的csrfToken字段: 4.3 服务器端验证 4.4 用户退出时移除token 5. 最佳实践总结 重要操作必须使用POST请求 :GET请求容易被利用进行CSRF攻击 使用CSRF Token :为每个表单生成唯一的token,并在服务器端验证 验证Referer头 :检查请求来源是否合法 设置SameSite Cookie属性 :限制第三方网站使用Cookie 双重提交Cookie :将token同时放在Cookie和请求参数中,服务器验证两者是否一致 定期更换Token :防止token被窃取后长期有效 敏感操作二次验证 :如密码修改等操作要求用户重新输入密码 通过以上措施,可以有效防止CSRF攻击,保护Web应用的安全。