Cross-Site Scripting
Cross-site scripting 的核心概念是攻擊者的程式碼在目標頁面的來源之下執行,因而取得那個來源的全部能力:讀寫頁面內容、讀取同來源的儲存、以使用者身分發出請求並讀到回應。取得這些能力的原因是 same-origin policy 以來源為單位授予權限,而這段程式碼在瀏覽器看來就屬於這個來源。注入點通常是使用者提供的資料被當成標記或程式碼輸出,而非當成文字輸出。
概念位置
它是一批防護的共同前提失效點。任何以「攻擊者讀不到某個值」為基礎的機制在它之下一併失效——頁面帶出的隨機防偽值讀得到,放在瀏覽器本地儲存或程式變數裡的憑證讀得到,畫面上已經呈現的資料也讀得到。判讀憑證住址時要把這一層當成分界:標成 HttpOnly 的 cookie 在這個攻擊面下讀不出來,而為了寫進請求標頭而放在 JavaScript 可及位置的憑證讀得出來,兩種帶法的取捨見 7.36 憑證在請求中怎麼帶。
它與 cross-site request forgery 的關係是能力包含:這裡做得到那邊的全部動作,還多了讀取回應與竊取憑證。差別在門檻與後果的持續性——那邊只需要受害者載入一個外部頁面,動作限於受害者的瀏覽器與會話期間;這裡要先有注入點,得手之後憑證可以離開受害者的裝置,在任何地方使用到過期或被撤銷為止。
可觀察訊號與例子
使用者提供的資料進入頁面時,接收它的位置決定要對它做什麼——落在標記裡與落在純文字裡不是同一件事,而同一段資料在不同位置需要的處理也不相同:放進 HTML 內文、放進屬性值、放進網址、放進行內腳本各有自己的跳脫規則,用單一套規則處理全部位置時,未涵蓋的那些位置留下缺口。
另一個訊號是程式碼把字串直接交給會解析標記的介面。這類介面把傳入的內容當成結構而非文字,資料與標記的邊界因此消失在呼叫點上,而呼叫點與資料來源之間往往隔了好幾層。
第三方腳本是自己這一側看不到的來源。頁面上每掛一個外部腳本,這個來源的能力就多交出一份,而那份程式碼的內容由對方的發佈流程決定、可以隨時改變。分析工具、客服元件、廣告與標籤管理器都屬於這一類,暴露面盤點要把它們逐一列出。
設計責任
自訂的過濾在這裡站不住:以黑名單擋特定字串的做法會被編碼變形與新出現的語法形態繞過,而清單永遠落後一步。按落點編碼處理的是「這個位置的解析規則是什麼」,涵蓋範圍由規則本身界定而非由清單長度界定,因此要交給框架或函式庫的既有機制。
內容安全政策(Content Security Policy,CSP)承擔的是第二層:注入成立之後限制那段程式碼能載入什麼、能往哪裡送資料。它降低得手之後的後果,前提是政策本身沒有寬鬆到失去意義。
憑證的住址要跟這個攻擊面一起決定。長效憑證放進標成 HttpOnly 的 cookie 能把它移出射程,而系統若需要把憑證寫進請求標頭,代價是那一份跟著落在射程內——縮短它的有效期與限制它的範圍是這一種帶法能做的限縮,撤銷粒度的取捨見 token revocation。