前段時(shí)間 ecology 密集發(fā)布了一系列補(bǔ)丁,修復(fù)幾個(gè)筆者之前儲(chǔ)備的 0day,本文就來(lái)介紹其中一個(gè)列比較有意思的,以及分享一下相關(guān)的挖掘思路。
背景
最近幾個(gè)月筆者都在研究 Java Web 方向,一方面是工作職責(zé)的調(diào)整,另一方面也想挑戰(zhàn)一下新的領(lǐng)域。對(duì)于漏洞挖掘而言,選擇一個(gè)具體目標(biāo)是非常重要的,經(jīng)過(guò)一段時(shí)間供應(yīng)鏈和生態(tài)的學(xué)習(xí)以及同事建議,兼顧漏洞挖掘難度和實(shí)戰(zhàn)效果選擇了 ecology OA作為第一個(gè)漏洞挖掘的目標(biāo)。
代碼審計(jì)
雖然本文介紹的是 Fuzzing,但之前也說(shuō)過(guò)很多次,自動(dòng)化漏洞挖掘只能作為一種輔助手段,是基于自身對(duì)代碼結(jié)構(gòu)的理解基礎(chǔ)上的提效方式。
在真正開(kāi)始挖漏洞之前,筆者花了好幾周的時(shí)間去熟悉目標(biāo)的代碼,并且對(duì)一些不清晰的動(dòng)態(tài)調(diào)用去進(jìn)行運(yùn)行時(shí)分析,最終才能在 20G 代碼之中梳理出大致的鑒權(quán)和路由流程。
通過(guò)分析 JavaEE 應(yīng)用注冊(cè)的路由,注意到其中一個(gè)映射:
ServletMapping[url-pattern=/services/*,name=XFireServlet]^/services(?=/)|^/servicesz]
其對(duì)應(yīng)的類(lèi)為org.codehaus.xfire.transport.http.XFireConfigurableServlet
:
<servlet>
<servlet-name>XFireServletservlet-name>
<display-name>XFireServletdisplay-name>
<servlet-class>org.codehaus.xfire.transport.http.XFireConfigurableServletservlet-class>
servlet>
<servlet-mapping>
<servlet-name>XFireServletservlet-name>
<url-pattern>/services/*url-pattern>
servlet-mapping>
XFire 考古
XFire[1]并不是 ecology 自己的業(yè)務(wù)代碼,而是一個(gè) SOAP Web 服務(wù)框架,它是作為 Apache Axis 的有效替代方案而開(kāi)發(fā)的。除了通過(guò)使用 StAX 實(shí)現(xiàn)良好性能的目標(biāo)外,XFire 的目標(biāo)還包括通過(guò)各種插件機(jī)制實(shí)現(xiàn)靈活性,API 的直觀(guān)操作以及與通用標(biāo)準(zhǔn)的兼容性。此外 XFire 非常適合集成到基于 Spring Framework 的項(xiàng)目中。
值得一提的是,XFire 目前已經(jīng)不再進(jìn)行開(kāi)發(fā),其官方繼任者是Apache CXF[2]。
XFire 的用法比較簡(jiǎn)單,首先在META-INF/xfire/services.xml
中定義需要導(dǎo)出的服務(wù),比如:
"1.0"encoding="UTF-8"?>
<beansxmlns="http://xfire.codehaus.org/config/1.0">
<service>
<name>WorkflowServicename>
<namespace>webservices.services.weaver.com.cnnamespace>
<serviceClass>weaver.workflow.webservices.WorkflowServiceserviceClass>
<implementationClass>weaver.workflow.webservices.WorkflowServiceImplimplementationClass>
<serviceFactory>org.codehaus.xfire.annotations.AnnotationServiceFactoryserviceFactory>
service>
beans>
這樣weaver.workflow.webservices.WorkflowService
就被認(rèn)為是導(dǎo)出服務(wù)。
可以直接被客戶(hù)端進(jìn)行調(diào)用。調(diào)用方式主要是通過(guò) SOAP 請(qǐng)求到 XFireServlet,例如調(diào)用上述服務(wù)可以發(fā)送 POST 請(qǐng)求到/services/WorkflowService
:
"1.0"encoding="UTF-8"?>
<Body>
<getUserId>
<p>evilpanp>
<p>2333p>
getUserId>
Body>
表示以指定參數(shù)調(diào)用服務(wù)的getUserId
方法。
SQL 注入
接下來(lái)回到漏洞本身,WorkflowService 服務(wù)的具體實(shí)現(xiàn)為WorkflowServiceImpl
,例如其中的 getUserId 就是服務(wù)導(dǎo)出的一個(gè)方法,其具體實(shí)現(xiàn)為:
@Override
publicStringgetUserId(Stringvar1,Stringvar2){
if(Util.null2String(var2).equals("")){
return"-1";
}elseif(Util.null2String(var1).equals("")){
return"-2";
}else{
RecordSetvar3=newRecordSet();
var3.executeQuery("selectidfromHrmResourcewhere"+var1+"=?andstatus<4?",var2);
return!var3.next()?"0":Util.null2s(var3.getString("id"),"0");
}
}
可以看到,一個(gè)教科書(shū)式的 SQL 注入就已經(jīng)找到了。
Service 鑒權(quán)
現(xiàn)在漏洞點(diǎn)找到了,觸發(fā)路徑也找到了,可實(shí)際測(cè)試的時(shí)候發(fā)現(xiàn)這個(gè)接口有些特殊的鑒權(quán),其鑒權(quán)邏輯為判斷請(qǐng)求地址是否是內(nèi)網(wǎng)地址,如果是的話(huà)就放行。
考慮到很多系統(tǒng)是集群部署的,且前面有一層或者多層負(fù)載均衡,因此實(shí)際請(qǐng)求服務(wù)的可能是經(jīng)過(guò)反向代理的請(qǐng)求,此時(shí)客戶(hù)端的真實(shí) IP 只能通過(guò)X-Forward-For
等頭部獲取。
這本來(lái)無(wú)可厚非,但是 HTTP 請(qǐng)求頭是可以被攻擊者任意設(shè)置的,因此 ecology 在此基礎(chǔ)上進(jìn)行了復(fù)雜的過(guò)濾,精簡(jiǎn)后的偽代碼如下:
privateStringgetRemoteAddrProxy(){
Stringip=null;
StringtmpIp=null;
tmpIp=this.getRealIp(this.request.getHeaders("RemoteIp"),ipList);
if(ip==null||ip.length()==0||"unknown".equalsIgnoreCase(ip)){
ip=tmpIp;
}
booleanisInternalIp=IpUtils.internalIp(ip);
if(isInternalIp){
ipList.add(this.request.getRemoteAddr());
tmpIp=IpUtils.getRealIp(ipList);
if(!IpUtils.internalIp(tmpIp)){
ip=tmpIp;
}
}
returnip!=null&&ip.length()!=0&&!"unknown".equalsIgnoreCase(ip)?ip:null;
}
IpUtils#internalIp
的判斷更為復(fù)雜,連byte[]
都出來(lái)了:
publicstaticbooleaninternalIp(Stringip){
if(ip!=null&&!ip.equals("127.0.0.1")&&!ip.equals("::1")&&!ip.equals("0000:1")){
if(ip.indexOf(":")!=-1&&ip.indexOf(":")==ip.lastIndexOf(":")){
ip=ip.substring(0,ip.indexOf(":"));
}
byte[]addr=(byte[])null;
if(isIpV4(ip)){
addr=textToNumericFormatV4(ip.trim());
}else{
addr=textToNumericFormatV6(ip.trim());
}
returnaddr==null?false:internalIp(addr);
}else{
returntrue;
}
}
publicstaticbooleaninternalIp(byte[]addr){
byteb0=addr[0];
byteb1=addr[1];
byteSECTION_1=true;
byteSECTION_2=true;
byteSECTION_3=true;
byteSECTION_4=true;
byteSECTION_5=true;
byteSECTION_6=true;
switch(b0){
case-84:
if(b1>=16&&b1<=?31){
returntrue;
}
case-64:
switch(b1){
case-88:
returntrue;
}
default:
returnfalse;
case10:
returntrue;
}
}
其邏輯是對(duì) getRemoteAddrProxy 取出來(lái)的 IP,如果路徑匹配webserviceList
且 IP 匹配webserviceIpList
前綴,就認(rèn)為是內(nèi)網(wǎng)地址的請(qǐng)求從而進(jìn)行放過(guò):
webserviceList=[
"/online/syncOnlineData.jsp",
"/services/",
"/system/MobileLicenseOperation.jsp",
"/system/PluginLicenseOperation.jsp",
"/system/InPluginLicense.jsp",
"/system/InMobileLicense.jsp"
];
webserviceIpList=[
"localhost",
"127.0.0.1",
"192.168.",
"10.",
"172.16.",
"172.17.",
"172.18.",
"172.19.",
"172.20.",
"172.21.",
"172.22.",
"172.23.",
"172.24.",
"172.25.",
"172.26.",
"172.27.",
"172.28.",
"172.29.",
"172.30.",
"172.31."
]
根據(jù)上面的代碼,你能發(fā)現(xiàn)鑒權(quán)繞過(guò)的漏洞嗎?
Fuzzing
也許對(duì)代碼比較敏感的審計(jì)人員可以通過(guò)上述鑒權(quán)代碼很快發(fā)現(xiàn)問(wèn)題,但說(shuō)實(shí)話(huà)我一開(kāi)始并沒(méi)有找到漏洞。于是我想到這個(gè)鑒權(quán)邏輯是否能單獨(dú)抽離出來(lái)使用 Fuzzing 的思路去進(jìn)行自動(dòng)化測(cè)試。
之前發(fā)現(xiàn) Java 也有一個(gè)基于 libFuzzer 的模糊測(cè)試框架Jazzer[3],但是試用之后發(fā)現(xiàn)比較雞肋,因?yàn)楹投M(jìn)制程序會(huì)自動(dòng) Crash 不同,Java 的 fuzz 需要自己指定 Sink,令其在觸達(dá)的時(shí)候拋出異常來(lái)構(gòu)造崩潰。
雖然說(shuō)沒(méi)法發(fā)現(xiàn)通用的漏洞,但是對(duì)于現(xiàn)在這個(gè)場(chǎng)景來(lái)說(shuō)正好是絕配,我們可以將目標(biāo)原始的鑒權(quán)代碼摳出來(lái),然后在未授權(quán)通過(guò)的時(shí)候拋出一個(gè)異常即可。構(gòu)建的 Test Harness 代碼如下:
publicstaticvoidfuzzerTestOneInput(FuzzedDataProviderdata){
Stringpoc=data.consumeRemainingAsString();
fuzzIP(poc);
}
publicstaticvoidfuzzIP(Stringpoc){
if(containsNonPrintable(poc))return;
XssRequestWeblogicx=newXssRequestWeblogic();
Stringout=x.getRemoteAddr(poc);
booleancheck2=check2(out);
if(check2){
thrownewFuzzerSecurityIssueHigh("FoundIP["+poc+"]");
}
}
publicstaticbooleancheck2(Stringipstr){
for(Stringip:webserviceIpList){
if(ipstr.startsWith(ip)){
returntrue;
}
}
returnfalse;
}
其中精簡(jiǎn)了一些 ecology 代碼中讀取配置相關(guān)的依賴(lài),將無(wú)關(guān)的邏輯進(jìn)行手動(dòng)剔除。
編譯好代碼后,使用以下命令開(kāi)始進(jìn)行 fuzz:
$./jazzer--cp=target/Test-1.0-SNAPSHOT.jar--target_class=org.example.App
不多一會(huì)兒,就已經(jīng)有了一個(gè)成功的結(jié)果!

可以看到圖中給出了127.0.0.10
這個(gè) payload,可以觸發(fā) IP 鑒權(quán)的繞過(guò)!反過(guò)來(lái)分析這個(gè) PoC,可以發(fā)現(xiàn)之所以能繞過(guò)是因?yàn)?code style="line-height:1.75;font-size:12.6px;color:rgb(221,17,68);background:rgba(27,31,35,.05);padding:3px 5px;">webserviceIpList只檢查了前綴,而127.0.0.10
可以在internalIp
返回False
,即認(rèn)為不是內(nèi)部 IP,但實(shí)際上 webserviceIpList 卻認(rèn)為是內(nèi)部 IP,從而導(dǎo)致了繞過(guò)。
如果只是從代碼上去分析的話(huà),可能一時(shí)半會(huì)并不一定能發(fā)現(xiàn)這個(gè)問(wèn)題,可是通過(guò) Fuzzing 在覆蓋率反饋的加持下,卻可以在幾秒鐘之內(nèi)找到正解,這也是人工審計(jì)無(wú)法比擬的。
漏洞補(bǔ)丁
通過(guò) IP 的鑒權(quán)繞過(guò)和 XFire 組件的 SQL 注入,筆者實(shí)現(xiàn)了多套前臺(tái)的攻擊路徑,并且在 HW 中成功打入多個(gè)目標(biāo)。因?yàn)楫?dāng)時(shí)提交的報(bào)告中帶了漏洞細(xì)節(jié),因此這個(gè)漏洞自然也就被官方修補(bǔ)了。如果沒(méi)有公開(kāi)的話(huà)這個(gè)洞短期也不太會(huì)被撞到。
漏洞修復(fù)的關(guān)鍵補(bǔ)丁如下:
diff--gita/src/weaver/security/webcontainer/IpUtils.javab/src/weaver/security/webcontainer/IpUtils.java
index6b3d8efc..e7482511100644
---a/src/weaver/security/webcontainer/IpUtils.java
+++b/src/weaver/security/webcontainer/IpUtils.java
@@-48,12+48,16@@publicclassIpUtils{
}
publicstaticbooleaninternalIp(Stringip){
-if(ip!=null&&!ip.equals("127.0.0.1")&&!ip.equals("::1")&&!ip.equals("0000:1")){
+if(ip==null||ip.equals("127.0.0.1")||ip.equals("::1")||ip.equals("0000:1")){
+returntrue;
+}elseif(ip.startsWith("127.0.0.")){
+returntrue;
+}else{
if(ip.indexOf(":")!=-1&&ip.indexOf(":")==ip.lastIndexOf(":")){
ip=ip.substring(0,ip.indexOf(":"));
}
其中把 equals 換成了 startsWith,并且還過(guò)濾了我們之前使用的 WorkflowService 組件。當(dāng)然還是沿襲 ecology 一貫的漏洞修復(fù)原則,不改業(yè)務(wù)代碼,只增加安全校驗(yàn),這也是對(duì)歷史遺留問(wèn)題的一種妥協(xié)吧。
總結(jié)
-
?對(duì)于 Java 這樣的內(nèi)存安全編程語(yǔ)言也是可以 fuzz 的,只不過(guò)目的是找出邏輯漏洞而不是內(nèi)存破壞;
-
?漏洞挖掘初期花時(shí)間投入到代碼審計(jì)中是有必要的,有助于理解項(xiàng)目整體結(jié)構(gòu)并在后期進(jìn)行針對(duì)性覆蓋;
-
?漏洞挖掘的時(shí)候重點(diǎn)關(guān)注邊界的系統(tǒng)和服務(wù),處于信任邊界之外的組件更有可能過(guò)于信任外部輸入導(dǎo)致安全問(wèn)題;
-
?對(duì)于看起來(lái)很復(fù)雜的數(shù)據(jù)處理模塊,可以充分利用 Fuzzing 的優(yōu)勢(shì),幫助我們快速找出畸形的 payload;
-
?模塊化 Fuzzing 的難點(diǎn)在于抽離代碼并構(gòu)建可編譯或者可以獨(dú)立運(yùn)行的程序,即構(gòu)建 Test Harness,跑起來(lái)測(cè)試用例你就已經(jīng)成功了 90%;
-
?軟件開(kāi)發(fā)和漏洞挖掘正好相反。開(kāi)發(fā)者會(huì)出于厭惡情緒刻意避開(kāi)復(fù)雜的歷史遺留代碼,而這些代碼卻是更可能出現(xiàn)問(wèn)題的地方。因此安全研究員要學(xué)會(huì)克服自己的厭惡情緒,做到 —— “明知山有屎,偏向屎山行”。
-
自動(dòng)化
+關(guān)注
關(guān)注
29文章
5754瀏覽量
82300 -
編程語(yǔ)言
+關(guān)注
關(guān)注
10文章
1955瀏覽量
36274 -
代碼
+關(guān)注
關(guān)注
30文章
4891瀏覽量
70358
原文標(biāo)題:總結(jié)
文章出處:【微信號(hào):哆啦安全,微信公眾號(hào):哆啦安全】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
云計(jì)算數(shù)據(jù)挖掘
靈玖軟件:NLPIR智能挖掘系統(tǒng)專(zhuān)注中文處理
AlphaFuzzer漏洞挖掘工具的使用
數(shù)據(jù)挖掘算法有哪幾種?
數(shù)據(jù)挖掘之基于關(guān)聯(lián)挖掘的商品銷(xiāo)售分析
機(jī)器學(xué)習(xí)與數(shù)據(jù)挖掘方法和應(yīng)用
數(shù)據(jù)挖掘淺析
關(guān)聯(lián)規(guī)則挖掘在數(shù)據(jù)錄入、校對(duì)系統(tǒng)中的應(yīng)用
怎么學(xué)習(xí)數(shù)據(jù)挖掘_如何系統(tǒng)地學(xué)習(xí)數(shù)據(jù)挖掘
代碼實(shí)例及詳細(xì)資料帶你入門(mén)Python數(shù)據(jù)挖掘與機(jī)器學(xué)習(xí)
淺析嵌入式數(shù)據(jù)挖掘模型應(yīng)用到銀行卡業(yè)務(wù)中的相關(guān)知識(shí)

成為數(shù)據(jù)挖掘工程師有哪些要求
某CMS的命令執(zhí)行漏洞通用挖掘思路分享

評(píng)論