這篇文章準備通過通過請求語句來看傳入的數據在代碼中流向,這樣或許更方便來理解這個漏洞。
http://[host]/[sugar]/index.php?module=Connectors&action=RunTest&source_id=ext_rest_insideview&ext_rest_insideview_[%27.phpinfo().%27]=1
最后的效果就是程序會執行phpinfo()函數。
流程分析
入口函數action_RunTest()
當訪問POC時,程序會進入到modules/Connectors/controller.php中的action_RunTest()函數中。
其中的source_id就是PoC中傳入的ext_rest_insideview,至于為什么會傳入這個,之后的分析會講到。
SourceFactory::getSource()
跟蹤SourceFactory::getSource()函數,進入include/connectors/sources/SourceFactory.php
發現在其中調用了ConnectorFactory::load()方法,其中的$class就是傳入的ext_rest_insideview
load()
跟蹤ConnectorFactory::load()函數,進入include/connectors/ConnectorFactory.php。發現load()函數又調用loadClass()函數。
在loadClass()函數中,會嘗試導入一個文件,導入的格式就是.../connectors/{$type}/{$dir}/$file。其中$type是傳入的sources,$dir是將$class(在本PoC中為ext_rest_insideview)字符串中的_替換為/的一個路徑,所以最后$dir的值為ext/rest/insideview,這個在圖片上也有顯示,$file就是路徑的最后一個值insideview.php。最后程序就會嘗試去尋找對應的文件,如果沒有找到就會報錯。
所以Poc中的source_id=ext_rest_insideview并不能隨便寫為任意值。假若寫為source_id=a_b_c,那么在執行loadClass()的時候無法找到文件導致程序無法往下執行,那么payload就無用了。
setsetProperties()
在對loadClass()分析完畢之后,最后回到入口函數action_RunTest()。
程序往下執行進入到setProperties()方法中。
其中的foreach()就會對傳入值賦值到$properties中,最后得到的$properties的值如左邊的圖所示,即為
[''][''.phpinfo().''] = '1';。
跟蹤setProperties(),進入include/connectors/sources/default/source.php,setProperties()代碼如下:
- public function setProperties($properties=array())
- {
- if(!emptyempty($this->_config) && isset($this->_config['properties'])) {
- $this->_config['properties'] = $properties;
- $this->config_decrypted = true; // Don't decrypt external configs
- }
- }
那么最后,得到在config中得到就是:
- $config['properties'][''][''.phpinfo().''] = '1';
- saveConfig()
對setProperties()分析完畢之后,回到入口函數action_RunTest()。
程序繼續往下執行,進入到saveConfig()中。
其中關鍵的地方就在于將變量$this_config中的鍵值對,調用override_value_to_string_recursive2()函數變為一個字符串。
override_value_to_string_recursive2()
跟蹤override_value_to_string_recursive2(),進入到include/utils/array_utils.php中。
- function override_value_to_string_recursive2($array_name, $value_name, $value, $save_empty = true) {
- if (is_array($value)) {
- $str = '';
- $newArrayName = $array_name . "['$value_name']";
- foreach($value as $key=>$val) {
- $str.= override_value_to_string_recursive2($newArrayName, $key, $val, $save_empty);
- }
- return $str;
- } else {
- if(!$save_empty && emptyempty($value)){
- return;
- }else{
- return "/$$array_name" . "['$value_name'] = " . var_export($value, true) . ";/n";
- }
- }
- }
可以看到這就是一個普通的將一個數組類型的變量轉化為一個字符串,最后$this_conifg變為:
- /***CONNECTOR SOURCE***/
- $config['name'] = 'InsideView©';
- $config['order'] = 65;
- $config['properties'][''][''.phpinfo().''] = '1';
這個賦值給變量$config_str
PoC執行
回到saveConfig()函數中,程序最后執行
file_put_contents("custom/modules/Connectors/connectors/sources/{$dir}/config.php", $config_str);
其中的$dir為ext/rest/insideview,那么最后程序就會在custom/modules/Connectors/connectors/sources/ext/rest/insideview/config.php寫入$config_str的值,最后就會觸發其中的phpinfo()函數,導致代碼執行。
最后在config.php中寫入的代碼是:
- $config = array (
- 'name' => 'InsideView©',
- 'order' => 65,
- 'properties' => array (
- ),
- );
自此漏洞就分析完畢了。
修復
修復方法很簡單,在override_value_to_string_recursive2()函數中進行修復:
- function override_value_to_string_recursive2($array_name, $value_name, $value, $save_empty = true) {
- $quoted_vname = var_export($value_name, true);
- if (is_array($value)) {
- $str = '';
- $newArrayName = $array_name . "[$quoted_vname]";
- foreach($value as $key=>$val) {
- $str.= override_value_to_string_recursive2($newArrayName, $key, $val, $save_empty);
- }
- return $str;
- } else {
- if(!$save_empty && emptyempty($value)){
- return;
- }else{
- return "/$$array_name" . "[$quoted_vname] = " . var_export($value, true) . ";/n";
- }
- }
- }
修復的代碼就是使用了var_export()函數對$value_name變量進行了字符串的表示。這樣寫之后,最終得到$config_str的值是:
- /***CONNECTOR SOURCE***/
- $config['name'] = 'InsideView©';
- $config['order'] = 65;
- $config['properties']['']['/'.phpinfo()./''] = '1';
上面的代碼就可以正常地寫入到文件中,不會觸發代碼執行了。
總結:
通過分步調試的方法,能夠對這個漏洞理解得更加的透徹,通過這個漏洞也增加了自己調試漏洞的能力。
新聞熱點
疑難解答