National Central University
Uedu Main Site
Explore Uedu
Student Console
Register as Member/Login
Research Informed Consent Center
Survey Center
Educational Big Data Challenge
Claim or create a course
Try an AI Scenario Task
Support & Messages
Uptime Data

UeduGPTs

--

Jupyters

3

Local AI

--

Uedu Code

--

中央大學 AQI 80 24°C PM2.5 21
AI Reply Desktop Notifications

Show a desktop notification when the AI TA finishes replying

Chat Message Notifications

Notify me when classmates post messages in the forum

Sound notification

Play an alert sound whenever there is a new notification

Unit 17

常見攻擊與防護

SQL 注入、XSS、CSRF,以及金鑰與除錯模式
Interaction實作 35 minute(s)

Learning Objectives

  • 說明 SQL 注入怎麼發生,並用參數化查詢修補
  • 說明 XSS 怎麼發生、Jinja2 的自動跳脫做了什麼、safe 過濾器的風險,以及 HttpOnly 的作用
  • 說明 CSRF 怎麼發生,並用 CSRF token 與 SameSite 防護
  • 說出金鑰管理、除錯模式、登入限流、檔案上傳各要注意什麼

實際攻擊三個模擬網站,再修好它們

這一課會實際攻擊三個網站,再把漏洞修掉。這三個網站都是模擬的,只在你自己的瀏覽器裡執行,不會連到任何真實的伺服器。

這些技巧只能用在自己的程式,或已取得書面授權的測試。 對別人的網站嘗試,即使只是好奇、沒有造成損害,也可能違法。發現真實網站有漏洞時,不要繼續測試,依對方公布的管道回報。

OWASP Top 10:2025 整理了最常見的網站安全風險,這一課和上一課碰到的都在裡面:

這一課的主題 對應的類別
上一課的權限漏洞、這一課的 CSRF A01 存取控制失效(Broken Access Control)
SQL 注入、XSS A05 注入(Injection)
除錯模式、預設設定沒改 A02 安全設定錯誤(Security Misconfiguration)

三種攻擊有共同的原因:把不可信的輸入,混進了會被執行的東西(SQL、HTML),或者瀏覽器自動帶上的 Cookie 被當成使用者的意願。

逐步看:一段輸入改變了 SQL 的意思

同一組輸入送給兩個版本。先注意 v1 組出來的 SQL,尤其是 -- 之後的內容;再看 v2 的 SQL 為什麼不受影響。

互動練習:不知道密碼也能登入

先攻擊,再修補。第二個任務在「信箱」欄位輸入下面這段(單引號也要),密碼隨便打:

' OR 1=1 --

修補時把 login 裡的兩行改成參數化查詢:

row = get_db().execute('SELECT * FROM users WHERE email = ? AND password = ?',
                       (email, password)).fetchone()

範例為了讓漏洞看得清楚,密碼存成明文,這本身也是錯的。真實網站依第 15 課的做法,用信箱查出該列,再 check_password_hash。

SQL 注入的防線是參數化查詢

OWASP 建議的主要防線是參數化查詢:SQL 的結構和資料分開送,資料再怎麼寫都不會變成 SQL。

不算防線 為什麼
把單引號過濾掉 攻擊手法很多,也會誤傷真的有單引號的資料(O'Brien)
前端驗證 email 格式 請求不一定來自你的頁面(上一課)
f-string 或 % 組 SQL 和 + 接字串是同一件事

欄位名稱與排序方向不能當參數:ORDER BY ? 不會照你想的運作。這類地方要走白名單:只接受程式裡列出來的幾個值,其他一律拒絕(第 12 課)。

逐步看:自動跳脫做了什麼

模板把同一段字串輸出兩次。按到最後一步,比較輸出區的兩行。

互動練習:一則留言偷讀 Cookie

XSS(Cross-Site Scripting,跨站腳本)的意思是:攻擊者的腳本,在其他使用者的瀏覽器裡,以你的網站的身分執行。下面的留言板用了 |safe,所以留言裡的 HTML 會原樣輸出。

第二個任務在留言框輸入:

<script>console.log('讀到 Cookie:' + document.cookie)</script>

送出後,頁面一載入腳本就會執行,結果在「伺服器紀錄」面板的[瀏覽器主控台]那一行。這個範例只把 Cookie 印出來;真正的攻擊會把它送到攻擊者的伺服器。

XSS 的防線

剛才兩個修改的作用不同:

  • 拿掉 |safe(根本解法):Flask 預設讓 Jinja2 自動跳脫,只有你明確標示 |safe 或 Markup 時才不跳脫。不要對使用者輸入用這兩個。 要讓使用者寫格式,就先經過白名單過濾的函式庫。
  • HttpOnly(降低損失):腳本讀不到 Cookie,但腳本還在執行,一樣能以使用者的身分送請求。Flask 的 Session Cookie 預設就有 HttpOnly,你自己 set_cookie 的要自己加。

還要注意:

  • 屬性值一定要加引號:<input value="{{ value }}">。沒有引號時,內容可以插入新的屬性。
  • 自動跳脫擋不住 <a href="{{ url }}"> 裡的 javascript: 網址,使用者提供的網址要檢查開頭是 http:// 或 https://。
  • 在前端 JavaScript 用 textContent,不要用 innerHTML 接資料(第 13 課)。
  • CSP(Content-Security-Policy 回應標頭)告訴瀏覽器只能載入哪些來源的腳本,即使有人注入成功也較難執行,是額外的一層防護。

CSRF:別的網站讓你的瀏覽器送出請求

CSRF(Cross-Site Request Forgery,跨站請求偽造)不需要偷到任何東西。流程是:

  1. 你登入了 A 網站,瀏覽器保存著 A 的 Session Cookie
  2. 你開了 B 網站,B 的頁面有一個送往 A 的表單
  3. 你按下按鈕(或頁面自己送出),瀏覽器自動帶上 A 的 Cookie
  4. A 看到有效的 Cookie,以為是你本人的操作

攻擊者看不到回應,也讀不到 Cookie。但改信箱、轉帳、刪資料這類動作,只要請求送到就完成了。

防線的想法:要求請求裡多帶一個 B 網站拿不到的值,這就是 CSRF token。它在你自己的頁面裡以隱藏欄位送出;B 網站因為同源政策(第 13 課)讀不到你網站的回應,所以無從得知。

逐步看:手寫一次 CSRF token

程式先取得 token,再用三種方式送出修改信箱的請求。

互動練習:另一個網站改掉你的信箱

網址列右邊的「開啟另一個網站」會打開模擬的 other.example。它的頁面有一個表單,送往這個網站的 /settings/email。

為了讓攻擊看得到,這個模擬環境裡沒有設定 SameSite 的 Cookie 會跟著跨站請求送出。 真實瀏覽器不一定如此:MDN 說明有些瀏覽器把沒設定的 Cookie 當作 Lax,而且這種預設的 Lax 較寬鬆,Cookie 設定後兩分鐘內的跨站 POST 仍會帶上。各瀏覽器不一致,所以不要依賴預設值,要自己設定。

任務三要改兩個地方,少一個都過不了:

<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
check_csrf()     # 放在 change_email 裡,改資料之前

任務五的設定寫法:app.config['SESSION_COOKIE_SAMESITE'] = 'Lax'。Cookie 的屬性是發 Cookie 時決定的,所以改完要按「清除 Cookie」重新登入,瀏覽器才會拿到新的屬性。

CSRF 的防線與其他該做的事

CSRF 的防線

  • 會改變資料的動作一律用 POST(或 PUT、DELETE),不要用 GET。
  • CSRF token:實際開發用 Flask-WTF 的 CSRFProtect,不必自己寫。
  • SameSite:Lax 不會在跨站 POST 帶上 Cookie,Strict 連從別的網站點連結進來也不帶。Flask 的 Session Cookie 預設沒有設定,要自己加。OWASP 的立場是 SameSite 屬於額外一層,不取代 token,所以兩個都做。

其他該做的事

項目 做法
secret_key、API 金鑰 不寫死在程式裡,不進 git,從環境變數讀(第 19 課)。金鑰一旦進過 git,即使之後刪掉,歷史紀錄裡還有,要換一把新的
除錯模式 正式環境一定要關。Flask 文件說明除錯器可以從瀏覽器執行任意 Python 程式
登入嘗試 限制次數,否則可以一直猜密碼。可以用 Flask-Limiter
上傳檔案 不能只看副檔名,要檢查內容、限制大小;檔名用 secure_filename 處理;不要存在 static 裡直接給人開
查詢與輸出 這一課的參數化與自動跳脫

更完整的清單請看 OWASP Top 10:2025。

想一想

1.

下面哪一行可以防止 SQL 注入?

2.

下面的程式會印出什麼?

from flask import Flask, render_template_string

app = Flask(__name__)
with app.app_context():
    print(render_template_string('{{ x }}', x='<b>Hi</b>'))
3.

Session Cookie 設了 HttpOnly,網站上還有 XSS 漏洞。下面哪個說法正確?

4.

為什麼 CSRF token 擋得住另一個網站送來的表單?

5.

secret_key 不小心進了公開的 git repository。之後用一次新的 commit 把它刪掉,夠嗎?

第 1 次作答

重點整理

攻擊 原因 主要防線
SQL 注入 輸入被當成 SQL 執行 參數化查詢;欄位名稱走白名單
XSS 輸入被當成 HTML、JavaScript 執行 自動跳脫,不對使用者輸入用 safe 過濾器;HttpOnly 與 CSP 降低損失
CSRF 瀏覽器自動帶 Cookie CSRF token 加上 SameSite;改資料不用 GET
  • 金鑰不寫死、不進 git,外洩就換;正式環境關除錯模式;登入要限流;上傳檔案要檢查內容
  • 這些技巧只用在自己的程式或獲得授權的測試

網站愈來愈大,也需要有方法確認修好的漏洞不會再回來。下一課把程式拆成多個檔案,並用自動測試把這些行為固定下來。

這一課完成了!
Next lesson:專案結構與自動測試
你目前沒有登入,學習進度只存在這個瀏覽器。 Login 後,進度會跟著帳號,換電腦也看得到。