前端資安學習筆記:用互動動畫看懂常見攻擊與防禦
前端資安學習筆記:用互動動畫看懂常見攻擊與防禦
前端工程師很常覺得「資安是後端的事」。但實際上,大部分攻擊最後都發生在瀏覽器裡:惡意腳本在使用者的分頁執行、Cookie 被瀏覽器自動帶出去、金鑰被打包進 bundle 送到每個人手上。
這篇是我整理前端資安概念時的學習筆記。我把最常見的資安問題各做成一個互動動畫,每個動畫都用同一個結構:
- 五個步驟:一步步看攻擊怎麼發生,每一步附上「駭客觀點」,說明攻擊者當下在想什麼。
- 根本原因:為什麼這件事會發生。
- 正確做法:實際可以用的修正程式碼。
先從這個像素小冒險開始:按下「開始冒險」會從第一關一路播到最後,也可以點下方的關卡直接跳過去。每一關的完整版動畫,都能從對話框或各段落的連結打開。
怎麼玩這篇文章:每一題都有一個會自己播放的攻擊流程,看完後打開右上角的「🛡 開啟防禦」,再看一次它在哪一步被擋下。其中七題附了可以直接動手的實驗,最後還有檢查清單和小測驗。
先建立心智模型:四類問題
這些問題看起來很雜,但歸納起來只有四種根本原因。先記住這四類,遇到新的漏洞時也比較容易判斷它屬於哪一種。
| 類別 | 核心觀念 | 包含的主題 |
|---|---|---|
| A. 注入與執行 | 瀏覽器分不出「資料」和「程式碼」 | 01 XSS、03 Token 儲存、14 CSP |
| B. 瀏覽器的自動行為被濫用 | 瀏覽器會「好心」幫你帶 Cookie、嵌入頁面、跟著跳轉 | 02 CSRF、11 CORS、12 Clickjacking、13 Open Redirect |
| C. 信任邊界放錯地方 | 送到瀏覽器的東西都是公開的,前端的檢查都能被繞過 | 04 前端授權、07 硬編碼機密、08 Console Log、09 Source Map |
| D. 供應鏈與傳輸 | 你信任的程式碼,在送到使用者之前可能已經被換掉 | 05 供應鏈攻擊、06 CDN SRI、10 混合內容 |
一句話總結:永遠不要信任來自使用者端的任何東西,也不要把秘密交給使用者端。
A. 注入與執行
01 XSS 跨站腳本攻擊
留言板沒有過濾輸入,讓惡意程式碼在別人的瀏覽器裡執行。
根本原因:伺服器把使用者輸入原封不動輸出成 HTML,瀏覽器分不出這是「內容」還是「程式碼」。
正確做法
function escapeHtml(str) {
return str.replace(/[&<>"']/g, (c) => ({
'&': '&', '<': '<', '>': '>', '"': '"', "'": ''',
}[c]));
}
res.send('<div>' + escapeHtml(comment) + '</div>');
補充:
- XSS 有三種:儲存型(存進資料庫)、反射型(藏在網址參數裡)、DOM 型(前端 JS 自己把資料塞進
innerHTML)。 - React / Vue 的
{value}綁定預設會跳脫,危險的是dangerouslySetInnerHTML、v-html、innerHTML,以及<a href={userInput}>(javascript:開頭的網址照樣會執行)。 - 真的需要顯示使用者提供的 HTML(例如富文字編輯器),用 DOMPurify 這類函式庫消毒,不要自己寫正規表達式。
03 Token 儲存:localStorage vs httpOnly Cookie
同一段惡意腳本,遇到不同的儲存方式,結果完全不同。
完整動畫是左右對照:同樣一個 XSS,token 放在 localStorage 會被一行 localStorage.getItem('token') 直接讀走;放在 httpOnly Cookie 裡,JavaScript 根本讀不到。
根本原因:localStorage 對頁面上所有 JavaScript 完全開放,只要有一個 XSS 縫隙,token 就沒了。
正確做法
res.cookie('token', jwt, {
httpOnly: true, // JS 讀不到
secure: true, // 只走 HTTPS
sameSite: 'strict' // 不跟著跨站請求送出(同時防 CSRF)
});
補充:httpOnly 是降低損害,不是阻止 XSS。腳本雖然偷不走 token,但還是能在當下以使用者身分發請求。所以它要跟 XSS 防護、CSP 一起用。另外改用 Cookie 之後就要開始考慮 CSRF(見 02)。
14 CSP 內容安全政策
CSP 是防 XSS 最有效的第二道防線,但很多專案根本沒設定。
就算某個地方漏掉跳脫、讓惡意腳本混進頁面,CSP 也能告訴瀏覽器:「只執行我允許的腳本、只連線到我允許的網域」。沒有 CSP 的話,瀏覽器預設不會限制腳本能不能對外連線。
正確做法
Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-r4nd0mBase64'
補充:
nonce每次回應都要重新產生一組隨機值,並加在合法的<script nonce="...">上,攻擊者注入的腳本沒有 nonce,就不會執行。- 避免
'unsafe-inline'和'unsafe-eval',加了等於把門打開一半。 - 上線前先用
Content-Security-Policy-Report-Only觀察一段時間,確認不會擋到正常功能再正式啟用。 connect-src可以限制fetch能連到哪裡,就算腳本跑起來,也很難把資料送出去。
- 你的<script src="/app.js">