溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

如何進行ThinkPHP框架SQL注入

發(fā)布時間:2021-10-11 10:54:06 來源:億速云 閱讀:133 作者:柒染 欄目:網(wǎng)絡(luò)管理

如何進行ThinkPHP框架SQL注入,很多新手對此不是很清楚,為了幫助大家解決這個難題,下面小編將為大家詳細講解,有這方面需求的人可以來學(xué)習(xí)下,希望你能有所收獲。

簡要描述

ThinkPHP是一個免費開源的,快速、簡單的面向?qū)ο蟮妮p量級PHP開發(fā)框架,是為了敏捷WEB應(yīng)用開發(fā)和簡化企業(yè)應(yīng)用開發(fā)而誕生的。ThinkPHP從誕生的12年間一直秉承簡潔實用的設(shè)計原則,在保持出色的性能和至簡的代碼的同時,也注重易用性。目前ThinkPHP框架是國內(nèi)使用量最大的框架之一,國內(nèi)用戶量眾多。

近日,360企業(yè)安全集團代碼衛(wèi)士團隊安全研究人員發(fā)現(xiàn)該框架V5.1.7-V5.1.8 版本在底層數(shù)據(jù)處理驅(qū)動解析數(shù)據(jù)的時候存在缺陷,一定場景下,攻擊者可以通過構(gòu)造惡意數(shù)據(jù)包利用SQL注入的方式獲取用戶數(shù)據(jù)庫內(nèi)容。360企業(yè)安全集團代碼衛(wèi)士團隊已第一時間和ThinkPHP團隊進行溝通修復(fù),建議相關(guān)用戶及時更新官方發(fā)布的新版本。

漏洞分析

注:該漏洞ThinkPHP官方團隊在報送當天(2018-04-06)緊急進行了修復(fù)處理,詳細請參考:https://github.com/top-think/framework/commit/39bb0fe6d50ee77e0779f646b10bce08c442a5e3

以下漏洞分析基于ThinkPHP V5.1.8(2018-04-05未更新版)

這里我們主要跟進分析執(zhí)行update操作的過程。為了方便理解,先直接放出函數(shù)的調(diào)用棧。

Mysql.php :200, think\db\builder\Mysql-> parseArrayData()

Builder.php :147, think\db\Builder-> parseData()

Builder.php :1139, think\db\Builder-> update()

Connection.php :1149, think\db\Connection-> update()

Query.php :2571, think\db\Query-> update()

Index.php :18, app\index\controller\Index-> testsql()

Container.php :285, ReflectionMethod ->invokeArgs()

Container.php :285, think\Container-> invokeReflectMethod()

Module.php :139, think\route\dispatch\Module-> run()

Url.php :31, think\route\dispatch\Url-> run()

App.php :378, think\App-> think\{closure}()

Middleware.php :119, call_user_func_array:{ C:\wamp64\www\think518\thinkphp\library\think\Middleware. php:119}()

Middleware.php :119, think\Middleware-> think\{closure}()

Middleware.php :74, call_user_func:{ C:\wamp64\www\think518\thinkphp\library\think\Middleware. php:74}()

Middleware.php :74, think\Middleware-> dispatch()

App.php :399, think\App-> run()

index.php :21, { main}()

缺陷關(guān)鍵點為thinkphp解析用戶傳遞過來的Data可控,且可以繞過安全檢查。

根據(jù)文件 Connection.php:1149,think\db\Connection->update()第1102行update函數(shù)分析,這個函數(shù)的主要功能是用于執(zhí)行update SQL語句。

//Connection.php:1149, think\db\Connection->update()

public function update(Query $query)

   {

       $options = $query->getOptions();


       if (isset($options ['cache']) && is_string($options['cache' ]['key'])) {

           $key = $options['cache']['key' ];                    

       }


       $pk   = $query->getPk($options );                    

       $data = $options['data'];


       if (empty($options ['where'])) {

           // 如果存在主鍵數(shù)據(jù) 則自動作為更新條件

           if (is_string($pk ) && isset( $data[$pk])) {                            

               $where[ $pk] = [$pk, '=' , $data[$pk]];

               if (!isset($key )) {

                   $key = $this->getCacheKey($query , $data[$pk]);

               }

               unset( $data[$pk]);

           } elseif (is_array($pk )) {

               // 增加復(fù)合主鍵支持

               foreach ($pk as $field ) {

                   if (isset($data [$field])) {

                       $where [$field] = [$field, '=', $data[$field ]];                            

                   } else {

                       // 如果缺少復(fù)合主鍵數(shù)據(jù)則不執(zhí)行

                       throw new Exception( 'miss complex primary data');

                   }

                   unset( $data[$field]);

               }

           }


           if (!isset($where )) {

               // 如果沒有任何更新條件則不執(zhí)行

               throw new Exception( 'miss update condition');

           } else {

               $options[ 'where']['AND'] = $where;

               $query-> setOption('where', ['AND' => $where ]);                        

           }

       } elseif (!isset($key ) && is_string( $pk) && isset ($options['where'][ 'AND'][$pk])) {                                    

           $key = $this->getCacheKey($query , $options['where'][ 'AND'][$pk]);

       }


       // 更新數(shù)據(jù)

       $query-> setOption('data', $data );                    


       // 生成UPDATE SQL語句

       $sql  = $this->builder->update ($query);

       $bind = $query->getBind();


       if (!empty($options ['fetch_sql'])) {

           // 獲取實際執(zhí)行的SQL語句

           return $this->getRealSql($sql , $bind);

       }


       // 檢測緩存

       $cache = Container::get( 'cache');


       if (isset($key ) && $cache-> get($key)) {                            

           // 刪除緩存

           $cache-> rm($key);

       } elseif (!empty($options ['cache']['tag'])) {

           $cache-> clear($options['cache' ]['tag']);

       }


       // 執(zhí)行操作

       $result = '' == $sql ? 0 : $this->execute($sql , $bind);


       if ($result) {                    

           if (is_string($pk ) && isset( $where[$pk])) {                            

               $data[ $pk] = $where [$pk];

           } elseif (is_string($pk ) && isset( $key) && strpos ($key, '|' )) {

               list( $a, $val) = explode('|', $key);

               $data[ $pk]     = $val ;                    

           }


           $query-> setOption('data', $data );                    

           $query-> trigger('after_update');

       }


       return $result;

   }

第1146行, $query->setOption('data',$data);這里將用戶傳遞的 $dataset到 $query變量中,為下一步的生成 UPDATE SQL語句做準備,執(zhí)行 $sql=$this->builder->update($query);語句,重點馬上要來了,跟進 Builder.php:1139,think\db\Builder->update()函數(shù)

//Builder.php:1139, think\db\Builder->update()

public function update(Query $query)

   {

       $options = $query->getOptions();


       $table = $this->parseTable($query , $options['table']);

       $data  = $this->parseData($query , $options['data']);


       if (empty($data )) {

           return '';

       }


       foreach ($data as $key => $val) {

           $set[] = $key . ' = ' . $val;

       }


       return str_replace(

           [ '%TABLE%', '%SET%', '%JOIN%', '%WHERE%' , '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'],

           [

               $this-> parseTable($query, $options ['table']),

               implode( ' , ', $set),

               $this-> parseJoin($query, $options ['join']),

               $this-> parseWhere($query, $options ['where']),

               $this-> parseOrder($query, $options ['order']),

               $this-> parseLimit($query, $options ['limit']),

               $this-> parseLock($query, $options ['lock']),

               $this-> parseComment($query, $options ['comment']),

           ],

           $this-> updateSql);

   }

剛剛我們將用戶可控的 $dataset到 $query['options']中,這里我們先獲取 $query['options']內(nèi)容到  $options中,然后對Data進行解析 $data=$this->parseData($query,$options['data']);

//Builder.php:147, think\db\Builder->parseData()

protected function parseData(Query $query, $data = [], $fields = [], $bind = [], $suffix = '')

   {

       if (empty($data )) {

           return [];

       }


       $options = $query->getOptions();


       // 獲取綁定信息

       if (empty($bind )) {

           $bind = $this->connection->getFieldsBind ($options['table']);

       }


       if (empty($fields )) {

           if ('*' == $options['field']) {                        

               $fields = array_keys($bind);

           } else {

               $fields = $options['field'];

           }

       }


       $result = [];


       foreach ($data as $key => $val) {

           $item = $this->parseKey($query , $key);


           if ($val instanceof Expression) {

               $result[ $item] = $val ->getValue();

               continue ;                

           } elseif (!is_scalar($val ) && ( in_array($key, (array) $query-> getOptions('json')) || 'json' == $this->connection-> getFieldsType($options[ 'table'], $key))) {

               $val = json_encode($val);

           } elseif (is_object($val ) && method_exists( $val, '__toString')) {

               // 對象數(shù)據(jù)寫入

               $val = $val->__toString();

           }


           if (false !== strpos($key, '->')) {

               list( $key, $name) = explode('->', $key);

               $item             = $this->parseKey( $query, $key);

               $result[ $item]    = 'json_set(' . $item . ', \'$.' . $name . '\', ' . $this->parseDataBind( $query, $key, $val , $bind, $suffix ) . ')' ;                                                

           } elseif (false === strpos($key, '.') && !in_array($key, $fields, true)) {

               if ($options['strict' ]) {

                   throw new Exception( 'fields not exists:[' . $key . ']');

               }

           } elseif (is_null($val )) {

               $result[ $item] = 'NULL';

           } elseif (is_array($val ) && ! empty($val)) {                            

               switch ($val[0 ]) {

                   case 'INC':

                       $result [$item] = $item . ' + ' . floatval($val[ 1]);

                       break ;                

                   case 'DEC':

                       $result [$item] = $item . ' - ' . floatval($val[ 1]);

                       break ;                

                   default :                

                       $value = $this->parseArrayData( $query, $val);

                       if ($value) {                    

                           $result [$item] = $value;

                       }

               }

           } elseif (is_scalar($val )) {

               // 過濾非標量數(shù)據(jù)

               $result[ $item] = $this ->parseDataBind($query, $key, $val, $bind , $suffix);

           }

       }


       return $result;

   }

在第115行,通過 foreach($dataas$key=>$val)處理 $data,然后解析 $key保存到  $item變量中去,之后執(zhí)行下面的判斷邏輯,想要合理地進入各個判斷分支,就要巧妙的構(gòu)造 $key和 $value也就是 $data的值。緊接著我們進入漏洞觸發(fā)點  $value=$this->parseArrayData($query,$val);,跟進函數(shù) $value=$this->parseArrayData($query,$val);

//Mysql.php:200, think\db\builder\Mysql->parseArrayData()

protected function parseArrayData(Query $query, $data)

   {

       list($type, $value) = $data;

       switch (strtolower($type)) {

           case 'point':

               $fun   = isset($data[2]) ? $data[2] : 'GeomFromText';

               $point = isset($data[3]) ? $data[3] : 'POINT';

               if (is_array($value)) {

                   $value = implode(' ', $value);

               }

               $result = $fun . '(\'' . $point . '(' . $value . ')\')';//需要簡單的構(gòu)造一下sql語句

               break;

           default:

               $result = false;

       }

       return $result;

   }

這里 $type、 $value和 $data均為可控值,那么函數(shù)返回的 $result也就是可控的。回到上一個  Builder.php文件中,將返回的結(jié)果賦值到 $result[$item]=$value;中,之后的生成SQL語句和常見的流程沒有任何差別不再展開具體分析。

驗證截圖

如何進行ThinkPHP框架SQL注入    

修復(fù)建議

更新受影響ThinkPHP版本到最新版本

看完上述內(nèi)容是否對您有幫助呢?如果還想對相關(guān)知識有進一步的了解或閱讀更多相關(guān)文章,請關(guān)注億速云行業(yè)資訊頻道,感謝您對億速云的支持。

向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI