今年,OWASP TOP10 2017候選版在四月初釋出,前三大漏洞成因,跨網站指令碼(Cross-site scripting,XSS)依舊名列其中。在前端盛行的年代,XSS是許多前端開發者熟悉或應熟悉的議題,也更應認識相關工具運用。

相對於其他弱點來說,XSS的成因簡單易懂,對使用者的輸入資料驗證不足(甚至沒有驗證),也沒有針對輸出的內容進行轉譯,攻擊者因而得以將惡意程式碼注入網頁,或者URL等可能的輸入來源,而瀏覽了惡意程式碼頁面的使用者,就有可能被劫持會話(Session hijacking)、釣魚(Phishing)、重導至惡意網站、進行非預期之操作(像是被更改了密碼),甚至成為蠕蟲攻擊的幫兇。

在談到XSS的文件中,經常會從最基本的Reflection XSS開始介紹,通常是透過沒有驗證輸入,也沒有轉譯輸出的搜尋頁面範例,簡單地在搜尋時夾雜HTML或JavaScript,在結果頁面上示範出如何呈現原HTML頁面中沒有的iframe,或者執行JavaScript取得Session ID,因為實際上惡意的注入並沒有儲存在伺服端,伺服端上就像鏡子般將惡意程式碼反射至使用者的頁面,故稱之為反射型。

相對地,若惡意注入的程式碼儲存在伺服端,則被歸類為Stored/Persistent XSS,由於惡意程式碼被儲存下來了,每次造訪就會有重複展示的機會(像是出現在討論區、留言版、推文等),因而容易被發展XSS蠕蟲攻擊。例如,2005年發生在MySpace上,史上第一隻XSS蠕蟲Samy就是Stored/Persistent XSS;2010年,在Twitter上,曾有多人仿效而產生的各式蠕蟲,也可以歸類在Stored/Persistent XSS。

對於Reflection XSS或Stored/Persistent XSS,基本上注入的惡意程式碼,都須發送至伺服端,通過漏洞後才回傳客戶端,一種DOM-based/Local XSS則不經此管道,而是伺服端傳回有漏洞的JavaScript,而後瀏覽器執行被注入客戶端的惡意程式碼。

例如,在請求上加上#,瀏覽器會認為這是個本地端錨點,而不會將之後的字串發至伺服端,伺服端驗證在此派不上用場。在一個真實的案例〈A Twitter DomXss, a wrong fix and something more〉中,就探討這樣的狀況,我們也看到XSS的防禦對策與難度。

如何防禦XSS,以及難以處理的部份

XSS的防禦基本上,不外乎加強對使用者輸入的驗證,以及在輸出時對內容進行轉譯,在輸入驗證上有白名單與黑名單兩種方式。

在白名單的處理方式上,通常是透過規則表示式(Regular expression),限制輸入值只能有合法的字元或規則(像是字串長度),而黑名單的方式,則是限制可能的惡意字元輸入,像是在最低限度上,必須過濾<、>、"、'、&等字元,甚至是script等含有特定程式指令的字串(現代瀏覽器其實都會檢測基本XSS模式,並在察覺時封鎖執行,但開發者不該依賴瀏覽器),在輸出時,一律對這類字元轉譯。

然而,實際的情況並沒有想像中那麼單純,檢查規則可被規避。像是:改變字元大小寫、透過填充額外字元、URL編碼或使用等價字元等方式,來繞過規則表示式的檢查;而能被注入XSS的地方太多,像是URL、特定的標籤或屬性、CSS、JavaScript通訊協定、CDATA等(別相信XSS只來自於GET),在輸出過濾與轉譯上,同樣也須考慮各種惡意字元的可能性,然而,轉譯的規則也可能被規避。

〈A Twitter DomXss, a wrong fix and something more〉談到的,就是規避檢查與轉譯的例子。話說Twitter雖然使用介面上極為簡單,卻是被XSS攻擊的常客,足見XSS防護之難,就像在〈為何XSS(跨網站腳本)漏洞難改?以twitter Mikeyy六代蠕蟲說明〉(https://goo.gl/YTv7hj)中,所寫到的:「有效防範XSS之難處在於,每個網站的架構之不同,可能造成多層的字串編解碼步驟,所以如何有效處理外來(不安全)的字串,因各網站而異」。

OWASP ESAPI

充分瞭解採用技術的細節、應用程式架構與XSS原理,像是多閱讀XSS Prevention Cheat Sheet之類的文件,撰寫程式時,盡可能考慮相關可能性,可大大避免XSS。但由於攻擊模式多,運用工具也有必要。

談到OWASP Top 10及避免漏洞攻擊之程式撰寫,多半會提及OWASP ESAPI(Enterprise Security API),這個專案定義了一系列介面,並提供了預設實作,對於常見的攻擊提供了防禦方案的參考,以Java版本的實現為例,預設實作是位於org.owasp.esapi.reference套件之下,可以基於其實現來擴充改進,若要更換實作品(以及一些安全策略),可以在ESAPI.properties中進行設定。

以輸入驗證的方案為例,可以透過ESAPI.validator()取得Validator實例(預設實作為reference套件中的DefaultValidator類別),該實例可用來檢查使用者輸入的長度、根據白名單與黑名單進行驗證、以及同義字的轉換,而白名單的根據是定義在validation.properties中的規則表示式。

除了基本的isValidInput、getValidInput方法之外,Validator也提供了像是getValidSafeHTML、getValidRedirectLocation之類的方法,在HTML等的驗證上,必須提供驗證規則來源,可以先使用antisamy-esapi.xml,其中就提供了幾百條預設規則,可見規則資料庫之重要性。

事實上,AntiSamy也是OWASP的專案,想要取得更多規則,可查看AntiSamy的專案頁面(https://goo.gl/VqY4Nt),開發者也可查看antisamy-ebay.xml、antisamy-myspace.xml等,確認定義的規則。

在輸出的過濾與轉譯方式上,我們現在可透過ESAPI.encoder()取得Encode實例(預設實作為reference套件中的DefaultEncoder類別),因為它在此提供了encodeForHTML、encodeForCSS、encodeForJavaScript、encodeForURL等方法。實際上,它也提供encodeForSQL方法,可作為基本的SQL Injecton防禦對策。

不過,在OWASP ESAPI的下載頁面中,可以看到的版本目前是2.1.0.1,然而,實際上存在著ESAPI 3.x的規劃,在Github上,2.x其實是被命名為esapi-java-legacy,只是由於缺少志願貢獻者的情況下,3.x處於停擺狀態,反而是esapi-java-legacy仍持續有提交記錄,造就2.x雖為legacy,卻比3.x還活躍的狀態(實際上,ESAPI除了Java、JavaScript之外,其他語言的實作也屬於停擺狀態)。

認識相關工具的必要

儘管XSS主要發生在前端,實際上屬於注入攻擊(Injection flaw),因此,一些防護概念也可套用在SQL Injection、重導、Expression Language Injection等注入式的攻擊裏,而XSS也容易結合其他攻擊模式(像是CSRF)而變化,所以,單憑開發者對安全的認知加強與攻擊模式的想像,不足以應對。

儘管OWASP ESAPI維護上緩慢,仍不失為一個參考方案(特別是開發者從未試著使用這類程式庫來實作安全的情況),在OWASP TOP10 2017中,仍建議了ESAPI。若想要有個不錯的文件起點,可以參考〈The OWASP Top Ten and ESAPI〉。

然而,OWASP ESAPI維護上的問題,就有可能在漏洞發生時,導致修補相對緩慢。在OWASP ESAPI官方上有個〈Should I use ESAPI?〉也讓開發者自問是否想運用ESAPI,並提供幾個更為活躍的替代子專案,像是OWASP Java Encoder Project、OWASP Java HTML Sanitizer等。

ESAPI的貢獻者之一,也在〈ESAPI No Longer an OWASP Flagship Project〉談到,ESAPI是時候該交棒給這些候選子專案了。

有機會的話,可以進一步瞭解這些工具甚至是原始碼的實現方式,也許最後未必會應用這些工具,但在這個過程中,也能學習與瞭解到,在建構安全的知識體系時,有哪些是必要的特性,以及實作上的各種考量。

專欄作者

熱門新聞

Advertisement