在前文中提到了「跨站指令碼(Cross-site Scripting)攻擊」。它和資料隱碼攻擊(SQL Injection)攻擊的目標不同。前者是透過從Web前端輸入資料至網站,導致網站輸出了被惡意控制的網頁內容,使得系統安全遭到破壞。而後者則是輸入了足以改變系統所執行之SQL述句內容的字串,使得系統最終達到攻擊的目的。

但從更一般性的角度來看,這兩種攻擊手法基本上是相通的。他們都是透過系統對於輸入資料的毫無檢核或是檢核不足,利用刻意製造出來的輸入資料,來讓系統產生不在預期內的有害行為。

過濾輸入資料有效嗎?

因此,當要防備這類型的攻擊時,大多數人直覺想到的方式,便是對使用者所提供的輸入資料進行過濾。在面對資料隱碼攻擊時,許多人會想到針對輸入資料中,可能會含有的SQL關鍵字串或字元進行過濾,例如,使用者的輸入資料中若含有單引號,那麼便可能製造出危險的SQL述句,因此,許多程式設計者便會想到要針對單引號進行escape,來杜絕資料隱碼的攻擊。而在前文中,我們也探討過,這種escape或過濾的方法,其實不難規避,例如,針對單引號,有心人士還是可以利用資料庫伺服器支援的特殊寫法,將單引號字元替換成其他的型式,使得escape的程式失效。

同樣的,面對跨站指令碼攻擊,許多程式設計者首先會想到的,也是針對有可能造成疑慮的使用者資料進行過濾。例如,針對<或者>字元、或者SCRIPT字串進行過濾,倘若使用者的輸入資料中含有可疑的字元或字串時,則進行改寫或禁止使用。這種針對特定的目標進行篩選過濾的方式,可以稱為黑名單式的過濾,因為,程式是先列出「不能出現」的對象清單,然後進行過濾。

當然,在跨站指令碼攻擊中,如果想利用黑名單式的過濾方式,當然也是行不通的。因為,單是javascript這個字串,有心人士就可以產生在HTML中等價、但是字串形式不為javascript的寫法。例如,使用#x的十六進位字元表示方式,來表示javascript這個字串中的每一個字元。由於改寫形式太多了,這使得想要利用黑名單的方式,幾乎是不可能的事。

因此,無論何種攻擊,想要有更嚴密的防備,在資料的過濾上,都應該採取白名單式的過濾。而這正和黑名單相反,它不是列出不被允許的對象,而是列出可被接受的對象。

以正向表列的方式管制

在面對資料隱碼攻擊時,我們建議針對每個欄位都明確定義出它該有的形式,例如日期欄位就只能出現數字和斜線字元,而ID欄位,僅能出現英文字母及底線字元等等。如此一來,想要透過資料植入有害的組成,就不是那麼容易可以辦到了。

對於同樣需要對輸入資料進行檢核的跨站指令碼攻擊來說,要做到輸入資料的過濾,最好的策略也是基於白名單來過濾。例如,允許使用者輸入HTML語法的地方,若僅允許輸入圖片,則可開放 形式的輸入,其餘則否。這麼一來,想要規避過濾的規則,難度就比較高了。

當然,採取較嚴格的白名單政策,程式在撰寫難度上比較高,此外,允許使用者資料輸入的形式也就更為受限,但這是為了安全必須付出的代價。

將網頁內容編碼,提升防禦力

除了針對輸入資料進行白名單式的過濾之外,針對輸出的頁面內容進行編碼,也是實務上能派上用場的技巧。輸入資料的過濾是針對可疑的資料進行防範,而針對輸入進行編碼,則是讓可能造成危害的資料變成無害。

慶幸的是,有許多程式語言都推出了為了防範跨站指令碼攻擊的程式庫,協助程式設計者針對HTML輸出內容進行編碼。例如PHP的htmlentities()或是htmlspecialchars()、ASP的Server.HTMLEncode()、ASP.NET的Server.HtmlEncode()等等。讓專門的程式庫來處理輸入內容的編碼,也可以減少程式設計者自行開發的額外成本,同時也能提供更為完善的防備考慮。而像微軟,更提供了一個名為Microsoft Anti-Cross Site Scripting Library的程式庫,提供了各種HTML、JavaScript、URL、XML、VBScript的過濾及編碼機制。如此一來.便可以透過這一套程式庫,將來自於使用者輸入的字串,或是以使用者輸入字串為基礎的輸出字串進行轉換,成為單純的文字,而不含可於瀏覽器上執行script程式,因此能夠降低遭受到攻擊的風險。

徹底分析程式碼可能的弱點

在撰寫程式時,如果能夠留意輸入資料的輸出,以及輸出頁面內容的編碼,相信可以增加不少防範的強度。但是,針對既有的程式碼,倘若存在跨站指令碼攻擊的漏洞,又該如何察覺並進行防範呢?基本上,你可以採取一個系統性的分析方法。

若想要審視你的程式碼是否具有跨站指令碼攻擊的問題,根據《Writing Secure Code》一書中的建議,首先,你必須要列舉出你的網站程式中所有接收自使用者端送出資料的地方。所謂使用者端的資料,包括了你的網站程式所讀取表格中的每個欄位、來自於網址中的查詢字串、cookies的值、HTTP的標頭等等。因為,不要忘了,所有來自於使用者的資料「都是邪惡的」。

找到了每個接收使用者端資料的地方後,便可以逐一追蹤每筆資料在應用程式中的流向,檢驗所接收到的資料,最終是否會反映到輸出的頁面結果中。這中間,你可能直接把接收來的資料稍微加工後,就當做輸出結果送出去了也有可能,先把所接收到的資料儲存在資料庫或檔案中,於日後才做為輸出結果送出。

倘若,你所找出來的資料最終會成為輸出頁面的一部份,那麼,你就應該檢查這份資料是否足夠「乾淨」,也就是說,你是否有針對這份資料進行足夠的過濾或在輸出時加上了編碼的動作。倘若沒有,那麼,這份資料就有可能成為跨站指令碼攻擊被發動的點。也就是說,你應該針對這份資料的輸入及輸出,進行相對應的處理。透過以上的步驟,有助於你審視既有的程式碼的問題。

除此之外,檢查你的JavaScript程式中,動用到innerHTML以及document.write()的地方,是否有安全疑慮,也會有幫助。另外,在JavaScript程式中使用eval(),同樣有可能造成安全問題。eval()函式允許在瀏覽器上直接將傳入該函式的參數做為JavaScritp算式或是可執行的述句,動態的評估算式之值或是加以執行。倘若,eval()所接收的參數之值,是來自於使用者輸入的部份資料,那麼可以造成的危機就大了,因為這意謂著,使用者有機會控制透過eval()所執行的JavaScript述句,系統安全形同門戶洞開。這也就是近來,為什麼許多守則都建議不要使用eval()的原因,因為太容易形成安全性的漏洞了。

跨站指令碼攻擊的確是十分難以防守的攻擊形式,但不小心,就有可能造成頗大的傷害。遺憾的是,還是有不少Web應用程式設計者並沒有小心的提防,希望可以透過這裡,帶給你基本的認知。關於跨站指令碼攻擊其中還充滿許多高深的探討和技巧,不在本文範圍之內,建議有興趣者自行鑽研之。

專欄作者

熱門新聞

Advertisement