Web安全之防范CSRF跨站请求伪造

跨站请求伪造(Cross-site request forgery),也被称为 one-click attack 或者 session riding,通常缩写为 CSRF 或者 XSRF, 是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法。跟跨网站脚本(XSS)相比,XSS 利用的是用户对指定网站的信任,CSRF 利用的是网站对用户网页浏览器的信任。与XSS攻击相比,CSRF攻击往往不大流行(因此对其进行防范的资源也相当稀少)和难以防范,所以被认为比XSS更具危险性。

攻击原理

跨站请求攻击,简单地说,是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并运行一些操作(如发邮件,发消息,甚至财产操作如转账和购买商品)。由于浏览器曾经认证过,所以被访问的网站会认为是真正的用户操作而去运行。这利用了web中用户身份验证的一个漏洞:简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的


从上图可以看出,要完成一次CSRF攻击,受害者必须依次完成下面步骤:

  1. 首先用户C浏览并登录了受信任站点A;
  2. 登录信息验证通过以后,站点A会在返回给浏览器的信息中带上已登录的cookie,cookie信息会在浏览器端保存一定时间(根据服务端设置而定);
  3. 完成这一步以后,用户在没有登出(清除站点A的cookie)站点A的情况下,访问恶意站点B;
  4. 这时恶意站点 B的某个页面向站点A发起请求,而这个请求会带上浏览器端所保存的站点A的cookie;
  5. 站点A根据请求所带的cookie,判断此请求为用户C所发送的。

透过例子能够看出,攻击者并不能通过CSRF攻击来直接获取用户的账户控制权,也不能直接窃取用户的任何信息。他们能做到的,是欺骗用户浏览器,让其以用户的名义运行操作。

防御策略

验证HTTP的Referer字段

HTTP头中有一个Referer字段,这个字段用以标明请求来源于哪个地址。在处理敏感数据请求时,通常来说,Referer字段应和请求的地址位于同一域名下。

这种办法简单易行,工作量低,仅需要在最后给所有安全敏感的请求统一增加一个拦截器来检查 Referer 的值就可以。

但这种办法也有其局限性,因其完全依赖浏览器发送正确的Referer字段。虽然http协议对此字段的内容有明确的规定,但并无法保证来访的浏览器的具体实现,亦无法保证浏览器没有安全漏洞影响到此字段。并且也存在攻击者攻击某些浏览器,篡改其Referer字段的可能。

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

Token一般在生成页面时,在Cookie中或者服务器的Session中保存一份,然后在页面上用隐藏的input控件保存一份。当用户发起请求时,带上页面上的Token做为参数,并在服务端将这个Token与Cookie或Session中的Token进行比较。如果相同的话,才为合法请求。

对于 GET 请求,token 将附在请求地址之后,这样 URL 就变成 http://url?csrftoken=tokenvalue

而对于 POST 请求来说,要在 form 的最后加上<input type=”hidden” name=”csrftoken” value=”tokenvalue”/>,这样就把 token 以参数的形式加入请求了。但是,在一个网站中,可以接受请求的地方非常多,要对于每一个请求都加上 token 是很麻烦的,并且很容易漏掉,通常使用的方法就是在每次页面加载时,使用 javascript 遍历整个 dom 树,对于 dom 中所有的 a 和 form 标签后加入 token。这样可以解决大部分的请求,但是对于在页面加载之后动态生成的 html 代码,这种方法就没有作用,还需要程序员在编码时手动添加 token。

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

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

然而这种方法的局限性非常大。XMLHttpRequest 请求通常用于 Ajax 方法中对于页面局部的异步刷新,并非所有的请求都适合用这个类来发起,而且通过该类请求得到的页面不能被浏览器所记录下,从而进行前进,后退,刷新,收藏等操作,给用户带来不便。另外,对于没有进行 CSRF 防护的遗留系统来说,要采用这种方法来进行防护,要把所有请求都改为 XMLHttpRequest 请求,这样几乎是要重写整个网站,这代价无疑是不能接受的。

将cookie设置为HttpOnly

CRSF攻击很大程度上是利用了浏览器的cookie,为了防止站内的XSS漏洞盗取cookie,需要在cookie中设置“HttpOnly”属性,这样通过程序(如JavaScript脚本、Applet等)就无法读取到cookie信息,避免了攻击者伪造cookie的情况出现。

Java防范方法

在filter中对HTTP请求的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); 
}

在 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(xhrToken != null && sToken.equals(xhrToken)){ 
       chain.doFilter(request, response); 
    }else if(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 中得到。客户端 html 中,主要是有两个地方需要加上token,一个是表单form另一个就是标签a。

将cookie设置为HttpOnly

在Java的Servlet的API中设置cookie为HttpOnly的代码如下:

response.setHeader( "Set-Cookie", "cookiename=cookievalue;HttpOnly");

ASP.NET防范方法

CSRF能成功是因为同一个浏览器会共享Cookies,也就是说,通过权限认证和验证是无法防止CSRF的。

我们需要在我们的页面生成一个Token,发请求的时候把Token带上。处理请求的时候需要验证Cookies+Token。ASP.NET框架已经帮我们集成了处理方法:

  1. 在页面上加入@Html.AntiForgeryToken()

     <form action="/Test/UserAdd" id="addUser" method="post">
     @Html.AntiForgeryToken()
     <input type="text" name="userName" value="normalUser">
     </form>
    
  2. 在后台代码中加上 ValidateAntiForgeryToken

     [HttpPost]
     [ValidateAntiForgeryToken]
     public ActionResult AddUser(string userName)
     {
         ViewBag.userName = userName;
         return View();
     }
    

此时,如果此时发起CSRF攻击,会提示错误。

参考资料

  1. 跨站请求伪造
  2. 跨站请求伪造(CSRF/XSRF)
  3. 常见的Web攻击手段之CSRF攻击
  4. Java解决CSRF问题
  5. CSRF攻击的应对之道
posted @ 2019-03-06 14:32  universal  阅读(1872)  评论(0编辑  收藏  举报