tp3.2 mysql elt出错_ThinkPHP3.2.3 SQL注入漏洞分析

本文分析了ThinkPHP3.2.3版本中的SQL注入漏洞,特别是where注入问题。通过代码调试,展示了从I方法接收参数到最终SQL构建的过程,指出由于在某些情况下未对特定函数进行过滤,导致SQL注入的可能性。尽管有think_filter函数,但无法防御某些类型的注入。此外,文中还探讨了find、select和delete操作的注入情况,分析了不同参数传递方式如何影响注入的可能性。
摘要由CSDN通过智能技术生成

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传参,然后选择一个过滤方式

d6726ca5bbad5d46fa3545828d6e6529.png

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

9de487855e0bdbe58431d39636658dfe.png

这里因为$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;

}

877c23b117d2dee554d16cfab1ebb16f.png

接下来进入find函数$options = $this->_parseOptions($options);

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

9e2bc34a1353287266c77da5429435c7.png

继续进入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语句

13f0b974e59a4b37b67f055b00bbeafa.png

然后执行

2fc4f6fbd198f7e623f6593d0103aa63.png

成功注入

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

f073cf960a80a4c02b700d5113281a0d.png

但是如果直接传入?id=String

那么就会触发_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']就是一个字符串

eb75113fed03d1f2427434f0c0ccbe4f.png

不会进行后续处理

如果传入的是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}

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值