红队实录系列(一)-从 NodeJS 代码审计到内网突破

概述

此实录起因是公司的一场红蓝对抗实战演习,首先通过内部自研资产平台通过分布式扫描对目标资产进行全端口指纹识别。在目标的一个IP资产开放了一个高端口Web服务,进一步通过指纹识别的方式发现是 OnlyOffice 服务。第一次遇到 OnlyOffice 服务(Express框架),后续整个渗透流程就是通过老洞到分析代码找到新利用方式,最终通过任意文件写新利用方式实现了RCE,突破进入内网。

老洞新用

资产收集找到了如图所示的资产,即使从来没碰到过,根据页面也能发现使用的是OnlyOffice。

扫描发现存在/index.html路由,直接拿到了目标的版本号,在dockerhub上找到了一样的版本onlyoffice/documentserver:5.4.2.46,5.4.2已经是3年前的版本了,很可能会存在一些老洞。 

 

搜索发现了存在多个老洞,https://github.com/moehw/poc_exploits,CVE-2021-3199是一个任意文件写漏洞,影响小于5.6.3的版本,并且还有POC https://github.com/moehw/poc_exploits/blob/master/CVE-2021-3199/poc_uploadImageFile.py

使用该POC验证我们的目标时,文件上传都无法成功。分析发现CVE-2021-3199 是利用的uploadImageFile方法,存在一定的限制。

var format = formatChecker.getImageFormat(buffer, undefined);
var formatStr = formatChecker.getStringFromFormat(format);

if (encrypted && PATTERN_ENCRYPTED === buffer.toString('utf8', 0, PATTERN_ENCRYPTED.length)) {
  formatStr = buffer.toString('utf8', PATTERN_ENCRYPTED.length, buffer.indexOf(';', PATTERN_ENCRYPTED.length));
}

uploadImageFile方法中,formatStr变量来自于根据文件内容进行识别。如果encrypted为true,那么就会从post body中解析出formatStr,从而实现控制文件名。

if (cfgTokenEnableBrowser) {
  let checkJwtRes = docsCoServer.checkJwtHeader(docId, req, 'Authorization', 'Bearer ', commonDefines.c_oAscSecretType.Session);
  if (!checkJwtRes) {
    //todo remove compatibility with previous versions
    checkJwtRes = docsCoServer.checkJwt(docId, req.query['token'], commonDefines.c_oAscSecretType.Session);
  }
  let transformedRes = checkJwtUploadTransformRes(docId, 'uploadImageFile', checkJwtRes);
  if (!transformedRes.err) {
    docId = transformedRes.docId || docId;
    encrypted = transformedRes.encrypted;
  } else {
    isValidJwt = false;
  }
}

而encrypted需要配置了cfgTokenEnableBrowser,"Directory traversal with Remote Code Execution when JWT is used in Document Server before 5.6.3",需要配置了JWT时才能利用,Docker环境默认未启用,目标也未启用。

其他的老洞没POC,只能下一份5.4.2.46版本的代码,分析是否存在其他的利用了。

DocService/sources/server.js 存在一个savefile路由,看到这名字就能猜到是文件上传相关的路由。

app.post('/savefile/:docid', rawFileParser, canvasService.saveFile);

路由方法具体实现如下:

exports.saveFile = function(req, res) {
  return co(function*() {
    let docId = 'null';
    try {
      let startDate = null;
      if (clientStatsD) {
        startDate = new Date();
      }

      let strCmd = req.query['cmd'];
      let cmd = new commonDefines.InputCommand(JSON.parse(strCmd));
      docId = cmd.getDocId();
      logger.debug('Start saveFile: docId = %s', docId);

      if (cfgTokenEnableBrowser) {
        let isValidJwt = false;
        let checkJwtRes = docsCoServer.checkJwt(docId, cmd.getTokenSession(), commonDefines.c_oAscSecretType.Session);
        if (checkJwtRes.decoded) {
          let doc = checkJwtRes.decoded.document;
          var edit = checkJwtRes.decoded.editorConfig;
          if (doc.ds_encrypted && !edit.ds_view && !edit.ds_isCloseCoAuthoring) {
            isValidJwt = true;
            docId = doc.key;
            cmd.setDocId(doc.key);
          } else {
            logger.warn('Error saveFile jwt: docId = %s\r\n%s', docId, 'access deny');
          }
        } else {
          logger.warn('Error saveFile jwt: docId = %s\r\n%s', docId, checkJwtRes.description);
        }
        if (!isValidJwt) {
          res.sendStatus(403);
          return;
        }
      }
      cmd.setStatusInfo(constants.NO_ERROR);
      yield* addRandomKeyTaskCmd(cmd);
      cmd.setOutputPath(constants.OUTPUT_NAME + pathModule.extname(cmd.getOutputPath()));
      yield storage.putObject(cmd.getSaveKey() &
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值