EMwI插件更新:防XSS攻擊

8月底的時候External Media without Import插件在github上收到一個pull request。對方指出我的代碼存在XSS漏洞。慚愧,直到最近才騰出時間仔細研究他說的問題。插件的1.0.2版本合併了對方的pull request,修復了該漏洞。


在修復之前,我的插件中有如下代碼(點擊查看源文件):


function admin_post_add_external_media_without_import() {
	$info = add_external_media_without_import();
	$redirect_url = 'upload.php';
	if ( ! isset( $info['id'] ) ) {
		$redirect_url = $redirect_url .  '?page=add-external-media-without-import&url=' . urlencode( $info['url'] );
		$redirect_url = $redirect_url . '&error=' . urlencode( $info['error'] );
		$redirect_url = $redirect_url . '&width=' . urlencode( $info['width'] );
		$redirect_url = $redirect_url . '&height=' . urlencode( $info['height'] );
		$redirect_url = $redirect_url . '&mime-type=' . urlencode( $info['mime-type'] );
	}
	wp_redirect( admin_url( $redirect_url ) );
	exit;
}

這個函數將width 、height 和mime-type等信息通過urlencode編碼後設置爲url的參數,然後重定向到該url。在url的響應函數中,我調用了urldecode讀取來這些信息:


<label><?php echo __('Width'); ?></label>
<input id="emwi-width" name="width" type="number" value="<?php echo urldecode( $_GET['width'] ); ?>">
<label><?php echo __('Height'); ?></label>
<input id="emwi-height" name="height" type="number" value="<?php echo urldecode( $_GET['height'] ); ?>">
<label><?php echo __('MIME Type'); ?></label>
<input id="emwi-mime-type" name="mime-type" type="text" value="<?php echo urldecode( $_GET['mime-type'] ); ?>">


問題就出在這段響應函數的代碼中。從$_GET中取出的值已經是經過urldecode 的。對$_GET再調用urlencode就可能會出問題。比如$info['mime-type'] = 'image/svg+xml' 的情況下,urlencode( $info['mime-type'] )的結果是'image%2fsvn%2bxml' , $_GET['mime-type']的值已經是解碼後的'image/svn+xml' ,再對它調用一次urldecode就變成了'image/svn xml' ,這就導致了錯誤的mime-type。PHP官方文檔對這個問題也有說明


然而直接使用$_GET也有安全性問題。以響應函數中將mime-type的值顯示在輸入框的代碼爲例,假設我們直接將$_GET的值打印到input的value中:


<input id="emwi-mime-type" name="mime-type" type="text" value="<?php echo $_GET['mime-type']; ?>">

這種情況下如果$_GET['mime-type'] 的值當中包含一段可執行代碼,那其中的代碼就會在瀏覽器中執行。比如,如果url的末尾是mime-type="><script>alert(window.location.hash)%3B<%2Fscript>"#Attack!,那麼上面的php代碼在瀏覽器中的輸出就會變成下面這樣:


<input id="emwi-mime-type" name="mime-type" type="text" value="\"><script>alert(window.location.hash);</script>"\"">

(上面這行代碼是在Chrome中實驗得出的結果,其中的\可能是瀏覽器自己轉義加上的。)

於是用戶瀏覽網頁的時候,alert 調用就會被執行,彈出Attack!提示框。

這樣會有什麼問題呢?假如有一個惡意用戶老王。他構造了一個url,url的mime-type(或其它任意參數)實際包含了一段老王自己編寫的代碼。然後他把這個url發給小紅讓小紅點擊。不明就裏的小紅點擊了這個url之後,由於網站的PHP代碼不夠健壯,將$_GET 直接輸出到了網頁中,因此老王的代碼就得以在小紅的瀏覽器中執行。這時老王的代碼就可以做很多事,比如竊取小紅瀏覽器裏的cookie,以及存儲在localStorage裏的用戶名、密碼等隱私信息。

老王的這種手段就叫XSS攻擊,全稱Cross-Site Scripting,跨站腳本攻擊。其原理和SQL注入類似,均是利用網站後臺代碼的漏洞,在參數裏填入可執行代碼,從而將攻擊者的代碼注入到本不允許其執行的地方(在XSS場景中就是用戶瀏覽器,在SQL注入場景中則是後臺數據庫),從而實現攻擊者的目的。

爲了應對XSS攻擊,就需要對$_GET 的值做些處理,對其中的特殊字符進行轉義,比如將<轉換成&lt;,然後才能將其輸出到網頁的html中。WordPress爲此提供了一個很方便的函數:esc_html (見:https://codex.wordpress.org/Function_Reference/esc_html)。這個函數不但會對html特殊字符進行轉義,還會檢查非法的UTF-8字符,考慮了各種情況。

因此將$_GET 的值輸出到網頁之前應先對其調用esc_html ,於是上文將$_GET 的值打印到input的value中的代碼應改爲:


<input id="emwi-mime-type" name="mime-type" type="text" value="<?php echo esc_html( $_GET['mime-type'] ); ?>">

這樣整個$_GET['mime-type'] 的值都會被當成純字符串而不存在被執行的可能。

值得一提的是,現代主流瀏覽器大都已經對XSS攻擊採取了防護措施。比如在Chrome上瀏覽含有XSS漏洞的網頁的話,其實會被Chrome攔截(Safari也一樣):




如果要重現上文寫到的XSS漏洞,允許攻擊者代碼執行的話,需要從命令行啓動Chrome,並且用--disable-xss-auditor 選項關閉Chrome的XSS檢查。Mac上的啓動命令如下:


'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome' --disable-xss-auditor

本文在我的獨立博客上的地址:http://zxtechart.com/2017/10/24/emwi-xss/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章