解析如何防止XSS跨站腳本攻擊
這些規(guī)則適用于所有不同類別的XSS跨站腳本攻擊,可以通過在服務(wù)端執(zhí)行適當(dāng)?shù)慕獯a來定位映射的XSS以及存儲的XSS,由于XSS也存在很多特殊情況,因此強(qiáng)烈推薦使用解碼庫。另外,基于XSS的DOM也可以通過將這些規(guī)則運(yùn)用在客戶端的不可信數(shù)據(jù)上來定位。
不可信數(shù)據(jù)
不可信數(shù)據(jù)通常是來自HTTP請求的數(shù)據(jù),以URL參數(shù)、表單字段、標(biāo)頭或者Cookie的形式。不過從安全角度來看,來自數(shù)據(jù)庫、網(wǎng)絡(luò)服務(wù)器和其他來源的數(shù)據(jù)往往也是不可信的,也就是說,這些數(shù)據(jù)可能沒有完全通過驗(yàn)證。
應(yīng)該始終對不可信數(shù)據(jù)保持警惕,將其視為包含攻擊,這意味著在發(fā)送不可信數(shù)據(jù)之前,應(yīng)該采取措施確定沒有攻擊再發(fā)送。由于應(yīng)用程序之間的關(guān)聯(lián)不斷深化,下游直譯程序執(zhí)行的攻擊可以迅速蔓延。
傳統(tǒng)上來看,輸入驗(yàn)證是處理不可信數(shù)據(jù)的最好辦法,然而,輸入驗(yàn)證法并不是注入式攻擊的最佳解決方案。首先,輸入驗(yàn)證通常是在獲取數(shù)據(jù)時(shí)開始執(zhí)行的,而此時(shí)并不知道目的地所在。這也意味著我們并不知道在目標(biāo)直譯程序中哪些字符是重要的。其次,可能更加重要的是,應(yīng)用程序必須允許潛在危害的字符進(jìn)入,例如,是不是僅僅因?yàn)镾QL認(rèn)為Mr. O'Malley名字包含特殊字符他就不能在數(shù)據(jù)庫中注冊呢?
雖然輸入驗(yàn)證很重要,但這始終不是解決注入攻擊的完整解決方案,最好將輸入攻擊作為縱深防御措施,而將escaping作為首要防線。#p#
解碼(又稱為Output Encoding)
“Escaping”解碼技術(shù)主要用于確保字符作為數(shù)據(jù)處理,而不是作為與直譯程序的解析器相關(guān)的字符。有很多不同類型的解碼,有時(shí)候也被成為輸出“解碼”。有些技術(shù)定義特殊的“escape”字符,而其他技術(shù)則包含涉及若干字符的更復(fù)雜的語法。
不要將輸出解碼與Unicode字符編碼的概念弄混淆了,后者涉及映射Unicode字符到位序列。這種級別的編碼通常是自動(dòng)解碼,并不能緩解攻擊。但是,如果沒有正確理解服務(wù)器和瀏覽器間的目標(biāo)字符集,有可能導(dǎo)致與非目標(biāo)字符產(chǎn)生通信,從而招致跨站XSS腳本攻擊。這也正是為所有通信指定Unicode字符編碼(字符集)(如UTF-8等)的重要所在。
Escaping是重要的工具,能夠確保不可信數(shù)據(jù)不能被用來傳遞注入攻擊。這樣做并不會對解碼數(shù)據(jù)造成影響,仍將正確呈現(xiàn)在瀏覽器中,解碼只能阻止運(yùn)行中發(fā)生的攻擊。
注入攻擊理論
注入攻擊是這樣一種攻擊方式,它主要涉及破壞數(shù)據(jù)結(jié)構(gòu)并通過使用特殊字符(直譯程序正在使用的重要數(shù)據(jù))轉(zhuǎn)換為代碼結(jié)構(gòu)。XSS是一種注入攻擊形式,瀏覽器作為直譯程序,攻擊被隱藏在HTML文件中。HTML一直都是代碼和數(shù)據(jù)最差的mashup,因?yàn)镠TML有很多可能的地方放置代碼以及很多不同的有效編碼。HTML是很復(fù)雜的,因?yàn)樗粌H是層次結(jié)構(gòu)的,而且還包含很多不同的解析器(XML、HTML、JavaScript、VBScript、CSS、URL等)。
要想真正明白注入攻擊與XSS的關(guān)系,必須認(rèn)真考慮HTML DOM的層次結(jié)構(gòu)中的注入攻擊。在HTML文件的某個(gè)位置(即開發(fā)者允許不可信數(shù)據(jù)列入DOM的位置)插入數(shù)據(jù),主要有兩種注入代碼的方式:#p#
Injecting UP,上行注入
最常見的方式是關(guān)閉現(xiàn)有的context并開始一個(gè)新的代碼context,例如,當(dāng)你關(guān)閉HTML屬性時(shí)使用">并開始新的<SCRIPT>標(biāo)簽。這種攻擊將關(guān)閉原始context(層次結(jié)構(gòu)的上層部分),然后開始新的標(biāo)簽允許腳本代碼執(zhí)行。記住,當(dāng)你試圖破壞現(xiàn)有context時(shí)你可以跳過很多層次結(jié)構(gòu)的上層部分,例如</SCRIPT>可以終止腳本塊,即使該腳本塊被注入腳本內(nèi)方法調(diào)用內(nèi)的引用字符,這是因?yàn)镠TML解析器在JavaScript解析器之前運(yùn)行。
Injecting DOWN,下行注入
另一種不太常見的執(zhí)行XSS注入的方式就是,在不關(guān)閉當(dāng)前context的情況下,引入一個(gè)subcontext。例如,將<IMG src="...">改為<IMG src="javascript:alert(1)">,并不需要躲開HTML屬性context,相反只需要引入允許在src屬性內(nèi)寫腳本的context即可。另一個(gè)例子就是CSS屬性中的expression()功能,雖然你可能無法躲開引用CSS屬性來進(jìn)行上行注入,你可以采用x ss:expression(document.write(document.cookie))且無需離開現(xiàn)有context。
同樣也有可能直接在現(xiàn)有context內(nèi)進(jìn)行注入,例如,可以采用不可信的輸入并把它直接放入JavaScript context。這種方式比你想象的更加常用,但是根本不可能利用escaping(或者任何其他方式)保障安全。從本質(zhì)上講,如果這樣做,你的應(yīng)用程序只會成為攻擊者將惡意代碼植入瀏覽器的渠道。
本文介紹的規(guī)則旨在防止上行和下行XSS注入攻擊。防止上行注入攻擊,你必須避免那些允許你關(guān)閉現(xiàn)有context開始新context的字符;而防止攻擊跳躍DOM層次級別,你必須避免所有可能關(guān)閉context的字符;下行注入攻擊,你必須避免任何可以用來在現(xiàn)有context內(nèi)引入新的sub-context的字符。
積極XSS防御模式
本文把HTML頁面當(dāng)作一個(gè)模板,模板上有很多插槽,開發(fā)者允許在這些插槽處放置不可信數(shù)據(jù)。在其他地方放置不可信數(shù)據(jù)是不允許的,這是“白名單”模式,否認(rèn)所有不允許的事情。
根據(jù)瀏覽器解析HTML的方式的不同,每種不同類型的插槽都有不同的安全規(guī)則。當(dāng)你在這些插槽處放置不可信數(shù)據(jù)時(shí),必須采取某些措施以確保數(shù)據(jù)不會“逃離”相應(yīng)插槽并闖入允許代碼執(zhí)行的context。從某種意義上說,這種方法將HTML文檔當(dāng)作參數(shù)化的數(shù)據(jù)庫查詢,數(shù)據(jù)被保存在具體文職并與escaping代碼context相分離。#p#
本文列出了最常見的插槽位置和安全放置數(shù)據(jù)的規(guī)則,基于各種不同的要求、已知的XSS載體和對流行瀏覽器的大量手動(dòng)測試,我們保證本文提出的規(guī)則都是安全的。
定義好插槽位置,開發(fā)者們在放置任何數(shù)據(jù)前,都應(yīng)該仔細(xì)分析以確保安全性。瀏覽器解析是非常棘手的,因?yàn)楹芏嗫雌饋頍o關(guān)緊要的字符可能起著重要作用。
為什么不能對所有不可信數(shù)據(jù)進(jìn)行HTML實(shí)體編碼?
可以對放入HTML文檔正文的不可行數(shù)據(jù)進(jìn)行HTML實(shí)體編碼,如<div>標(biāo)簽內(nèi)。也可以對進(jìn)入屬性的不可行數(shù)據(jù)進(jìn)行實(shí)體編碼,尤其是當(dāng)屬性中使用引用符號時(shí)。但是HTML實(shí)體編碼并不總是有效,例如將不可信數(shù)據(jù)放入<script>標(biāo)簽、事件處理器(如onmouseover) 、CSS內(nèi)部或URL內(nèi)等。即使你在每個(gè)位置都使用HTML實(shí)體編碼的方法,仍然不能抵御跨站腳本攻擊。對于放入不可信數(shù)據(jù)的HTML文檔部分,必須使用escape語法,這也是下面即將討論的問題。
你需要一個(gè)安全編碼庫
編寫編碼器并不是非常難,不過也有不少隱藏的陷阱。例如,你可能會使用一下解碼捷徑(JavaScsript 中的"),但是,這些很容易被瀏覽器中的嵌套解析器誤解,應(yīng)該使用一個(gè)安全的專門解碼庫以確保這些規(guī)則能夠正確執(zhí)行。
XSS防御規(guī)則
下列規(guī)則旨在防止所有發(fā)生在應(yīng)用程序的XSS攻擊,雖然這些規(guī)則不允許任意向HTML文檔放入不可信數(shù)據(jù),不過基本上也涵蓋了絕大多數(shù)常見的情況。你不需要采用所有規(guī)則,很多企業(yè)可能會發(fā)現(xiàn)第一條和第二條就已經(jīng)足以滿足需求了。請根據(jù)自己的需求選擇規(guī)則。#p#
No.1 – 不要在允許位置插入不可信數(shù)據(jù)
第一條規(guī)則就是拒絕所有數(shù)據(jù),不要將不可信數(shù)據(jù)放入HTML文檔,除非是下列定義的插槽。這樣做的理由是在理列有解碼規(guī)則的HTML中有很多奇怪的context,讓事情變得很復(fù)雜,因此沒有理由將不可信數(shù)據(jù)放在這些context中。
<script>...NEVERPUTUNTRUSTEDDATAHERE...</script> directlyinascript
<!--...NEVERPUTUNTRUSTEDDATAHERE...--> insideanHTMLcomment
<div...NEVERPUTUNTRUSTEDDATAHERE...=test/> inanattributename
<...NEVERPUTUNTRUSTEDDATAHERE...href="/test"/> inatagname
更重要的是,不要接受來自不可信任來源的JavaScript代碼然后運(yùn)行,例如,名為“callback”的參數(shù)就包含JavaScript代碼段,沒有解碼能夠解決。
No.2 – 在向HTML元素內(nèi)容插入不可信數(shù)據(jù)前對HTML解碼
這條規(guī)則適用于當(dāng)你想把不可信數(shù)據(jù)直接插入HTML正文某處時(shí),這包括內(nèi)部正常標(biāo)簽(div、p、b、td等)。大多數(shù)網(wǎng)站框架都有HTML解碼的方法且能夠躲開下列字符。但是,這對于其他HTML context是遠(yuǎn)遠(yuǎn)不夠的,你需要部署其他規(guī)則。
<body>...ESCAPEUNTRUSTEDDATABEFOREPUTTINGHERE... </body>
<div>...ESCAPEUNTRUSTEDDATABEFOREPUTTINGHERE...</div>
以及其他的HTML常用元素
使用HTML實(shí)體解碼躲開下列字符以避免切換到任何執(zhí)行內(nèi)容,如腳本、樣式或者事件處理程序。在這種規(guī)格中推薦使用十六進(jìn)制實(shí)體,除了XML中5個(gè)重要字符(&、<、 >、 "、 ')外,還加入了斜線符,以幫助結(jié)束HTML實(shí)體。
&-->&
<--><
>-->>
"-->"
'-->''isnotrecommended
/-->/forwardslashisincludedasithelpsendanHTMLentity
ESAPI參考實(shí)施
Stringsafe=ESAPI.encoder().encodeForHTML(request.getParameter("input"));
#p#
No.3 – 在向HTML常見屬性插入不可信數(shù)據(jù)前進(jìn)行屬性解碼
這條規(guī)則是將不可信數(shù)據(jù)轉(zhuǎn)化為典型屬性值(如寬度、名稱、值等),這不能用于復(fù)雜屬性(如href、src、style或者其他事件處理程序)。這是及其重要的規(guī)則,事件處理器屬性(為HTML JavaScript Data Values)必須遵守該規(guī)則。
<divattr=...ESCAPEUNTRUSTEDDATABEFOREPUTTINGHERE...>content</div> insideUNquotedattribute
<divattr='...ESCAPEUNTRUSTEDDATABEFOREPUTTINGHERE...'>content</div> insidesinglequotedattribute
<divattr="...ESCAPEUNTRUSTEDDATABEFOREPUTTINGHERE...">content</div> insidedoublequotedattribute
除了字母數(shù)字字符外,使用小于256的ASCII值&#xHH格式(或者命名的實(shí)體)對所有數(shù)據(jù)進(jìn)行解碼以防止切換屬性。這條規(guī)則應(yīng)用廣泛的原因是因?yàn)殚_發(fā)者常常讓屬性保持未引用,正確引用的屬性只能使用相應(yīng)的引用進(jìn)行解碼。未引用屬性可以被很多字符破壞,包括[space] % * + , - / ; < = > ^ 和 |。
ESAPI參考實(shí)施
String safe = ESAPI.encoder().encodeForHTMLAttribute( request.getParameter( "input" ) );
No.4 – 在向HTML JavaScript Data Values插入不可信數(shù)據(jù)前,進(jìn)行JavaScript解碼
這條規(guī)則涉及在不同HTML元素上制定的JavaScript事件處理器。向這些事件處理器放置不可信數(shù)據(jù)的唯一安全位置就是“data value”。在這些小代碼塊放置不可信數(shù)據(jù)是相當(dāng)危險(xiǎn)的,因?yàn)楹苋菀浊袚Q到執(zhí)行環(huán)境,因此請小心使用。
<script>alert('...ESCAPEUNTRUSTEDDATABEFOREPUTTINGHERE...')</script> insideaquotedstring
<script>x=...ESCAPEUNTRUSTEDDATABEFOREPUTTINGHERE...</script> onesideofanexpression
<divonmouseover=...ESCAPEUNTRUSTEDDATABEFOREPUTTINGHERE...</div> insideUNquotedeventhandler
<divonmouseover='...ESCAPEUNTRUSTEDDATABEFOREPUTTINGHERE...'</div> insidequotedeventhandler
<divonmouseover="...ESCAPEUNTRUSTEDDATABEFOREPUTTINGHERE..."</div> insidequotedeventhandler
除了字母數(shù)字字符外,使用小于256的ASCII值xHH格式 對所有數(shù)據(jù)進(jìn)行解碼以防止將數(shù)據(jù)值切換至腳本內(nèi)容或者另一屬性。不要使用任何解碼捷徑(如" )因?yàn)橐米址赡鼙幌冗\(yùn)行的HTML屬性解析器相匹配。如果事件處理器被引用,則需要相應(yīng)的引用來解碼。這條規(guī)則的廣泛應(yīng)用是因?yàn)殚_發(fā)者經(jīng)常讓事件處理器保持未引用。正確引用屬性只能使用相應(yīng)的引用來解碼,未引用屬性可以使用任何字符(包括[space] % * + , - / ; < = > ^ 和|)解碼。同時(shí),由于HTML解析器比JavaScript解析器先運(yùn)行,關(guān)閉標(biāo)簽?zāi)軌蜿P(guān)閉腳本塊,即使腳本塊位于引用字符串中。
ESAPI參考實(shí)施
Stringsafe=ESAPI.encoder().encodeForJavaScript(request.getParameter("input"));
#p#
No.5 – 在向HTML 樣式屬性值插入不可信數(shù)居前,進(jìn)行CSS解碼
當(dāng)你想將不可信數(shù)據(jù)放入樣式表或者樣式標(biāo)簽時(shí),可以用此規(guī)則。CSS是很強(qiáng)大的,可以用于許多攻擊。因此,只能在屬性值中使用不可信數(shù)據(jù)而不能在其他樣式數(shù)據(jù)中使用。不能將不可信數(shù)據(jù)放入復(fù)雜的屬性(如url,、behavior、和custom (-moz-binding))。同樣,不能將不可信數(shù)據(jù)放入允許JavaScript的IE的expression屬性值。
<style>selector{property:...ESCAPEUNTRUSTEDDATABEFOREPUTTINGHERE...;}</style> propertyvalue
<spanstyle=property:...ESCAPEUNTRUSTEDDATABEFOREPUTTINGHERE...;>text</style> propertyvalue
除了字母數(shù)字字符外,使用小于256的ASCII值HH格式對所有數(shù)據(jù)進(jìn)行解碼。不要使用任何解碼捷徑(如" )因?yàn)橐米址赡鼙幌冗\(yùn)行的HTML屬性解析器相匹配,防止將數(shù)據(jù)值切換至腳本內(nèi)容或者另一屬性。同時(shí)防止切換至expression或者其他允許腳本的屬性值。如果屬性被引用,將需要相應(yīng)的引用進(jìn)行解碼,所有的屬性都應(yīng)該被引用。未引用屬性可以使用任何字符(包括[space] % * + , - / ; < = > ^ 和|)解碼。同時(shí),由于HTML解析器比JavaScript解析器先運(yùn)行,</script>標(biāo)簽?zāi)軌蜿P(guān)閉腳本塊,即使腳本塊位于引用字符串中。
ESAPI參考實(shí)施
Stringsafe=ESAPI.encoder().encodeForCSS(request.getParameter("input"));
No.6- 在向HTML URL屬性插入不可信數(shù)據(jù)前,進(jìn)行URL解碼
當(dāng)你想將不可信數(shù)據(jù)放入鏈接到其他位置的link中時(shí)需要運(yùn)用此規(guī)則。這包括href和src屬性。還有很多其他位置屬性,不過我們建議不要在這些屬性中使用不可信數(shù)據(jù)。需要注意的是在javascript中使用不可信數(shù)據(jù)的問題,不過可以使用上述的HTML JavaScript Data Value規(guī)則。
<ahref=http://...ESCAPEUNTRUSTEDDATABEFOREPUTTINGHERE...>link</a> anormallink
<imgsrc='http://...ESCAPEUNTRUSTEDDATABEFOREPUTTINGHERE...'/> animagesource
<scriptsrc="http://...ESCAPEUNTRUSTEDDATABEFOREPUTTINGHERE..."/> ascriptsource
除了字母數(shù)字字符外,使用小于256的ASCII值%HH 解碼格式對所有數(shù)據(jù)進(jìn)行解碼。在數(shù)據(jù)中保護(hù)不可信數(shù)據(jù):URL不能夠被允許,因?yàn)闆]有好方法來通過解碼來切換URL以避免攻擊。所有的屬性都應(yīng)該被引用。未引用屬性可以使用任何字符(包括[space] % * + , - / ; < = > ^ 和|)解碼。 請注意實(shí)體編碼在這方面是沒用的。
ESAPI參考實(shí)施
Stringsafe=ESAPI.encoder().encodeForURL(request.getParameter("input"));