臺(tái)灣白帽O(jiān)range Tsai(蔡政達(dá))受邀前往本屆 Black Hat USA 和 DEFCON 26發(fā)表議題演講,在《Breaking Parser Logic! Take Your Path Normalization Off and Pop 0days Out》的演講中,他分享了如何基于“不一致性”安全問題,綜合利用4個(gè)功能性Bug,實(shí)現(xiàn)對(duì)亞馬遜(Amazon)協(xié)同平臺(tái)系統(tǒng)的遠(yuǎn)程代碼執(zhí)行。
以下是他的詳細(xì)技術(shù)分享:
背景說明
在過去兩年時(shí)間里,我重點(diǎn)在研究一些“不一致(inconsistency)”的安全問題,這是什么問題呢?這就有點(diǎn)類似我去年在 Black Hat 的演講以及《GitHub SSRF to RCE》的研究一樣,我先通過發(fā)現(xiàn)URL解析器和URL獲取器之間的不一致問題,形成了整體SSRF繞過,最終實(shí)現(xiàn)更嚴(yán)重的漏洞利用。
另外,這篇由@0x09AL寫的文章《Bypassing Web-Application Firewalls by abusing SSL/TLS》,也詳細(xì)闡述了 “不一致“ 安全問題導(dǎo)致的重要漏洞,值得拜讀。
有了之前的基礎(chǔ),今年我著重研究了路徑解析器和規(guī)范化之間的不一致安全問題。理論上來說,因?yàn)椴煌瑢?duì)象實(shí)體具備不同的標(biāo)準(zhǔn)和實(shí)現(xiàn)需求,所以很難開發(fā)出一款設(shè)計(jì)嚴(yán)格而全面的解析器。但當(dāng)解析器出現(xiàn)安全Bug時(shí),為了不影響業(yè)務(wù)邏輯,研發(fā)上通常的做法是采用某種替代方法或是增加某種過濾器,而不是直接給Bug打補(bǔ)丁,最后的影響是治標(biāo)不治本。所以,這樣一來,如果過濾器和調(diào)用方法之間存在任何不一致問題,就可能輕松繞過系統(tǒng)本身設(shè)置的安全機(jī)制。
當(dāng)我在閱讀一些漏洞分析報(bào)告時(shí),我注意到了一種叫”URL路徑參數(shù)“( URL Path Parameter)的功能特性。一些研究人員已經(jīng)指出,如果編程出現(xiàn)錯(cuò)誤,這種特性可能會(huì)導(dǎo)致安全問題。通過點(diǎn)點(diǎn)滴滴的關(guān)聯(lián)分析,我發(fā)現(xiàn)這種特性可以完美地應(yīng)用在多層體系結(jié)構(gòu)中,而且默認(rèn)情況下,不必編碼出錯(cuò),就存在攻擊面,可導(dǎo)致漏洞利用。如果你在反向代理中使用了Java后端服務(wù),那么就可能存在這種漏洞!
早先在2015年時(shí),我首先是在一次紅隊(duì)測(cè)試中發(fā)現(xiàn)了這種攻擊面,之后,我覺得這個(gè)問題威力超強(qiáng),也想看看安全圈內(nèi)的知悉面,于是,我就在WCTF 2016比賽的自出題中設(shè)計(jì)了一道相關(guān)題目。
WCTF是由Belluminar和360共同舉辦的比賽,這與其他CTF比賽中的解題模式(Jeopardy)和攻防模式(Attack-Defense)不一樣,它邀請(qǐng)了全球各國(guó)的前10名團(tuán)隊(duì),每個(gè)團(tuán)隊(duì)都需要設(shè)計(jì)兩道挑戰(zhàn)題目,所以總共有20個(gè)挑戰(zhàn)題目。你解題數(shù)量越多,你得到的點(diǎn)數(shù)就越多。然而,最后卻沒人能解出我出的這道題目。所以,那時(shí)我認(rèn)為這種技術(shù)可能還并不為大多數(shù)人知曉。另外,我也對(duì)DirBuster、wFuzz、DirB 和 DirSearch這些掃描器作了測(cè)試,但只有DirSearch在2017年5月加入了這種掃描規(guī)則。
因此,今年我打算分享這個(gè)議題。但為了說服Black Hat 的審查委員會(huì),我需要有力的用例支撐。所以,我又重拾挖洞,然而在測(cè)試中我發(fā)現(xiàn),這種攻擊面不僅可以造成敏感信息泄露,還能繞過訪問控制列表(像我發(fā)現(xiàn)的這個(gè)優(yōu)步OneLogin登錄繞過漏洞),在某些漏洞眾測(cè)項(xiàng)目中還能導(dǎo)致遠(yuǎn)程代碼執(zhí)行(RCE)。在這篇文章中,我就來介紹,利用這種 “不一致” 攻擊面問題,綜合4個(gè)功能Bug,實(shí)現(xiàn)對(duì)亞馬遜協(xié)同平臺(tái)的遠(yuǎn)程代碼執(zhí)行(RCE)。
多層架構(gòu)的不一致性,可以形象的用以下圖片來表示:
前言
首先,感謝亞馬遜(Amazon)開放的漏洞披露策略,與亞馬遜安全團(tuán)隊(duì)Nuxeo的合作非常順暢,僅從漏洞上報(bào)進(jìn)程來看,就可以看出亞馬遜快速的漏洞響應(yīng)速度,以及他們積極的應(yīng)對(duì)措施。
開始我們要從網(wǎng)站 corp.amazon.com 說起,這貌似是亞馬遜的一個(gè)內(nèi)部協(xié)同系統(tǒng),從網(wǎng)站的底部版權(quán)信息來看,該系統(tǒng)由開源項(xiàng)目Nuxeo部署構(gòu)建。而Nuxeo又是一個(gè)龐大的Java項(xiàng)目,且剛開始我只是想提高一下我的Java審計(jì)技術(shù)。所以故事就從這里說起吧!
四個(gè)漏洞(Bug)
對(duì)我來說,當(dāng)我拿到Java源碼時(shí),我首先會(huì)看看 pom.xml 配置文件,然后再去查找是否存在過期的引用包。在Java生態(tài)系統(tǒng)中,很多漏洞都像 OWASP Top 10 – A9 描述的組件漏洞那樣,如涉及Struts2,、FastJSON、XStream等反序列化組件時(shí),就可能存在漏洞
pom.xml主要描述了項(xiàng)目的maven坐標(biāo),依賴關(guān)系,開發(fā)者需要遵循的規(guī)則,缺陷管理系統(tǒng),組織和licenses,以及其他所有的項(xiàng)目相關(guān)因素,是項(xiàng)目級(jí)別的配置文件。
這里的Nuxeo項(xiàng)目中,初看貌似其中的包都是最新的,但我卻發(fā)現(xiàn)了一個(gè)”老朋友“ – Seam框架。Seam是基于 JBoss 的web框架,隸屬紅帽Linux系統(tǒng)的分支,在早前幾年非常流行,但現(xiàn)在仍然存在大量基于Seam的web應(yīng)用。
我曾在2016年對(duì)Seam進(jìn)行過審計(jì),也發(fā)現(xiàn)了其中一些風(fēng)險(xiǎn)隱患,但最終在這里,貌似也無法直接照搬實(shí)現(xiàn)。我們先繼續(xù)往下分析。
BUG 01:路徑規(guī)范化錯(cuò)誤導(dǎo)致的訪問控制列表(ACL)繞過
當(dāng)從WEB-INF/web.xml文件中查看訪問策略時(shí),我發(fā)現(xiàn)Nuxeo項(xiàng)目使用了一個(gè)通用的驗(yàn)證過濾器NuxeoAuthenticationFilter,并把/*類型目錄映射到了這個(gè)過濾器上。這種驗(yàn)證機(jī)制下,大部份網(wǎng)頁(yè)都需要進(jìn)行驗(yàn)證,但也存在一個(gè)包含login.jsp這樣頁(yè)面的訪問入口白名單,所有這些功能都由一個(gè)名為bypassAuth的方法來具體實(shí)現(xiàn):
protected boolean bypassAuth(HttpServletRequest httpRequest) {
// init unAuthenticatedURLPrefix
try {
unAuthenticatedURLPrefixLock.readLock().lock();
String requestPage = getRequestedPage(httpRequest);
for (String prefix : unAuthenticatedURLPrefix) {
if (requestPage.startsWith(prefix)) {
return true;
}
}
} finally {
unAuthenticatedURLPrefixLock.readLock().unlock();
}
// ...
return false;
}
從上述代碼可知,bypassAuth方法會(huì)檢索當(dāng)前請(qǐng)求頁(yè)面與unAuthenticatedURLPrefix作一個(gè)比較,但是,bypassAuth方法是如何去檢索當(dāng)前請(qǐng)求頁(yè)面的呢?為此,Nuxeo寫了一個(gè)getRequestedPage方法來從HttpServletRequest.RequestURI中提取出當(dāng)前的請(qǐng)求頁(yè)面,這樣一來,第一個(gè)問題就出在這里了!
protected static String getRequestedPage(HttpServletRequest httpRequest) {
String requestURI = httpRequest.getRequestURI();
String context = httpRequest.getContextPath() + '/';
String requestedPage = requestURI.substring(context.length());
int i = requestedPage.indexOf(';');
return i == -1 requestedPage : requestedPage.substring(0, i);
}
為了去處理URL路徑參數(shù),Nuxeo用分號(hào)對(duì)所有的尾部進(jìn)行了截?cái)啵牵琔RL路徑參數(shù)的行為是各式各樣的,每個(gè)web服務(wù)器都有自己的實(shí)現(xiàn)方式,Nuxeo的這種處理方式在WildFly、JBoss 和 WebLogic中可能會(huì)很安全,但在這里的Tomcat下可能就有問題了。也就是,getRequestedPage方法和 Servlet 容器之間的差異導(dǎo)致的安全問題!
由于它的截?cái)鄼C(jī)制,我們就能偽造一個(gè)與訪問控制列表(ACL)白名單匹配,但又是Servlet 容器中未經(jīng)授權(quán)的請(qǐng)求。這里,我們選擇login.jsp作為前綴請(qǐng)求文件,訪問控制列表(ACL)繞過的請(qǐng)求如下:
$ curl -I https://collaborate-corp.amazon.com/nuxeo/[unauthorized_area]
HTTP/1.1 302 Found
Location: login.jsp
...
$ curl -I https://collaborate-corp.amazon.com/nuxeo/login.jsp;/..;/[unauthorized_area]
HTTP/1.1 500 Internal Server Error
...
從上可以看到,我們?cè)O(shè)計(jì)了繞過重定向進(jìn)行身份驗(yàn)證的請(qǐng)求,但多數(shù)響應(yīng)頁(yè)面返回的是一個(gè)500的錯(cuò)誤。因?yàn)?servlet 邏輯無法獲取到一個(gè)有效的用戶框架信息,所以它拋出了一個(gè) Java 的NullPointerException異常。盡管出現(xiàn)這樣的錯(cuò)誤,我們還是一樣可以從中找到突破口。(PS:除了這種方法之外,我還發(fā)現(xiàn)了另外一種快捷的入侵方式,留作下次再分享)
BUG 02:代碼重用功能導(dǎo)致的部分表達(dá)式調(diào)用(EL invocation)
像我前述的,在Seam框架中其實(shí)存在很多風(fēng)險(xiǎn)隱患,因此,對(duì)于我來說,下一步就是盡量嘗試?yán)蒙厦娣治龅牡谝粋(gè)BUG來實(shí)現(xiàn)對(duì) Seam 中的 servlet 進(jìn)行未授權(quán)訪問測(cè)試。接下來,我會(huì)詳細(xì)解釋其中涉及的不同應(yīng)用功能。
為了對(duì)瀏覽器的重定向跳轉(zhuǎn)進(jìn)行控制,Seam調(diào)用了一系列的HTTP參數(shù),而且這些參數(shù)都存在隱患,如actionOutcome就是其中之一。在2013年,研究人員@meder就在其中發(fā)現(xiàn)了一個(gè)遠(yuǎn)程代碼執(zhí)行漏洞,你可以認(rèn)真讀讀他的這篇文章-《 CVE-2010-1871: JBoss Seam Framework remote code execution》,但在這里,我們要來討論的是另一個(gè)參數(shù) – actionMethod。
actionMethod 是一個(gè)特殊的參數(shù),它會(huì)從查詢字符串中調(diào)用特定的 JBoss 表達(dá)式,這種方式貌似不安全,但調(diào)用也是有一些前提條件的。具體的實(shí)現(xiàn)過程可在callAction中查看到:
https://github.com/seam2/jboss-seam/blob/f3077fee9d04b2b3545628cd9e6b58c859feb988/jboss-seam/src/main/java/org/jboss/seam/navigation/Pages.java#L697
如果要調(diào)用表達(dá)式(EL),必須要滿足以下前提條件:
1 參數(shù)actionMethod的值必須是配對(duì)的,也就是像這樣的 FILENAME:EL_CODE
2 FILENAME部份的值必須是在Nuxeo中context-root下的真實(shí)文件
3 FILENAME對(duì)應(yīng)的真實(shí)文件中必須包含”#{EL_CODE}”(包括兩個(gè)雙引號(hào))
這種FILENAME對(duì)應(yīng)的真實(shí)文件就像以下這個(gè) login.xhtml 文件一樣:
div class="entry">
div class="label">
h:outputLabel id="UsernameLabel" for="username">Username:h:outputLabel>
div>
div class="input">
s:decorate id="usernameDecorate">
h:inputText id="username" value="#{user.username}" required="true">h:inputText>
s:decorate>
div>
div>
這樣,你就可以通過下述URL鏈接來調(diào)用表達(dá)式 user.username :
http://host/whatever.xhtmlactionMethod=/foo.xhtml:user.username
BUG 03:二次評(píng)估判斷導(dǎo)致的表達(dá)式注入(EL injection)
上一個(gè)BUG中的功能錯(cuò)誤看起來比較符合,但是卻不能控制context-root下的任意文件,這樣也就無法在遠(yuǎn)程目標(biāo)服務(wù)器中調(diào)用任意表達(dá)式(EL)。然而這里卻存在一個(gè)特別厲害的功能BUG….:
嚴(yán)重點(diǎn)說就是,如果上一個(gè)BUG能返回一個(gè)字符串,并且這個(gè)字符串看起來像一個(gè)表達(dá)式,那么 Seam 框架將會(huì)被再次調(diào)用!
以下是一些詳細(xì)的調(diào)用棧信息:
callAction(Pages.java)
handleOutcome(Pages.java)
handleNavigation(SeamNavigationHandler.java)
interpolateAndRedirect(FacesManager.java)
interpolate(Interpolator.java)
interpolateExpressions(Interpolator.java)
createValueExpression(Expressions.java)
利用這個(gè)酷炫的功能,如果我們能控制返回值,也就能間接執(zhí)行任意表達(dá)式(EL)了!這就有點(diǎn)像二進(jìn)制漏洞利用中的返回導(dǎo)向編程技術(shù)(Return-Oriented Programming, ROP),所以,剩下來的就是要找到一個(gè)具備這種字符串返回功能的適合組件來實(shí)現(xiàn)BUG利用了。
在這個(gè)用例中,我們就選擇widgets/suggest_add_new_directory_entry_iframe.xhtml中的組件:
set var="directoryNameForPopup"
value="#{request.getParameter('directoryNameForPopup')}"
cache="true">
set var="directoryNameForPopup"
value="#{nxu:test(empty directoryNameForPopup, select2DirectoryActions.directoryName, directoryNameForPopup)}"
cache="true">
if test="#{not empty directoryNameForPopup}">
為什么選擇這個(gè)呢?因?yàn)槠渲械膔equest.getParameter會(huì)返回一個(gè)我們可以控制的,來自查詢字符串中的字符串!雖然整個(gè)標(biāo)記是為了分配一個(gè)變量,但我們可對(duì)其語法語義進(jìn)行濫用!
所以,基于上述這段代碼,我們可以把第二階段 Payload 放到變量 directoryNameForPopup 中,然后再利用第一個(gè)BUG,綜合起來就能實(shí)現(xiàn)無需驗(yàn)證的任意表達(dá)式(EL)執(zhí)行了,以下是PoC:
http://host/nuxeo/login.jsp;/..;/create_file.xhtml
actionMethod=widgets/suggest_add_new_directory_entry_iframe.xhtml:request.getParameter('directoryNameForPopup')
&directoryNameForPopup=/#{HERE_IS_THE_EL}
難道就只能這樣了嗎?當(dāng)然不!雖然我們可以執(zhí)行任意表達(dá)式,但卻無法成功反彈控制shell。接著往下看!
BUG 04:繞過表達(dá)式黑名單導(dǎo)致的遠(yuǎn)程代碼執(zhí)行漏洞(RCE)
Seam 官方也清楚其中的表達(dá)式語言(EL)存在的問題,所以,自Seam 2.2.2.Final版本之后,就在其中加入了一個(gè)新的表達(dá)式黑名單,用它來阻止一些不安全的調(diào)用!而且不幸的是, Nuxeo開源項(xiàng)目?jī)?nèi)置使用了 Seam 的 2.3.1.Final 最新版本,所以,我們必須要找到一種能成功繞過表達(dá)式黑名單的有效方法。而該表達(dá)式黑名單可以在 resources/org/jboss/seam/blacklist.properties 中找到:
.getClass(
.class.
.addRole(
.getPassword(
.removeRole(
經(jīng)過一番鉆研,我發(fā)現(xiàn)這種黑名單機(jī)制僅只是簡(jiǎn)單的字符串匹配規(guī)則,眾所周知,黑名單機(jī)制通常都不算是一種好策略。初次看到這個(gè)黑名單,我就想到了對(duì)Struts2 S2-020的繞過方法,它和這里的黑名單繞過有著異曲同工之妙,它們都使用了類似數(shù)組的操作符來繞過黑名單機(jī)制,只需把這里的:
"".getClass().forName("java.lang.Runtime")
修改為:
""["class"].forName("java.lang.Runtime")
就OK了,是不是非常簡(jiǎn)單!是的!好了!
所以,現(xiàn)在最后的剩下的事情就是利用 JBoss 表達(dá)式語言(EL)編寫 shellcode 了,采用Java 反射 API來獲取 java.lang.Runtime 對(duì)象,并列出其中所有的涉及方法。getRuntime()方法會(huì)返回一個(gè) Runtime 實(shí)例, exec(String)方法則會(huì)執(zhí)行我們的預(yù)置命令!
綜合以上Bug01、Bug02、Bug03和Bug04,就能實(shí)現(xiàn)RCE漏洞的執(zhí)行。以下就是大致的實(shí)現(xiàn)步驟:
1、用路徑規(guī)范化錯(cuò)誤造成訪問控制列表(ACL)繞過;
2、繞過白名單機(jī)制實(shí)現(xiàn)未授權(quán)的 Seam servlet 訪問;
3、使用Seam功能的actionMethod參數(shù)去調(diào)用文件中的合適組件suggest_add_new_directory_entry_iframe.xhtml;
4、在HTTP參數(shù)directoryNameForPopup中準(zhǔn)備第二階段Payload;
5、使用類似數(shù)組的操作符來繞過表達(dá)式語言(EL)黑名單;
6、用 Java 反射型 API 來編寫shellcode;
7、靜待反彈控制 shell,成為黑客大佬。
以下就是整個(gè)漏洞利用exploit:
執(zhí)行最終的Perl測(cè)試腳本,可以成功獲取到反彈控制shell:
修復(fù)措施
JBoss
因?yàn)镾eam框架存在的安全隱患最為直接,所以我曾在2016年9月曾把這些功能性Bug,以郵件方式通報(bào)給了它的官方應(yīng)用商JBoss (security@jboss.org),但得到的卻是這樣的回復(fù):
非常感謝你的漏洞通報(bào)。
目前,Seam只包含在我們 JBOSS 企業(yè)應(yīng)用平臺(tái)(EAP)的 5.0版本中,并不包含在其中的6和7版本中。而且,JBOSS 企業(yè)應(yīng)用平臺(tái)(EAP)即將在2016年11月停止維護(hù)更新,你用來測(cè)試的上游版本是3年前就發(fā)布的了。
在我們對(duì)JBOSS 企業(yè)應(yīng)用平臺(tái)(EAP)5.0版本的維護(hù)更新中,我們只接收一些高危或關(guān)鍵的漏洞問題。你強(qiáng)調(diào)的RCE漏洞實(shí)現(xiàn),前提需要在攻擊中先上傳一個(gè)文件,所以某種程度上來說,這就會(huì)致使漏洞影響降級(jí)。
在Seam項(xiàng)目生命周期的這個(gè)階段,我們不會(huì)費(fèi)心去修復(fù)這類安全問題。非常感謝,也希望你今后繼續(xù)向我們通報(bào)安全問題。
所以,由于項(xiàng)目的終止問題,這些問題功能Bug從未得到過官方的補(bǔ)丁修復(fù),但是,現(xiàn)實(shí)網(wǎng)絡(luò)世界中卻還存在著大量基于Seam的應(yīng)用。所以,如果你用了Seam,建議你盡快采用Nuxeo給出的方案來緩解這些問題。
Amazon
經(jīng)過快速響應(yīng)調(diào)查,Amazon安全團(tuán)隊(duì)第一時(shí)間隔離了存在漏洞的協(xié)同平臺(tái)服務(wù)器,和我及時(shí)討論了相關(guān)緩解措施,并告知了我他們具體的修復(fù)措施。
Nuxeo
經(jīng)由Amazon的通報(bào),Nuxeo也快速的釋出了8.10的一個(gè)補(bǔ)丁,該補(bǔ)丁通過覆蓋了callAction() 方法來修復(fù)了其中的功能性Bug。具體請(qǐng)參考這里的補(bǔ)丁文件
|