前段時間 ecology 密集發布了一系列補丁,修復幾個筆者之前儲備的 0day,本文就來介紹其中一個列比較有意思的,以及分享一下相關的挖掘思路。
背景
最近幾個月筆者都在研究 Java Web 方向,一方面是工作職責的調整,另一方面也想挑戰一下新的領域。對于漏洞挖掘而言,選擇一個具體目標是非常重要的,經過一段時間供應鏈和生態的學習以及同事建議,兼顧漏洞挖掘難度和實戰效果選擇了 ecology OA作為第一個漏洞挖掘的目標。
代碼審計
雖然本文介紹的是 Fuzzing,但之前也說過很多次,自動化漏洞挖掘只能作為一種輔助手段,是基于自身對代碼結構的理解基礎上的提效方式。
在真正開始挖漏洞之前,筆者花了好幾周的時間去熟悉目標的代碼,并且對一些不清晰的動態調用去進行運行時分析,最終才能在 20G 代碼之中梳理出大致的鑒權和路由流程。
通過分析 JavaEE 應用注冊的路由,注意到其中一個映射:
ServletMapping[url-pattern=/services/*,name=XFireServlet]^/services(?=/)|^/servicesz]
其對應的類為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 自己的業務代碼,而是一個 SOAP Web 服務框架,它是作為 Apache Axis 的有效替代方案而開發的。除了通過使用 StAX 實現良好性能的目標外,XFire 的目標還包括通過各種插件機制實現靈活性,API 的直觀操作以及與通用標準的兼容性。此外 XFire 非常適合集成到基于 Spring Framework 的項目中。
值得一提的是,XFire 目前已經不再進行開發,其官方繼任者是Apache CXF[2]。
XFire 的用法比較簡單,首先在META-INF/xfire/services.xml
中定義需要導出的服務,比如:
"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
就被認為是導出服務。
可以直接被客戶端進行調用。調用方式主要是通過 SOAP 請求到 XFireServlet,例如調用上述服務可以發送 POST 請求到/services/WorkflowService
:
"1.0"encoding="UTF-8"?>
<Body>
<getUserId>
<p>evilpanp>
<p>2333p>
getUserId>
Body>
表示以指定參數調用服務的getUserId
方法。
SQL 注入
接下來回到漏洞本身,WorkflowService 服務的具體實現為WorkflowServiceImpl
,例如其中的 getUserId 就是服務導出的一個方法,其具體實現為:
@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");
}
}
可以看到,一個教科書式的 SQL 注入就已經找到了。
Service 鑒權
現在漏洞點找到了,觸發路徑也找到了,可實際測試的時候發現這個接口有些特殊的鑒權,其鑒權邏輯為判斷請求地址是否是內網地址,如果是的話就放行。
考慮到很多系統是集群部署的,且前面有一層或者多層負載均衡,因此實際請求服務的可能是經過反向代理的請求,此時客戶端的真實 IP 只能通過X-Forward-For
等頭部獲取。
這本來無可厚非,但是 HTTP 請求頭是可以被攻擊者任意設置的,因此 ecology 在此基礎上進行了復雜的過濾,精簡后的偽代碼如下:
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
的判斷更為復雜,連byte[]
都出來了:
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;
}
}
其邏輯是對 getRemoteAddrProxy 取出來的 IP,如果路徑匹配webserviceList
且 IP 匹配webserviceIpList
前綴,就認為是內網地址的請求從而進行放過:
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."
]
根據上面的代碼,你能發現鑒權繞過的漏洞嗎?
Fuzzing
也許對代碼比較敏感的審計人員可以通過上述鑒權代碼很快發現問題,但說實話我一開始并沒有找到漏洞。于是我想到這個鑒權邏輯是否能單獨抽離出來使用 Fuzzing 的思路去進行自動化測試。
之前發現 Java 也有一個基于 libFuzzer 的模糊測試框架Jazzer[3],但是試用之后發現比較雞肋,因為和二進制程序會自動 Crash 不同,Java 的 fuzz 需要自己指定 Sink,令其在觸達的時候拋出異常來構造崩潰。
雖然說沒法發現通用的漏洞,但是對于現在這個場景來說正好是絕配,我們可以將目標原始的鑒權代碼摳出來,然后在未授權通過的時候拋出一個異常即可。構建的 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;
}
其中精簡了一些 ecology 代碼中讀取配置相關的依賴,將無關的邏輯進行手動剔除。
編譯好代碼后,使用以下命令開始進行 fuzz:
$./jazzer--cp=target/Test-1.0-SNAPSHOT.jar--target_class=org.example.App
不多一會兒,就已經有了一個成功的結果!
![aaffd0a0-5292-11ee-a25d-92fbcf53809c.png](https://file1.elecfans.com/web2/M00/A2/FF/wKgZomUCylqASL_AAAHEy9Vnx9k992.png)
可以看到圖中給出了127.0.0.10
這個 payload,可以觸發 IP 鑒權的繞過!反過來分析這個 PoC,可以發現之所以能繞過是因為webserviceIpList
只檢查了前綴,而127.0.0.10
可以在internalIp
返回False
,即認為不是內部 IP,但實際上 webserviceIpList 卻認為是內部 IP,從而導致了繞過。
如果只是從代碼上去分析的話,可能一時半會并不一定能發現這個問題,可是通過 Fuzzing 在覆蓋率反饋的加持下,卻可以在幾秒鐘之內找到正解,這也是人工審計無法比擬的。
漏洞補丁
通過 IP 的鑒權繞過和 XFire 組件的 SQL 注入,筆者實現了多套前臺的攻擊路徑,并且在 HW 中成功打入多個目標。因為當時提交的報告中帶了漏洞細節,因此這個漏洞自然也就被官方修補了。如果沒有公開的話這個洞短期也不太會被撞到。
漏洞修復的關鍵補丁如下:
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,并且還過濾了我們之前使用的 WorkflowService 組件。當然還是沿襲 ecology 一貫的漏洞修復原則,不改業務代碼,只增加安全校驗,這也是對歷史遺留問題的一種妥協吧。
總結
-
?對于 Java 這樣的內存安全編程語言也是可以 fuzz 的,只不過目的是找出邏輯漏洞而不是內存破壞;
-
?漏洞挖掘初期花時間投入到代碼審計中是有必要的,有助于理解項目整體結構并在后期進行針對性覆蓋;
-
?漏洞挖掘的時候重點關注邊界的系統和服務,處于信任邊界之外的組件更有可能過于信任外部輸入導致安全問題;
-
?對于看起來很復雜的數據處理模塊,可以充分利用 Fuzzing 的優勢,幫助我們快速找出畸形的 payload;
-
?模塊化 Fuzzing 的難點在于抽離代碼并構建可編譯或者可以獨立運行的程序,即構建 Test Harness,跑起來測試用例你就已經成功了 90%;
-
?軟件開發和漏洞挖掘正好相反。開發者會出于厭惡情緒刻意避開復雜的歷史遺留代碼,而這些代碼卻是更可能出現問題的地方。因此安全研究員要學會克服自己的厭惡情緒,做到 —— “明知山有屎,偏向屎山行”。
-
自動化
+關注
關注
29文章
5641瀏覽量
79714 -
編程語言
+關注
關注
10文章
1951瀏覽量
35020 -
代碼
+關注
關注
30文章
4837瀏覽量
69122
原文標題:總結
文章出處:【微信號:哆啦安全,微信公眾號:哆啦安全】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
靈玖軟件:NLPIR智能挖掘系統專注中文處理
AlphaFuzzer漏洞挖掘工具的使用
數據挖掘算法有哪幾種?
數據挖掘淺析
關聯規則挖掘在數據錄入、校對系統中的應用
怎么學習數據挖掘_如何系統地學習數據挖掘
代碼實例及詳細資料帶你入門Python數據挖掘與機器學習
淺析嵌入式數據挖掘模型應用到銀行卡業務中的相關知識
![淺析嵌入式數據<b class='flag-5'>挖掘</b>模型應用到銀行卡業務中的<b class='flag-5'>相關</b>知識](https://file.elecfans.com/web1/M00/91/DE/o4YBAFzbzgWAAPdtAACoKD5q7o4016.png)
評論