原创 | ThinkPHP3.2.3 SQL注入漏洞分析

  • A+
所属分类:安全文章
原创 | ThinkPHP3.2.3 SQL注入漏洞分析
点击上方蓝字 关注我吧

where注入


在控制器写如下demo开始测试

public function ghtwf01(){      $data = M('users')->where('id='.I('id'))->find();      dump($data);    }


$data处下断点跟踪调试,F7进入

I方法中判断传参方式

switch(strtolower($method)) {        case 'get'     :             $input =& $_GET;          break;        case 'post'    :             $input =& $_POST;          break;        case 'put'     :             if(is_null($_PUT)){              parse_str(file_get_contents('php://input'), $_PUT);          }          $input   =  $_PUT;                  break;        case 'param'   :            switch($_SERVER['REQUEST_METHOD']) {                case 'POST':                    $input  =  $_POST;                    break;                case 'PUT':                  if(is_null($_PUT)){                      parse_str(file_get_contents('php://input'), $_PUT);                  }                  $input   =  $_PUT;                    break;                default:                    $input  =  $_GET;            }


判断出是GET传参,然后选择一个过滤方式

原创 | ThinkPHP3.2.3 SQL注入漏洞分析


跟进,返回过滤函数htmlspecialchars,然后使用该函数过滤传入的参数

原创 | ThinkPHP3.2.3 SQL注入漏洞分析


这里因为$data不是数组所以没有使用think_filter函数过滤,我们也来看一下think_filter函数的内容

function think_filter(&$value){  // TODO 其他安全过滤
// 过滤查询特殊字符 if(preg_match('/^(EXP|NEQ|GT|EGT|LT|ELT|OR|XOR|LIKE|NOTLIKE|NOT BETWEEN|NOTBETWEEN|BETWEEN|NOTIN|NOT IN|IN)$/i',$value)){ $value .= ' '; }}


并没有过滤updatexml报错注入函数,就算使用了think_filter过滤了,也没办法防御

接下来进入where函数,作用是将$where的值放入options['where']

if(isset($this->options['where'])){            $this->options['where'] =   array_merge($this->options['where'],$where);        }else{            $this->options['where'] =   $where;        }

原创 | ThinkPHP3.2.3 SQL注入漏洞分析


接下来进入find函数

$options   =   $this->_parseOptions($options);

_parseOptions函数的作用是获取到表名和别名

原创 | ThinkPHP3.2.3 SQL注入漏洞分析


继续进入select函数

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


跟进buildSelectSql函数
public function buildSelectSql($options=array()) {        if(isset($options['page'])) {            // 根据页数计算limit            list($page,$listRows)   =   $options['page'];            $page    =  $page>0 ? $page : 1;            $listRows=  $listRows>0 ? $listRows : (is_numeric($options['limit'])?$options['limit']:20);            $offset  =  $listRows*($page-1);            $options['limit'] =  $offset.','.$listRows;        }        $sql  =   $this->parseSql($this->selectSql,$options);        return $sql;    }


再跟进parseSql方法


public function parseSql($sql,$options=array()){        $sql   = str_replace(            array('%TABLE%','%DISTINCT%','%FIELD%','%JOIN%','%WHERE%','%GROUP%','%HAVING%','%ORDER%','%LIMIT%','%UNION%','%LOCK%','%COMMENT%','%FORCE%'),            array(                $this->parseTable($options['table']),                $this->parseDistinct(isset($options['distinct'])?$options['distinct']:false),                $this->parseField(!empty($options['field'])?$options['field']:'*'),                $this->parseJoin(!empty($options['join'])?$options['join']:''),                $this->parseWhere(!empty($options['where'])?$options['where']:''),                $this->parseGroup(!empty($options['group'])?$options['group']:''),                $this->parseHaving(!empty($options['having'])?$options['having']:''),                $this->parseOrder(!empty($options['order'])?$options['order']:''),                $this->parseLimit(!empty($options['limit'])?$options['limit']:''),                $this->parseUnion(!empty($options['union'])?$options['union']:''),                $this->parseLock(isset($options['lock'])?$options['lock']:false),                $this->parseComment(!empty($options['comment'])?$options['comment']:''),                $this->parseForce(!empty($options['force'])?$options['force']:'')            ),$sql);        return $sql;    }


这里主要看parseWhere方法

protected function parseWhere($where) {        $whereStr = '';        if(is_string($where)) {            // 直接使用字符串条件            $whereStr = $where;        }else{ // 使用数组表达式            $operate  = isset($where['_logic'])?strtoupper($where['_logic']):'';            if(in_array($operate,array('AND','OR','XOR'))){                // 定义逻辑运算规则 例如 OR XOR AND NOT                $operate    =   ' '.$operate.' ';                unset($where['_logic']);            }else{                // 默认进行 AND 运算                $operate    =   ' AND ';            }            foreach ($where as $key=>$val){                if(is_numeric($key)){                    $key  = '_complex';                }                if(0===strpos($key,'_')) {                    // 解析特殊条件表达式                    $whereStr   .= $this->parseThinkWhere($key,$val);                }else{                    // 查询字段的安全过滤                    // if(!preg_match('/^[A-Z_|&-.a-z0-9(),]+$/',trim($key))){                    //     E(L('_EXPRESS_ERROR_').':'.$key);                    // }                    // 多条件支持                    $multi  = is_array($val) &&  isset($val['_multi']);                    $key    = trim($key);                    if(strpos($key,'|')) { // 支持 name|title|nickname 方式定义查询字段                        $array =  explode('|',$key);                        $str   =  array();                        foreach ($array as $m=>$k){                            $v =  $multi?$val[$m]:$val;                            $str[]   = $this->parseWhereItem($this->parseKey($k),$v);                        }                        $whereStr .= '( '.implode(' OR ',$str).' )';                    }elseif(strpos($key,'&')){                        $array =  explode('&',$key);                        $str   =  array();                        foreach ($array as $m=>$k){                            $v =  $multi?$val[$m]:$val;                            $str[]   = '('.$this->parseWhereItem($this->parseKey($k),$v).')';                        }                        $whereStr .= '( '.implode(' AND ',$str).' )';                    }else{                        $whereStr .= $this->parseWhereItem($this->parseKey($key),$val);                    }                }                $whereStr .= $operate;            }            $whereStr = substr($whereStr,0,-strlen($operate));        }        return empty($whereStr)?'':' WHERE '.$whereStr;    }


跟进parseThinkWhere方法
protected function parseThinkWhere($key,$val) {        $whereStr   = '';        switch($key) {            case '_string':                // 字符串模式查询条件                $whereStr = $val;                break;            case '_complex':                // 复合查询条件                $whereStr = substr($this->parseWhere($val),6);                break;            case '_query':                // 字符串模式查询条件                parse_str($val,$where);                if(isset($where['_logic'])) {                    $op   =  ' '.strtoupper($where['_logic']).' ';                    unset($where['_logic']);                }else{                    $op   =  ' AND ';                }                $array   =  array();                foreach ($where as $field=>$data)                    $array[] = $this->parseKey($field).' = '.$this->parseValue($data);                $whereStr   = implode($op,$array);                break;        }        return '( '.$whereStr.' )';    }


因为$key=__string,所以直接$whereStr = $val,然后break

最后返回完整的
SQL语句

原创 | ThinkPHP3.2.3 SQL注入漏洞分析


然后执行

原创 | ThinkPHP3.2.3 SQL注入漏洞分析


成功注入


find/select/delete注入

三种注入方法都一样,这里以
find注入为例
在控制器写如下
demo开始测试
public function ghtwf01(){      $data = M('users')->find(I('id'));      dump($data);    }

$data处下断点跟踪调试,F7进入

同样的I方法判断传参方式,因为传入的是数组,所以这次会使用think_filter函数过滤,但是没有过滤报错函数,所以导致过滤没用

然后同样的在find方法里面跟进_parseOptions方法,因为$options['where']String,所以不会执行if里面的_parseType方法

if(isset($options['where']) && is_array($options['where']) && !empty($fields) && !isset($options['join'])) {            // 对数组查询条件进行字段类型检查            foreach ($options['where'] as $key=>$val){                $key            =   trim($key);                if(in_array($key,$fields,true)){                    if(is_scalar($val)) {                        $this->_parseType($options['where'],$key);                    }......


继续看一看_parseType方法有什么过滤呢
protected function _parseType(&$data,$key) {        if(!isset($this->options['bind'][':'.$key]) && isset($this->fields['_type'][$key])){            $fieldType = strtolower($this->fields['_type'][$key]);            if(false !== strpos($fieldType,'enum')){                // 支持ENUM类型优先检测            }elseif(false === strpos($fieldType,'bigint') && false !== strpos($fieldType,'int')) {                $data[$key]   =  intval($data[$key]);            }elseif(false !== strpos($fieldType,'float') || false !== strpos($fieldType,'double')){                $data[$key]   =  floatval($data[$key]);            }elseif(false !== strpos($fieldType,'bool')){                $data[$key]   =  (bool)$data[$key];            }        }    }


使用$data[$key] = intval($data[$key]);进行了强制int,导致无法注入

回到刚才的分析中,进入
select->buildSelectSql->parseSql->parseWhere

原创 | ThinkPHP3.2.3 SQL注入漏洞分析


但是如果直接传入

?id=1 and 1=updatexml(1,concat(0x7e,(database()),0x7e),1)


那么就会触发_parseOptions里面的_parseType方法,导致注入失败

因为如果这样,这儿的$options['where']是一个数组,满足执行_parseType的条件,但是不满足in_array($key,$fields,true),还是进不去_parseType函数,但是就能逃过吗,后面执行了unset函数,删除了$options['where']['id'],注入语句都没有,也就没法注入了


梳理一下
find注入,关键代码位于find方法

public function find($options=array()) {        if(is_numeric($options) || is_string($options)) {            $where[$this->getPk()]  =   $options;            $options                =   array();            $options['where']       =   $where;        }......

如果传入的是id['where']

那么$options['where']就是一个字符串


原创 | ThinkPHP3.2.3 SQL注入漏洞分析


不会进行后续处理

如果传入的是
id,那么$options就会变成一个二维数组,$options['where'][id]才是字符串,$options['where']是一个数组
public function find($options=array()) {//$options = String        if(is_numeric($options) || is_string($options)) {            $where[$this->getPk()]  =   $options;//$where['id'] = String            $options                =   array();            $options['where']       =   $where;//$options['where'] = {"id":String}        }

原创 | ThinkPHP3.2.3 SQL注入漏洞分析
原创 | ThinkPHP3.2.3 SQL注入漏洞分析
原创 | ThinkPHP3.2.3 SQL注入漏洞分析

原创 | ThinkPHP3.2.3 SQL注入漏洞分析
你要的分享、在看与点赞都在这儿~

本文始发于微信公众号(SecIN技术平台):原创 | ThinkPHP3.2.3 SQL注入漏洞分析

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: