iT邦幫忙

2026 iThome 鐵人賽

DAY 20
0
Security

新手開發者的資安第一課:30 天搞懂 OWASP Top 10 與常見漏洞系列 第 20 篇

Day 20:Web 漏洞––跨站請求偽造(CSRF)與 Unauthorized Action

  • 分享至 

  • xImage
  •  

在上篇 Day 19 中,我們介紹了針對使用者瀏覽器執行惡意腳本的 XSS;今天我們要探討另一個經常與 XSS 被拿來比較的漏洞——跨站請求偽造(CSRF)。

如果說 XSS 是「在你的瀏覽器裡執行攻擊者的程式碼」,那麼 CSRF 就是「利用瀏覽器對目標網站的信任,冒用你的身分發起未授權操作(Unauthorized Action)」。

CSRF

CSRF的核心原理在於:瀏覽器在發送 HTTP 請求時,會自動帶上該目標網域的所有 Cookie(包含 Session ID)。

當使用者已經登入受害網站 A(瀏覽器存有 A 網站的 Session Cookie),隨後在同一個瀏覽器開啟了攻擊者控制的惡意網站 B 時,網站 B 可以透過 HTML 標籤(如 <img>、<form>)或 JavaScript 發起指向網站 A 的請求。此時,瀏覽器會自動將網站 A 的 Cookie 夾帶進請求中,導致網站 A 的後端伺服器誤以為這是使用者本人發起的合法操作。

CSRF攻擊手法

CSRF 專門攻擊會改變伺服器狀態的操作,例如:修改密碼、變更 Email、轉帳、購買商品或發送文章。

GET 型態 CSRF 攻擊

若目標網站的 API 設計不當,允許使用 GET 請求來執行修改資料、轉帳等狀態變更操作,攻擊者只需在惡意網站、論壇文章或 HTML 郵件中嵌入帶有惡意 URL 的資源標籤(如 <img>、<iframe> 或 <script>),當瀏覽器試圖渲染或載入這些資源時,會自動向該 URL 發送 GET 請求,並順便附帶受害者的 Cookie。

日常範例:
小明剛登入完網路銀行 bank.com 處理完業務,沒有點擊登出就把分頁分開。隨後他在另一個分頁看論壇文章,文章內嵌入了一張看似普通、其實是隱藏的圖片:

HTML
<!-- 惡意網站/論壇文章內嵌入的隱藏圖片 -->
<img src="https://bank.com/api/transfer?to_account=attacker123&amount=10000" width="0" height="0" />

瀏覽器載入文章看到<img>標籤,便自動對bank.com發起 GET 請求。因為小明的登入 Session 尚未過期,瀏覽器自動帶上了小明在 bank.com 的 Cookie。銀行伺服器收到請求後無法區分這是「圖片載入」還是「小明點擊」,直接執行轉帳,小明的 10,000 元瞬間轉給了攻擊者。

POST 型態 CSRF 攻擊

安全的網站會將狀態變更操作限制為 POST / PUT / DELETE 請求,但這無法單靠 HTTP Method(POST / PUT / DELETE) 阻斷CSRF。

攻擊者可以在自己控制的惡意網頁上放置一個隱藏的 HTML <form> 表單,並將action指向目標網站的 API。透過 JavaScript 在頁面載入時自動執行 .submit(),同樣能在背景強制發送POST請求並帶上 Cookie。

日常範例:
小華登入了知名社群網站 social.com。隨後他在 LINE 群組收到一封來自「免費領取遊戲虛寶」的釣魚連結,點開後是一個看似安全的遊戲廣告頁面。

這個廣告頁面的背景藏有以下程式碼:

HTML
<!-- 惡意網站上的隱藏表單 -->
<form id="csrfForm" action="https://social.com/api/user/email" method="POST">
  <input type="hidden" name="email" value="attacker@evil.com" />
</form>

<script>
  // 頁面載入完成後,自動在背景提交表單
  window.onload = function() {
    document.getElementById('csrfForm').submit();
  };
</script>

小華點進網頁不到 0.5 秒,JavaScript 就自動將隱藏表單提交給 social.com。瀏覽器自動補上小華在 social.com 的身份 Cookie。之後,社群網站接到 POST 請求,將小華帳號綁定的 Email修改為attacker@evil.com。隨後攻擊者只要使用「忘記密碼」功能,就能順利奪取小華的社群帳號。

實務情境

情境:使用者變更密碼 API
僅依賴 Cookie 驗證,無 CSRF 防護

Python
# Python / Flask 不安全範例
@app.route("/user/change-password", methods=["POST"])
def change_password():
    # 僅靠 Session/Cookie 驗證身分,極易遭受 CSRF 攻擊
    user_id = session.get("user_id")
    if not user_id:
        return "Unauthorized", 401
    
    new_password = request.form.get("password")
    update_password(user_id, new_password)
    return "Password updated successfully"

使用 Anti-CSRF Token 防護

Python
# Python / Flask 安全範例(檢查 CSRF Token)
@app.route("/user/change-password", methods=["POST"])
def change_password():
    user_id = session.get("user_id")
    if not user_id:
        return "Unauthorized", 401
    
    # 驗證前端提交的 Token 是否與 Session 中儲存的一致
    user_csrf_token = request.form.get("csrf_token")
    if not user_csrf_token or user_csrf_token != session.get("csrf_token"):
        return "Invalid CSRF Token", 403
        
    new_password = request.form.get("password")
    update_password(user_id, new_password)
    return "Password updated successfully"

防禦 CSRF 的三道防線

1. Anti-CSRF Token(Synchronizer Token Pattern)
伺服器為每個 Session 或每個請求產生一個隨機、不可預測的加密 Token,並儲存在 Session 中。當前端發起 POST/PUT/DELETE 請求時,必須在表單欄位或 HTTP Header(如 X-CSRF-Token)中帶上此 Token。

跨站的惡意網站(AttackerSite)雖然可以發起請求並讓瀏覽器自動帶上 Cookie,但因為惡意網站無法讀取目標網站的Token,因此無法構造出含有正確 Token 的請求。

2. Cookie SameSite 屬性
瀏覽器引入了 SameSite 屬性,可以直接從瀏覽器底層阻斷跨站 Cookie 的發送:

HTTP
Set-Cookie: session_id=xyz123; Secure; HttpOnly; SameSite=Lax

SameSite=Strict:最嚴格。只要是跨站請求(從其他網站連過來),完全不發送該 Cookie。

SameSite=Lax(現代主流瀏覽器預設值):允許部分安全的跨站導向(如點擊一般超連結 <a> 的 GET 請求),但阻斷所有跨站 POST 表單、<img>、<iframe> 或 AJAX / Fetch 請求帶上 Cookie。

SameSite=None:允許跨站發送 Cookie,但必須開啟 Secure。

3. 重新身分驗證與 Header 檢查
操作二次驗證: 針對轉帳、修改密碼、修改綁定手機等敏感操作,要求使用者輸入舊密碼、簡訊驗證碼 (OTP) 或圖形驗證碼。檢查 HTTP Header: 在後端校驗 Origin 與 Referer Header,確保請求確實來自企業自身的官方網域。


上一篇
Day 19:Web 漏洞––跨站腳本攻擊(XSS)與 Cookie 劫持風險
下一篇
Day 21:Web 漏洞–敏感資訊洩漏
系列文
新手開發者的資安第一課:30 天搞懂 OWASP Top 10 與常見漏洞 共 26 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言