Ecma TC39 規範提議 - Literals in script
動機
HTML 的 DOM (文檔對象模型)提供了一系列的機制將任意字元串轉換為標籤(.innerHTML = ...)或代碼(scriptEl.innerText =..., el.onclick = ..., etc)。每一種機制都能觸發 XSS 攻擊,攻擊者可以將代碼插入到我們不期望的上下文中,導致基於 DOM 的 XSS 攻擊,那正是我們一直試圖避免的。
一種解決此問題的好方法(谷歌也在使用)是移除基於字元串的 API ,而是使用強類型的介面,它會在應用程序進入時進行強制的檢測和過濾。如果開發人員將自己置身於這種體系之中,他們將不必深入審查每種 XSS 接收器的用法,從而有更多的精力去關注生成類型對象的代碼,如: SafeHtml 或 SafeUrl 。可信類型的策略就是為了做到這點。
在大多數情況下,這種機制可以完全可以在 DOM 和 WebIDL(web介面定義語言) 中實現,不需要觸及底層的語言。然而,谷歌內部的安全審查員發現,如果沒有語言層面的 hook ,很難在網路上普及。
Closure 編譯器可以分辨作為文本嵌入到程序中的字元串,以及作為某些操作(方法調用,屬性獲取等)的結果的字元串。它強制約束類 goog.string.Const ,從而保證對象只能由文本創建,這在谷歌的生產代碼中是相當常見的,也被證明是安全的(在沒有直接注入腳本的情況下攻擊者是不能控制文本的值的)。
也就是說,開發人員可以先創建一個 goog.string.Const,接著生成一個 SafeUrl 對象,然後在工廠方法中使用:
const url = goog.string.Const.from("https://safe.test/totally/safe/url");nreturn SafeUrl.fromConstant(url);n
如果能在平台上應用這個斷言,而不是完全依賴基於時間的檢測,那將是十分有用的。客戶端的 斷言能夠提高檢測的穩健性,進行深度的防禦,從而建立一個安全的網路,尤其是代碼在程序發布後不經編譯偷偷溜走的情況下。
提議
我沒有足夠的語言背景來給出可靠的建議。相反,我有上面的用例,以及我想從平台方面看到的模糊草圖。我很樂意從真正了解這方面語言特性的人那裡得到反饋。
考慮到這一點:關於可能合理也可能不合理的猜測如下!
文本字元串類型
一種方法是使用一種與文本字元串相對應的新的字元串類型,WebIDL 可以在其之上建立一種新的字元串類型,以便在執行類型檢查時區分文本類型。也就是說,今天我們可以生成如下的WebIDL 代碼片段:
interface TrustedHTML {n static TrustedHTML escape(DOMString html);n}; n
這個介面可以這樣調用:TrustedHTML.escape("Literalstring!"),並且 調用TrustedHTML.escape(formField.value) 可以根據需要轉義字元串。
理想情況下我們可以添加如下內容:
interface TrustedHTML { n static TrustedHTML createFromLiteral(LiteralString html);n}; n
這個介面可以這樣調用:TrustedHTML.createFromLiteral("Literalstring!"),但調用TrustedHTML.createFromLiteral(formField.value) 會拋出一個 TypeError。
理想情況下,我們還可以將文字與其他文字(在代碼庫中常見的線寬限制)結合使用。也就是說,我們會允許以下內容:
TrustedHTML.createFromLiteral("A marginally longer literal string that seems to keep going " +n "and going and going and going. Wow, what a long string.");n
同樣理想的情況下,我們會跟蹤字元串的字面意思。也就是說,我們會允許以下內容:
let a = "Literal string!";nTrustedHTML.createFromLiteral(a);nnlet b = "Another literal!";nTrustedHTML.createFromLiteral(a + b);nnlet c = a + b;nTrustedHTML.createFromLiteral(c);n
限制標籤的功能
另一方法是在標籤模板字元串上建立類似的體系,也就是說,可以想像如下代碼:
function trustedUrlizer(templateString) {n return TrustedUrl.unsafelyCreate(templateString);n}nnreturn trustedUrlizer`https://safe.test/totally/safe/url`;n
如果我們也有一種機制使標籤函數僅接收模板字元串,這將是很好的,也就是說,trustedUrlizer(formField.value) 將會執行失敗 或許還會拋出類型錯誤。
Daniel Ehrenberg 在 mikewest/tc39-proposal-literals#2 中也稍微提了一下:
我想像 API 的表層是這樣的:模板標籤的第一個參數有個額外的屬性:文本,它表明了傳入模板的參數是否是個文本類型。我們可以通過將其表示為一個內部插槽,並將其文本作為一個自己的getter來使其變為不可偽造,這可以防止攻擊者使用任何具有literal: true屬性的舊對象調用您的模板:
// Un-monkey-patchable way to get the getternlet getLiteral = Object.getOwnPropertyDescriptor((_ => _)``, "literal").get; nnfunction literalString(strings, ...keys) {n if (!getLiteral.call(strings)) throw new Error();n return String.raw(strings, ...keys);n}n
這個 literalString 模板標籤就像 String.raw ,但是如果不傳入文本的話就會拋出異常。它輸出一個文本類型的字元串。這或許是值得推崇的方式來調用需要文本字元串的方法。因為strings對象(及其內部raw對象)被凍結,所以不能破壞文本字元串內容。
為此,我只補充一點,我們希望確保文本在 WebIDL 中被使用的,以便我們對內置標籤函數進行強制的檢測,但這好像超出了我們今天討論的範圍。
常見問題
- 不能只用 Closure 或其它基於時間的檢查來做這件事情嗎?
是的,實際上谷歌正在內部這樣做。參與構建過程的人員希望通過在已經完成的構建時間分析的基礎上對客戶端檢查進行分層來使其更健壯。上面提到的
TrustedTypes
也會受益於fromLiteral
構建機制,正如谷歌代碼庫中的情況表明,這個機制既安全又可用。 - 我們需要在多大程度上追蹤文本?一個很好的問題!我很欣慰! 有一件事我們不需要跟蹤,就像使用文字作為對象的關鍵。也就是說,我非常高興把{「a」:「value」}和{a:「value」}和obj [「a」] =「value」作為鍵值相同的值。
現有技術
- 被Closure編譯器支持的
goog.string.Const
。 - GWT還提供了
SafeHtml.fromSafeConstant進行
編譯時檢查。 - https://github.com/google/safe-html-types/blob/master/doc/safehtml-types.md講述了在C ++(
TrustedResourceUrl::FromConstant
)中類似概念的用法。
原文:mikewest/tc39-proposal-literals
推薦閱讀:
※白帽子挖洞—跨站腳本攻擊(XSS)篇
※淺析圖片XSS中的哪些技術問題
※好書一起讀(147):XSS,CSRF,SQL注入
※「每日一題」XSS 是什麼?
※了解黑客:這是黑客思維