CSRF攻击的原理及其预防

Django是一个相对来说功能比较全的web框架,当然也提供了一些安全的机制。当然我现在接触Django时间不长,去实战做项目的时间也不长,所以这篇文章会持续更新。


CSRF攻击

参考链接:

http://www.cnblogs.com/hyddd/archive/2009/04/09/1432744.html

https://segmentfault.com/a/1190000007932293

首先,csrf是什么呢?

CSRF(cross-site request forgery),中文名叫做跨站请求伪造,也被称为:one click attack/session riding,缩写为:CSRF/XSRF。

csrf可以做什么呢?

你这可以这么理解CSRF攻击:攻击者盗用了你的身份,以你的名义发送恶意请求。CSRF能够做的事情包括:以你名义发送邮件,发消息,盗取你的账号,甚至于购买商品,虚拟货币转账……造成的问题包括:个人隐私泄露以及财产安全。

csrf的攻击过程,举例说明:

  • 假设abc用户登录银行的网站进行操作, 同时也访问了攻击者预先设置好的网站.
  • abc点击了攻击者网站的某一个链接,这个链接是http://www.bank.com/xxxx指向银行,银行服务器会根据这个链接携带的参数会进行转账操作.
  • 银行服务器在执行转账操作之前会进行SESSION验证是否登录, 但是由于abc已经登录了银行网站,攻击者的链接也是www.bank.com.所以攻击的链接就会携带session id到银行服务器.
  • 由于session id是正确的,所以银行会判断操作是由本人发起的,执行转账操作.
演示一下:

根据上面的说明,我们来模拟一下攻击的过程.

  • 有www.bank.com跟www.hacker.com.用户abc登录www.bank.com网站之后点击了www.hacker.com的点击抽大奖的诱骗链接
  • 此链接会向www.bank.com发起一个post请求.由于请求域名为www.bank.com,所以请求会携带www.bank.com的session id.

下面是www.hacker.com的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form method="post" action="http://www.bank.com/transfer.php">
<input type="hidden" name="from" value="abc">
<input type="hidden" name="money" value="10000">
<input type="hidden" name="to" value="hacker">
<input type="button" onclick="submit()" value="点击抽大奖">
</form>
</body>

可以发现,www.hacker.com的网页中包含了一个向www.bank.com发起的post请求.并且表单都没隐藏了,只有一个诱骗用户点击的按钮.

完整的实例,详见github:https://github.com/chenjiayao/example/tree/master/csrf

预防办法

知道了CSRF攻击是怎样操作的,那么该怎么去预防这种攻击呢?目前有两种方法:

referer 验证

根据HTTP协议,在http请求头中包含一个referer的字段,这个字段记录了该http请求的原地址。通常情况下,执行转账操作的post请求 www.bank.com/transfer.php应该是点击www.bank.com 网页的按钮来触发的操作,这个时候转账请求的referer应该是www.bank.com.而如果黑客要进行csrf攻击,只能在自己的网站www.hacker.com上伪造请求.伪造请求的referer是www.hacker.com.所以我们通过对比post请求的referer是不是www.bank.com就可以判断请求是否合法.

这种方式验证比较简单,网站开发者只要在post请求之前检查referer就可以,但是由于referer是由浏览器提供的.虽然http协议有要求不能篡改referer的值.但是一个网站的安全性绝对不能交由其他人员来保证.

token验证

从上面的样式可以发现,攻击者伪造了转账的表单,那么网站可以在表单中加入了一个随机的token来验证.token随着其他请求数据一起被提交到服务器.服务器通过验证token的值来判断post请求是否合法.由于攻击者没有办法获取到页面信息,所以它没有办法知道token的值.那么伪造的表单中就没有该token值.服务器就可以判断出这个请求是伪造的.

在实战中我使用的随机的 csrftoken 方法,这是Django自带的一种安全机制,并不是任何一个post请求都能被响应,必须跟随的csrftoken也符合条件,这样才能成功响应。

具体的方法是在前端的界面中我们在每个form的结构中的结束符/之前添加变量

1
{% csrf_token %}

举个例子,这是忘记密码界面的form表单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<form id="jsFindPwdForm" method="post" action="{% url 'forget_pwd' %}" autocomplete="off">
<input type='hidden' name='csrfmiddlewaretoken' value='mymQDzHWl2REXIfPMg2mJaLqDfaS1sD5' />
<div class="form-group marb20 ">
<label>帐&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;号</label>
<input type="text" id="account" name="email" value="" placeholder="请输入注册邮箱" />
</div>
<div class="form-group captcha1 marb38">
<label>验&nbsp;证&nbsp;码</label>
{{ forgetpwd_form.captcha }}
<!--<img src="/captcha/image/997454dc58799e806f18d21eac296581a8c6ef02/" alt="captcha" class="captcha" /> <input id="id_captcha_0" name="captcha_0" type="hidden" value="997454dc58799e806f18d21eac296581a8c6ef02" /> <input autocomplete="off" id="id_captcha_1" name="captcha_1" type="text" />-->
</div>
<div class="error btns" id="jsForgetTips">{% for key,error in forgetpwd_form.errors.items %}{{ error }}{% endfor %}{{ msg }}</div>
<input type="hidden" name="sms_type" value="1">
<input class="btn btn-green" id="jsFindPwdBtn" type="submit" value="提交" />
<p class="form-p" style="bottom:40px;">您还可以<a href="{% url 'login' %}"> [直接登录]</a></p>
<input type='hidden' name='csrfmiddlewaretoken' value='5I2SlleZJOMUX9QbwYLUIAOshdrdpRcy' />
{% csrf_token %}
</form>

添加之后呢,在前端界面上会自动的随机生成一个token,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<form id="jsFindPwdForm" method="post" action="/forget/" autocomplete="off">
<input type='hidden' name='csrfmiddlewaretoken' value='mymQDzHWl2REXIfPMg2mJaLqDfaS1sD5' />
<div class="form-group marb20 ">
<label>帐&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;号</label>
<input type="text" id="account" name="email" value="" placeholder="请输入注册邮箱" />
</div>
<div class="form-group captcha1 marb38">
<label>验&nbsp;证&nbsp;码</label>
<img src="/captcha/image/6e38fc1b2e3f110ce77eb7e69023c9cb4022635c/" alt="captcha" class="captcha" /><input id="id_captcha_0" name="captcha_0" type="hidden" value="6e38fc1b2e3f110ce77eb7e69023c9cb4022635c" />
<input autocapitalize="off" autocomplete="off" autocorrect="off" spellcheck="false" id="id_captcha_1" name="captcha_1" type="text" />
<!--<img src="/captcha/image/997454dc58799e806f18d21eac296581a8c6ef02/" alt="captcha" class="captcha" /> <input id="id_captcha_0" name="captcha_0" type="hidden" value="997454dc58799e806f18d21eac296581a8c6ef02" /> <input autocomplete="off" id="id_captcha_1" name="captcha_1" type="text" />-->
</div>
<div class="error btns" id="jsForgetTips"></div>
<input type="hidden" name="sms_type" value="1">
<input class="btn btn-green" id="jsFindPwdBtn" type="submit" value="提交" />
<p class="form-p" style="bottom:40px;">您还可以<a href="/login/"> [直接登录]</a></p>
<input type='hidden' name='csrfmiddlewaretoken' value='5I2SlleZJOMUX9QbwYLUIAOshdrdpRcy' />
<input type='hidden' name='csrfmiddlewaretoken' value='ncSFbA4k5u0IvH8JZiyWGqCXPb8nvCIS' />
</form>

把细节拉出来:

1
2
<input type='hidden' name='csrfmiddlewaretoken' value='5I2SlleZJOMUX9QbwYLUIAOshdrdpRcy' />
<input type='hidden' name='csrfmiddlewaretoken' value='ncSFbA4k5u0IvH8JZiyWGqCXPb8nvCIS' />

这是系统自动生成的两个hidden类型的input,用户在提交post请求将表单内容提交给后端的时候,服务器会验证csrftoken,只有在验证成功之后才能成功提交,因为攻击者不知道表单提交的时候的csrftoken,可以有效的预防。