天天看點

web前端安全基礎與攻防(續篇)

web前端安全基礎與攻防(續篇)

繼續前面的内容,由于轉發到一些論壇時,限制了每個文章中,添加圖檔連結的數量,而前篇包含的圖檔數量已經達到上限,是以開一個新帖繼續讨論。

還記得在前篇中有一段針對使用 IE 浏覽器使用者的攻擊載荷,如下所示 :

上面代碼中,使用 IE 實作的XHR(XMLHttpRequest)對象通路百度首頁并且通過javascript讀取傳回的資料(responseText),并且可以寫入目前頁面。

這是因為 IE 的同源政策沒有很好地對XMLHttpRequest進行限制,例如,上述代碼可能位于本地硬碟的一個HTML文本中(C:\Users\shayi\Desktop\XssPayloadTest.html)

我們已經在上一篇博文中看到,IE 預設允許XHR跨域加載并讀寫資源;對于其它浏覽器(FireFox 與 chrome)而言,本地檔案系統路徑與“百度首頁”是不同源的,是以它們會限制目前HTML文本所在源中的javascript讀寫從百度首頁傳回的資料,換言之,這兩個浏覽器的同源政策預設僅允許XHR加載,讀寫相同源中的資料,除非對浏覽器以及目标站點的web伺服器配置為啟用HTML5規範中引入的“跨域資源共享”(CORS)。

下面的示例代碼,通過使用非 IE 浏覽器支援的XHR對象,嘗試跨域加載資源并寫入至目前頁面DOM中的一個節點:

(引用自《白帽子講web安全一書》,略作修改)

這個在HTML文檔頭部(head元素内)引入的javascript定義了兩個函數: LoadXMLDoc()通過浏覽器支援的XHR類型來跨域發起HTTP GET請求并加載資源;

 state_Change()檢查對方web伺服器傳回的HTTP響應狀态碼,然後決定是讀取響應資料的内容并寫入目前頁面(狀态碼為200);還是給出伺服器端傳回的錯誤資訊(除200以外的其它狀态碼)。

在HTML文檔體(body元素内),通過實際調用LoadXMLDoc()來對看雪論壇首頁發起跨域請求(注意,目前“源”是本地檔案系統上的測試用HTML頁面),然後嘗試将對方傳回的“響應文本”(responseText)寫入目前頁面DOM的T1節點的内部HTML文本中,并且在文檔體中添加一個按鈕,使用者點選時将再次通過XHR對象,發起對我的51cto部落格頁面的跨域請求。

上面代碼在FireFox中的測試結果如下所示:

web前端安全基礎與攻防(續篇)

最後要指出一點,浏覽器一般通過URL中的協定,主機名與端口号來判斷一個頁面文檔所屬的域,而在一個新打開的頁面的URL中,隻要3者之一與前面那個頁面的URL不同,都會被浏覽器的同源政策認定為屬于不同域,進而阻止新開啟頁面上的腳本通過前者頁面的URL來發起跨域請求,而符合HTML5 CORS 規範的XHR 發起的跨域請求則不在此限,當然前提是需要經過目标域(即要“跨”的域)中的伺服器許可,如果目标域的web伺服器在傳回的 Access-Control-Allow-Origin 響應頭中指定的值與發起跨域XHR所屬的源始域相同,那麼浏覽器将允許這個XHR跨域請求。

為了更清楚說明XHR詢問是否可以CORS的場景,假設一個HTML頁面所屬域為

http://www.webSite.com(即該頁面通過這個URL加載),在該頁面中,有一段生成跨域HXR的  javascript 代碼如下:

xhr.open() 的第三個參數為 true,表明請求為異步發送,這是預設值,有些浏覽器為了性能上的考量,甚至拒絕将該參數設定為 false(同步),如果你這麼做了, Firebug 将會給出類似下面的提示:

web前端安全基礎與攻防(續篇)

遇到 xhr.send() 語句時,浏覽器會向 www.friendlySite.com  發起一個HTTP 請求,用于“證明”www.friendlySite.com 是否願意與 http://www.webSite.com 共享資源(通過後者打開的頁面文檔),請求頭如下:

以上HTTP請求(這個包含 CORS 請求頭的 HTTP 請求又稱為“Preflight”請求)中,注意 Origin 與 Accept-Control-Request-Method 這2個頭部,源始文檔所屬域為 www.webSite.com  ,是以 Origin 頭部就是這個值;源始文檔中的XHR 對象請求的方法為 PUT,是以 Accept-Control-Request-Method 頭部就是該值。還要注意浏覽器使用的是 HTTP OPTIONS 方法,該方法在最初的HTTP協定規範中,用于查詢web伺服器支援的 HTTP 方法類型,現在則演變為用于查詢對方的 CORS“許可政策”。

如果目标域 www.friendly.com  允許 CORS ,其傳回的HTTP 響應頭應該如下所示:

以上HTTP響應中,Access-Control-Allow-Origin 響應頭的值與發出跨域請求的XHR 對象所在頁面文檔所屬的域是同一個,表明目标域允許與 www.webSite.com

的 CORS ,如此一來,浏覽器将允許 www.webSite.com 域頁面中的XHR對象和後續的 javascript 代碼讀取,操縱和顯示來自 www.friendly.com 域傳回的資料,例如讀寫 xhr.responseText 的内容時,才不會發生錯誤。

另外,Access-Control-Max-Age 響應頭的含義為,該響應的有效期,以秒為機關,例如其值為 3600 時,浏覽器在一小時内對目标域的每次跨域通路,都不需要使用 Preflight 請求,這就減少在請求頭中增加多餘的頭部造成的用戶端與伺服器端處理負擔,以及鍊路帶寬的浪費(每次少傳輸幾十到上百位元組總是好的);但是一小時後,浏覽器必須再次發送 Preflight 請求至伺服器,以更新目标域的 CORS 許可政策,一般而言,目标域為了其自身資料的安全考量,Access-Control-Max-Age 的值不會設的太長,但是像上例中的10秒那麼短也不太實際,各位可以自行通路其它網際網路上的站點,通過工具來測試這個值。

Access-Control-Allow-Credentials 請求頭的值如果為 true,那麼 www.webSite.com 域頁面中的 XHR 對象以及 javascript 腳本代碼,将能夠讀寫

由目标域 www.friendly.com 設定的 cookie,HTTP 基本認證字串(base-64 格式的使用者名與密碼,即目标域賦予的使用者會話令牌),摘要認證字串(MD5 或其它摘要算法加密的使用者名與密碼),以及由目标域 www.friendly.com 設定的用戶端 SSL 證書(假設目标域是電子商務網站或網上銀行,需要驗證企業使用者的身份,就有可能用到用戶端 SSL 證書),以上這些證書和認證資訊,以及 cookie 形式的會話令牌,都将能夠被 www.webSite.com 域頁面中的 XHR 對象以及 javascript 腳本代碼讀寫通路;一般而言,如果目标域和源始域之間沒有很好的友情和信任關系,Access-Control-Allow-Credentials 響應頭的值應該為 false 會比較安全。 

(作為對比,前面的本地檔案系統上的測試HTML頁面中的 javascript 代碼無法将來自看雪以及51cto域的 xhr.responseText 寫入至測試頁面,就是因為浏覽器沒有收到來自看雪以及51cto域允許與“本地域”進行CORS的響應頭。)

前面提到,當沒有啟用 CORS 時,浏覽器的同源政策禁止 XHR 對象跨域讀寫資源;但是 iframe 标簽則不受浏覽器的同源政策限制,可以随意跨域加載資源,細節容後作介紹,這裡先來看看一個 iframe 标簽的效果:

上面文檔是為了相容早期不支援 iframe 标簽的浏覽器量身定做的:具體而言,不支援 iframe 的浏覽器在碰到這個标簽的時候,将無法跨域加載頁面資源,而是顯示标簽内部的資料,就本例而言,會提示使用者需要使用支援 iframe 的浏覽器才能正常顯示内容;相反,目前的浏覽器多數都支援 iframe 标簽了,使用它們打開這個文檔,就不會顯示提示資訊,以 FireFox 為例:

web前端安全基礎與攻防(續篇)
web前端安全基礎與攻防(續篇)
web前端安全基礎與攻防(續篇)
web前端安全基礎與攻防(續篇)
web前端安全基礎與攻防(續篇)

通常以大寫的 X 開頭的頭部(包括請求與響應頭)都是 HTTP 規範中的“擴充”頭部;

上圖中的 X-XSS-Protection 響應頭是用于告訴用戶端浏覽器開啟内置的 XSS 過濾機制(其值為 1 時,表示開啟),隻要浏覽器實作了标準中定義的 X-XSS-Protection 規範,那麼應該都能識别這個響應頭(同時還需要目标站點的 web 伺服器能夠意識到傳遞這個響應頭對于用戶端安全的重要性,作為對比,百度的 web 伺服器就不會傳遞這個響應頭)

X-XSS-Protection 起到的防禦效果是有限的,僅能阻止反射型 XSS,也就是當攻擊載荷出現在 URL 中送出至伺服器時,如果伺服器沒有過濾并且直接在響應中将攻擊載荷“反射”回用戶端,那麼得益于先前或本次響應設定的 X-XSS-Protection 頭部,浏覽器将檢查并過濾危害字元;X-XSS-Protection 無法防禦存儲型 XSS。

上面一系列圖檔中出現的 X-Frame-Options 已經介紹過,主要是用來防止攻擊者通過 iframe 跨域嵌入頁面内容,構造出點選劫持(ClickJacking)的僞裝 UI ,欺騙使用者點選。如果将 X-Frame-Options 頭部的值設為 DENY ,那麼任何頁面都不能通過 iframe 嵌入其響應體中的 HTML 文檔(如上所述,實際上設定了 X-Frame-Options 頭部的伺服器不會在響應體中跨域發送 HTML 文檔),即便是同源(相同域)的頁面也不行。

由此可見,google 的安全政策還是做的比較完善的,畢竟是世界領先的網際網路雲計算,雲存儲公司,以及資訊檢索服務提供商。

《有效阻止各種類型XSS的CSP(内容安全政策)》

以 facebook 伺服器為例,當浏覽器向其請求一個頁面文檔時,支援 CSP 的伺服器在攜帶該文檔的 HTTP 響應頭部會添加一個  content-security-policy 響應頭,支援 CSP 的浏覽器在解析,渲染該文檔之前,必須先讀取該頭部的值,以了解伺服器對該頁面資源設定的用戶端安全加載政策,這樣,即便頁面中包含攻擊載荷,也會因為該攻擊載荷沒有從 CSP 允許的域加載資源,而使攻擊失敗。另外,XSS 攻擊通常無法注入,修改響應頭部,是以該字段可以被用于正确判斷伺服器的本意:

對于上面的 CSP ,浏覽器将會作出如下解釋,并用其作為判斷是否加載頁面資源的标準:

1。 default-src * 表示允許從任何域加載任何資源,在這條指令中引入通配符星号是相當危險的,雖然該指令的優先級最低(政策沖突時,以其它指令為準,而不是 default-src),但一般也不推薦用通配符,這裡應該是 facebook 前端開發工程師犯的低級安全錯誤;

2。script-src 指令表示允許從哪些域加載腳本檔案,這裡允許的域有:

     https://*.facebook.com

     http://*.facebook.com

     https://*.fbcdn.net

     http://*.fbcdn.net

     *.facebook.net(估計租下了 .net 通用頂級域下的這個子域,才敢如此設定)

     *.google-analytics.com

     *.virtualearth.net

     *.google.com(google 一向是web安全的模範站點)

     127.0.0.1:*(這是多餘的吧?)

     *.spotilocal.com:*(允許該域伺服器上的任何端口。。。可以對目标進行端口掃描了)

     'unsafe-inline'

(表示允許該頁面中内嵌的腳本,即通過 script 标簽引入的,由于攻擊者也可以注入 script 标簽,這就說明 facebook 有一套良好的 XSS 過濾機制,才敢使用 unsafe-inline 這個參數;另外,如果沒有顯式指定這個參數,則預設禁止内嵌腳本,但這就無法執行“善良的”script 标簽内代碼)

     'unsafe-eval'

(表示允許執行 eval() 家族的函數,後者通常用于在“嚴峻”的已過濾環境中執行惡意腳本,這表明 facebook 對自己的 XSS 防火牆很有自信)

      https://*.akamaihd.net

      http://*.akamaihd.net

      *.atlassolutions.com

3。style-src * 同樣表示允許從任何域加載樣式表檔案(危險!);

      'unsafe-inline' 表示允許内嵌樣式,即通過 style 标簽引入的;

4。connect-src 表示允許通過 XHR 對象向哪些域發起跨域請求并讀寫響應,這裡

    允許“跨”的域有:

    https://*.facebook.com

    http://*.facebook.com

    https://*.fbcdn.net

    http://*.fbcdn.net

    *.facebook.net

    *.spotilocal.com:*

    https://*.akamaihd.net

    http://*.akamaihd.net

    wss://*.facebook.com:*

    ws://*.facebook.com:*

    https://fb.scanandcleanlocal.com:*

    *.atlassolutions.com

    http://p_w_upload.fbsbx.com

    https://p_w_upload.fbsbx.com      

對于任何其它站點伺服器傳回的 CSP 政策,都可以按照類似上述方法來解釋,推測出浏覽器最終将隻允許哪些内容。

《關于HTML文檔的兩種解析模式:HTML 與 XHTML 之間的差别》

浏覽器根據HTML文檔起始處的“<!DOCTYPE xxxx>”來判斷應該使用何種解析模式,當xxxx的值為“html”時,表示為HTML解析模式,該模式對文檔的文法要求比較寬松,浏覽器的HTML解析引擎會容許并且自動修正絕大多數的文法錯誤,例如下面這個例子:

可以看到,上述文檔的HTML标簽大小寫混用;body元素與a元素沒有閉合;

a元素的href屬性的值(百度首頁的URL)沒有用雙引号包含;存在一個沒有起始(标簽)的oops元素;img元素使用全大寫而且沒有閉合。。。。這些文法錯誤都可以在浏覽器中得到修正,前提是使用HTML解析模式,以 IE 為例,打開上述文檔,IE 會在其程序位址空間中建構DOM節點時,修正錯誤,缺少的元素:

web前端安全基礎與攻防(續篇)
web前端安全基礎與攻防(續篇)
web前端安全基礎與攻防(續篇)

另一方面,XHTML(衍生自XML)解析模式則非常嚴格,文檔中存在上述任意一種類型的錯誤都會導緻浏覽器無法正常顯示該文檔,或者給出錯誤提示。

再者,采用HTML解析模式,遇到“<script>”,“<style>”,“<textarea>”,“<xmp>”等元素标簽,都會緻使浏覽器切換到特殊解析模式,例如碰到script标簽,調用javascript解析引擎;碰到style标簽調用CSS解析器等等。

而在XML類文檔中(包括XHTML),這些标簽内部還需要嵌套一個前後不對稱格式的資料标簽,如下所示:

上例中,“<![CDATA[”與“]]>”字元串标簽之間才是真正添加javascript代碼的位置。

例如,BurpSuite Proxy 子產品攔截到的從“博文視點”站點首頁,傳回的HTTP響應體的HTML文檔中,就包含了對“<![CDATA[]]>”的使用:

web前端安全基礎與攻防(續篇)

HTML 與 XHTML 解析模式差異總結:

使用XHTML解析時,僅遇到 script 或 style 标簽,還無法切換進入到對應的解析模式(調用 javascript 或 css 解析引擎),而是必須添加“<![CDATA[....]]>”資料标簽,并且将腳本代碼或樣式表放在其中以實心句點表示的位置處,才能正常工作。

對于下面這個沒有按照規範編寫的img元素:

“<img src= "http://www.baidu.com"  title=""onerror="alert('xss')" class=examples>”

一共有4個屬性:src,title,onerror,class。其中class屬性的值沒有使用雙引号包含,三種浏覽器各自使用不同的方式解析這個img元素:

web前端安全基礎與攻防(續篇)
web前端安全基礎與攻防(續篇)
web前端安全基礎與攻防(續篇)

從上面可以看出,隻要是元素屬性的值,無論是哪一種浏覽器,在解析的時候都會自動向其添加雙引号。另外,onerror 屬性的值為一個javascript語句,運作結果是彈出提示框。

如果将包含javascript語句的雙引号改成“反引号”(`),則三種浏覽器在解析時,都會自動在反引号外側再添加雙引号,換言之,無論img元素屬性的值為何,總是使用雙引号包含,而這會導緻添加反引号的javascript代碼無法執行:

web前端安全基礎與攻防(續篇)

這裡還有一個很重要的知識點:一般而言,浏覽器在解析img元素的任意屬性值時,無論原始文檔中是否将屬性值用雙引号包含,浏覽器都會自動将其包含。

但是,如果屬性的值為以左尖括号開始的元素,例如script,并且原始文檔中沒有将其用雙引号包含,那麼浏覽器在解析的時候會産生混亂(對于三大浏覽器而言都一樣),雖然這樣并不會導緻執行script标簽中的javascript代碼,但是浏覽器會給出一些錯誤提示(IE),或者會幹擾到對img元素其它屬性值的正常解析(FireFox 與 chrome),例如原始文檔中的代碼:

其中title的屬性值為以左尖括号起始的script标簽,沒有使用雙引号包含,那麼浏覽器們會如何解析呢:

web前端安全基礎與攻防(續篇)
web前端安全基礎與攻防(續篇)
web前端安全基礎與攻防(續篇)
web前端安全基礎與攻防(續篇)

從上面的解析結果還可以發現一個事實,那就是浏覽器在解析時會自動忽略(不處理)分隔屬性與屬性值的等号(=)兩側的空格字元。在原始文檔中,img元素的title屬性後先接上一個空格符,然後依序是等号,第二個空格符,左尖括号。。。。而在浏覽器解析時忽略了兩側的等号。

(img元素與第一個屬性之間的空格符不會被省略,而且是必須的)

web前端安全基礎與攻防(續篇)

《浏覽器如何解析HTML文檔中,錯誤的元素标簽嵌套》

以下面代碼為例,其在body元素内部存在錯誤的标簽嵌套:

web前端安全基礎與攻防(續篇)
web前端安全基礎與攻防(續篇)
web前端安全基礎與攻防(續篇)

通過前面的例子可知,浏覽器不支援在元素屬性名中使用特殊字元,那麼,浏覽器是否支援在元素名稱中使用特殊字元,例如左尖括号以及等号呢?考慮下面這段代碼:

在body元素内部,試圖将第二個左尖括号開始的字元串作為 i 元素名稱的一部分,

來看看浏覽器們會如何解析這個文檔:

web前端安全基礎與攻防(續篇)
web前端安全基礎與攻防(續篇)

IE 中的情況與上述兩者類似,這裡就不截圖說明了。大家可以自行嘗試向 i 元素名稱中添加等号(=),浏覽器也會将其當成是元素名稱的組成字元,從某種意義上而言,這種解析上的缺陷不得不視為目前版本的一個安全隐患。

在chrome的解析截圖中,看到了将特殊字元進行HTML實體編碼的用法,其實,

浏覽器的HTML解析引擎應該都能識别在HTML文檔的文本節點與元素屬性值内,以HTML實體編碼的字元序列,并将其解碼,還原成可列印的ASCII字元。

這就是chrome将文本節點中的左尖括号編碼的原因——它可以解碼成明文字元。

這裡隐含了一條重要的安全規則,浏覽器在解析時應該遵守,否則就有可能在用戶端産生xss漏洞,假設經由伺服器端傳回的HTML文檔中,已經對文本節點與元素屬性值進行了HTML實體編碼(或者10進制,16進制編碼),意味着這些被編碼的字元是具有危害性的(一般而言,普通字元不需要編碼)

那麼浏覽器就不能将解碼後的危害性字元序列(如“<script>”)當成指令執行,隻能作為資料或字元串顯示,或者索性不解碼,在HTML文檔中顯示編碼字元。

因為伺服器端的xss過濾器已經意識到,危害性字元出現在文本節點與元素屬性值中,可能導緻xss漏洞,是以将其編碼,而浏覽器能做的就是,僅顯示解碼後的内容,而不是執行,或者保持原樣輸出。

為了更好地說明其中的微妙之處,來看一看下面的幾個執行個體:

這個例子在body元素内部的文本節點中分别插入了HTML實體編碼的“→”,解碼後的效果是一個指向右邊的 unicode 箭頭;以及HTML 16進制編碼的“😹”,解碼後的效果是一個表示成喜極而泣的貓臉的 unicode 字元。

因為這2個字元都是非危險性的,是以三大浏覽器都能将其解碼後顯示:

web前端安全基礎與攻防(續篇)
web前端安全基礎與攻防(續篇)

令人詫異的是,以相容性與安全性,國際化著稱的chrome浏覽器,不能識别以HTML 16進制編碼的喜極而泣貓臉字元,是以這裡就不發圖了,各位可以自行驗證。

第二個例子指出,浏覽器可以識别并解碼在元素屬性值中,以HTML 16進制編碼的非危害性字元,盡管後者通常是不需要編碼的:

img元素的src屬性的值中,“t”是HTML 16進制編碼後的 ASCII 字元“t”;“:”是采用相同編碼格式的 ASCII 字元“:”;“/”是 ASCII 字元“/”

是以浏覽器解碼後應該能識别src的屬性值為“http://ucenter.51cto.com/avatar.php?uid=4681835&size=middle”

這是我的頭像的URL,用浏覽器打開上述文檔,如果能正确加載頭像,說明解碼成功:

web前端安全基礎與攻防(續篇)

然而,浏覽器無法識别以及解碼出現在屬性名稱中的HTML編碼字元,(這個限制還包括了對屬性名稱指派的等号)

以上面的例子而言,這會造成浏覽器無法加載頭像,其中,等号使用了HTML 16進制編碼:

以及在屬性名稱中出現的HTML 編碼字元:

各位可以自行測試。

《頁面使用 UTF-7 字元編碼導緻的 XSS 漏洞詳解》

很多安全類書籍都介紹到 UTF-7 字元編碼會導緻 XSS 漏洞,但是部分内容各執一詞,且描述不清楚,這裡總結如下:

UTF-7 編碼是 unicode 編碼的一類子集,後者包含的其它編碼類型有 UTF-8,UTF-16LE(小端法,将代表編碼字元的最低有效位元組放在前面),UTF-16BE(大端法,将代表編碼字元的最高有效位元組放在前面),UTF-32LE,UTF-32BE 等等。在 UTF-7 編碼方案中,左尖括号被編碼成“+ADW-”;右尖括号被編碼成“+AD4-”,其中的加号與減号分别辨別着 UTF-7 編碼序列的開始與結束;如果伺服器端的 XSS 過濾器沒有過濾這兩個UTF-7 編碼格式的左右尖括号,(或沒有将其編碼成其它無害形式的字元),并且用戶端浏覽器的版本過于老舊(例如 IE 7),那麼當被注入攻擊載荷的頁面傳回至用戶端時,浏覽器将解碼 script 标簽前後的 +ADW- 與 +AD4- 字元,還原成明文的左右尖括号,進而執行 script 标簽内部的惡意代碼:

以上文檔中,web 伺服器(愚蠢到)在 meta 元素中顯式指定字元集為 UTF-7,如果浏覽器同樣愚蠢(例如 IE 7以下版本,美其名曰是為了保持向後相容)遵循這個設定字元集的指令,就會解碼任何 UTF-7 字元,最終導緻彈出提示框。需要補充說明的是,多數現代浏覽器均已經不支援 UTF-7 了,因而上述文檔在最新版本的三大浏覽器中測試,都不會彈出提示框;另外,實際的觸發環境和條件更為複雜,考慮如下場景中,假設使用者的浏覽器是 IE 6/7 :

1。如果 web 伺服器在傳回的 HTTP Content-Type 響應頭中,沒有明确設定字元集編碼,并且響應體中的 HTML 文檔中的 meta 元素内,也沒有設定字元集編碼,那麼浏覽器在碰到任何 UTF-7 字元時,都會嘗試确定其編碼方案,并且解碼還原成明文字元,以上述文檔為例,就會彈出提示框;

2。如果 web 伺服器在傳回的 HTTP Content-Type 響應頭中,明确設定字元集編碼為 UTF-8,并且響應體中的 HTML 文檔中的 meta 元素内設定的字元編碼為 UTF-7,那麼浏覽器最終将按照響應頭中的設定(響應頭的優先級比響應體中 meta 标簽的優先級高 ),以上述文檔為例,不會彈出提示框(因為采用 UTF-8 字元編碼時,浏覽器無法識别,解碼 +ADW- 與 +AD4- 字元);

3。如果 web 伺服器在傳回的 HTTP Content-Type 響應頭中,設定字元集編碼為 UTF-7,并且響應體中的 HTML 文檔中的 meta 元素内設定的字元編碼為 UTF-8,

根據浏覽器的采用優先級原則,上述文檔将會彈出提示框;

總結,應對 UTF-7 XSS攻擊,從最終使用者防禦的角度來看,應該確定浏覽器總是處在目前的最新版本狀态;從 web 站點防禦的角度來看,應該明确在響應頭與響應體的 HTML 文檔中,設定字元編碼為 UTF-8 或者其它安全的編碼方案;

對于每個包含HTML内容的響應,web應用需要在其中包含 Content-type 頭部,并且用“charset=”指令設定一個标準的,(浏覽器)可辨識的字元集,

例如:

Content-Type: text/html; charset=utf-8

或者: 

Content-Type: text/html; charset=ISO-8859-1

(如前所述,還要確定響應中的所有可能位置指定了相同的字元集)

并且過濾掉任何 UTF-7 危險字元,如果沒有把握通過程式設計屏蔽所有危害字元,則可以模仿浏覽器的 HTML 解析引擎将使用者輸入的内容在記憶體中保留一份副本,将其渲染成 HTML 頁面,在渲染結果中查找任何明文的危害字元,對其進行編碼過濾,然後再次渲染,再過濾,直到完全清除幹淨後,才可以把最終的文檔傳回給用戶端。

(這也是許多優秀的伺服器端 XSS 過濾器,WAF 采用的工作模式)

《存儲型 XSS 示例》

一些社交網站如 twitter 提供的站内短信功能,在其中會通過 a 标簽的 href 屬性值進行短信連結的導航,這個屬性值是使用者能夠控制的輸入資料,是以 twitter 對其進行了嚴格輸入檢查,編碼,過濾等操作,主要是過濾尖括号以及 script,embed,object 等危害性元素。

假設用于生成短信連結的輸入框的初始 HTML 代碼如下:

在上面場景中,攻擊者想方設法要繞過 twitter 伺服器的 XSS 過濾器,最終目的是讓輸入的攻擊載荷存儲在由伺服器的 web 應用維護的,某個能夠公開通路的“頁面”(類似新浪微網誌的“廣場”功能),其它使用者通路這個頁面時,XSS 漏洞在使用者浏覽器中觸發,執行惡意腳本代碼。由于上面提到的過濾規則,插入尖括号的方法已經不再适用,必須尋找新的方法來繞過,例如下面這個攻擊載荷使用了衆多web 前端黑客技術:

1。雙引号元素屬性閉合;

2。 onmouseover 事件結合 eval 函數執行任意代碼;

3。對 URL 進行 UTF-16 編碼繞過 XSS 防火牆;

4。利用社會工程學攻擊來引誘使用者點選連結;

其中,郵件符号 @ 後面的第一個雙引号用于閉合原始 HTML 代碼中, 等号後面的第一個雙引号,于是攻擊者可以在後面插入自定義内容;style屬性将字型增大到 99像素,保證受害者能夠“準确地”點選到具誘惑性的惡意連結; 屬性代表的事件在使用者将滑鼠指針移動到該元素(a 元素)範圍内時觸發,事件觸發後,執行自定義代碼,将目前文檔的 URL 重定向到百度首頁(這裡經過我淨化,實際的攻擊載荷中,其 URL 肯定是由攻擊者控制的站點上的惡意 .js 檔案);需要注意,考慮到 twitter 的 XSS 防火牆可能會過濾以“http://”起始的字元串,是以使用 UTF-16 編碼(\u002f)對惡意 URL 中的正斜杠(/)進行轉義,這樣就能繞過伺服器的 XSS 防火牆(隻要不是精确比對,輸入字串就能通過檢查),關鍵在于,轉義後的字元序列在作為 HTTP 的響應體内容傳回至用戶端後,浏覽器會解碼,還原成正斜杠,導緻最終通路該 URL ;另外, 屬性的值必須是能夠執行的函數或者代碼快,這就是調用 eval 函數的目的;最後,使用 class 後面的正斜杠來注釋掉原始 HTML 代碼中的第二個雙引号,避免浏覽器因為文法錯誤而無法執行腳本。

補充說明一點,以 “\u”開始的6個 ASCII 字元,是 javascript 語言核心中,用于

unicode 字元的轉義序列,“\u”後面的4個 ASCII 字元(或2個16進制數),可以采用 UTF-8/16/32 等編碼形式;這裡使用的是 UTF-16 編碼(三種編碼都是 unicode 編碼的子集),使用這些編碼是為了支援一些不能正常顯示 unicode 字元的計算機系統。實際上,unicode 字元轉義使用與 ASCII 字元的16進制編碼相同的

映射;例如,對于字元“alert”,其 ASCII 16進制編碼形為“\61\6c\65\72\74”;其 unicode UTF-16 編碼轉義形式為“\u0061\u006c\u0065\u0072\u0074” ,第2個16進制數與 ASCII 字元的16進制編碼相同,第1個16進制數在編碼 ASCII 字元時為 00;在編碼其它 unicode 字元時不為 00。

javascript 語言核心(以及實作它的浏覽器内部的 javascript 解釋引擎)直接支援

unicode 字元轉義序列,這意味着,隻有這些轉義字元出現在頁面中與 javascript 上下文相關的環境中時,才能被浏覽器正确解碼,這些環境包括但不限于(其它上下文各位可以自行挖掘):

1。 script 元素内部;

2。 支援綁定事件處理程式的元素内部;

3。支援在屬性值中使用 javascrip 僞協定的元素;

如果直接在 HTML 文檔中(例如普通的元素内)使用 unicode 字元轉義序列 ,浏覽器可能無法識别并解碼成相應的 unicode 字元,下面通過例子示範:

第8行直接在 body 元素(頁面文檔體)中寫入ASCII 字元串“alert”的 unicode UTF-16 編碼轉義形式 “\u0061\u006c\u0065\u0072\u0074”,浏覽器将不會解碼并還原成“alert”,而是直接在頁面輸出“\u0061\u006c\u0065\u0072\u0074”;

第10行在 a 元素的 href 屬性值中使用 javascript 僞協定,其中的“alert”保留字使用 unicode UTF-16 編碼轉義形式,浏覽器會解碼并還原成 “alert”,導緻在點選名為“百度首頁”的超連結時,彈出顯示“xss!”的資訊框;

第11行給 input 元素的 onfocus (滑鼠點選聚焦輸入框)事件綁定的回調函數正是采用 unicode UTF-16 編碼轉義形式的 “alert”,浏覽器會解碼并還原,導緻使用者點選輸入框時觸發事件處理程式,彈出提示框;

第13行在 script 元素内調用 document.write(),作為其參數傳遞的正是 unicode UTF-16 編碼轉義形式的 “alert”,浏覽器會解碼并還原,最終在頁面輸出 alert 字元串。

下面的截圖驗證了浏覽器的 javascript 解析引擎在上述三種 javascript 執行上下文中,遇到 unicode  字元轉義序列時,所表現出的行為:

web前端安全基礎與攻防(續篇)

這個小實驗再次說明了基于黑名單過濾的 XSS 防火牆是不安全的:你也許想到要在元素屬性值中過濾 alert ,script,javascript 等字元串,但是你能考慮周全地過濾掉所有相應字元串的 unicode 字元轉義序列嗎?黑名單比對曝露的受攻擊面是如此之廣,以至于任何漏網之魚都可能被攻擊者用于發起 XSS !

為了增強連結的視覺誘惑性,我在 a 元素的 innerHTML 中,添加了名為“邀請碼發放頁面”的文本節點(請勿在論壇中嘗試!),最終的注入效果(即存儲到伺服器上的漏洞頁面)如下所示:

使用目前任意類型的最新版浏覽器打開上面 HTML 檔案,就會重定向到百度首頁,這就完整的模拟出攻擊載荷成功繞過,存儲到伺服器上,并且傳回給用戶端後,使用者浏覽器成功執行代碼的場景,截圖如下:

web前端安全基礎與攻防(續篇)

繼續閱讀