CSRF攻击与Django防范

1,573 阅读5分钟

什么是CSRF

CSRF,全称 Cross Site Request Forgery,跨站请求伪造,是一种欺骗受害用户在已登录的web应用上按照攻击者的指令执行恶意操作的攻击行为。攻击的影响范围取决于受害用户所拥有的的权限,因为攻击者是依赖用户在当前会话中的鉴权进行恶意操作的。

CSRF是如何工作的

CSRF仅在用户已被授权的情况下才能生效。通过CSRF,攻击者可以绕开web应用的鉴权机制,进而利用受害者的用户权限对web应用进行恶意操作。比如网上银行等场景。

下面是CSRF攻击的两个主要组成部分:

  1. 第一步是欺骗用户点击一个链接或者加载一个页面,通常是利用一些社交手段诱使用户点击一个恶意链接。
  2. 第二部是在这个恶意链接中,通过用户的浏览器向web应用发起一个伪造的请求。这个伪造请求看起来是合法的,但是携带的是攻击者伪造的数据。与此同时,浏览器会在请求中带上与该web应用关联的cookie。因为cookie中包含了用户之前登陆的token鉴权信息,在用户没有登出或者token没有过期之前,该鉴权信息是持续有效的。这样攻击者就可以依靠该cookie直接获取到用户的权限进行恶意操作了。

(HTTP session和cookie机制请见cookie与session的区别以及在Django中的实现)

CSRF攻击实际上就是利用了HTTP会话的cookie机制,因为浏览器会在每次请求中都带上与web应用关联的cookie。

CSRF攻击示例

假设这是一个合法的url,用于向指定的User用户转账1000块钱。

http://example.com/transfer?amount=1000&account=User

当用户发送该请求时,如果用户已经登录过web应用且鉴权通过,则浏览器会在请求中自动携带包含鉴权信息的cookie,这样用户进行转账时就不需要再次进行登录验证;如果用户还未登录鉴权,则会需要先进行登录鉴权,然后浏览器才能进一步携带cookie信息发起转账请求。 web应用需要拿到转账请求中携带的cookie用于验证用户信息,才能得知是要从哪个用户的账户中将钱转出。

1. 使用GET请求进行攻击

如果web应用可以接受GET请求,那攻击者只需要在恶意链接的html中加入如下的代码,通过img标签自动向web应用伪造一个转账请求。利用浏览器自动携带cookie的机制,就可以从用户账户中盗走1000块钱。

<img src="http://example.com/transfer?amount=1000&account=Fred" />

该场景中的web应用除了存在CSRF漏洞以外,还有一个问题:任何的GET请求都应该是“只读“的,不应该对数据产生任何影响。所以该场景中的web应用设计是不合理的。

2. 使用POST请求进行攻击

下面是使用POST进行攻击的html表单代码

<h1>You Are a Winner!</h1>
<form action="http://example.com/api/account" method="post">
 <input type="hidden" name="Transaction" value="withdraw" />
 <input type="hidden" name="Amount" value="1000" />
 <input type="submit" value="Click Me"/>
</form>

试想如下攻击场景:

  1. 用户登录web应用example.com,并进行了鉴权。
  2. web应用对用户进行授权,并在响应中携带了包含鉴权token的cookie。
  3. 用户在没有登出的情况下,又打开了一个包含上述html表单的恶意页面。(注意该表单中的action是将请求发送到存在漏洞的web应用,而不是恶意页面本身,这就是Cross-Site,跨站请求)
  4. 用户点击恶意页面中的"Click Me"按钮提交,浏览器在请求中携带cookie发给web应用。
  5. 伪造的请求利用携带的cookie通过了web应用的鉴权,取得了用户所拥有的的权限,进行恶意操作。

值得注意的是,SSL是无法防护CSRF攻击的。因为攻击者只需要在伪造请求中指定https访问即可。

防范CSRF攻击

最常用的CSRF防范机制就是使用csrf token机制。

  1. csrf token基于一个随机生成的秘钥secret,并通过salt hash方式加密生成csrftoken,插入到Cookie中。该csrftoken在用户登录阶段生成,在session结束前保持不变。

  1. 每一个响应的POST表单中,都会插入一个隐藏的csrfmiddlewaretoken字段。该字段的值也是对1中的secret进行salt hash,每次请求表单页面都会使用一个随机的salt,所以每次响应中表单里面插入的csrfmiddlewaretoken都是不一样的。

  1. 对于每一次HTTP请求,只要request method不是GET, HEAD, OPTIONS or TRACE,都会要求同时在request header的cookie中携带csrftoken,以及在request body中携带csrfmiddlewaretoken,并对二者进行校验。如果缺失或者校验不通过,则返回403 error。

  1. 在校验csrftoken和csrfmiddlewaretoken时,不是直接对比两个token,而是解密后对比二者的秘钥secret是否相同。因为即使用户每次请求在表单中获得的csrfmiddlewaretoken都是变化的,但是在整个session中,secret是保持不变的。

注意,在POST表单中使用csrf token时,要确保表单是提交给自己的web应用的,而不是提交给外部的应用。比如在表单action中指定提交到baidu或者其他网站,别人是无法识别请求中的csrf token的,并且同时也存在泄漏csrf token的风险。

总结

cookie因为其自动在请求中携带的特点,很容易受到CSRF攻击。攻击者不需要获取用户的cookie,也可以直接利用用户的cookie进行恶意操作。CSRF攻击的影响取决于用户所拥有的权限。

所以我们在web应用设计中要注意,一方面是要严格遵守HTTP规范,GET请求一定是”只读“的,避免接收GET请求进行状态变更(如数据修改等);另一方面是对于所有设计状态变更的请求,如POST、PUT、DELETE等,都需要进行csrf token校验。

【本文参考】

Introduction to CSRF

Cross Site Request Forgery protection