zoukankan      html  css  js  c++  java
  • ThinkPHP最新版本SQL注入漏洞

    如下controller即可触发SQL注入:

    code 区域

    public function test()

    {

        $uname = I('get.uname');

        $u = M('user')->where(array(

            'uname' => $uname

        ))->find();

        dump($u);

    }



    为什么?

    我们看看代码。我从github下载的最新源码:https://github.com/liu21st/thinkphp

    /ThinkPHP/Library/Think/Db/Driver.class.php 531
    行:

    code 区域

    // where子单元分析

    protected function parseWhereItem($key,$val) {

    $whereStr = '';

    if(is_array($val)) {

    if(is_string($val[0])) {

    if(preg_match('/^(EQ|NEQ|GT|EGT|LT|ELT)$/i',$val[0])) { // 比较运算

    $whereStr .= $key.' '.$this->comparison[strtolower($val[0])].' '.$this->parseValue($val[1]);

    }elseif(preg_match('/^(NOTLIKE|LIKE)$/i',$val[0])){// 模糊查找

    if(is_array($val[1])) {

    $likeLogic = isset($val[2])?strtoupper($val[2]):'OR';

    if(in_array($likeLogic,array('AND','OR','XOR'))){

    $likeStr = $this->comparison[strtolower($val[0])];

    $like = array();

    foreach ($val[1] as $item){

    $like[] = $key.' '.$likeStr.' '.$this->parseValue($item);

    }

    $whereStr .= '('.implode(' '.$likeLogic.' ',$like).')';

    }

    }else{

    $whereStr .= $key.' '.$this->comparison[strtolower($val[0])].' '.$this->parseValue($val[1]);

    }

    }elseif('bind'==strtolower($val[0])){ // 使用表达式

    $whereStr .= $key.' = :'.$val[1];

    }elseif('exp'==strtolower($val[0])){ // 使用表达式

    $whereStr .= $key.' '.$val[1];

    }elseif(preg_match('/IN/i',$val[0])){ // IN 运算

    if(isset($val[2]) && 'exp'==$val[2]) {

    $whereStr .= $key.' '.strtoupper($val[0]).' '.$val[1];

    }else{

    if(is_string($val[1])) {

    $val[1] = explode(',',$val[1]);

    }

    $zone = implode(',',$this->parseValue($val[1]));

    $whereStr .= $key.' '.strtoupper($val[0]).' ('.$zone.')';

    }

    }elseif(preg_match('/BETWEEN/i',$val[0])){ // BETWEEN运算

    $data = is_string($val[1])? explode(',',$val[1]):$val[1];

    $whereStr .= $key.' '.strtoupper($val[0]).' '.$this->parseValue($data[0]).' AND '.$this->parseValue($data[1]);

    }else{

    E(L('_EXPRESS_ERROR_').':'.$val[0]);

    }

    }else {

    $count = count($val);

    $rule = isset($val[$count-1]) ? (is_array($val[$count-1]) ? strtoupper($val[$count-1][0]) : strtoupper($val[$count-1]) ) : '' ;

    if(in_array($rule,array('AND','OR','XOR'))) {

    $count = $count -1;

    }else{

    $rule = 'AND';

    }

    for($i=0;$i<$count;$i++) {

    $data = is_array($val[$i])?$val[$i][1]:$val[$i];

    if('exp'==strtolower($val[$i][0])) {

    $whereStr .= $key.' '.$data.' '.$rule.' ';

    }else{

    $whereStr .= $this->parseWhereItem($key,$val[$i]).' '.$rule.' ';

    }

    }

    $whereStr = '( '.substr($whereStr,0,-4).' )';

    }

    }else {

    //对字符串类型字段采用模糊匹配

    $likeFields = $this->config['db_like_fields'];

    if($likeFields && preg_match('/^('.$likeFields.')$/i',$key)) {

    $whereStr .= $key.' LIKE '.$this->parseValue('%'.$val.'%');

    }else {

    $whereStr .= $key.' = '.$this->parseValue($val);

    }

    }

    return $whereStr;

    }



    这就是处理where条件的函数,我们看到如下片段:

    code 区域

    }elseif(preg_match('/BETWEEN/i',$val[0])){ // BETWEEN运算

    $data = is_string($val[1])? explode(',',$val[1]):$val[1];

    $whereStr .= $key.' '.strtoupper($val[0]).' '.$this->parseValue($data[0]).' AND '.$this->parseValue($data[1]);

    }



    当匹配/BETWEEN/i$val[0]时,则将strtoupper($val[0])直接插入了SQL语句。

    这个匹配:preg_match('/BETWEEN/i',$val[0]),明显是有问题的。因为这个匹配没加^$也就是首尾限定,所以只要我们的$val[0]中含有between时,这个匹配就可以成立,就产生了一个SQL注入。

    为了防止I函数对我们输入的过滤影响,我们看看I函数:

    code 区域

    function I($name,$default='',$filter=null,$datas=null) {

        if(strpos($name,'/')){ // 指定修饰符

            list($name,$type)     =    explode('/',$name,2);

        }

    if(strpos($name,'.')) { // 指定参数来源

    list($method,$name) = explode('.',$name,2);

    }else{ // 默认为自动判断

    $method = 'param';

    }

    switch(strtolower($method)) {

    case 'get' : $input =& $_GET;break;

    case 'post' : $input =& $_POST;break;

    case 'put' : parse_str(file_get_contents('php://input'), $input);break;

    case 'param' :

    switch($_SERVER['REQUEST_METHOD']) {

    case 'POST':

    $input = $_POST;

    break;

    case 'PUT':

    parse_str(file_get_contents('php://input'), $input);

    break;

    default:

    $input = $_GET;

    }

    break;

    case 'path' :

    $input = array();

    if(!empty($_SERVER['PATH_INFO'])){

    $depr = C('URL_PATHINFO_DEPR');

    $input = explode($depr,trim($_SERVER['PATH_INFO'],$depr));

    }

    break;

    case 'request' : $input =& $_REQUEST; break;

    case 'session' : $input =& $_SESSION; break;

    case 'cookie' : $input =& $_COOKIE; break;

    case 'server' : $input =& $_SERVER; break;

    case 'globals' : $input =& $GLOBALS; break;

    case 'data' : $input =& $datas; break;

    default:

    return NULL;

    }

    if(''==$name) { // 获取全部变量

    $data = $input;

    $filters = isset($filter)?$filter:C('DEFAULT_FILTER');

    if($filters) {

    if(is_string($filters)){

    $filters = explode(',',$filters);

    }

    foreach($filters as $filter){

    $data = array_map_recursive($filter,$data); // 参数过滤

    }

    }

    }elseif(isset($input[$name])) { // 取值操作

    $data = $input[$name];

    $filters = isset($filter)?$filter:C('DEFAULT_FILTER');

    if($filters) {

    if(is_string($filters)){

    $filters = explode(',',$filters);

    }elseif(is_int($filters)){

    $filters = array($filters);

    }

     

    foreach($filters as $filter){

    if(function_exists($filter)) {

    $data = is_array($data) ? array_map_recursive($filter,$data) : $filter($data); // 参数过滤

    }elseif(0===strpos($filter,'/')){

        // 支持正则验证

        if(1 !== preg_match($filter,(string)$data)){

            return isset($default) ? $default : NULL;

        }

    }else{

    $data = filter_var($data,is_int($filter) ? $filter : filter_id($filter));

    if(false === $data) {

    return isset($default) ? $default : NULL;

    }

    }

    }

    }

    if(!empty($type)){

        switch(strtolower($type)){

            case 's': // 字符串

                $data     =    (string)$data;

                break;

            case 'a':    // 数组

                $data     =    (array)$data;

                break;

            case 'd':    // 数字

                $data     =    (int)$data;

                break;

            case 'f':    // 浮点

                $data     =    (float)$data;

                break;

            case 'b':    // 布尔

                $data     =    (boolean)$data;

                break;

        }

    }

    }else{ // 变量默认值

    $data = isset($default)?$default:NULL;

    }

    is_array($data) && array_walk_recursive($data,'think_filter');

    return $data;

    }



    较前些版本有些改进:

    1.
    加了类型强制转换$type,但在默认情况下$type是空的,强制类型转换是不存在的。

    2.
    is_array($data) && array_walk_recursive($data,'think_filter');放在最后一行。我们看看think_filter这个过滤函数:

    code 区域

    function think_filter(&$value){

        // TODO 其他安全过滤

     

        // 过滤查询特殊字符

    if(preg_match('/^(EXP|NEQ|GT|EGT|LT|ELT|OR|LIKE|NOTLIKE|BETWEEN|IN)$/i',$value)){

    $value .= ' ';

    }

    }



    这个实际上就是对我之前那个漏洞的一个解决方案,将一些关键词后面加空格。但我们看到,这个正则是存在"^$"首尾限定符的。所以只有传入参数完全"等于"BETWEEN的时候才会被加上空格,而且这里加上空格也不会影响漏洞的产生,因为漏洞位置的正则没有加^$首尾限定符。



    还有一个说明:之前thinkphp出了个"错误"的补丁,这个补丁已经被官方去掉了,所以不用考虑那个补丁造成的一些干扰。

    漏洞证明:

    那我们回到最初那段代码:

    code 区域

    public function test()

    {

        $uname = I('get.uname');

        $u = M('user')->where(array(

            'uname' => $uname

        ))->find();

        dump($u);

    }



    这个代码,我们来测试一下:



    果然是有漏洞的,我们看看具体执行的SQL语句:



    就在BETWEEN处。除了BETWEEN外还有IN,我就一块说明了。

    Onethink
    演示:



    细我就不说了,和之前一样。

    修复方案:

    正则一定要写明确:

    /^BETWEEN$/i

    版权声明:转载请注明来源 phith0n@乌云

  • 相关阅读:
    Yii2的深入学习--事件Event
    PHP的学习--可变函数
    PHP的学习--可变变量
    Yii2的深入学习--yiiaseObject 类
    xdotool模拟击键和鼠标移动
    Yii2的深入学习--继承关系
    Yii2的深入学习--自动加载机制
    用Qt写软件系列五:一个安全防护软件的制作(1)
    TinyOS和Deluge的安装模拟(二)
    TinyOS和Deluge的安装模拟(一)
  • 原文地址:https://www.cnblogs.com/h4ck0ne/p/5154649.html
Copyright © 2011-2022 走看看