CSRF 攻击的应对之道

安全系列文章首发javascript

Java 代码示例

下文将以 Java 为例,对上述三种方法分别用代码进行示例。不管使用何种方法,在服务器端的拦截器必不可少,它将负责检查到来的请求是否符合要求,而后视结果而决定是否继续请求或者丢弃。在 Java 中,拦截器是由 Filter 来实现的。咱们能够编写一个 Filter,并在 web.xml 中对其进行配置,使其对于访问全部须要 CSRF 保护的资源的请求进行拦截。html

在 filter 中对请求的 Referer 验证代码以下java

清单 1. 在 Filter 中验证 Referer
// 从 HTTP 头中取得 Referer 值
String referer=request.getHeader("Referer"); 
// 判断 Referer 是否以 bank.example 开头
if((referer!=null) &&(referer.trim().startsWith(“bank.example”))){ 
   chain.doFilter(request, response); 
}else{ 
   request.getRequestDispatcher(“error.jsp”).forward(request,response); 
}
复制代码

以上代码先取得 Referer 值,而后进行判断,当其非空并以 bank.example 开头时,则继续请求,不然的话多是 CSRF 攻击,转到 error.jsp 页面。web

若是要进一步验证请求中的 token 值,代码以下算法

清单 2. 在 filter 中验证请求中的 token
HttpServletRequest req = (HttpServletRequest)request; 
HttpSession s = req.getSession(); 
 
// 从 session 中获得 csrftoken 属性
String sToken = (String)s.getAttribute(“csrftoken”); 
if(sToken == null){ 
 
   // 产生新的 token 放入 session 中
   sToken = generateToken(); 
   s.setAttribute(“csrftoken”,sToken); 
   chain.doFilter(request, response); 
} else{ 
 
   // 从 HTTP 头中取得 csrftoken 
   String xhrToken = req.getHeader(“csrftoken”); 
 
   // 从请求参数中取得 csrftoken 
   String pToken = req.getParameter(“csrftoken”); 
   if(sToken != null && xhrToken != null && sToken.equals(xhrToken)){ 
       chain.doFilter(request, response); 
   }else if(sToken != null && pToken != null && sToken.equals(pToken)){ 
       chain.doFilter(request, response); 
   }else{ 
       request.getRequestDispatcher(“error.jsp”).forward(request,response); 
   } 
}
复制代码

首先判断 session 中有没有 csrftoken,若是没有,则认为是第一次访问,session 是新创建的,这时生成一个新的 token,放于 session 之中,并继续执行请求。若是 session 中已经有 csrftoken,则说明用户已经与服务器之间创建了一个活跃的 session,这时要看这个请求中有没有同时附带这个 token,因为请求可能来自于常规的访问或是 XMLHttpRequest 异步访问,咱们分别尝试从请求中获取 csrftoken 参数以及从 HTTP 头中获取 csrftoken 自定义属性并与 session 中的值进行比较,只要有一个地方带有有效 token,就断定请求合法,能够继续执行,不然就转到错误页面。生成 token 有不少种方法,任何的随机算法均可以使用,Java 的 UUID 类也是一个不错的选择。安全

除了在服务器端利用 filter 来验证 token 的值之外,咱们还须要在客户端给每一个请求附加上这个 token,这是利用 js 来给 html 中的连接和表单请求地址附加 csrftoken 代码,其中已定义 token 为全局变量,其值能够从 session 中获得。服务器

清单 3. 在客户端对于请求附加 token
function appendToken(){ 
   updateForms(); 
   updateTags(); 
} 
 
function updateForms() { 
   // 获得页面中全部的 form 元素
   var forms = document.getElementsByTagName('form'); 
   for(i=0; i<forms.length; i++) { 
       var url = forms[i].action; 
 
       // 若是这个 form 的 action 值为空,则不附加 csrftoken 
       if(url == null || url == "" ) continue; 
 
       // 动态生成 input 元素,加入到 form 以后
       var e = document.createElement("input"); 
       e.name = "csrftoken"; 
       e.value = token; 
       e.type="hidden"; 
       forms[i].appendChild(e); 
   } 
} 
 
function updateTags() { 
   var all = document.getElementsByTagName('a'); 
   var len = all.length; 
 
   // 遍历全部 a 元素
   for(var i=0; i<len; i++) { 
       var e = all[i]; 
       updateTag(e, 'href', token); 
   } 
} 
 
function updateTag(element, attr, token) { 
   var location = element.getAttribute(attr); 
   if(location != null && location != '' '' ) { 
       var fragmentIndex = location.indexOf('#'); 
       var fragment = null; 
       if(fragmentIndex != -1){ 
 
           //url 中含有只至关页的锚标记
           fragment = location.substring(fragmentIndex); 
           location = location.substring(0,fragmentIndex); 
       } 
        
       var index = location.indexOf('?'); 
 
       if(index != -1) { 
           //url 中已含有其余参数
           location = location + '&csrftoken=' + token; 
       } else { 
           //url 中没有其余参数
           location = location + '?csrftoken=' + token; 
       } 
       if(fragment != null){ 
           location += fragment; 
       } 
        
       element.setAttribute(attr, location); 
   } 
}
复制代码

在客户端 html 中,主要是有两个地方须要加上 token,一个是表单 form,另外一个就是连接 a。这段代码首先遍历全部的 form,在 form 最后添加一隐藏字段,把 csrftoken 放入其中。而后,代码遍历全部的连接标记 a,在其 href 属性中加入 csrftoken 参数。注意对于 a.href 来讲,可能该属性已经有参数,或者有锚标记。所以须要分状况讨论,以不一样的格式把 csrftoken 加入其中。session

若是你的网站使用 XMLHttpRequest,那么还须要在 HTTP 头中自定义 csrftoken 属性,利用 dojo.xhr 给 XMLHttpRequest 加上自定义属性代码以下:app

清单 4. 在 HTTP 头中自定义属性
var plainXhr = dojo.xhr; 
 
// 重写 dojo.xhr 方法
dojo.xhr = function(method,args,hasBody) { 
   // 确保 header 对象存在
   args.headers = args.header || {}; 
        
   tokenValue = '<%=request.getSession(false).getAttribute("csrftoken")%>'; 
   var token = dojo.getObject("tokenValue"); 
    
   // 把 csrftoken 属性放到头中
   args.headers["csrftoken"] = (token) ? token : " "; 
   return plainXhr(method,args,hasBody); 
};
复制代码

这里改写了 dojo.xhr 的方法,首先确保 dojo.xhr 中存在 HTTP 头,而后在 args.headers 中添加 csrftoken 字段,并把 token 值从 session 里拿出放入字段中。异步

相关文章
相关标签/搜索