谈谈Django的CSRF插件的漏洞
- 2019 年 12 月 11 日
- 笔记
今年十月份我的第二本书《基于Django的电子商务网站设计》出版了,在这本书中我不仅介绍了如何利用Django框架搭建电子商务网站,也论述了如何利用python的requests类对所创建的电子商务产品进行接口测试。在书写极乐口测试代码过程中,我遇到的最大的困难就是如何通过测试程序绕过Django的防止CSRF攻击的插件,通过近一个多月的努力我终于解决了这个问题,但是同时也揭露了Django框架的防止CSRF攻击的插件的漏洞。首先我们来看一下什么是CSRF攻击。
1、什么是CSRF攻击?
我们假设一个网站http://www.a.com/login.html的HTML代码如下:
<html><head></head><body><h4>用户登录</h4><form name="form" action=" /login/"><p>用户名:<input id="name" type="text" maxlength="50"></p><p>密码:< input id="password" type="password" maxlength="50"></p><p>验证码:<img src="/images/csr.jpg"></p>< input id="submit" type="submit" value="提交"></body></html> |
---|
大家都知道,采用验证码的目的是为了防止“黑客”,利用机器来通过穷举的方法来试图登录系统。检查验证码是否正确用的往往是前端做的判断。这样,“黑客”可以采用自己的网站建立如下页面:
<html><head></head><body><h4>用户登录</h4><form name="form" action="http://www.a.com/login/"><p>用户名:<input id="name" type="text" maxlength="50"></p><p>密码:< input id="password" type="password" maxlength="50"></p>< input id="submit" type="submit" value="提交"></body></html> |
---|
大家可以看见,在这段代码中验证码没有了,form的action变成了绝对路径http://www.a.com/login/,这样“黑客”就绕过了前端的验证,可以对自己代码进行编写自动化脚本实现用穷举的方法来试图登录系统。这个就是CSRF攻击。
2、Django的CSRF插件是如何解决CSRF攻击的
下面让我们来看一下Django的CSR插件是如何解决CSRF攻击的。Django利用了一个名为django.middleware.csrf.CsrfViewMiddleware的中间件(可以在Django的settings.py中设置)利用CSRF令牌的方式来控制。具体方式生成一个一百个字符的随机字符串作为CSRF令牌,在login表单中产生一个名为csrfmiddlewaretoken的hidden表单,把这个CSRF令牌的值放入这个字段中,然后在提交这个表单的时候产生一个名为csrftoken的cookie,这个cookie的值也是CSRF令牌的值。
<html><head></head><body><h4>用户登录</h4><form name="form" action="http://www.a.com/login/"><input type='hidden' name='csrfmiddlewaretoken' value='Pxpy5PDU3i1imqd0XZrK4ct6pZRIknHT48UE60GRrKtmqW7UCPq66pddXp0fzTpx' /><p>用户名:<input id="name" type="text" maxlength="50"></p><p>密码:< input id="password" type="password" maxlength="50"></p>< input id="submit" type="submit" value="提交"></body></html> |
---|
后台检查如果hidden表单的值与csrftoken的cookie的值一致,则返回200返回码,进入登录后的页面,否则返回403返回码,拒绝进入系统。由于这个CSRF令牌是随机生成的一百个字符的字符串,“黑客”是很难猜到这个字符的,所以就达到了CSRF的攻击防护。
3、Django的CSRF插件的漏洞
3.1通过requests类破解
但是这个CSRF插件是有漏洞的,在页面login.html页面载入后,黑客可以通过某种手段(比如正则表达式)获得这个CSRF令牌(即hidden中的一百个字符值),然后构造一个名为csrftoken的cookie,名为刚才过的的CSRF令牌值,这样就有了下面的代码。
import requests…#进入登录页面 try: data = requests.get(self.Login_url) except Exception as e: print(e) text = data.text csrf_token = str(re.findall(r"name='csrfmiddlewaretoken' value='(.+?)' />",text)) csrf_token = csrf_token[2:-2] payload ={"username":"cindy","password":"123456","csrfmiddlewaretoken":csrf_token} cookies = {"csrftoken":csrf_token} try: data = requests.post(self.Product_list_url,data=payload,cookies=cookies) except Exception as e: print(e) |
---|
代码“csrf_token =str(re.findall(r"name='csrfmiddlewaretoken' value='(.+?)'/>",text))”是通过re.findall正则方法获得CSRF令牌,存在csrf_token变量中,由于用这个方法获得的值是“["CSRF令牌值"]”格式的,也就是说去前面多了个“["”,后面多了个“"]”,所以后面用语句“csrf_token = csrf_token[2:-2]”过滤出来,然后利用requests的post方法,先构造post参数:“payload={"username":"cindy","password":"123456","csrfmiddlewaretoken":csrf_token}”,这里“"csrfmiddlewaretoken":csrf_token”让表单csrfmiddlewaretoken仍旧为csrf_token值。通过cookies = {"csrftoken":csrf_token}构造cookes值,通过cookies=cookies作为post参数传给后台。这样表单csrfmiddlewaretoken的值与cookie的csrftoken值是一致的,所以,登录通过。
后来,我惊奇的发现不用这么麻烦,我们直接把表单csrfmiddlewaretoken的值与cookie的csrftoken值设置相同,即:
csrf_token = csrf_token = "Pxpy5PDU3i1imqd0XZrK4ct6pZRIknHT48UE60GRrKtmqW7UCPq66pddXp0fzTpx" payload ={"username":"cindy","password":"123456","csrfmiddlewaretoken":csrf_token} cookies = {"csrftoken":csrf_token} try: data = requests.post(self.Product_list_url,data=payload,cookies=cookies) except Exception as e: print(e) |
---|
3.2通过selenium框架破解
下面的代码是利用selenium做基于GUI的自动化测试代码。
def test_CheckLogin(self): … self.driver.find_element_by_id(,"id_username").clear() self.driver.find_element_by_id("id_username").send_keys(username) self.driver.find_element_by_id("id_password").clear() self.driver.find_element_by_id("id_password").send_keys(password) csrftoken = self.driver.find_element_by_name("csrfmiddlewaretoken").get_attribute("value") self.driver.add_cookie({"name":"csrftoken","value":csrftoken}) self.driver.find_element_by_class_name("form-signin").submit() … |
---|
代码通过csrftoken =self.driver.find_element_by_name("csrfmiddlewaretoken").get_attribute("value")获取表单csrfmiddlewaretoken的值,通过elf.driver.add_cookie({"name":"csrftoken","value":csrftoken})把这个值放入到名为csrftoken的cookies中。
3.3通过JMeter破解
在JMeter也可以破解,如下图:
通过正则表达式提取器获取login.html中的hidden值。
把获得的值放入名为csrftoken的cookie中
把获得的值仍旧作为csrfmiddlewaretoken表单参数传给后台处理。
3.4通过LoadRunne破解
在LoadRunner中,录制完毕,脚本就直接把csrfmiddlewaretoken表单参数作为名为csrftoken的cookie传给后台,不用做任何代码修改。正是不可思议。
web_add_cookie("csrftoken=D7rghfxcDXMOPlz1txbY5KigHQJasLoW0cilXmpONF87D64JM2eb2qKULO4zGc8Z; DOMAIN=192.168.0.106");… web_submit_data("login_action", "Action=http://192.168.0.106:8000/login_action/", … "Name=csrfmiddlewaretoken", "Value=D7rghfxcDXMOPlz1txbY5KigHQJasLoW0cilXmpONF87D64JM2eb2qKULO4zGc8Z", ENDITEM, "Name=username", "Value={username}", ENDITEM, "Name=password", "Value={password}", ENDITEM, LAST); |
---|