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 78 24°C PM2.5 18
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 06

Session 與 Cookie

工作階段劫持、固定攻擊與 Cookie 屬性
Interaction 30 minute(s)

Learning Objectives

  • 說明工作階段 ID 與簽章 Cookie 兩種設計的差異
  • 解釋 Flask 的 session 為何可以解碼卻無法竄改,以及金鑰外洩的後果
  • 在模擬網站上觀察工作階段劫持與工作階段固定,並修補登入與登出流程
  • 依需求設定 Cookie 的 HttpOnly、Secure、SameSite 與有效期,並說明各屬性的限制

HTTP 的無狀態特性與工作階段

學生登入選課系統後,接著開啟課表、送出加選,每一步都是獨立的 HTTP 請求。HTTP 是無狀態協定(stateless),每個請求的語意可以單獨理解,協定本身不記得上一個請求來自誰。網站若要知道「這個請求來自剛才登入的那位學生」,必須讓瀏覽器在每次請求都帶上一項憑據,這項憑據通常放在 Cookie。

建立在這項憑據之上的連續互動稱為工作階段(session)。對伺服器而言,持有憑據的人會被視為已登入的使用者,所以這項憑據的價值等同於一次已通過驗證的登入。常見的設計有兩種:

設計 瀏覽器保存的內容 內容存放位置 伺服器端能否立即撤銷
工作階段 ID(session ID) 一個隨機字串 伺服器(資料庫或快取) 可以,刪除紀錄即可
簽章 Cookie(Flask 預設) 資料本身+簽章 瀏覽器 不能直接撤銷,需要另外設計

工作階段 ID 必須無法猜測。OWASP 建議至少 64 位元的熵,並由密碼學安全的亂數產生器產生,例如 Python 的 secrets.token_urlsafe()。流水號或時間戳記都可以被推算,不應作為工作階段 ID。

簽章與加密的差別

Flask 預設的 session 把資料序列化後放進 Cookie,並以 secret_key 計算簽章。簽章的作用是偵測竄改,不是隱藏內容。Flask 文件的說明是:使用者可以看到 Cookie 的內容,但不能修改,除非知道用來簽章的金鑰。

這項設計有三個直接的後果:

  • 不要把機密放進 session。 任何人都可以解碼 Cookie 的內容。
  • 金鑰是整個機制的基礎。 持有金鑰的人可以為任意帳號簽出有效的 Cookie,等同於能以任何人的身分登入。金鑰不可以寫死在程式碼或提交到版本庫(第 14 課)。
  • 更換金鑰會讓所有既有工作階段失效。 這是金鑰外洩後必要的處理,代價是所有使用者都需要重新登入。金鑰外洩時,不可以把舊金鑰放進 Flask 的 SECRET_KEY_FALLBACKS,否則偽造的 Cookie 依然有效。

互動練習:簽章 Cookie 與金鑰外洩

本區塊為模擬環境。 網站是真的 Flask,只在你的瀏覽器內執行,不連線到任何外部系統,帳號與金鑰皆為虛構。未經授權對真實網站進行相同操作可能觸犯法律(第 1 課)。

按「啟動伺服器」後依任務操作。下方的「Cookie」分頁可以直接修改瀏覽器保存的值,「Session」分頁會同時顯示未經驗證的解碼內容與伺服器驗證簽章後讀到的內容。

工作階段劫持與登出

工作階段劫持(session hijacking)是指他人取得有效的憑據後,以該使用者的身分操作。伺服器看到的只是一個有效的 Cookie,無法從這一點分辨持有者。憑據外流的途徑包括未加密的傳輸(第 4 課)、跨站腳本(第 8 課)、寫進日誌(第 16 課),以及共用電腦上未登出的瀏覽器。

因此,登出必須讓憑據在伺服器端失效。只請瀏覽器刪除 Cookie,只影響這一台瀏覽器,已經流出的副本依然有效。OWASP 的工作階段管理指引明確要求在伺服器端使工作階段失效。

簽章 Cookie 在這一點上有結構性的限制。Flask 的 session.clear() 只讓伺服器回應一個清除 Cookie 的指令,伺服器沒有保存任何可以刪除的紀錄。舊 Cookie 的副本在時效內(PERMANENT_SESSION_LIFETIME,預設 31 天)仍能通過簽章驗證;Flask 文件提到,縮短這個值有助於降低重送攻擊的風險。如果需要立即撤銷的能力,可以改用伺服器端工作階段,或在資料庫保存每位使用者的工作階段版本號並放進 Cookie 比對,兩者都需要在每個請求查詢伺服器端的狀態。

互動練習:工作階段劫持與伺服器端登出

本區塊為模擬環境。 網站是真的 Flask,只在你的瀏覽器內執行,不連線到任何外部系統,帳號與金鑰皆為虛構。未經授權對真實網站進行相同操作可能觸犯法律(第 1 課)。

這個網站把工作階段存在伺服器的資料表 sessions(下方「資料庫」分頁),瀏覽器只保存隨機的 sid。練習中的「貼回 sid」代表憑據已經外流,他人在自己的瀏覽器使用這個值。

修補時將 logout() 改為先刪除伺服器端的紀錄:

db = get_db()
db.execute('DELETE FROM sessions WHERE sid = ?', (request.cookies.get('sid'),))
db.commit()

工作階段固定與登入後換發

工作階段固定(session fixation)與劫持的方向相反。攻擊者不去竊取憑據,而是先讓受害者的瀏覽器使用一個攻擊者已知的 sid,等受害者登入。如果網站在登入時沿用原本的 sid,這個值就變成已登入的憑據,攻擊者不需要知道密碼。

防護方式是權限改變時換發新的識別碼。OWASP 的指引要求在任何權限層級改變之後重新產生工作階段 ID,登入是最重要的時間點,舊的識別碼也要一併銷毀。

Flask 預設的簽章 Cookie 沒有伺服器端的識別碼,所以沒有完全相同的問題,但有類似的風險:登入前寫入 session 的資料會延續到登入之後。範例在登入成功時都先呼叫 session.clear(),目的是讓登入後的 session 只包含登入時寫入的內容。

互動練習:工作階段固定與換發

本區塊為模擬環境。 網站是真的 Flask,只在你的瀏覽器內執行,不連線到任何外部系統,帳號與金鑰皆為虛構。未經授權對真實網站進行相同操作可能觸犯法律(第 1 課)。

練習中手動把 sid 改成固定值,代表攻擊者已設法讓瀏覽器使用這個值,例如同一網域下的其他子網域設定了 Cookie。

修補時將 login() 驗證成功之後的部分改為一律換發:

db = get_db()
db.execute('DELETE FROM sessions WHERE sid = ?', (request.cookies.get('sid'),))
sid = secrets.token_urlsafe(32)
db.execute('INSERT INTO sessions (sid, user) VALUES (?, ?)', (sid, request.form['username']))
db.commit()
resp = redirect(url_for('index'))
resp.set_cookie('sid', sid, httponly=True, samesite='Lax')
return resp

Cookie 的屬性

伺服器以 Set-Cookie 標頭發出 Cookie 時,可以附加屬性來限制 Cookie 的使用範圍。各屬性處理的問題不同,也都有限制:

屬性 作用 不能解決的問題
HttpOnly 頁面的 JavaScript 讀不到這個 Cookie 不能阻止跨站腳本本身;腳本仍可以用目前使用者的身分送出請求(第 8 課)
Secure 只在 HTTPS 請求送出(localhost 例外) 不代表網站內容可信(第 4 課)
SameSite 限制跨站請求是否帶上 Cookie 不能取代 CSRF 防護權杖(第 9 課)
Domain 指定可以收到 Cookie 的網域及其子網域 設定後範圍會變大;省略時只回傳給原主機,工作階段 Cookie 應該省略
Max-Age/Expires 設定有效期,兩者並存時以 Max-Age 為準 只控制瀏覽器何時刪除;已流出的副本仍需由伺服器端的時效檢查處理

Flask 的工作階段 Cookie 可以用設定值控制:SESSION_COOKIE_HTTPONLY(預設 True)、SESSION_COOKIE_SECURE(預設 False)、SESSION_COOKIE_SAMESITE(預設不設定)。瀏覽器在未設定 SameSite 時的行為並不一致,部分瀏覽器以 Lax 為預設,所以應該明確設定。

Secure 的效果在本課的模擬環境中無法觀察:瀏覽器對 localhost 視為例外,模擬瀏覽器也不區分 HTTP 與 HTTPS。下方練習只確認標頭的內容。

互動練習:設定 Cookie 屬性

本區塊為模擬環境。 網站是真的 Flask,只在你的瀏覽器內執行,不連線到任何外部系統,帳號與金鑰皆為虛構。未經授權對真實網站進行相同操作可能觸犯法律(第 1 課)。

首頁下方的程式會顯示 document.cookie 讀到的內容,代表頁面上任何 JavaScript 都能讀到的範圍。每次修改設定後要重新登入,瀏覽器才會收到新的 Cookie。

有效期可以這樣設定:

from datetime import timedelta

app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(minutes=30)
# 登入成功時,寫在 session.clear() 之後
session.permanent = True

session.permanent = True 時,Flask 預設每次回應都重新簽出 Cookie,這個值因此是閒置時效:外流的副本只要持續使用就可以一直延長。需要固定的上限時,設定 SESSION_REFRESH_EACH_REQUEST = False,或在伺服器端另外記錄登入時間並檢查。

重點整理

  • HTTP 是無狀態協定,工作階段的憑據通常放在 Cookie,持有憑據的人會被視為已登入
  • 工作階段 ID 把資料存在伺服器,可以立即撤銷;簽章 Cookie 把資料存在瀏覽器,不能直接撤銷
  • 簽章能偵測竄改但不隱藏內容,機密資料不應放進 Flask 的 session;金鑰外洩時,任何帳號的 Cookie 都可以被偽造,必須更換金鑰
  • 登出要讓憑據在伺服器端失效;只刪除瀏覽器的 Cookie,不影響已流出的副本
  • 登入等權限改變的時間點要換發新的識別碼,並銷毀舊的識別碼,以防止工作階段固定
  • HttpOnly、Secure、SameSite 各自處理不同的問題,都不能取代修補跨站腳本與 CSRF 防護權杖
  • 只在自己擁有或獲得明確授權的系統上進行測試

下一課討論注入類的漏洞:SQL 注入。

小測驗

1.

關於 Flask 預設的 session,下列敘述何者正確?

2.

發現 Flask 的 secret_key 被提交到公開版本庫,最優先的處理是什麼?

3.

一個網站使用 Flask 預設的簽章 Cookie,登出時呼叫 session.clear()。Cookie 在登出前被他人複製,這份副本之後的狀況如何?

4.

為什麼登入成功後要換發新的工作階段 ID?

5.

將工作階段 Cookie 設為 HttpOnly 後,下列何者仍然可能發生?

6.

工作階段 Cookie 設定了 Domain=campus.example,會造成什麼影響?

第 1 次作答
這一課完成了!
Next lesson:SQL 注入
你目前沒有登入,學習進度只存在這個瀏覽器。 Login 後,進度會跟著帳號,換電腦也看得到。