王應軍,傅建明,姜百合
(武漢大學 計算機學院,武漢 430072)
跨站請求偽造(Cross-site Request Forgery,CSRF)攻擊就是攻擊者通過一些技術手段欺騙用戶的瀏覽器去訪問一個自己曾經認證過的網站并執行一些操作(如發郵件、發消息、加好友、甚至是財產操作)。由于瀏覽器曾經認證過,因此被訪問的網站會認為是真正用戶的操作而去執行,實際上是執行攻擊者構造的操作。
隨著互聯網、云計算、移動計算的高速發展,網站提供的功能越來越豐富,用戶能夠進行的操作也越來越多,從而也存在更多潛在的CSRF攻擊。根據OWASP[1]發布的Web應用十大安全隱患報告,CSRF排名前八位。2007年,谷歌Gmail遭受了嚴重的CSRF攻擊[2],攻擊者通過在惡意網站上偽造對受害者Gmail中郵件的轉發請求鏈接,使得成千上萬用戶的郵件遭到泄露。新浪某社區因存在CSRF漏洞,誘騙用戶點擊可造成蠕蟲[3]。Facebook和Dropbox在聯合使用過程中,因為auth使用不當,導致利用CSRF攻擊可以上傳任意文件到Facebook[4]。據不完全統計,截止2016年5月,在烏云漏洞平臺[5]上提交的CSRF漏洞數累計有330多個,說明CSRF漏洞廣泛存在。
目前主流防御CSRF的思路包括驗證請求來源與防止請求重放。驗證請求來源如驗證用戶提交請求的referer,但referer是由客戶端發送可以被偽造從而產生漏報。防止請求重放的常見方法包括在Form表單中增加Token或者在執行敏感操作時需要用戶輸入驗證碼,但要求用戶輸入驗證碼時會影響用戶體驗。基于Token的防御方法也能夠利用postMessage、JSONP的方法,獲取Token進而執行CSRF攻擊[6]。以上的防御方法存在著用戶體驗差、兼容性差、防御方法易被繞過、Token泄漏導致CSRF攻擊等多種問題,不能在實際應用中有效地防御CSRF攻擊。
為此,本文提出一種基于URL地址請求參數名隨機化的CSRF防御方案。通過對請求參數名進行隨機化,使其不可預測從而使攻擊者無法進行重放。服務端接受瀏覽器請求后對請求參數名進行驗證來判斷請求是否合法。隨機化的密鑰在每次建立會話時產生,攻擊者無法預知。被隨機化的參數也是在服務器端隨機化之后發送到瀏覽器,攻擊者無法獲知隨機化的請求參數名。所以,基于參數名的隨機化的CSRF防御方案本身不會泄露隨機化參數的信息,同時隨機化的操作是在服務器端完成,在保障用戶體驗的同時防御CSRF攻擊。
CSRF防御方法根據CSRF檢測位置的不同可以分為服務端防御、客戶端防御。客戶端防御側重于跨域請求的審查;服務端防御側重于防止請求重放。
服務端常用的防御方法就是采用基于驗證碼的防御方法、基于referer的驗證防御方法與Token的防御方法。
基于驗證碼的防御方案是在關鍵位置要求用戶輸入驗證碼。但是由于輸入驗證碼的用戶體驗較差,目前這種方案沒有廣泛使用。基于referer的防御思路是服務器端驗證請求的referer。但是由于用戶的隱私設置或者是網絡傳輸的問題,服務器最終不一定能得到referer,因此基于referer的防御方法存在漏報、誤報的問題,也沒有被廣泛采用。
基于Token的防御方法[7]的思路是在需要進行敏感、重要操作時,在對應的Form表單的地方添加Token,Token一般是一個隨機數。當用戶通過表單提交請求時,這個Token也會一并提交上去。在正常訪問時,客戶端瀏覽器能夠正確得到這個Token。但是在CSRF攻擊中,攻擊者無法事先知道這個Token,從而能夠防御CSRF攻擊。目前這種防御方法,如Django、Ruby on Rails、ThinkPHP被廣泛地采用。但是這種防御方法也存在缺陷:1)如果對Cookie處理不當,則導致CSRF的防御方法可以被繞過[8];2)Web應用與第三方進行跨域交互時可能會泄露Token,如利用JSONP、postMessage可以獲取到Token[9]。
客戶端的防御方法一般都是在瀏覽器端識別出CSRF攻擊,阻斷CSRF的請求或者剝離所有的認證信息。鑒于服務器的防護方法需要修改服務器端的代碼,很多的學者都是研究基于客戶端的防御方法。CsFire[10]是一款基于Firefox的插件,CSFire對跨域的2個網站的相關性進行判斷以此界定是否為CSRF攻擊。CsFire如果發現跨域的2個網站之前存在交互,則認為此請求合法,否則認為是CSRF攻擊。但是此工具在面對Flicker、Yahoo等第三方登錄的情形時會存在很大的誤報。BEAP[11]則是通過判斷當前用戶發起的請求是否是基于用戶意圖以此來防御CSRF攻擊的一款瀏覽器插件。BEAP通過監控用戶網頁行為以此判斷當前點擊是否是基于用戶的意圖點擊。例如如果用戶在地址欄輸入URL地址,則認為是基于用戶的本意訪問;如果用戶點擊郵件中的鏈接,則認為是CSRF攻擊,從而阻止該請求。BEAP這種方式存在很大的誤報,目前很多網站注冊都需要用戶點擊郵件中的注冊鏈接進行確認,BEAP對這類行為都會產生誤報。文獻[12]提出的同源信用應用協議以Web站點的信用度為指標,計算訪問請求地址的信用來決定該地址是否可達,防止從跨域名網站引入惡意資源。但是該方法還是通過插件的方式加以實現的,導致協議還是可以被攻擊者破解從而繞過。
從研究者們的研究成果以及實際應用可以看出,基于客戶端的防御存在兼容性、用戶體驗差、漏報、誤報以及易被繞過等不同程度的問題。基于服務器端的防御才能夠有效地防御CSRF攻擊,但是基于referer的防御方法由于referer容易被偽造從而繞過檢測。驗證碼雖然能夠有效地防御CSRF攻擊,但是用戶體驗差。所以,目前最常用的防御方法是基于Token的防御方法,但是Token也存在著Token泄露的問題。
本文提出基于隨機化的參數名跨站請求偽造的防御方法,將攻擊者可偽造的請求參數名作為隨機化對象,使其具有隨機性和不可預測性,以此防御CSRF攻擊。隨機化技術是一種新型的保護系統免受攻擊的通用方法,可以有效地打破同構和穩定性的環境,以異構和隨機的環境來應對未知的攻擊。
目前也有很多的學者研究如何將隨機化的技術應用在Web安全防御中。文獻[13-14]均提出通過指令集隨機化的方法防御SQL 注入攻擊,其主要思想就是對SQL語句中的關鍵字后添加隨機數,之后對組裝的SQL語句進行驗證。但是由于實現難度較高,最后采用的是通過代理服務器的方式實現的。由于代理服務器需要對HTML解析和分析,因此這種方式的兼容性、開銷都存在問題。文獻[15]提出基于指令隨機化的XSS的檢測方法,通過HTML/JavaScript關鍵字會在末尾追加隨機生成的字符串,攻擊者無法預測,由于這種方式實現復雜同樣采用的是代理的方式,因此也會存在兼容性差、開銷大的問題。文獻[16]提出網頁標記隨機化對抗XSS 攻擊,鑒于通過代理的方式實現隨機化的效果較差,該方法主要是在PHP平臺上實現。在服務器通過修改Smarty的代碼來對HTML的標簽進行標記,在客戶端通過代理的方式對標記的標簽進行驗證,雖然具有很好的效果,但是僅適用于使用了Smarty的PHP代碼。
鑒于目前隨機化的方法在Web安全方面開始逐步應用,但是針對CSRF的防御卻并沒有使用隨機化的方法,同時之前研究者們提出的針對CSRF的防御方法均存在不同程度的問題,本文提出了基于參數名的隨機化防御方法,不同于通過代理的方式進行隨機化和去隨機化,本文主要是基于源代碼的分析,提供一種通用的CSRF防御思路并且在PHP開源程序上加以驗證。
本節首先分析CSRF攻擊的原理,然后提出一種基于請求參數名隨機化的CSRF防御方法,最后說明基于隨機化防御方法的PHP代碼的實現。
CSRF利用網站對用戶網頁瀏覽器的信任,欺騙用戶的瀏覽器去執行非本意的操作。CSRF攻擊的原理如圖1所示。

圖1 CSRF攻擊原理
當網站A收到了用戶瀏覽器發送的轉賬請求R時,由于瀏覽器攜帶了用戶的Cookie,網站A認為請求R是由用戶發送的就會按照用戶的權限處理步驟5的請求,這樣網站B就達到了模擬用戶操作的目的,成功地實施CSRF攻擊。CSRF能夠成功實施的一個重要的原因是在于所有的用戶在登錄網站A時看到的網頁結構與參數名都是一樣的,攻擊者很容易構造出一個與普通用戶一樣的操作的請求。
目前主要的HTTP請求方法是GET和POST。從數據表現形式上來看,GET請求方法的請求數據顯式地嵌入在URL中;而POST請求方法的請求數據隱式地嵌入在HTML表單中,提交表單時用戶無法看到提交的內容。但是無論是通過什么方式提交數據,提交的字段名對于所有的用戶都是不變的。對于用戶來說,假設轉賬請求的字段名為fromaccount、toaccount、money。攻擊者看到轉賬請求的字段名同樣是fromaccount、toaccount、money。這種靜態的可預測的參數名,使得所有的用戶發送請求時的參數名都一樣,因而攻擊者能夠偽造出“合法”的惡意請求實施CSRF攻擊。這種CSRF攻擊方法本質上就是一種重放攻擊。
根據2.1節中對CSRF攻擊原理的分析發現,阻止請求重放就能夠有效地防御CSRF攻擊。通過Token的方式來防御CSRF的防御方法利用的就是阻止攻擊者重放的思想。本文提出的基于請求參數名隨機化的防御方法,將傳統的靜態可預測的請求參數名作為隨機化為不可預測的字符串以防御CSRF攻擊,本質上也是一種阻止攻擊者請求重放的方法,但是比Token方法能夠更有效地防御CSRF攻擊。
在隨機化算法的選擇方面,考慮到需要對參數進行加密和解密,還要保證在用戶訪問的時候參數名稱不同,綜合考慮采用的是對稱性加密算法,將加密密鑰保存在SESSION中。為了保證用戶在每次與網站建立會話時的加密密鑰都是不同的,密鑰的生成及用戶與服務建立會話的時間相關,同時采用摘要算法生成密鑰。這樣就保證了同一用戶在不同時刻以及不同用戶在同一時刻訪問網站所得到的網站的參數名都是不同的,攻擊者無法猜測到用戶的隨機化的參數名,也無法獲知密鑰,保證了CSRF攻擊難以實施。整個隨機化防御原理如圖2所示。
當用戶向服務器發送請求之后,服務端如果判斷用戶不是第一次訪問,則直接返回已經隨機化后的頁面。如果用戶是第一次訪問,則創建隨機化密鑰,同時對訪問頁面中的參數進行隨機化處理,返回隨機化之后的頁面給用戶。用戶在頁面中填寫完畢發送至服務器之后,服務器對隨機化的參數進行還原。如果服務器能夠還原成為原來的參數,則正常響應用戶的請求;否則,服務器判定為CSRF攻擊,拒絕響應。
為了方便開發者部署本文的方案,本文實現一個PHP的第三方庫CSRFP[17],用于實現生成隨機化密鑰、參數名隨機化以及對已經隨機化的參數名還原。通過對國內外常見的PHP開源程序(wordpress、joomla、drupal、discuz、phpcms、dedecms、74cms)的分析,發現這些程序都會引入公共文件,如config.php、common.php、functions.php等。這些文件一般都是集合了程序常使用的功能,如格式化字符串、頁面輸出,同時也用于對GET、POST參數過濾,以防止XSS、SQL注入攻擊。在這些公共文件中引入CSFRP庫就可以在整個程序中對參數進行隨機化和對隨機化的參數名還原。開發者可以自行選擇需要進行隨機化防御的位置。開發者通過引入CSRFP庫,不需要修改原來程序的邏輯和代碼就能夠很容易實現對網站的加固。
考慮到目前CSRF攻擊主要是POST型的攻擊。所以,在實驗過程中只對表單中的參數名進行隨機化。
對參數名進行隨機化的示例代碼如下:
name="">
通過在公共文件類似common.php中引入CSRFP庫,就能夠完成對隨機化的參數名進行還原,代碼如下:
foreach ($_POST as $random_name=> $value) {
$name=CSRFP::decrypt($random_name);
$_POST[$name]=$_POST[$random_name];}
通過解密還原的代碼可以看出,主要就是對$_POST中的已經隨機化的random_name進行解密還原為name。如果隨機化的random_name是被攻擊者篡改或者是由攻擊者創建的,那么在進行解密為name時無法正確還原,之后的處理流程就會失敗,從而丟棄這次請求,保護網站不會受到CSRF攻擊。CSRF隨機化防御流程如圖3所示。

圖3 隨機化防御流程
對比2.1節CSRF的攻擊原理,當使用了隨機化參數名的CSRF防御方法之后,參數名稱完全是隨機的字符串(步驟2)。當攻擊者發起CSRF攻擊時,攻擊者無法知道新的參數名而是按照之前的參數名發起攻擊(步驟4)。當服務器端收到這些參數名進行校驗時,檢驗失敗,從而判定為不是正常請求,拒絕響應(步驟5和步驟6)。通過這種方法可以有效地防御CSRF攻擊。
本節對基于隨機化參數名CSRF防御方法的有效性和性能進行測試以及安全性分析,同時與目前主流的基于Token的CSRF防御方法進行對比分析。實驗分析方法是通過在本地搭建Apache、MySQL、PHP的網站運行環境,應用隨機化的防御方案加固存在CSRF漏洞的PHP開源程序,檢測有效性;通過記錄應用隨機化的防御方案之后的訪問時間來分析性能。
在有效性方面,實驗選取了國內外存在CSRF漏洞的開源PHP程序進行部署和實驗分析。根據Czeskis[18]所進行的實驗方法,選取國外的PHP開源程序是Selectapix、UseBB、PHPDug、PoMMo。國內的PHP開源程序選擇的是PHPCMS、EmpireCMS、FineCMS和蟬知CMS。所選取的PHP開源程序都存在CSRF漏洞。同時還與目前主流的CSRF防御方法——基于Token的防御方法進行了對比實驗。實驗結果如表1所示,其中,√為可以被防御,—為無法防御。

表1 隨機化防御實驗結果
從實驗結果可以發現,采用基于隨機化的防御方法比基于Token的防御方法更有效。例如PHPCMS的CSRF攻擊使用Token無法防御,但是使用基于隨機化的方法可以被防御。PHPCMS的CSRF漏洞出現添加友情鏈接的地方,默認情況下會在添加鏈接后面加上Token,第三方鏈接就能夠得到Token,造成了Token的泄露。當管理員審核此鏈接時,就會執行CSRF攻擊。
如果采用隨機化的防御方法,不會在任何地方添加Token,那么就不存在泄露的問題,所以,能夠防御CSRF攻擊。而FineCMS的CSRF攻擊,2種防御方法均無法防御,原因在于FineCMS是通過XSS的方法得到了Token,從而執行CSRF攻擊。無論是采用Token還是隨機化的防御方法,都可以通過XSS的方法得到相關參數名和參數值,所以無法防御。
綜合實驗結果可以看出,基于參數名的隨機化方案比基于Token的防御方法能夠更加有效地防御CSRF攻擊。
在隨機化的方法中,需要生成隨機化密鑰并對參數進行加密。所以,性能測試包括不同的對稱性加密算法與不同摘要算法的性能差異、基于Token的防御方法的性能差異、隨機化的input數量對性能的影響。
實驗方法是用PHP編寫一個常見的用戶注冊頁面,此頁面中包含了一個注冊的Form表單,通過增加Form表單中的input數量來測試input數量對防御方法的性能的影響。性能測試的方法采用的是文獻[19]的分析方法,性能測試的數據是基于頁面所加載的時間,頁面加載的時間是以Chrome Dev Tools Load的時間作為訪問時間,連續訪問10次獲取平均值作為最終的訪問時間。圖4是性能測試的實驗結果。

圖4 隨機化防御性能測試結果
圖4橫坐標標識的是Form表單中的input數量,縱坐標標識頁面加載的時間,每一條曲線表示不同的摘要算法和對稱性加密算法組合一起的隨機化算法。曲線Token表示的是在Form表單中增加一個隱藏的Token,頁面曲線表示只有Form表單(以下均稱為頁面曲線)。
從圖4可以看出,不同的摘要算法和對稱性算法組合在一起的隨機化算法之前沒有很大的差別,但是隨著Form表單中的input數量變多,算法的耗時增加。基于Token的防御方法的耗時保持不變。雖然如此,但是基于隨機化參數名防御方法的耗時與基于Token的防御方法耗時相差不大,最大差距為15 ms,與頁面曲線的差距為25 ms,最大差距為40 ms,不影響用戶的體驗。在空間消耗方面,基于Token的防御方法和基于隨機化參數名的方法的空間消耗是一樣的,對于每一個用戶,只需要保存32位的字符串即可。對于Token來說,保存的是Form表單中的Token域的值,對于基于隨機化參數名的方法來說,保存的是隨機化的密鑰,兩者的長度都是32位。所以,2種方法在空間損耗上面沒有差異性。
在性能損耗方面,可以發現當只有2個input標簽時,基于隨機化算法的耗時與基于Token的防御方法耗時相當。所以,當Form中的input數量較多時,開發者可以有選擇性地只對其中的幾個input標簽進行隨機化,在保證隨機化方法的有效性的同時還可以確保性能開銷較小。
安全性分析主要包括2個方面,即防御方法本身的安全性以及程序使用了防御方法之后的安全性。
對于防御方法本身的安全性分析的前提在于,攻擊者只能發起CSRF攻擊而無法通過其他的攻擊方法使隨機化的防御方法失效,比如通過SQL注入、文件讀取的方式獲得網站的源代碼從而繞過CSRF的驗證。如果攻擊者需要繞過防御方法就必須要知道隨機化密鑰和隨機化的加密算法。密鑰的生成在每一次用戶與網站建立會話時都不一樣,與當前服務器端的時間相關,所以攻擊者無法猜解出密鑰的,同時密鑰是保存在SESSION中的,攻擊者無法通過任何的攻擊手段得到SESSION中的值,從而保證了隨機化密鑰的安全性。至于隨機化加密算法的安全性,由于加密算法是保存在服務器端,除非攻擊者能夠得到網站的源代碼,在一般情況下攻擊者是無法得到源代碼的,因此加密算法也是比較安全的。
本文對比使用了防御方案前后的請求流程,隨機化防御分析如圖5所示。

圖5 隨機化防御分析對比
在圖5中,虛線標識的是使用隨機化防御之后在整個流程中發生變化的地方,包括頁面的隨機化和后臺的驗證操作。其中在客戶端的改變是頁面中的參數的變化,由原來的具有含義的參數變為了無意義的字符串。從前臺頁面中分析,對攻擊者來說,頁面中的參數名發生改變,并沒有增加新的輸入和輸出以及功能方面的問題,因此雖然參數名發生了變化,但是并沒有增加新的攻擊點。后臺服務器端程序的改變在于隨機化密鑰的生成和保存、隨機化參數名以及參數驗證。密鑰的生成是由算法完成,密鑰的保存是保存在程序中的SESSION,SESSION是保存在服務器端的鍵值對,密鑰的生成和存儲對程序的整個功能不會有影響。其次是對Form表單中的參數進行隨機化,通過對參數名進行隨機化的加密操作,不會加入新的輸入輸出和新功能,只會改變頁面中的參數名,因此不會引入新的問題。最后是對參數名的驗證,通過圖5的流程發現,對參數名的檢驗是在服務器處理之前,這種處理方式在目前的Web開發中十分常見,類似Java中的Filter、PHP中的include common.php。如果驗證通過,再由后面后臺服務器的程序進行處理。所以,對參數名的檢驗并不會影響整個程序的功能,只是多增加了對參數名的檢驗和過濾。
因此通過綜合分析,當程序應用了隨機化的防御方案之后,除了在SESSION中保存隨機化的密鑰,并沒有在程序中引用新的變量、新的輸入和輸出,隨機化的參數檢驗不會對程序的整個流程造成影響,這種防御部署方式也是目前Web開發中常用的防御部署方式。
針對目前流行的CSRF攻擊,本文提出一種參數名隨機化CSRF防御方法。該隨機化方法部署在Web服務端,開發者應用本文開發的PHP開源庫,自行選擇需要隨機化的參數名,利用請求和響應中隨機化參數名的一致性識別偽造請求,從而檢測CSRF攻擊。與已有基于客戶端的防御方法相比,隨機化方法的漏報和誤報較低。與已有服務端的Token策略相比,不存在因為JSONP和postMessage的方法使得Token泄露而導致CSRF攻擊的情況。實驗結果表明,CSRF隨機化防御方案比基于Token的方法能夠更加有效地防御CSRF攻擊,同時不影響用戶體驗。隨著其不斷應用,該方案會得到不斷的完善。
為了能夠更加方便地應用基于參數名隨機化CSRF的防御方法,下一步在Web服務器如apache、nginx上以插件的形式進行參數名隨機化,以在不修改程序代碼的情況下完成參數名隨機化的防護工作,同時解決性能開銷的問題。