JAVA漏洞修复-CSRF漏洞

Posted 下辈子想做头猪

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JAVA漏洞修复-CSRF漏洞相关的知识,希望对你有一定的参考价值。

Struts框架


JSP使用<s:token/>标签,在struts配置文件中增加token拦截器
页面代码:

<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>

在struts.xml中配置:

<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>

流程说明:

  1. 客户端申请token

  1. 服务器端生成token,并存放在session中,同时将token发送到客户端

  1. 客户端存储token,在请求提交时,同时发送token信息

  1. 服务器端统一拦截同一个用户的所有请求,验证当前请求是否需要被验证(不是所有请求都验证重复提交)

  1. 验证session中token是否和用户请求中的token一致,如果一致则放行

  1. session清除会话中的token,为下一次的token生成作准备

  1. 并发重复请求到来,验证token和请求token不一致,请求被拒绝


SpringMVC

具体思路:

  1. 跳转页面前生成随机token,并存放在session中

  1. form中将token放在隐藏域中,保存时将token放头部一起提交

  1. 获取头部token, 与session中的token比较,一致则通过

  1. 生成新的token,并传给前端


一: 配置拦截器

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

二: 拦截器实现 CSRFInterceptor.java

import java.io.OutputStream;import java.io.PrintWriter;import java.lang.reflect.Method;import java.util.HashMap;import java.util.Map;
import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;
import org.springframework.web.method.HandlerMethod;import org.springframework.web.servlet.ModelAndView;import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import com.alibaba.fastjson.JSONObject;import com.cnpc.framework.annotation.RefreshCSRFToken;import com.cnpc.framework.annotation.VerifyCSRFToken;import com.cnpc.framework.base.pojo.ResultCode;import com.cnpc.framework.constant.CodeConstant;import com.cnpc.framework.utils.CSRFTokenUtil;import com.cnpc.framework.utils.StrUtil;
/** * CSRFInterceptor 防止跨站请求伪造拦截器 */public class CSRFInterceptor extends HandlerInterceptorAdapter {
@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("---------->" + request.getRequestURI()); System.out.println(request.getHeader("X-Requested-With")); // 提交表单token 校验 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校验 * * @param request */ 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); }}


ESAPI


一: 产生一个新的csrfToken,在用户第一次登陆的时候保存在session中

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


二:对于所有需要保护的表单,增加一个隐藏的csrfToken字段。下面的addCSRFToken方法可以被所有需要保护的URL链接调用,如果是表单的话,可以增加一个字段,名字叫"ctoken",值为DefaultHTTPUtilities.getCSRFToken()。

URL上:

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

表单中:

<input type="hidden" name="ctoken" value="<%=ESAPI.DefaultHTTPUtilities.getCSRFToken() %>">


三: 在服务器端那些需要保护的方法执行之前,首先检查一下提交过来的token与session中的token是否一致,如果不一致,则被认为是一个伪造的请求(CSRF攻击)

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

四: 在用户退出或者session过期的时候 移除csrfToken

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


以上是关于JAVA漏洞修复-CSRF漏洞的主要内容,如果未能解决你的问题,请参考以下文章

安全牛学习笔记CSRF跨站请求伪造攻击漏洞的原理及解决办法

CSRF漏洞原理+案例演示及修复策略

Redis CSRF漏洞分析及云Redis安全措施介绍

什么不是java中易出现文件操作漏洞的方法

java csrf漏洞修改不修改页面

CSRF 漏洞