CSRF

  • CSRF全拼為Cross Site Request Forgery冬殃,譯為跨站請(qǐng)求偽造魂角。
  • CSRF指攻擊者盜用了你的身份遂填,以你的名義發(fā)送惡意請(qǐng)求。
    • 包括:以你名義發(fā)送郵件逼蒙,發(fā)消息从绘,盜取你的賬號(hào),甚至于購買商品是牢,虛擬貨幣轉(zhuǎn)賬......
    • 造成的問題:個(gè)人隱私泄露以及財(cái)產(chǎn)安全僵井。

CSRF攻擊示意圖

  • 客戶端訪問服務(wù)器時(shí)沒有同服務(wù)器做安全驗(yàn)證
    CSRF攻擊示意圖.png

防止CSRF攻擊

步驟

  1. 在客戶端向后端請(qǐng)求界面數(shù)據(jù)的時(shí)候,后端會(huì)往響應(yīng)中的cookie中設(shè)置csrf_token的值
  2. 在Form表單中添加一個(gè)隱藏的字段驳棱,值也是csrf_token
  3. 在用戶點(diǎn)擊提交的時(shí)候驹沿,會(huì)帶上這兩個(gè)值向后臺(tái)發(fā)起請(qǐng)求
  4. 后端接收到請(qǐng)求,會(huì)以此啊幾件事件:
    • 從cookie中取出csrf_token的值
    • 從表單數(shù)據(jù)中取出隱藏的csrf_token的值
    • 進(jìn)行對(duì)比
  5. 如果比較之后兩個(gè)值一樣蹈胡,那么代表是正常的請(qǐng)求,如果沒有取到或者比較不一樣朋蔫,代表不是正常的請(qǐng)求罚渐,不會(huì)執(zhí)行下一步操作

代碼演示

未進(jìn)行 csrf 校驗(yàn)的 WebA
  • 后端代碼實(shí)現(xiàn)
from flask import Flask, render_template, make_response
from flask import redirect
from flask import request
from flask import url_for

app = Flask(__name__)


@app.route('/', methods=["POST", "GET"])
def index():
    if request.method == "POST":
        # 取到表單中提交上來的參數(shù)
        username = request.form.get("username")
        password = request.form.get("password")

        if not all([username, password]):
            print('參數(shù)錯(cuò)誤')
        else:
            print(username, password)
            if username == 'laowang' and password == '1234':
                # 狀態(tài)保持,設(shè)置用戶名到cookie中表示登錄成功
                response = redirect(url_for('transfer'))
                response.set_cookie('username', username)
                return response
            else:
                print('密碼錯(cuò)誤')

    return render_template('temp_login.html')


@app.route('/transfer', methods=["POST", "GET"])
def transfer():
    # 從cookie中取到用戶名
    username = request.cookies.get('username', None)
    # 如果沒有取到驯妄,代表沒有登錄
    if not username:
        return redirect(url_for('index'))

    if request.method == "POST":
        to_account = request.form.get("to_account")
        money = request.form.get("money")
        print('假裝執(zhí)行轉(zhuǎn)操作荷并,將當(dāng)前登錄用戶的錢轉(zhuǎn)賬到指定賬戶')
        return '轉(zhuǎn)賬 %s 元到 %s 成功' % (money, to_account)

    # 渲染轉(zhuǎn)換頁面
    response = make_response(render_template('temp_transfer.html'))
    return response

if __name__ == '__main__':
    app.run(debug=True, port=9000)
  • 前端登錄頁面代碼
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登錄</title>
</head>
<body>

<h1>我是網(wǎng)站A,登錄頁面</h1>

<form method="post">
    <label>用戶名:</label><input type="text" name="username" placeholder="請(qǐng)輸入用戶名"><br/>
    <label>密碼:</label><input type="password" name="password" placeholder="請(qǐng)輸入密碼"><br/>
    <input type="submit" value="登錄">
</form>

</body>
</html>
  • 前端代碼轉(zhuǎn)賬代碼
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>轉(zhuǎn)賬</title>
</head>
<body>
<h1>我是網(wǎng)站A青扔,轉(zhuǎn)賬頁面</h1>

<form method="post">
    <label>賬戶:</label><input type="text" name="to_account" placeholder="請(qǐng)輸入要轉(zhuǎn)賬的賬戶"><br/>
    <label>金額:</label><input type="number" name="money" placeholder="請(qǐng)輸入轉(zhuǎn)賬金額"><br/>
    <input type="submit" value="轉(zhuǎn)賬">
</form>

</body>
</html>
攻擊網(wǎng)站B代碼
  • 后端代碼實(shí)現(xiàn)
from flask import Flask
from flask import render_template

app = Flask(__name__)

@app.route('/')
def index():
    return render_template('temp_index.html')

if __name__ == '__main__':
    app.run(debug=True, port=8000)
  • 前端代碼實(shí)現(xiàn)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<h1>我是網(wǎng)站B</h1>

<form method="post" action="http://127.0.0.1:9000/transfer">
    <input type="hidden" name="to_account" value="999999">
    <input type="hidden" name="money" value="190000" hidden>
    <input type="submit" value="點(diǎn)擊領(lǐng)取優(yōu)惠券">
</form>

</body>
</html>

--運(yùn)行測(cè)試源织,在用戶登錄網(wǎng)站A的情況下翩伪,點(diǎn)擊網(wǎng)站B的按鈕,可以實(shí)現(xiàn)偽造訪問

在網(wǎng)站A中模擬實(shí)現(xiàn)csrf_token校驗(yàn)的流程
  • 添加生成csrf_token的函數(shù)
# 生成csrf_token的函數(shù)
def generate_csrf():
  return  bytes.decode(base64.b64encode(os.urandom(48)))
  • 在渲染轉(zhuǎn)賬頁面的時(shí)候谈息,做以下幾件事情:
    • 生成csrf_token的值
    • 在返回轉(zhuǎn)賬頁面的響應(yīng)里面設(shè)置csrf_token到cookie中
    • 將csrf_token保存到變單的隱藏字段中
@app.route('/transfer', methods=["POST", "GET"])
def transfer():
    ...
    # 生成 csrf_token 的值
    csrf_token = generate_csrf()

    # 渲染轉(zhuǎn)換頁面缘屹,傳入 csrf_token 到模板中
    response = make_response(render_template('temp_transfer.html', csrf_token=csrf_token))
    # 設(shè)置csrf_token到cookie中,用于提交校驗(yàn)
    response.set_cookie('csrf_token', csrf_token)
    return response
  • 在轉(zhuǎn)賬模板表單中添加 csrf_token 隱藏字段
<form method="post">
    <input type="hidden" name="csrf_token" value="{{ csrf_token }}">
    <label>賬戶:</label><input type="text" name="to_account" placeholder="請(qǐng)輸入要轉(zhuǎn)賬的賬戶"><br/>
    <label>金額:</label><input type="number" name="money" placeholder="請(qǐng)輸入轉(zhuǎn)賬金額"><br/>
    <input type="submit" value="轉(zhuǎn)賬">
</form>
  • 在執(zhí)行轉(zhuǎn)賬邏輯之前進(jìn)行 csrf_token 的校驗(yàn)
if request.method == "POST":
    to_account = request.form.get("to_account")
    money = request.form.get("money")
    # 取出表單中的 csrf_token
    form_csrf_token = request.form.get("csrf_token")
    # 取出 cookie 中的 csrf_token
    cookie_csrf_token = request.cookies.get("csrf_token")
    # 進(jìn)行對(duì)比
    if cookie_csrf_token != form_csrf_token:
        return 'token校驗(yàn)失敗侠仇,可能是非法操作'
    print('假裝執(zhí)行轉(zhuǎn)操作轻姿,將當(dāng)前登錄用戶的錢轉(zhuǎn)賬到指定賬戶')
    return '轉(zhuǎn)賬 %s 元到 %s 成功' % (money, to_account)
  • 運(yùn)行測(cè)試,用戶直接在網(wǎng)站A操作沒有問題逻炊,在去網(wǎng)站B進(jìn)行操作互亮,發(fā)現(xiàn)轉(zhuǎn)賬不成功,因?yàn)榫W(wǎng)站B獲取不到表單中的csrf_token的隱藏字段余素,而且瀏覽器有 同源策略豹休,網(wǎng)站B是獲取不到網(wǎng)站A的cookie的,所以就解決了 跨站請(qǐng)求偽造 的問題

在 Flask 項(xiàng)目中解決 CSRF 攻擊

CSRF

  • 包含請(qǐng)求體的請(qǐng)求都需要開啟CSRF
# CSRF1. 在app創(chuàng)建的地方開啟CSRFProtect保護(hù)
# 開啟保護(hù)之后,程序會(huì)獲取cookie中的隨機(jī)值,以及從表單或者ajax中獲取隨機(jī)值,進(jìn)行對(duì)比
# if 對(duì)比失敗,則無法訪問路由
# 后續(xù)需要設(shè)置隨機(jī)值到cookie中,以及增加ajax的headers
from flask_wtf.csrf import CSRFProtect
...
app.config.from_object(Config)
...
CSRFProtect(app)
  • CSRFProtect只做校驗(yàn)工作桨吊,cookie中的csrf_token和表單中的csrf_token需要我們自己實(shí)現(xiàn)
思路分析

一威根、在 前端發(fā)起的POST請(qǐng)求 中沒有進(jìn)行csrf_token校驗(yàn),根據(jù)csrf_token校驗(yàn)原理屏积,具體操作步驟有以下幾步:

  1. 后端生成csrf_token的值医窿,在前端請(qǐng)求界面的時(shí)候?qū)⒅祩鹘o前端,傳給前端的方式可能有以下兩種方式:
    • 在模版中的From表單中的添加隱藏的字段
    • 將csrf_token使用cookie的方式傳給前端
  2. 在前端 發(fā)起請(qǐng)求時(shí)炊林,在表單或者請(qǐng)求頭中帶上指定的csrf_token
  3. 后端在接收請(qǐng)求之后姥卢,取到前端發(fā)送過來的csrf_token,與第一步生成的csrf_token值進(jìn)行校驗(yàn)
  4. 如果校驗(yàn)對(duì)csrf_token一致渣聚,則代表是正常的請(qǐng)求独榴,否則是偽造的請(qǐng)求,不予通過

二奕枝、在Flask中棺榔,CSRFProtect這個(gè)類專門只對(duì)指定的app進(jìn)行csrf_token校驗(yàn)操作,所以開發(fā)者需要做以下的操作:
- 生成csrf_token的值
- 將csrf_token的值傳給前端瀏覽器

- 在前端請(qǐng)求是帶上csrf_token值
CSRF機(jī)制流程圖.png
完成代碼邏輯
  • 生成csrf_token的值
# CSRF2. 增加請(qǐng)求鉤子,在請(qǐng)求之后設(shè)置cookie
# 我們無法判斷用戶第一次訪問網(wǎng)頁隘道,是哪個(gè)網(wǎng)頁症歇,不能寫死給那個(gè)路由的某個(gè)我網(wǎng)頁
# 所有就需要對(duì)所有飛請(qǐng)求進(jìn)行監(jiān)聽
# 導(dǎo)入生成 csrf_token 值的函數(shù)
from flask_wtf.csrf import generate_csrf
# 調(diào)用函數(shù)生成 csrf_token
csrf_token = generate_csrf()
  • 將 csrf_token 的值傳給前端瀏覽器
    • 實(shí)現(xiàn)思路:可以在請(qǐng)求勾子函數(shù)中完成此邏輯
# CSRF3. generate_csrf()-->源碼-->csrf_token會(huì)被緩存起來,多次調(diào)用,只會(huì)返回相同的token(被強(qiáng)制刪除或者cookie過期,會(huì)生成新的token)
# 如果獲取csrf_token,可以使用session['csrf_token']--->token會(huì)被自動(dòng)擴(kuò)展到session中
# 應(yīng)該對(duì)所有的post/put/delete/請(qǐng)求增加ajax的headers或者表單的隱藏字段(對(duì)數(shù)據(jù)有修改/提交/設(shè)置)
    
@app.after_request
def after_request(response):
    # 調(diào)用函數(shù)生成 csrf_token
    csrf_token = generate_csrf()
    # 通過 cookie 將值傳給前端
    response.set_cookie("csrf_token", csrf_token)
    return response
  • 在前端請(qǐng)求時(shí)帶上 csrf_token 值
    • 根據(jù)登錄和注冊(cè)的業(yè)務(wù)邏輯谭梗,當(dāng)前采用的是 ajax 請(qǐng)求
    • 所以在提交登錄或者注冊(cè)請(qǐng)求時(shí)忘晤,需要在請(qǐng)求頭中添加 X-CSRFToken 的鍵值對(duì)
$.ajax({
    url:"/passport/register",
    type: "post",
    headers: {
        "X-CSRFToken": getCookie("csrf_token")
    },
    data: JSON.stringify(params),
    contentType: "application/json",
    success: function (resp) {
        if (resp.errno == "0"){
            // 刷新當(dāng)前界面
        location.reload()
        }else {
            $("#register-password-err").html(resp.errmsg)
            $("#register-password-err").show()
        }
    }
})

打開 CSRFProtect 的代碼,并運(yùn)行測(cè)試 注:X-CSRFToken 這個(gè) key 是 CSRFProtect 這個(gè)類里面指定的激捏,具體請(qǐng)點(diǎn)擊進(jìn)入到此類查看源代碼

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末设塔,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子远舅,更是在濱河造成了極大的恐慌闰蛔,老刑警劉巖痕钢,帶你破解...
    沈念sama閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異序六,居然都是意外死亡任连,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門难咕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來课梳,“玉大人,你說我怎么就攤上這事余佃∧喝校” “怎么了?”我有些...
    開封第一講書人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵爆土,是天一觀的道長椭懊。 經(jīng)常有香客問我,道長步势,這世上最難降的妖魔是什么氧猬? 我笑而不...
    開封第一講書人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮坏瘩,結(jié)果婚禮上盅抚,老公的妹妹穿的比我還像新娘。我一直安慰自己倔矾,他們只是感情好妄均,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著哪自,像睡著了一般丰包。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上壤巷,一...
    開封第一講書人閱讀 49,741評(píng)論 1 289
  • 那天邑彪,我揣著相機(jī)與錄音,去河邊找鬼胧华。 笑死寄症,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的矩动。 我是一名探鬼主播有巧,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼铅忿!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起灵汪,我...
    開封第一講書人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤檀训,失蹤者是張志新(化名)和其女友劉穎柑潦,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體峻凫,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡渗鬼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了荧琼。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片譬胎。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖命锄,靈堂內(nèi)的尸體忽然破棺而出堰乔,到底是詐尸還是另有隱情,我是刑警寧澤脐恩,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布镐侯,位于F島的核電站,受9級(jí)特大地震影響驶冒,放射性物質(zhì)發(fā)生泄漏苟翻。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一骗污、第九天 我趴在偏房一處隱蔽的房頂上張望崇猫。 院中可真熱鬧,春花似錦需忿、人聲如沸诅炉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽汞扎。三九已至,卻和暖如春擅这,著一層夾襖步出監(jiān)牢的瞬間澈魄,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來泰國打工仲翎, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留痹扇,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓溯香,卻偏偏與公主長得像鲫构,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子玫坛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容