談談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); |
---|