0x05 清楚地知道系统参数过滤情况

0x05.1 原生 GET,POST,REQUEST

使用原生GET,POST,REQUEST变量是完全不过滤的

测试方法:
最简单的就是这样了
顺便找处系统中可外部访问的地方
如图:


根据结果就可以确定当使用了原生GET,POST,REQUEST变量带入数据库之类,是有可能产生注入,储蓄xss之类的

0x05.2 系统外部变量获取函数 get(),post(),request()

路径:PbootCMS-V1.2.1\core\function\helper.php
函数_1:function get(
函数_2:function post(
函数_3:function request(

这3个函数 get(), post(), request()
函数:filter(
最后都会调用 filter函数
这个函数没有用所以直接看 filter 里面调用的方法 escape_string函数进行最终过滤

因为filter无用的内容太多所以我们忽略掉直接看escape_string 函数

路径:PbootCMS-V1.2.1\core\function\handle.php
函数:function escape_string(

// 获取转义数据,支持字符串、数组、对象
function escape_string($string, $dropStr = true)
{
    if (! $string)
        return $string;
    if (is_array($string)) { // 数组处理
        foreach ($string as $key => $value) {
            $string[$key] = escape_string($value);
        }
    } elseif (is_object($string)) { // 对象处理
        foreach ($string as $key => $value) {
            $string->$key = escape_string($value);
        }
    } else { // 字符串处理
        if ($dropStr) {
            $string = preg_replace('/(0x7e)|(0x27)|(0x22)|(updatexml)|(extractvalue)|(name_const)|(concat)/i', '', $string);
        }
        $string = htmlspecialchars(trim($string), ENT_QUOTES, 'UTF-8');
        $string = addslashes($string);
    }
    return $string;
}

可以看得到所有传过来的内容都会先过一个正则匹配过滤
会将 0x7e,0x27,0x22,updatexml,extractvalue,name_const,concat 将其替换为''

然后在进行
htmlspecialchars 函数的html实体编码
addslashes 函数转义。

注意点:
    1. 过滤只针对 value
    2. key 完全无不过滤
    3. 那个正则绕过很简单只需要  updatupdatexmlexml 经过过滤以后就可以转为 updatexml

那么看到这里对于系统的初步情况其实就已经很明确了

0x06 查看系统DB类,了解数据库底层运行方式

在挖sql注入的时候,我最喜欢的就是先看这个系统的底层了。
会对挖洞有很大的帮助,看的时候并不需要全看懂,只需要对一些关键的地方看个大概即可

路径:PbootCMS-V1.2.1\core\basic\Model.php

看了一下以后发现,这里没什么好讲的,因为里面全是字符串拼接。

例如: where方法

/**
     * 连贯操作:设置查询条件
     *
     * @param mixed $where
     *            设置条件,可以为字符串、数组,
     *            字符串模式:如"id<1","name like %1",
     *            数组模式:array('username'=>'xie',"realname like '%谢%'")
     * @param string $inConnect
     *            调用本方法时$where参数数组内部的条件默认使用AND连接
     * @param string $outConnect
     *            调用本方法时与前面条件使用AND连接
     * @param boolean $fuzzy
     *            条件是否为模糊匹配,即in匹配
     * @return \core\basic\Model
     */
    final public function where($where, $inConnect = 'AND', $outConnect = 'AND', $fuzzy = false)
    {
        if (! $where) {
            return $this;
        }
        if (isset($this->sql['where']) && $this->sql['where']) {
            $this->sql['where'] .= ' ' . $outConnect . '(';
        } else {
            $this->sql['where'] = 'WHERE(';
        }
        if (is_array($where)) {
            $where_string = '';
            $flag = false;
            foreach ($where as $key => $value) {
                if ($flag) { // 条件之间内部AND连接
                    $where_string .= ' ' . $inConnect . ' ';
                } else {
                    $flag = true;
                }
                if (! is_int($key)) {
                    if ($fuzzy) {
                        $where_string .= $key . " like '%" . $value . "%' ";
                    } else {
                        $where_string .= $key . "='" . $value . "' ";
                    }
                } else {
                    $where_string .= $value;
                }
            }
            $this->sql['where'] .= $where_string . ')';
        } else {
            $this->sql['where'] .= $where . ')';
        }
        return $this;
    }

初步结论:整个db 类的底层都是类似的字符串拼接,所以(≧∇≦)ノ 只要能够带入 '或是\ 那么就可以确定是有注入的了

注意点:在查看的时候发现了 insert 方法的注释,也是需要注意的,这里我把代码贴一下

/**
     * 数据插入模型
     *
     * @param array $data
     *            可以为一维或二维数组,
     *            一维数组:array('username'=>"xsh",'sex'=>'男'),
     *            二维数组:array(
     *            array('username'=>"xsh",'sex'=>'男'),
     *            array('username'=>"gmx",'sex'=>'女')
     *            )
     * @param boolean $batch
     *            是否启用批量一次插入功能,默认true
     * @return boolean|boolean|array
     */
    final public function insert(array $data = array(), $batch = true)
    {
        // 未传递数据时,使用data函数插入数据
        if (! $data && isset($this->sql['data'])) {
            return $this->insert($this->sql['data']);
        }
        if (is_array($data)) {

            if (! $data)
                return;
            if (count($data) == count($data, 1)) { // 单条数据
                $keys = '';
                $values = '';
                foreach ($data as $key => $value) {
                    if (! is_numeric($key)) {
                        $keys .= "`" . $key . "`,";
                        $values .= "'" . $value . "',";
                    }
                }
                if ($this->autoTimestamp || (isset($this->sql['auto_time']) && $this->sql['auto_time'] == true)) {
                    $keys .= "`" . $this->createTimeField . "`,`" . $this->updateTimeField . "`,";
                    if ($this->intTimeFormat) {
                        $values .= "'" . time() . "','" . time() . "',";
                    } else {
                        $values .= "'" . date('Y-m-d H:i:s') . "','" . date('Y-m-d H:i:s') . "',";
                    }
                }
                if ($keys) { // 如果插入数据关联字段,则字段以关联数据为准,否则以设置字段为准
                    $this->sql['field'] = '(' . substr($keys, 0, - 1) . ')';
                } elseif (isset($this->sql['field']) && $this->sql['field']) {
                    $this->sql['field'] = "({$this->sql['field']})";
                }
                $this->sql['value'] = "(" . substr($values, 0, - 1) . ")";
                $sql = $this->buildSql($this->insertSql);
            } else { // 多条数据
                if ($batch) { // 批量一次性插入
                    $key_string = '';
                    $value_string = '';
                    $flag = false;
                    foreach ($data as $keys => $value) {
                        if (! $flag) {
                            $value_string .= ' SELECT ';
                        } else {
                            $value_string .= ' UNION All SELECT ';
                        }
                        foreach ($value as $key2 => $value2) {
                            // 字段获取只执行一次
                            if (! $flag && ! is_numeric($key2)) {
                                $key_string .= "`" . $key2 . "`,";
                            }
                            $value_string .= "'" . $value2 . "',";
                        }
                        $flag = true;
                        if ($this->autoTimestamp || (isset($this->sql['auto_time']) && $this->sql['auto_time'] == true)) {
                            if ($this->intTimeFormat) {
                                $value_string .= "'" . time() . "','" . time() . "',";
                            } else {
                                $value_string .= "'" . date('Y-m-d H:i:s') . "','" . date('Y-m-d H:i:s') . "',";
                            }
                        }
                        $value_string = substr($value_string, 0, - 1);
                    }
                    if ($this->autoTimestamp || (isset($this->sql['auto_time']) && $this->sql['auto_time'] == true)) {
                        $key_string .= "`" . $this->createTimeField . "`,`" . $this->updateTimeField . "`,";
                    }
                    if ($key_string) { // 如果插入数据关联字段,则字段以关联数据为准,否则以设置字段为准
                        $this->sql['field'] = '(' . substr($key_string, 0, - 1) . ')';
                    } elseif (isset($this->sql['field']) && $this->sql['field']) {
                        $this->sql['field'] = "({$this->sql['field']})";
                    }
                    $this->sql['value'] = $value_string;
                    $sql = $this->buildSql($this->insertMultSql);
                    // 判断SQL语句是否超过数据库设置
                    if (get_db_type() == 'mysql') {
                        $max_allowed_packet = $this->getDb()->one('SELECT @@global.max_allowed_packet', 2);
                    } else {
                        $max_allowed_packet = 1 * 1024 * 1024; // 其他类型数据库按照1M限制
                    }
                    if (strlen($sql) > $max_allowed_packet) { // 如果要插入的数据过大,则转换为一条条插入
                        return $this->insert($data, false);
                    }
                } else { // 批量一条条插入
                    foreach ($data as $keys => $value) {
                        $result = $this->insert($value);
                    }
                    return $result;
                }
            }
        } elseif ($this->sql['from']) {
            if (isset($this->sql['field']) && $this->sql['field']) { // 表指定字段复制
                $this->sql['field'] = "({$this->sql['field']})";
            }
            $sql = $this->buildSql($this->insertFromSql);
        } else {
            return;
        }
        return $this->getDb()->amd($sql);
    }

简单的说,就是insert 方法支持二维数组,当使用二维数组时表示批量插入。
所以如果我们可以插入二维数组并且可以控制key那么我们就可以注入了。

下图为触发点查看:

0x07 系统情况初步集合

经过0x05.1与0x05.2 还有0x06组合下来我们其实可以确定了一些基本漏洞了

5.1 反应给我的情况

首先是xss漏洞

储蓄xss
如果是使用了 5.1 的外部变量并且入库的时候没有转义的话,那么就会产生xss

反射xss
反不动 只要 GET 请求中出现了 A-Z a-z 0-9 之外的数,就会直接报错

然后是sql注入漏洞
如果是使用了 5.1 的外部变量并且入库的时候没有转义的话,那么就会产生sql注入

5.2 反应给我的情况
首先是xss漏洞

储蓄xss
如果是使用了 5.2 的外部变量入库的,想找xss 那么就要看使用了 htmlspecialchars_decode 函数的地方,否则的话就只能查看类似这种点 <img src=# {{variate}}> variate 可控的情况

反射xss
射不动 只要 GET 请求中出现了 A-Z a-z 0-9 之外的数,就会直接报错

然后是sql注入漏洞
key 没有过滤所以如果我能够控制 key 进入sql的话,那么就基本上百分之90有sql注入了

6.0 给我的情况

底层全是字符串拼接,只要能够引入 ' 或是 \ 就可以造成注入

5.1 只要我们可以控制就有注入

5.2 因为所有的 value 都会进行 htmlspecialchars 函数的html实体编码 addslashes 函数转义
所以利用value 注入不现实,那么我们找注入的关键点就是查看key了

嗯,可以看到这就很明了了,我的审计收集工作也正式算是做完了。
接下来就是正式审计了。
不过其实到这一步,基本上在看两眼搜索一下打打 debug 就可以确定漏洞了。

源链接

Hacking more

...