您好,登錄后才能下訂單哦!
如何進行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版本到最新版本
看完上述內(nèi)容是否對您有幫助呢?如果還想對相關(guān)知識有進一步的了解或閱讀更多相關(guān)文章,請關(guān)注億速云行業(yè)資訊頻道,感謝您對億速云的支持。
免責(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)容。