CSRF spring mvc 跨站请求伪造防御
2016-03-30 11:18
756 查看
CSRF
CSRF(Cross-site request forgery跨站请求伪造,也被称为“One Click Attack”或者Session Riding,通常缩写为CSRF或者XSRF,是一种对网站的恶意利用。尽管听起来像跨站脚本(XSS),但它与XSS非常不同,并且攻击方式几乎相左。XSS利用站点内的信任用户,而CSRF则通过伪装来自受信任用户的请求来利用受信任的网站。与XSS攻击相比,CSRF攻击往往不大流行(因此对其进行防范的资源也相当稀少)和难以防范,所以被认为比XSS更具危险性。
攻击示例
如:一个网站用户Bob可能正在浏览聊天论坛,而同时另一个用户Alice也在此论坛中,并且后者刚刚发布了一个具有Bob银行链接的图片消息。设想一下,Alice编写了一个在Bob的银行站点上进行取款的form提交的链接,并将此链接作为图片src。如果Bob的银行在cookie中保存他的授权信息,并且此cookie没有过期,那么当Bob的浏览器尝试装载图片时将提交这个取款form和他的cookie,这样在没经Bob同意的情况下便授权了这次事务。
CSRF是一种依赖web浏览器的、被混淆过的代理人攻击(deputy attack)。在上面银行示例中的代理人是Bob的web浏览器,它被混淆后误将Bob的授权直接交给了Alice使用。
下面是CSRF的常见特性:
依靠用户标识危害网站
利用网站对用户标识的信任
欺骗用户的浏览器发送HTTP请求给目标站点
另外可以通过IMG标签会触发一个GET请求,可以利用它来实现CSRF攻击。
spring mvc 框架下防御策略
思路概要:
1.初始化页面时在token隐藏域。
2.表单提交后带入token到后台,验证token,如成功继续操否为受到攻击。
3.操作完之后重新生成token到页面隐藏域。
代码示例:
创建拦截器:
注解类:
配置拦截:
在jsp页面中添加隐藏域
修改公用Ajax
使用场景
为需要防御的Controller加上@VerifyCSRFToken(verifyCSRFToken = true)注解
@RefreshCSRFToken(refreshCSRFToken=true):如新打开的页面,弹出框体需要CSRFToken时使用
CSRF(Cross-site request forgery跨站请求伪造,也被称为“One Click Attack”或者Session Riding,通常缩写为CSRF或者XSRF,是一种对网站的恶意利用。尽管听起来像跨站脚本(XSS),但它与XSS非常不同,并且攻击方式几乎相左。XSS利用站点内的信任用户,而CSRF则通过伪装来自受信任用户的请求来利用受信任的网站。与XSS攻击相比,CSRF攻击往往不大流行(因此对其进行防范的资源也相当稀少)和难以防范,所以被认为比XSS更具危险性。
攻击示例
如:一个网站用户Bob可能正在浏览聊天论坛,而同时另一个用户Alice也在此论坛中,并且后者刚刚发布了一个具有Bob银行链接的图片消息。设想一下,Alice编写了一个在Bob的银行站点上进行取款的form提交的链接,并将此链接作为图片src。如果Bob的银行在cookie中保存他的授权信息,并且此cookie没有过期,那么当Bob的浏览器尝试装载图片时将提交这个取款form和他的cookie,这样在没经Bob同意的情况下便授权了这次事务。
CSRF是一种依赖web浏览器的、被混淆过的代理人攻击(deputy attack)。在上面银行示例中的代理人是Bob的web浏览器,它被混淆后误将Bob的授权直接交给了Alice使用。
下面是CSRF的常见特性:
依靠用户标识危害网站
利用网站对用户标识的信任
欺骗用户的浏览器发送HTTP请求给目标站点
另外可以通过IMG标签会触发一个GET请求,可以利用它来实现CSRF攻击。
spring mvc 框架下防御策略
思路概要:
1.初始化页面时在token隐藏域。
2.表单提交后带入token到后台,验证token,如成功继续操否为受到攻击。
3.操作完之后重新生成token到页面隐藏域。
代码示例:
创建拦截器:
/** * <一句话功能简述> * <功能详细描述> * 防止跨站请求伪造拦截器 * 为每个返回的页面添加CSRFToken参数 * @author Tangguilin * @version [版本号, 2016年3月26日] */ public class AvoidCSRFInterceptor extends HandlerInterceptorAdapter { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String url = request.getRequestURI(); if (!url.endsWith(".do")) { return true; } HandlerMethod handlerMethod = (HandlerMethod)handler; Method method = handlerMethod.getMethod(); VerifyCSRFToken annotation = method.getAnnotation(VerifyCSRFToken.class); if (annotation != null) { String xrq = request.getHeader("X-Requested-With");//是否为Ajax标志 //非法的跨站请求校验 if (annotation.verifyCSRFToken() && !verifyCSRFToken(request)) { if (StringUtil.isEmpty(xrq)) { //form表单提交,url get方式,刷新csrftoken并跳转提示页面 request.getSession(false).setAttribute("CSRFToken", TokenProcessor.getInstance().generateToken(request)); response.setContentType("application/json;charset=UTF-8"); PrintWriter out = response.getWriter(); out.print("非法请求"); response.flushBuffer(); return false; } else { //刷新CSRFToken,返回错误码,用于ajax处理,可自定义 BaseDataResp baseResp = new BaseDataResp(); String csrftoken = TokenProcessor.getInstance().generateToken(request); request.getSession(false).setAttribute("CSRFToken", csrftoken); baseResp.setCode(IDiMengResultCode.SystemManager.CSRF); response.setContentType("application/json;charset=UTF-8"); PrintWriter out = response.getWriter(); out.print(baseResp.toString()); response.flushBuffer(); return false; } } } return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { String url = request.getRequestURI(); if (!url.endsWith(".do")) { return; } //第一次生成token if (modelAndView != null) { if (request.getSession(false) == null || StringUtil.isEmpty((String)request.getSession(false).getAttribute("CSRFToken"))) { request.getSession(false).setAttribute("CSRFToken", TokenProcessor.getInstance().generateToken(request)); return; } } //刷新token HandlerMethod handlerMethod = (HandlerMethod)handler; Method method = handlerMethod.getMethod(); RefreshCSRFToken refreshAnnotation = method.getAnnotation(RefreshCSRFToken.class); String xrq = request.getHeader("X-Requested-With");//是否为Ajax标志 if (refreshAnnotation != null && refreshAnnotation.refreshCSRFToken() && StringUtil.isEmpty(xrq)) { request.getSession(false).setAttribute("CSRFToken", TokenProcessor.getInstance().generateToken(request)); return; } VerifyCSRFToken verifyAnnotation = method.getAnnotation(VerifyCSRFToken.class); if (verifyAnnotation != null) { //成功后刷新token if (verifyAnnotation.verifyCSRFToken()) { if (StringUtil.isEmpty(xrq)) { request.getSession(false).setAttribute("CSRFToken", TokenProcessor.getInstance().generateToken(request)); } else { Map<String, String> map = new HashMap<String, String>(); map.put("CSRFToken", TokenProcessor.getInstance().generateToken(request)); response.setContentType("application/json;charset=UTF-8"); OutputStream out = response.getOutputStream(); out.write((",'csrf':" + JSONObject.toJSONString(map) + "}").getBytes("UTF-8")); } } } } /** <一句话功能简述> * 处理跨站请求伪造 * 针对需要登录后才能处理的请求,验证CSRFToken校验 * @author tangguilin * @param request */ protected boolean verifyCSRFToken(HttpServletRequest request) { String requstCSRFToken = request.getHeader("CSRFToken");//请求中的CSRFToken if (StringUtil.isEmpty(requstCSRFToken)) { return false; } String sessionCSRFToken = (String)request.getSession().getAttribute("CSRFToken"); if (StringUtil.isEmpty(sessionCSRFToken)) { return false; } return requstCSRFToken.equals(sessionCSRFToken); } }
注解类:
/** * 跨站请求仿照注解 * 刷新CSRFToken * @author Tangguilin * @version [版本号, 2016年3月28日] */ @Target({java.lang.annotation.ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface RefreshCSRFToken { /** * 刷新CSRFToken * @author tangguilin * @return */ public abstract boolean refreshCSRFToken(); } /** * 跨站请求仿照注解 * * @author Tangguilin * @version [版本号, 2016年3月28日] */ @Target({java.lang.annotation.ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface VerifyCSRFToken { /** * 需要验证防跨站请求 * @author tangguilin * @return */ public abstract boolean verifyCSRFToken(); }
配置拦截:
<mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/**"/> <!-- 定义在mvc:interceptor下面的表示是对特定的请求才进行拦截的 --> <bean class="com.front.interceptor.AvoidCSRFInterceptor"/> </mvc:interceptor> </mvc:interceptors>
在jsp页面中添加隐藏域
<input type="hidden" name="CSRFToken" value="${CSRFToken }"></input>
修改公用Ajax
ajax: function(options) { var datas = null; if (options["isAjaxForm"]) { $("#" + options["formId"]).submit(); } else { if (options["serialize"]) { if (options["formId"]) { datas = $("#" + options["formId"]).serialize(); } else { datas = $(document.forms[0]).serialize(); } } var defaultOptions = { type: "post", async: false, data: datas }; options = $.extend(defaultOptions, options); var path=""; if(options["url"].indexOf(basePath)==-1){ path=basePath; } var headers = {}; headers['CSRFToken'] = $("input[name='CSRFToken']").val(); $.ajax({ type: options["type"], headers: headers, async: options["async"], dataType: options["dataType"], url: path + options["url"], data: options["data"], success: function(data) { //登录超时刷新页面后跳转首页 if("2000062"==data.code){ window.location.reload(); } //处理跨站请求伪造 if("666666" == data.code){ if(Dialog){ Dialog.show("操作失败,请刷新页面重试","error"); }else{ alert("操作失败,请刷新页面重试"); } return false; } if (typeof(options["success"]) == "function") { options["success"](data); } }, error: function(data) { //CSRFToken处理 if(data && data.readyState && data.readyState == '4'){ var responseBody = data.responseText; if(responseBody){ responseBody = "{'retData':"+responseBody; var resJson = eval('(' + responseBody + ')'); $("input[name='CSRFToken']").val(resJson.csrf.CSRFToken); if (typeof(options["success"]) == "function") { options["success"](resJson.retData); } } return ; } //登录超时跳转登录页 /*if(data.responseText){ window.location.reload();//刷新当前页面. return; }*/ if (typeof(options["error"]) == "function") { options["error"](data); }else{ alert("请求地址:"+options["url"]+" 出现异常!请联系管理员!"); } } }); } }
使用场景
为需要防御的Controller加上@VerifyCSRFToken(verifyCSRFToken = true)注解
@RefreshCSRFToken(refreshCSRFToken=true):如新打开的页面,弹出框体需要CSRFToken时使用
相关文章推荐
- Servlet
- Dubbo与Zookeeper、SpringMVC整合和使用
- Eclipse快速补全快捷键失效,修改方法
- 第二章 第二个spring-boot程序
- java中的枚举类型
- java中的枚举类型
- 【JavaScipt】可选的分号
- struts文件上传与下载简单DEMO
- 一、 Spring启动时加载和初始化bean概述
- 5.spring注解@Required、@Autowired、@qualifier、@resource
- java用ireport模板生成PDF文件
- SpringMVC中hibernate中一对多关系,懒加载转JSON问题
- java通过虚拟机设置参数的方式动态获取当前项目的绝对路径
- java for和foreach的区别
- Java线程.
- Spring MVC 注解方式 静态类 注入bean
- 100天JAVA学习计划03-浅谈方法
- JAVA中生成Excel方法
- Ubuntu14.04下安装配置openJDK1.7
- 为什么做java的web开发我们会使用struts2,springMVC和spring这样的框架?