CSRF和token以及用django实现
csrf
CSRF(Cross-Site Request Forgery,跨站点伪造请求)是一种网络攻击方式,该攻击可以在受害者毫不知情的情况下以受害者名义伪造请求发送给受攻击站点,从而在未授权的情况下执行在权限保护之下的操作,具有很大的危害性。具体来讲,可以这样理解CSRF攻击:攻击者盗用了你的身份,以你的名义发送恶意请求,对服务器来说这个请求是完全合法的,但是却完成了攻击者所期望的一个操作,比如以你的名义发送邮件、发消息,盗取你的账号,添加系统管理员,甚至于购买商品、虚拟货币转账等。
什么是CSRF攻击
- 首先用户C浏览并登录了受信任站点A;
- 登录信息验证通过以后,站点A会在返回给浏览器的信息中带上已登录的cookie,cookie信息会在浏览器端保存一定时间(根据服务端设置而定);
- 完成这一步以后,用户在没有登出(清除站点A的cookie)站点A的情况下,访问恶意站点B;
- 这时恶意站点 B的某个页面向站点A发起请求,而这个请求会带上浏览器端所保存的站点A的cookie;
- 站点A根据请求所带的cookie,判断此请求为用户C所发送的。
因此,站点A会报据用户C的权限来处理恶意站点B所发起的请求,而这个请求可能以用户C的身份发送 邮件、短信、消息,以及进行转账支付等操作,这样恶意站点B就达到了伪造用户C请求站点 A的目的。
受害者只需要做下面两件事情,攻击者就能够完成CSRF攻击:
- 登录受信任站点 A,并在本地生成cookie;
- 在不登出站点A(清除站点A的cookie)的情况下,访问恶意站点B。
很多情况下所谓的恶意站点,很有可能是一个存在其他漏洞(如XSS)的受信任且被很多人访问的站点,这样,普通用户可能在不知不觉中便成为了受害者。
实例
-
假设某银行网站A以GET请求来发起转账操作,转账的地址为
www.regina.com/transfer.do?accountNum=l000l&money=10000
,参数accountNum表示转账的账户,参数money表示转账金额。 -
而某大型论坛B上,一个恶意用户上传了一张图片,而图片的地址栏中填的并不是图片的地址,而是前而所说的转账地址:
<img src="http://www.regina.com/transfer.do?accountNum=l000l&money=10000">
当你登录网站A后,没有及时登出,这时你访问了论坛B,不幸的事情发生了,你会发现你的账号里面少了10000块…
为什么会这样呢
,在你登录银行A时,你的浏览器端会生成银行A的cookie,而当你访问论坛B的时候,页面上的标签需要浏览器发起一个新的HTTP请求,以获得图片资源,当浏览器发起请求时,请求的却是银行A的转账地址www.regina.com/transfer.do?accountNum=l000l&money=10000
,并且会带上银行A的cookie信息,结果银行的服务器收到这个请求后,会以为是你发起的一次转账操作,因此你的账号里边便少了10000块。
因此,这种GET请求的方式早已不安全了,通常都会使用POST请求的方式交互。
假设银行将其转账方式改成POST提交,而论坛B恰好又存在一个XSS漏洞,恶意用户在它的页面上植入如下代码:
<form id="aaa" action="http://www.regina.com/transfer.do" method="POST" display="none">
<input type="text" name="accountNum" value="10001"/>
<input type="text" name="money" value="10000"/>
</form>
<script>
var form = document.forms("aaa");
form.submit();
</script>
如果你此时恰好登录了银行A,且没有登出,当你打开上述页面后,脚本会将表单aaa提交,把accountNum和money参数传递给银行的转账地址http://www.xxx.com/transfer.do
,同样的,银行以为是你发起的一次转账会从你的账户中扣除10000块。
token
token其实就是一个令牌,用于用户验证的,token的诞生离不开CSRF。正是由于上面的Cookie/Session的状态保持方式会出现CSRF,所以才有了token。
token的特点:
- 无状态、可扩展
- 支持移动设备
- 跨程序调用
- 安全
token的机制:
基于Token的身份验证的过程如下:
- 用户登录校验,校验成功后就返回Token给客户端
- 客户端收到token后保存在客户端,token可以保存在Cookies 或 Local Storage 或 Session Storage中。
- 客户端每次访问API是携带Token到服务器端
- 服务器端采用filter过滤器校验。验证传递的token和算法生成的token是否一致,校验成功则返回请求数据,校验失败则返回错误码
当我们在程序中认证了信息并取得 token 之后,我们便能通过这个 token 做许多的事情。我们甚至能创建一个基于权限的token传给第三方应用程序,这些第三方程序能够获取到我们的数据(当然只限于该 token 被允许访问的数据)。
一直困扰的一个问题就是,为什么恶意网站不能利用用户浏览器中的token,而能利用Cookie呢?
这是因为,在信任网站的HTML或js中,会向服务器传递参数token,不是通过Cookie传递的,若恶意网站要伪造用户的请求,也必须伪造这个token,否则用户身份验证不通过。但是,同源策略限制了恶意网站不能拿到信任网站的Cookie内容,只能使用,所以就算是token是存放在Cookie中的,恶意网站也无法提取出Cookie中的token数据进行伪造。也就无法传递正确的token给服务器,进而无法成功伪装成用户了。
之前我们选择的方式是注释掉settings文件里csrf相关的中间件,但这种方式实际上是关掉了这个安全机制,其实并不是安全的。
<form action="" method="post">
{% csrf_token %}
<input type="hidden" name="" value="">
name <input type="text">
pwd <input type="password">
<input type="submit">
</form>
这里新加入了一种input类型叫hidden,这一句话里的name和value虽然是空,但是 会自动将生成的csrf_token值放进去,然后一并传给服务器端
这样做可以实现csrf的验证,但是如果前后端进行分离,这样的渲染就不好实现,所以优化操作可以使用ajax请求
<script>
$.ajax({
url: "../getToken",
success:function (res) {
console.log(res);
}
})
</script>
from django.middleware.csrf import get_token
def getToken(request):
token = get_token(request)
return HttpResponse(token)
通过这样的方式我们就可以拿到一个前后端分离,但是依然可以获取的token值,接下里我们再对token值进行一些使用。
<form action="" method="post">
name <input type="text" class="name">
pwd <input type="password" class="pwd">
<input type="button" value="ajax submit" class="ajax_button"> {#button本身不带有任何事件#}
</form>
<script>
$.ajax({
url: "../getToken",
success:function (res) {
console.log(res);
localStorage.setItem("token",res);
}
})
$(".ajax_button").click(function () {
$.ajax({
url: "../login/",
type: "post",
data:{
user: $(".user").val(),
pwd: $(".pwd").val(),
csrfmiddlewaretoken:localStorage.getItem("token"),
},
success:function (res) {
console.log(res);
}
})
})
</script>
当在访问这个页面的时候,我们把获取到的token值存到了localStorage
里面,然后再进行表单提交的时候,我们再把token值从里面获取出来。
方式1:放在请求数据中。
$.ajax({
url: "/csrf_test/",
method: "post",
data: {"name": $("[name="name"]").val(),
"password": $("[name="password"]").val(),
"csrfmiddlewaretoken":$("[name="csrfmiddlewaretoken"]").val()
},
success: function (data) {
console.log("成功了")
console.log(data) },
})
方式2:放在请求头
$.ajax({
url: "/csrf_test/",
method: "post",
headers:{"X-CSRFToken":"token值"}, // 注意放到引号里面
data:{}
}