跨站请求伪造漏洞

首先说明一下什么是CSRF(Cross Site Request Forgery)?php

跨站请求伪造是指攻击者能够在第三方站点制造HTTP请求并以用户在目标站点的登陆态发送到目标站点,而目标站点未校验请求来源使第三方成功伪造请求。html

为何会有CSRF?web

JS控制浏览器发送请求的时候,浏览器是根据目标站点,而不是来源站点,来发送cookie的,若是当前会话中有目标站点的cookie,就发送出去。核心问题是浏览器的会话机制,是跨站请求伪造漏洞的根源。算法

 

CSRF(Cross-site request forgery跨站请求伪造,也被称成为“one click attack”或者session riding,一般缩写为CSRF或者XSRF,是一种对网站的恶意利用。浏览器


解决方法有三种:安全

1 使用token服务器

2 限制refercookie

3 使用验证码技术session

 

 

1、CSRF攻击原理并发

 

CSRF攻击原理比较简单,如图1所示。其中Web A为存在CSRF漏洞的网站,Web B为攻击者构建的恶意网站,User C为Web A网站的合法用户。

 

跨站请求伪造CSRF防御方法

 

图1 CSRF攻击原理

 

1. 用户C打开浏览器,访问受信任网站A,输入用户名和密码请求登陆网站A;

 

2.在用户信息经过验证后,网站A产生Cookie信息并返回给浏览器,此时用户登陆网站A成功,能够正常发送请求到网站A;

 

3. 用户未退出网站A以前,在同一浏览器中,打开一个TAB页访问网站B;

 

4. 网站B接收到用户请求后,返回一些攻击性代码,并发出一个请求要求访问第三方站点A;

 

5. 浏览器在接收到这些攻击性代码后,根据网站B的请求,在用户不知情的状况下携带Cookie信息,向网站A发出请求。网站A并不知道该请求实际上是由B发起的,因此会根据用户C的Cookie信息以C的权限处理该请求,致使来自网站B的恶意代码被执行。

 

2、CSRF漏洞防护

 

CSRF漏洞防护主要能够从三个层面进行,即服务端的防护、用户端的防护和安全设备的防护。

 

一、 服务端的防护

 

.1.1 验证HTTP Referer字段

 

根据HTTP协议,在HTTP头中有一个字段叫Referer,它记录了该HTTP请求的来源地址。在一般状况下,访问一个安全受限页面的请求必须 来自于同一个网站。好比某银行的转帐是经过用户访问http://bank.test/test?page=10&userID=101& amp;money=10000页面完成,用户必须先登陆bank.test,而后经过点击页面上的按钮来触发转帐事件。当用户提交请求时,该转帐请求的 Referer值就会是转帐按钮所在页面的URL(本例中,一般是以bank. test域名开头的地址)。而若是攻击者要对银行网站实施CSRF攻击,他只能在本身的网站构造请求,当用户经过攻击者的网站发送请求到银行时,该请求的 Referer是指向攻击者的网站。所以,要防护CSRF攻击,银行网站只须要对于每个转帐请求验证其Referer值,若是是以bank. test开头的域名,则说明该请求是来自银行网站本身的请求,是合法的。若是Referer是其余网站的话,就有多是CSRF攻击,则拒绝该请求。

 

1.2 在请求地址中添加token并验证

 

CSRF攻击之因此可以成功,是由于攻击者能够伪造用户的请求,该请求中全部的用户验证信息都存在于Cookie中,所以攻击者能够在不知道这些验 证信息的状况下直接利用用户本身的Cookie来经过安全验证。由此可知,抵御CSRF攻击的关键在于:在请求中放入攻击者所不能伪造的信息,而且该信息 不存在于Cookie之中。鉴于此,系统开发者能够在HTTP请求中以参数的形式加入一个随机产生的token,并在服务器端创建一个拦截器来验证这个 token,若是请求中没有token或者token内容不正确,则认为多是CSRF攻击而拒绝该请求。

 

1.3 在HTTP头中自定义属性并验证

 

自定义属性的方法也是使用token并进行验证,和前一种方法不一样的是,这里并非把token以参数的形式置于HTTP请求之中,而是把它放到 HTTP头中自定义的属性里。经过XMLHttpRequest这个类,能够一次性给全部该类请求加上csrftoken这个HTTP头属性,并把 token值放入其中。这样解决了前一种方法在请求中加入token的不便,同时,经过这个类请求的地址不会被记录到浏览器的地址栏,也不用担忧 token会经过Referer泄露到其余网站。

 

二、 其余防护方法

 

1. CSRF攻击是有条件的,当用户访问恶意连接时,认证的cookie仍然有效,因此当用户关闭页面时要及时清除认证cookie,对支持TAB模式(新标签打开网页)的浏览器尤其重要。

 

2. 尽可能少用或不要用request()类变量,获取参数指定request.form()仍是request. querystring (),这样有利于阻止CSRF漏洞攻击,此方法只不能彻底防护CSRF攻击,只是必定程度上增长了攻击的难度。

 

代码示例:

 

Java 代码示例

 

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

 

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

 

清单 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 页面。

 

若是要进一步验证请求中的 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 加入其中。

 

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

 

清单 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 里拿出放入字段中。

 

PHP代码示例:

 

请看下面一个简单的应用,它容许用户购买钢笔或铅笔。界面上包含下面的表单:

 

<form action="buy.php" method="POST"> <p> Item: <select name="item"> <option name="pen">pen</option> <option name="pencil">pencil</option> </select><br /> Quantity: <input type="text" name="quantity" /><br /> <input type="submit" value="Buy" /> </p></form>

 

下面的buy.php程序处理表单的提交信息:

 

<?php session_start(); $clean = array(); if (isset($_REQUEST['item'] && isset($_REQUEST['quantity'])) { /* Filter Input ($_REQUEST['item'], $_REQUEST['quantity']) */ if (buy_item($clean['item'], $clean['quantity'])) { echo '<p>Thanks for your purchase.</p>'; } else { echo '<p>There was a problem with your order.</p>'; } }?>

 

攻击者会首先使用这个表单来观察它的动做。例如,在购买了一支铅笔后,攻击者知道了在购买成功后会出现感谢信息。注意到这一点后,攻击者会尝试经过访问下面的URL以用GET方式提交数据是否能达到一样的目的:

 

http://store.example.org/buy.php?item=pen&quantity=1

 

若是能成功的话,攻击者如今就取得了当合法用户访问时,能够引起购买的URL格式。在这种状况下,进行跨站请求伪造攻击很是容易,由于攻击者只要引起受害者访问该URL便可。

 

请看下面对前例应用更改后的代码:

 

php
  session_start(); $token = md5(uniqid(rand(), TRUE)); $_SESSION['token'] = $token; $_SESSION['token_time'] = time();?>

 

表单:

 

<form action="buy.php" method="POST"> <input type="hidden" name="token" value="<?php echo $token; ?>" /> <p> Item: <select name="item"> <option name="pen">pen</option> <option name="pencil">pencil</option> </select><br /> Quantity: <input type="text" name="quantity" /><br /> <input type="submit" value="Buy" /> </p></form>

 

经过这些简单的修改,一个跨站请求伪造攻击就必须包括一个合法的验证码以彻底模仿表单提交。因为验证码的保存在用户的session中的,攻击者必 须对每一个受害者使用不一样的验证码。这样就有效的限制了对一个用户的任何攻击,它要求攻击者获取另一个用户的合法验证码。使用你本身的验证码来伪造另一 个用户的请求是无效的。

 

该验证码能够简单地经过一个条件表达式来进行检查:

 

<?php if (isset($_SESSION['token']) && $_POST['token'] == $_SESSION['token']) { /* Valid Token */ }?>

 

你还能对验证码加上一个有效时间限制,如5分钟:

 

<?php $token_age = time() - $_SESSION['token_time']; if ($token_age <= 300) { /* Less than five minutes has passed. */ }?>

 

经过在你的表单中包括验证码,你事实上已经消除了跨站请求伪造攻击的风险。能够在任何须要执行操做的任何表单中使用这个流程。

 

 

http://netsecurity.51cto.com/art/201308/407554.htm

http://netsecurity.51cto.com/art/201104/256035.htm

相关文章
相关标签/搜索