csrf是什么? Django中csrf的原理?

CSRF攻击

CSRF(英语:Cross-site request forgery)是跨站请求伪造的缩写,也被称为XSRF, 是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法,CSRF利用的是网站对用户网页浏览器的信任,。

举个栗子

  • 受害者登录a.com,并保留了登录凭证(Cookie)。
  • 攻击者引诱受害者访问了b.com。
  • b.com 向 a.com 发送了一个请求:a.com/act=xx。浏览器会默认携带a.com的Cookie。
  • a.com接收到请求后,对请求进行验证,并确认是受害者的凭证,误以为是受害者自己发送的请求。
  • a.com以受害者的名义执行了act=xx。
  • 攻击完成,攻击者在受害者不知情的情况下,冒充受害者,让a.com执行了自己定义的操作。

受害者登录a.com网站是正规的网站,登陆后浏览器记录下cookie信息,在cookie成效的时间段内,受害者无意或被引诱访问b.com恶意网站,b.com 网站恶意提交一条向a网站的请求(可以是任何修改内容的请求),浏览器会认为是你访问a.com网站 会带上之前登录过的cookie信息,这就完成了一次受害者不知情的情况下的让a.com执行了自己定义的操作。

<form action="http://bank.example/withdraw" method=POST>
    <input type="hidden" name="account" value="xiaoming" />
    <input type="hidden" name="amount" value="10000" />
    <input type="hidden" name="for" value="hacker" />
</form>
<script> document.forms[0].submit(); </script> 

防护策略

CSRF通常从第三方网站发起,被攻击的网站无法防止攻击发生,只能通过增强自己网站针对CSRF的防护能力来提升安全性。
CSRF(通常)发生在第三方域名。
CSRF攻击者不能获取到Cookie等信息,只是使用,是浏览器的同源策略认为是同一域名请求带上了cookie,攻击者就是利用了这一点

  1. 同源检测
    既然CSRF大多来自第三方网站,那么我们就直接禁止外域(或者不受信任的域名)对我们发起请求。
    在HTTP协议中,每一个异步请求都会携带两个Header,用于标记来源域名:
  • Origin Header

  • Referer Header
    这两个Header在浏览器发起请求时,大多数情况会自动带上,并且不能由前端自定义内容。 服务器可以通过解析这两个Header中的域名,确定请求的来源域。

  • IE11同源策略: IE 11 不会在跨站CORS请求上添加Origin标头,Referer头将仍然是唯一的标识。最根本原因是因为IE 11对同源的定义和其他浏览器有不同,有两个主要的区别,可以参考MDN Same-origin_policy#IE_Exceptions

  • 302重定向: 在302重定向之后Origin不包含在重定向的请求中,因为Origin可能会被认为是其他来源的敏感信息。对于302重定向的情况来说都是定向到新的服务器上的URL,因此浏览器不想将Origin泄漏到新的服务器上。

根据HTTP协议,在HTTP头中有一个字段叫Referer,记录了该HTTP请求的来源地址。 对于Ajax请求,图片和script等资源请求,Referer为发起请求的页面地址。对于页面跳转,Referer为打开页面历史记录的前一个页面地址。因此我们使用Referer中链接的Origin部分可以得知请求的来源域名。

这种方法并非万无一失,Referer的值是由浏览器提供的,虽然HTTP协议上有明确的要求,但是每个浏览器对于Referer的具体实现可能有差别,并不能保证浏览器自身没有安全漏洞。使用验证 Referer 值的方法,就是把安全性都依赖于第三方(即浏览器)来保障,从理论上来讲,这样并不是很安全。在部分情况下,攻击者可以隐藏,甚至修改自己请求的Referer。
2 . csrf token (常用)

前面讲到CSRF的另一个特征是,攻击者无法直接窃取到用户的信息(Cookie,Header,网站内容等),仅仅是冒用Cookie中的信息。而CSRF攻击之所以能够成功,是因为服务器误把攻击者发送的请求当成了用户自己的请求。那么我们可以要求所有的用户请求都携带一个CSRF攻击者无法获取到的Token。服务器通过校验请求是否携带正确的Token,来把正常的请求和攻击的请求区分开,也可以防范CSRF的攻击。

CSRF Token的防护策略分为三个步骤:
  • 将CSRF Token输出到页面中
    1,在用户登陆的时候,服务器需要给这个用户生成一个Token,该Token通过散列对数据进行加密,一般Token都包括随机字符串和时间戳的组合,生成的token存放在前端的cookie中。
    2,页面提交的请求前端从cookie读取token携带token,在request的拦截器中带上在header中或者post body中
    3 后端对cookie中的csrftoken和 header中的csrftoken进行反向散列验证 csrftoken的secret值是否相同
    4,因为攻击者不能读取到cookie中csrf所以无法在伪造的请求中通过js读取到并添加到请求

Django中csrf的实现

django通过’django.middleware.csrf.CsrfViewMiddleware’这个中间层来完成。

这个中间件是全局的,每次请求会检查cookie中是否带有csrftoken,没有者自动会设置给浏览器,下次浏览器发起post请求就会带上
在django当中防御csrf攻击的方式有两种

  1. 在表单当中附加csrftoken
  2. 通过request请求中添加X-CSRFToken请求头。
  3. 注意:Django默认对所有的POST请求都进行csrftoken验证,若验证失败则403错误侍候。
    在经过CsrfViewMiddleware,django会为浏览器带上一个随机生成的token存在浏览器的cookie中,
    前端请求例如在vue的axios请求拦截器带上csrftoken,后端验证
验证算法
def _unsalt_cipher_token(token):
    """
    Given a token (assumed to be a string of CSRF_ALLOWED_CHARS, of length
    CSRF_TOKEN_LENGTH, and that its first half is a salt), use it to decrypt
    the second half to produce the original secret.
    """
    salt = token[:CSRF_SECRET_LENGTH]
    token = token[CSRF_SECRET_LENGTH:]
    chars = CSRF_ALLOWED_CHARS
    pairs = zip((chars.index(x) for x in token), (chars.index(x) for x in salt))
    secret = ''.join(chars[x - y] for x, y in pairs)  # Note negative values are ok
    return secret
生成算法
def _salt_cipher_secret(secret):
    """
    Given a secret (assumed to be a string of CSRF_ALLOWED_CHARS), generate a
    token by adding a salt and using it to encrypt the secret.
    """
    salt = _get_new_csrf_string()
    chars = CSRF_ALLOWED_CHARS
    pairs = zip((chars.index(x) for x in secret), (chars.index(x) for x in salt))
    cipher = ''.join(chars[(x + y) % len(chars)] for x, y in pairs)
    return salt + cipher
算法验证
import string

CSRF_SECRET_LENGTH = 32
CSRF_ALLOWED_CHARS = string.ascii_letters + string.digits
# 请求的csrftoken
rq = '8gwQopM342Uwtn1CJs8pW1OZpqeqwuFzEXbyaIHXwpmeUnMWxe2kcEXBqdHhQzPv'
# cookie中csrftoken
c = 'psmhmo29i2KIgPwVlJWW5riAw7h3UTimV91Z8HX3KpcqHPhf9vQRl4rcxUKUeYsi'


def _unsalt_cipher_token(token):
    """
    Given a token (assumed to be a string of CSRF_ALLOWED_CHARS, of length
    CSRF_TOKEN_LENGTH, and that its first half is a salt), use it to decrypt
    the second half to produce the original secret.
    """
    salt = token[:CSRF_SECRET_LENGTH]
    token = token[CSRF_SECRET_LENGTH:]
    chars = CSRF_ALLOWED_CHARS
    pairs = zip((chars.index(x) for x in token), (chars.index(x) for x in salt))
    secret = ''.join(chars[x - y] for x, y in pairs)  # Note negative values are ok
    return secret


def get_token_no(token):
    chars = CSRF_ALLOWED_CHARS
    return [chars.index(i) for i in token]


rq_secret = _unsalt_cipher_token(rq)
rq_no = get_token_no(rq_secret)

print('rq的token secret是:', rq_secret)
print('rq_no: ', rq_no)

c_secret = _unsalt_cipher_token(c)
c_no = get_token_no(c_secret)
print('c的token secret是:', c_secret)
print('c_no: ', c_no)

>>>
rq的token secret是: GRPSWt54CxCSBaVuYW45qNjMbXD1ufk6
rq_no:  [32, 43, 41, 44, 48, 19, 57, 56, 28, 23, 28, 44, 27, 0, 47, 20, 50, 48, 56, 57, 16, 39, 9, 38, 1, 49, 29, 53, 20, 5, 10, 58]
c的token secret是: GRPSWt54CxCSBaVuYW45qNjMbXD1ufk6
c_no:  [32, 43, 41, 44, 48, 19, 57, 56, 28, 23, 28, 44, 27, 0, 47, 20, 50, 48, 56, 57, 16, 39, 9, 38, 1, 49, 29, 53, 20, 5, 10, 58]

chars = abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
可以看出来django验证算法是
csrf='8gwQopM342Uwtn1CJs8pW1OZpqeqwuFzEXbyaIHXwpmeUnMWxe2kcEXBqdHhQzPv'
csrftoen前面32位是盐值,后32位是token值,
盐值和token值在chars中对应的下标值相减得到的下标值再对应的chars中的值就是secret,(因为生成csrftoken时是相加所以不会出现相减时为负数的情况)
对比两次的secret 相等就是验证成功。

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值