「在表單裡輸入的文字,卻在另一個人的畫面上作為指令碼執行起來」——這就是 XSS。本文從零講解其原理與可靠的防範方法(不會寫出攻擊步驟)。
三種類型
XSS 按「惡意字串經由何處被執行」分為三種。
| 類型 | 如何被植入 |
|---|---|
| 儲存型(Stored) | 被保存在貼文、個人資料等處,在所有瀏覽者的瀏覽器中執行(最危險) |
| 反射型(Reflected) | URL 參數等被原樣反射到頁面上,在點擊了連結的人那裡執行 |
| DOM 型(DOM-based) | 不經由伺服器,由瀏覽器端的 JS 危險地處理輸入而引發 |
為什麼危險(原理)
瀏覽器會把頁面中寫的 <script> 當作「正規程式碼」執行。當攻擊者的字串原樣作為 HTML 混入頁面時,瀏覽器無法把它和真正的程式碼區分開。
危害是「在那個頁面上能做的一切」。如果說 RCE 是伺服器端的最壞情況,那麼 XSS 就相當於用戶端(使用者)的最壞情況。
防禦:真正關鍵的是「輸出時跳脫」
XSS 的基本原則是用「輸出」而非「輸入」來防。在把值顯示到畫面上的那一刻,將其轉換為不會被當作 HTML 解析的形式。
輸出時跳脫(最重要)
把來自使用者的值顯示到畫面上時,將 < > & " ' 等轉換為 HTML 實體。要按輸出所處的位置(HTML 正文·屬性·JS·URL)採用各自正確的方式。
不要破壞框架的自動跳脫
React/Vue/各類範本預設會自動跳脫。只要避免 dangerouslySetInnerHTML 或 v-html 這類「直接插入生 HTML」的做法,就能防住大部分情況。
用 CSP(Content-Security-Policy)做多層防禦
萬一有遺漏,也能在瀏覽器端阻止未經許可的指令碼執行。透過限制行內指令碼等手段把危害降到最低。
保護 Cookie
給工作階段 Cookie 加上 HttpOnly,讓 JS 無法讀取。即便被竊取也能限制危害範圍。
本站的觀點:最大的防禦是「別自己開洞」
現代框架預設就傾向於安全一側。許多 XSS 正是在開發者 「想直接放入 HTML」而刻意關掉自動跳脫的那一刻產生的。如果確實要允許 HTML,就不要用自己寫的字串處理,而要經過久經驗證的 sanitize 工具。「為了方便而放棄安全」才是最大的風險。
接下來閱讀
FAQ
QXSS 會造成什麼後果?
攻擊者植入的指令碼會作為真實頁面的一部分在受害者的瀏覽器中執行。結果可能導致工作階段被竊取(冒充登入)、輸入被偷看、頁面被竄改、自動執行其他操作等。
QXSS 最主要的防禦是什麼?
是「輸出時跳脫」。把來自使用者的值顯示到頁面上時,將其轉換為不會被當作 HTML 解析的形式。React/Vue 等範本預設就會這樣做,所以不要自己破壞這套自動跳脫(避免使用 dangerouslySetInnerHTML 等)就是最有效的對策。此外再用 CSP 做多層防禦。
Q在輸入時做 sanitize 就夠了嗎?
往往不夠。關鍵在於「按輸出所處的上下文(HTML 正文·屬性·JS·URL)分別正確地 escape」。只靠輸入過濾,會因為搞錯上下文而出現遺漏。只有在確實要允許 HTML 時,才使用可信賴的 sanitize 工具。