第七色在线视频,2021少妇久久久久久久久久,亚洲欧洲精品成人久久av18,亚洲国产精品特色大片观看完整版,孙宇晨将参加特朗普的晚宴

Flask 防御 CSRF 攻擊

在上一個小節(jié)中講解了 CSRF 攻擊與防御的原理,本小節(jié)首先講解了基于校驗 Token 檢測 CSRF 攻擊的基本思想和步驟,然后通過一個銀行轉賬的實例演示了 CSRF 的攻擊與防御。

1. 基于校驗 Token 檢測 CSRF 攻擊

1.1 基本思想

在 Flask 中防御 CSRF 攻擊的最常見方法是基于校驗 Token 檢測 CSRF 攻擊,它的基本思想如下:

  • 在服務端生成一個隨機的、不可預測的字符串,該字符串稱為 CSRF Token;
  • 客戶獲取安全相關的頁面時,服務端將 CSRF Token 作為隱藏字段發(fā)送給客戶端;
  • 客戶端提出請求時,將頁面的中的隱藏字段一并發(fā)送給服務端;
  • 服務端處理請求時,提取請求中的 CSRF Token,與服務端生成的 CSRF Token 進行比對,如果相同則請求是合法的,如果不相同則請求是 CSRF 攻擊。

因為 CSRF Token 的值是隨機的、不可預測的,攻擊者無法構造一個帶有合法 CSRF Token 的請求實施 CSRF 攻擊,從而阻斷了 CSRF 攻擊。

1.2 具體的步驟

以一個銀行轉賬的例子說明防御 CSRF 攻擊的具體步驟,如下圖所示:

1. 登錄

用戶輸入賬戶名和密碼登錄。

2. 驗證通過

賬戶名和密碼匹配,則驗證通過。

3. 生成 CSRF Token

用戶驗證通過后,服務端生成一個隨機的、不可預測的字符串 CSRF Token,并將它存儲在 Session 中。

4. 訪問轉賬頁面

用戶訪問轉賬頁面。

5. 返回轉賬頁面

服務端返回轉賬頁面的 HTML,將存儲在 Session 中的 CSRF Token 作為隱藏字段返回給客戶端。例如,服務端返回的轉賬頁面可能如下:

<form action="/transfer" method="POST">
  <input type="hidden" name="csrfToken" value="IZiglWi1k2e3z!m@z$">
  <input type="text" name="name" placeholder="接收用戶" />
  <input type="text" name="amount" placeholder="轉賬數(shù)量" />
  <input type="submit" name="submit" value="轉賬">
</form>

在第 2 行,名稱為 csrfToken 的隱藏字段包含了 CSRF Token 的值,字符串 “IZiglWi1k2e3z!m@z$” 是隨機的、無法被猜測的,即攻擊者無法獲取 CSRF Token、無法構造一個合法的轉賬請求。

6. 轉賬請求

客戶端發(fā)出轉賬請求,將隱藏字段 CSRF Token 一并發(fā)送給服務端。

7. 比對 Token

服務端收到轉賬請求后,提取請求中的 CSRF Token,與服務端生成的 CSRF Token 進行比對,如果相同則請求是合法的,如果不相同則請求是 CSRF 攻擊。

2. 演示 CSRF 的攻擊與防御

2.1 程序簡介

本節(jié)通過具體的案例演示 CSRF 的攻擊與防御,案例中的假設如下:

  • 銀行網(wǎng)站,提供在線轉賬功能;
  • 銀行網(wǎng)站中有兩個用戶:受害者 victim 和攻擊者 hacker,他們的賬戶中各有 100 元;
  • 惡意網(wǎng)站,由攻擊者 hacker 創(chuàng)辦,當受害者 victim 沒有退出銀行網(wǎng)站的情況下,去訪問惡意網(wǎng)站,會在不知情的情況下,向 hacker 轉賬 50 元。

為了演示 CSRF 攻擊與防御,本節(jié)包括了 2 個程序:

程序 源代碼目錄 首頁 URL
銀行網(wǎng)站 bank http://localhost:8888
惡意網(wǎng)站 malicious http://localhost:4444

銀行網(wǎng)站和惡意網(wǎng)站運行在同一臺機器上,通過端口號區(qū)分:銀行網(wǎng)站監(jiān)聽端口 8888,惡意網(wǎng)站監(jiān)聽端口 4444。

2.2 演示 CSRF 攻擊

下面的視頻演示了 CSRF 攻擊的操作過程:

2.3 演示防御 CSRF 攻擊

下面的視頻演示了防御 CSRF 攻擊的操作過程:

2. 實現(xiàn) bank 程序

2.1 程序下載

bank 程序實現(xiàn)了銀行網(wǎng)站的功能,包括 2 個源文件,點擊下載例子代碼 bank。:

源文件 功能
bank/app.py 后端服務程序
bank/templates/index.html 首頁模板文件

2.2 首頁模板 templates/index.html

2.2.1 登錄后的界面

用戶訪問網(wǎng)站首頁時,根據(jù)是否登錄顯示不同的內容,如果用戶已經(jīng)登錄,則顯示如下:

<html>
<head>
<meta charset='utf-8'>
<title>中國銀行</title>
</head>

<body>
<h1>中國銀行</h1>

{% if hasLogin %}
<h2>1. 基本信息</h2>
<h3>你好, {{user.name}},你的賬戶剩余 {{user.amount}} 元</a></h3>
<h2>2. 轉賬</h2>
<form action="/transfer" method="POST">
  <input type="hidden" name="csrfToken" value="{{ csrfToken }}">
  <input type="text" name="name" placeholder="接收用戶" />
  <input type="text" name="amount" placeholder="轉賬數(shù)量" />
  <input type="submit" name="submit" value="轉賬">
</form>
<h2>3. 退出</h2>
<form action="/logout" method="POST">
  <input type="submit" name="submit" value="退出">
</form>

在第 10 行,如果參數(shù) hasLogin 為真,表示用戶已經(jīng)登錄,則顯示在第 11 行到第 23 行的內容。

在第 11 行到第 12 行,顯示用戶的基本信息:姓名和賬戶余額。

在第 13 行到第 19 行,顯示用于轉賬的表單,使用 POST 方法向服務端的 /transfer 頁面提出轉賬請求;字段 csrfToken 存儲了服務端發(fā)送的 CSRF Token,提交表單時,會將該字段一并提交;字段 name 是轉賬的接收賬戶名;字段 amount 是轉賬的數(shù)量。

在第 20 行到第 23 行,使用 POST 方法向服務端的 /logout 頁面退出登錄。

2.2.2 沒有登錄的界面

用戶訪問網(wǎng)站首頁時,根據(jù)是否登錄顯示不同的內容,如果用戶還沒有登錄,則顯示如下:

{% else %}
<h2>登錄</h2>
<form action="/login" method="POST">
  <input type="text" name="name" placeholder="用戶" />
  <input type="password" name="password" placeholder="密碼" />
  <input type="submit" name="submit" value="登錄">
</form>
{% endif %}

</body>
</html>

在第 2 行到第 7 行,顯示用于登錄的表單,使用 POST 方法向服務端的 /login 頁面登錄。

2.3 后端服務 app.py

2.3.1 引入相關模塊

#!/usr/bin/python3
from flask import Flask, request, session, render_template, redirect
import os, base64
import sys

app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(24)

在第 2 行,引入 os 模塊和 base64 模塊,需要使用 os.urandom 和 b64encode 用于生成 CSRF Token。

在第 7 行,F(xiàn)lask 程序在使用 Session 時,需要配置 SECRET_KEY。

2.3.2 用戶數(shù)據(jù)庫

class User:
    def __init__(self, name, password, amount):
        self.name = name
        self.password = password
        self.amount = amount

users = [
    User('victim', '123', 100),
    User('hacker', '123', 100)
]

def findUser(name):
    for user in users:
        if user.name == name:
            return user
    return None

def checkUser(name, password):
    for user in users:
        if user.name == name and user.password == password:
            return user
    return None

在第 1 行,定義類 User 用于描述銀行賬戶信息,包括:姓名、密碼、賬戶余額等屬性。

在第 7 行,預定義了兩個用戶:victim 和 hacker,將它們存儲在全局變量 users 中。

在第 12 行,定義函數(shù) findUser,在 users 中根據(jù)姓名查找 user。

在第 18 行,定義函數(shù) checkUser,在 users 中根據(jù)姓名和密碼查找 user。

2.3.3 首頁面

@app.route('/')
def index():
    hasLogin = session.get('hasLogin')
    name = session.get('name')
    user = findUser(name)
    csrfToken = getCsrfToken()
    session['csrfToken'] = csrfToken
    return render_template('index.html', hasLogin = hasLogin, user = user, csrfToken = csrfToken)

設置首頁面 / 的處理函數(shù)為 index,函數(shù)在 session 中查找 hasLogin、name、csrfToken 變量,將它們傳遞給頁面模板 index.html。

在第 6 行,調用函數(shù) getCsrfToken() 生成一個隨機的、不可預測的 CSRF Token,并將其存儲在 Session 中。

2.3.4 登錄頁面

@app.route('/login', methods = ['POST'])
def login():
    name = request.form['name']
    password = request.form['password']
    user = checkUser(name, password)
    if user != None:
        session['hasLogin'] = True
        session['name'] = name
        return redirect('/')
    else:
        return '登錄失敗'

設置頁面 /login 的處理函數(shù)為 login,該函數(shù)首先提取請求中的 name 和 password,然后調用 checkUser 在所有的 users 中查找匹配的 User。

如果找到了匹配的 User,則設置 Session 中的 hasLogin 為真,調用 redirect(’/’) 讓客戶端瀏覽器重定向到首頁面。

2.3.5 退出頁面

@app.route('/logout', methods = ['POST'])
def logout():
    session['hasLogin'] = False
    session['name'] = None
    return redirect('/')

設置頁面 /logout 的處理函數(shù)為 logout,該函數(shù)設置 Session 中的 hasLogin 為假,調用 redirect(’/’) 讓客戶端瀏覽器重定向到首頁面。

2.3.5 檢查 CSRF 攻擊

def getCsrfToken():
    return bytes.decode(base64.b64encode(os.urandom(16)))

def checkCsrfAttack():
    csrfTokenFromRequest = request.form.get('csrfToken')
    csrfTokenFromSession = session.get('csrfToken')
    return csrfTokenFromRequest != csrfTokenFromSession

函數(shù) getCsrfToken 返回一個隨機的字符串,os.urandom(16) 產生一個包含 16 個字節(jié)的 bytes,base64.b64encode 將 bytes 轉換為 base64 編碼的字符串。

函數(shù) checkCsrfAttack 檢測 CSRF 攻擊,在第 5 行,從請求的表單中獲取參數(shù) csrfToken,在第 6 行,從 Session 中獲取變量 csrfToken。對兩者進行比較,如果相等,表示此次請求合法;如果不相等,表示此次請求是 CSRF 攻擊。

2.3.6 轉賬頁面

@app.route('/transfer', methods = ['POST'])
def transfer():
    if not session.get('hasLogin'):
        return '請先登錄'

    if checkFlag and checkCsrfAttack():
        print('警告:檢測到 CSRF 攻擊!')
        return '轉賬失敗'

    sourceName = session['name']
    sourceUser = findUser(sourceName)

    targetName = request.form['name']
    amount = int(request.form['amount'])
    targetUser = findUser(targetName)

    if targetUser != None:
        sourceUser.amount -= amount
        targetUser.amount += amount
        return redirect('/')
    else:
        return '轉賬失敗'

設置頁面 /transfer 的處理函數(shù)為 transfer。在第 3 行,如果 Session 中的 hasLogin 變量未假,表示請求來自于未登錄的用戶,返回 ‘轉賬失敗’。

在第 6 行,如果 checkFlag 為真并且 checkCsrfAttack 函數(shù)檢測到了 CSRF 攻擊,在控制臺打印 CSRF 的警告,返回 ‘轉賬失敗’。如果 checkFlag 為假,程序不檢測 CSRF 攻擊。

在第 10 行到第 15 行,獲取來源賬戶,并從轉賬請求中獲取參數(shù):轉賬數(shù)量、接受賬戶。

在第 18 行和第 19 行,進行轉賬操作,最后調用 redirect(’/’) 讓客戶端瀏覽器重定向到首頁面。

2.3.6 設置選項

checkFlag = False
if len(sys.argv) == 2 and sys.argv[1] == 'check':
    checkFlag = True

app.run(debug = True, port = 8888)

設置全局變量 checkFlag,如果 checkFlag 為真,程序檢測 CSRF 攻擊;如果 checkFlag 為假,程序不檢測 CSRF 攻擊。

3. 實現(xiàn) malicious 程序

3.1 程序下載

malicious 程序實現(xiàn)了惡意網(wǎng)站的功能,包括 2 個源文件,點擊下載例子代碼 malicious。:

源文件 功能
bank/app.py 后端服務程序
bank/templates/index.html 首頁模板文件

3.2 首頁模板 templates/index.html

惡意網(wǎng)站的頁面包括兩部分:

  • 正常顯示的部分
  • 實施 CSRF 攻擊的代碼

3.2.1 正常顯示的部分

<html>
<head>
<meta charset='utf-8'>
<title>惡意網(wǎng)站</title>
</head>

<body>
<h1>惡意網(wǎng)站</h1>
<ul>
  <li>在網(wǎng)站中放置吸引人的內容,例如賭博、色情、盜版小說等,吸引人來訪問
  <li>如果用戶已經(jīng)登錄了某銀行網(wǎng)站,訪問惡意網(wǎng)站首頁時,自動向銀行網(wǎng)站發(fā)起轉賬請求
</ul>

通常惡意網(wǎng)站會放置吸引人的內容,例如賭博、色情、盜版小說等,誘導受害者來訪問。

3.2.2 隱藏 iframe 和 表單

<style>
iframe {
    display: none;
}

form {
    display: none;   
}
</style>

CSRF 攻擊需要使用 HTML 中的 iframe 和 表單元素,因此在惡意網(wǎng)站中設置 CSS 屬性,讓 iframe 和表單隱藏不可見。

3.2.3 實施 CSRF 攻擊的代碼

<iframe name="iframe"></iframe>

<form action="http://localhost:8888/transfer" method="POST" target='iframe'>
  <input type="text" name="name" value="hacker" placeholder="接收用戶"/>
  <input type="text" name="amount" value="50" placeholder="轉賬數(shù)量"/>
  <input type="submit" id="submit" value="轉賬">
</form>

<script>
var submit = document.getElementById('submit');
submit.click();
</script>

</body>
</html>

在第 3 行,定義了一個提交轉賬請求的表單,相關屬性如下:

  • action 是銀行轉賬的頁面;
  • target 指向一個 iframe,向銀行網(wǎng)站提交表單請求后,在指定的 iframe 中顯示銀行網(wǎng)站的返回的內容,因為 iframe 被設置為不可見,因此訪問者察覺不到訪問銀行轉賬的操作;
  • 名稱為 ‘name’ 的文本字段是轉賬的接收賬戶,值為 hacker,表示向 hacker 轉賬;
  • 名稱為 ‘amount’ 的文本字段是轉賬的數(shù)量,值為 50,表示轉賬 50 元。

在第 10 行,獲取表單中的提交按鈕,在第 11 行,模擬點擊提交按鈕,向銀行發(fā)起轉賬請求。

3.3 后端服務 app.py

#!/usr/bin/python3
from flask import Flask, render_template
app = Flask(__name__)

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

if __name__ == '__main__':
    app.run(debug = True, port = 4444)

在第 5 行,訪問頁面 / 時,服務端返回頁面模板 index.html;在第 10 行,在端口號 4444 上進行監(jiān)聽。

4. 小結

本小節(jié)了基于校驗 Token 檢測 CSRF 攻擊的方法,然后通過一個銀行轉賬的實例演示了 CSRF 的攻擊與防御,使用思維導圖概括如下:

圖片描述