Dedecms v5.8 未授权RCE 漏洞

0#漏洞简述

2021年9月30日,老外曝光了关于国产知名框架DedeCMS的远程未授权RCE漏洞,在国内安全圈引起了广泛关注和分析。
团队内部也专门对此漏洞进行了复现和分析,学习到了一些实用的知识。

以下内容,是翻译自该老外的博客,之所以没有第一时间放出来,是为了防止造成恶劣的影响。
原文地址:https://srcincite.io/blog/2021/09/30/chasing-a-dream-pwning-the-biggest-cms-in-china.html

1#漏洞详细信息
在这篇博文中,我将分享对 Dedecms(或翻译成英文的“Chasing a Dream”CMS)的技术评论,包括它的攻击面以及它与其他应用程序的不同之处。最后,我将结束一个影响v5.8.1 预发布的预认证远程代码执行漏洞。这是一款有趣的软件,因为它自最初发布以来已有 14 年的历史,而且 PHP 多年来发生了很大变化。

对于网上搜索“什么是中国最大的CMS”很快发现,多 源 的状态是DEDECMS是最流行的。然而,这些来源几乎都有一个共同点:它们都是旧的。

所以,我决定做一个粗略的搜索:

该产品部署非常广泛,但自2020 年 12 月 11 日推出以来,此处详述的漏洞影响了少数站点,并且从未将其纳入发布版本。

威胁建模
免责声明:我没有实际威胁建模的经验。在审核目标时,我问自己的第一件事是:如何将输入接受到应用程序中?
好吧,事实证明这个目标的问题的答案是在include/common.inc.php脚本中:
<span style="color:#111111"><span style="background-color:#ffffff"><span style="background-color:#eeeeff"><span style="background-color:#eeeeff"><code><strong>function</strong> _RunMagicQuotes(<strong>&</strong><span style="color:teal">$svar</span>)
{
    <strong>if</strong> (<strong>!@</strong><span style="color:#0086b3">get_magic_quotes_gpc</span>()) {
        <strong>if</strong> (<span style="color:#0086b3">is_array</span>(<span style="color:teal">$svar</span>)) {
            <strong>foreach</strong> (<span style="color:teal">$svar</span> <strong>as</strong> <span style="color:teal">$_k</span> <strong>=></strong> <span style="color:teal">$_v</span>) {
                <span style="color:teal">$svar</span>[<span style="color:teal">$_k</span>] <strong>=</strong> <span style="color:#990000"><strong>_RunMagicQuotes</strong></span>(<span style="color:teal">$_v</span>);
            }
 
        } <strong>else</strong> {
            <strong>if</strong> (<span style="color:#0086b3">strlen</span>(<span style="color:teal">$svar</span>) <strong>></strong> <span style="color:#009999">0</span> <strong>&&</strong> <span style="color:#0086b3">preg_match</span>(<span style="color:#dd1144">'#^(cfg_|GLOBALS|_GET|_POST|_COOKIE|_SESSION)#'</span>, <span style="color:teal">$svar</span>)) {
                <strong>exit</strong>(<span style="color:#dd1144">'Request var not allow!'</span>);
            }
            <span style="color:teal">$svar</span> <strong>=</strong> <span style="color:#0086b3">addslashes</span>(<span style="color:teal">$svar</span>);
        }
    }
    <strong>return</strong> <span style="color:teal">$svar</span>;
}
 
<span style="color:#999988"><em>//...</em></span>
 
<strong>if</strong> (<strong>!</strong><span style="color:#0086b3">defined</span>(<span style="color:#dd1144">'DEDEREQUEST'</span>)) {
    <span style="color:#999988"><em>//检查和注册外部提交的变量   (2011.8.10 修改登录时相关过滤)</em></span>
    <strong>function</strong> CheckRequest(<strong>&</strong><span style="color:teal">$val</span>)
    {
        <strong>if</strong> (<span style="color:#0086b3">is_array</span>(<span style="color:teal">$val</span>)) {
            <strong>foreach</strong> (<span style="color:teal">$val</span> <strong>as</strong> <span style="color:teal">$_k</span> <strong>=></strong> <span style="color:teal">$_v</span>) {
                <strong>if</strong> (<span style="color:teal">$_k</span> <strong>==</strong> <span style="color:#dd1144">'nvarname'</span>) {
                    <strong>continue</strong>;
                }
 
                <span style="color:#990000"><strong>CheckRequest</strong></span>(<span style="color:teal">$_k</span>);
                <span style="color:#990000"><strong>CheckRequest</strong></span>(<span style="color:teal">$val</span>[<span style="color:teal">$_k</span>]);
            }
        } <strong>else</strong> {
            <strong>if</strong> (<span style="color:#0086b3">strlen</span>(<span style="color:teal">$val</span>) <strong>></strong> <span style="color:#009999">0</span> <strong>&&</strong> <span style="color:#0086b3">preg_match</span>(<span style="color:#dd1144">'#^(cfg_|GLOBALS|_GET|_POST|_COOKIE|_SESSION)#'</span>, <span style="color:teal">$val</span>)) { <span style="color:#999988"><em>// 2</em></span>
                <strong>exit</strong>(<span style="color:#dd1144">'Request var not allow!'</span>);
            }
        }
    }
 
    <span style="color:#990000"><strong>CheckRequest</strong></span>(<span style="color:teal">$_REQUEST</span>);
    <span style="color:#990000"><strong>CheckRequest</strong></span>(<span style="color:teal">$_COOKIE</span>);
 
    <strong>foreach</strong> (<strong>array</strong>(<span style="color:#dd1144">'_GET'</span>, <span style="color:#dd1144">'_POST'</span>, <span style="color:#dd1144">'_COOKIE'</span>) <strong>as</strong> <span style="color:teal">$_request</span>) {
        <strong>foreach</strong> (<span style="color:teal">$$_request</span> <strong>as</strong> <span style="color:teal">$_k</span> <strong>=></strong> <span style="color:teal">$_v</span>) {
            <strong>if</strong> (<span style="color:teal">$_k</span> <strong>==</strong> <span style="color:#dd1144">'nvarname'</span>) {
                <span style="color:teal">${$_k}</span> <strong>=</strong> <span style="color:teal">$_v</span>;
            } <strong>else</strong> {
                <span style="color:teal">${$_k}</span> <strong>=</strong> <span style="color:#990000"><strong>_RunMagicQuotes</strong></span>(<span style="color:teal">$_v</span>); <span style="color:#999988"><em>// 1</em></span>
            }
 
        }
    }
}
</code></span></span></span></span>
如果我们在这里密切注意,我们可以在[1]看到代码重新启用,该代码register_globals已在PHP 5.4 中删除。

register_globals过去一直是应用程序的一个大问题,并且支持非常丰富的攻击面,这也是 PHP 过去声誉不佳的原因之一。还要注意,它们不保护[2]处的全局数组$_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
<span style="color:#111111"><span style="background-color:#ffffff"><span style="background-color:#eeeeff"><span style="background-color:#eeeeff"><code><strong>foreach</strong> (<span style="color:teal">$_FILES</span> <strong>as</strong> <span style="color:teal">$_key</span> <strong>=></strong> <span style="color:teal">$_value</span>) {
    <strong>foreach</strong> (<span style="color:teal">$keyarr</span> <strong>as</strong> <span style="color:teal">$k</span>) {
        <strong>if</strong> (<strong>!</strong><strong>isset</strong>(<span style="color:teal">$_FILES</span>[<span style="color:teal">$_key</span>][<span style="color:teal">$k</span>])) {
            <strong>exit</strong>(<span style="color:#dd1144">"DedeCMS Error: Request Error!"</span>);
        }
    }
    <strong>if</strong> (<span style="color:#0086b3">preg_match</span>(<span style="color:#dd1144">'#^(cfg_|GLOBALS)#'</span>, <span style="color:teal">$_key</span>)) {
        <strong>exit</strong>(<span style="color:#dd1144">'Request var not allow for uploadsafe!'</span>);
    }
    <span style="color:teal">$$_key</span> <strong>=</strong> <span style="color:teal">$_FILES</span>[<span style="color:teal">$_key</span>][<span style="color:#dd1144">'tmp_name'</span>];
    <span style="color:#a61717"><span style="background-color:#e3d2d2">$</span></span>{<span style="color:teal">$_key</span> <span style="color:#009999">.</span> <span style="color:#dd1144">'_name'</span>} <strong>=</strong> <span style="color:teal">$_FILES</span>[<span style="color:teal">$_key</span>][<span style="color:#dd1144">'name'</span>];  <span style="color:#999988"><em>// 4</em></span>
    <span style="color:#a61717"><span style="background-color:#e3d2d2">$</span></span>{<span style="color:teal">$_key</span> <span style="color:#009999">.</span> <span style="color:#dd1144">'_type'</span>} <strong>=</strong> <span style="color:teal">$_FILES</span>[<span style="color:teal">$_key</span>][<span style="color:#dd1144">'type'</span>] <strong>=</strong> <span style="color:#0086b3">preg_replace</span>(<span style="color:#dd1144">'#[^0-9a-z\./]#i'</span>, <span style="color:#dd1144">''</span>, <span style="color:teal">$_FILES</span>[<span style="color:teal">$_key</span>][<span style="color:#dd1144">'type'</span>]);
    <span style="color:#a61717"><span style="background-color:#e3d2d2">$</span></span>{<span style="color:teal">$_key</span> <span style="color:#009999">.</span> <span style="color:#dd1144">'_size'</span>} <strong>=</strong> <span style="color:teal">$_FILES</span>[<span style="color:teal">$_key</span>][<span style="color:#dd1144">'size'</span>] <strong>=</strong> <span style="color:#0086b3">preg_replace</span>(<span style="color:#dd1144">'#[^0-9]#'</span>, <span style="color:#dd1144">''</span>, <span style="color:teal">$_FILES</span>[<span style="color:teal">$_key</span>][<span style="color:#dd1144">'size'</span>]);
 
    <strong>if</strong> (<span style="color:#0086b3">is_array</span>(<span style="color:#a61717"><span style="background-color:#e3d2d2">$</span></span>{<span style="color:teal">$_key</span> <span style="color:#009999">.</span> <span style="color:#dd1144">'_name'</span>}) <strong>&&</strong> <span style="color:#0086b3">count</span>(<span style="color:#a61717"><span style="background-color:#e3d2d2">$</span></span>{<span style="color:teal">$_key</span> <span style="color:#009999">.</span> <span style="color:#dd1144">'_name'</span>}) <strong>></strong> <span style="color:#009999">0</span>) {
        <strong>foreach</strong> (<span style="color:#a61717"><span style="background-color:#e3d2d2">$</span></span>{<span style="color:teal">$_key</span> <span style="color:#009999">.</span> <span style="color:#dd1144">'_name'</span>} <strong>as</strong> <span style="color:teal">$key</span> <strong>=></strong> <span style="color:teal">$value</span>) {
            <strong>if</strong> (<strong>!</strong><span style="color:#0086b3">empty</span>(<span style="color:teal">$value</span>) <strong>&&</strong> (<span style="color:#0086b3">preg_match</span>(<span style="color:#dd1144">"#\.("</span> <span style="color:#009999">.</span> <span style="color:teal">$cfg_not_allowall</span> <span style="color:#009999">.</span> <span style="color:#dd1144">")$#i"</span>, <span style="color:teal">$value</span>) <strong>||</strong> <strong>!</strong><span style="color:#0086b3">preg_match</span>(<span style="color:#dd1144">"#\.#"</span>, <span style="color:teal">$value</span>))) {
                <strong>if</strong> (<strong>!</strong><span style="color:#0086b3">defined</span>(<span style="color:#dd1144">'DEDEADMIN'</span>)) {
                    <strong>exit</strong>(<span style="color:#dd1144">'Not Admin Upload filetype not allow !'</span>);
                }
            }
        }
    } <strong>else</strong> {
        <strong>if</strong> (<strong>!</strong><span style="color:#0086b3">empty</span>(<span style="color:#a61717"><span style="background-color:#e3d2d2">$</span></span>{<span style="color:teal">$_key</span> <span style="color:#009999">.</span> <span style="color:#dd1144">'_name'</span>}) <strong>&&</strong> (<span style="color:#0086b3">preg_match</span>(<span style="color:#dd1144">"#\.("</span> <span style="color:#009999">.</span> <span style="color:teal">$cfg_not_allowall</span> <span style="color:#009999">.</span> <span style="color:#dd1144">")$#i"</span>, <span style="color:#a61717"><span style="background-color:#e3d2d2">$</span></span>{<span style="color:teal">$_key</span> <span style="color:#009999">.</span> <span style="color:#dd1144">'_name'</span>}) <strong>||</strong> <strong>!</strong><span style="color:#0086b3">preg_match</span>(<span style="color:#dd1144">"#\.#"</span>, <span style="color:#a61717"><span style="background-color:#e3d2d2">$</span></span>{<span style="color:teal">$_key</span> <span style="color:#009999">.</span> <span style="color:#dd1144">'_name'</span>}))) {
            <strong>if</strong> (<strong>!</strong><span style="color:#0086b3">defined</span>(<span style="color:#dd1144">'DEDEADMIN'</span>)) {
                <strong>exit</strong>(<span style="color:#dd1144">'Not Admin Upload filetype not allow !'</span>);
            }
        }
    }
 
    <strong>if</strong> (<span style="color:#0086b3">empty</span>(<span style="color:#a61717"><span style="background-color:#e3d2d2">$</span></span>{<span style="color:teal">$_key</span> <span style="color:#009999">.</span> <span style="color:#dd1144">'_size'</span>})) {
        <span style="color:#a61717"><span style="background-color:#e3d2d2">$</span></span>{<span style="color:teal">$_key</span> <span style="color:#009999">.</span> <span style="color:#dd1144">'_size'</span>} <strong>=</strong> <strong>@</strong><span style="color:#0086b3">filesize</span>(<span style="color:teal">$$_key</span>); <span style="color:#999988"><em>// 3</em></span>
    }
</code></span></span></span></span>
<span style="color:#111111"><span style="background-color:#ffffff"><span style="background-color:#eeeeff"><span style="background-color:#eeeeff"><code>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
</code></span></span></span></span>
我没有报告这些错误,因为它们没有产生任何影响(否则我会称它们为漏洞)。开放 URL 重定向错误本身无法进一步攻击攻击者,如果没有小工具链,则无法触发 phar 反序列化错误。

不过,训练有素的眼睛会发现一些特别有趣的东西。在第[4] 行,代码使用_name未过滤的字符串创建了一个攻击者控制的变量_RunMagicQuotes。这意味着具有管理员凭据的攻击者可以sys_payment.php通过_RunMagicQuotes使用文件上传绕过该函数来触发脚本中的 SQL 注入:

作为参考,我们可以看到 SQL 注入在内部是如何表现的dede/sys_payment.php:
<span style="color:#111111"><span style="background-color:#ffffff"><span style="background-color:#eeeeff"><span style="background-color:#eeeeff"><code><span style="color:#999988"><em>//配置支付接口</em></span>
<strong>else</strong> <strong>if</strong> (<span style="color:teal">$dopost</span> <strong>==</strong> <span style="color:#dd1144">'config'</span>) { <span style="color:#999988"><em>// 5</em></span>
    <strong>if</strong> (<span style="color:teal">$pay_name</span> <strong>==</strong> <span style="color:#dd1144">""</span> <strong>||</strong> <span style="color:teal">$pay_desc</span> <strong>==</strong> <span style="color:#dd1144">""</span> <strong>||</strong> <span style="color:teal">$pay_fee</span> <strong>==</strong> <span style="color:#dd1144">""</span>) { <span style="color:#999988"><em>// 6</em></span>
        <span style="color:#990000"><strong>ShowMsg</strong></span>(<span style="color:#dd1144">"您有未填写的项目!"</span>, <span style="color:#dd1144">"-1"</span>);
        <strong>exit</strong>();
    }
    <span style="color:teal">$row</span> <strong>=</strong> <span style="color:teal">$dsql</span><strong>-></strong><span style="color:#990000"><strong>GetOne</strong></span>(<span style="color:#dd1144">"SELECT * FROM `#@__payment` WHERE id='</span><span style="color:teal">$pid</span><span style="color:#dd1144">'"</span>);
    <strong>if</strong> (<span style="color:teal">$cfg_soft_lang</span> <strong>==</strong> <span style="color:#dd1144">'utf-8'</span>) {
        <span style="color:teal">$config</span> <strong>=</strong> <span style="color:#990000"><strong>AutoCharset</strong></span>(<span style="color:#0086b3">unserialize</span>(<span style="color:#990000"><strong>utf82gb</strong></span>(<span style="color:teal">$row</span>[<span style="color:#dd1144">'config'</span>])));
    } <strong>else</strong> <strong>if</strong> (<span style="color:teal">$cfg_soft_lang</span> <strong>==</strong> <span style="color:#dd1144">'gb2312'</span>) {
        <span style="color:teal">$config</span> <strong>=</strong> <span style="color:#0086b3">unserialize</span>(<span style="color:teal">$row</span>[<span style="color:#dd1144">'config'</span>]);
    }
    <span style="color:teal">$payments</span> <strong>=</strong> <span style="color:#dd1144">"'code' => '"</span> <span style="color:#009999">.</span> <span style="color:teal">$row</span>[<span style="color:#dd1144">'code'</span>] <span style="color:#009999">.</span> <span style="color:#dd1144">"',"</span>;
    <strong>foreach</strong> (<span style="color:teal">$config</span> <strong>as</strong> <span style="color:teal">$key</span> <strong>=></strong> <span style="color:teal">$v</span>) {
        <span style="color:teal">$config</span>[<span style="color:teal">$key</span>][<span style="color:#dd1144">'value'</span>] <strong>=</strong> <span style="color:teal">${$key}</span>;
        <span style="color:teal">$payments</span> <span style="color:#009999">.</span><strong>=</strong> <span style="color:#dd1144">"'"</span> <span style="color:#009999">.</span> <span style="color:teal">$key</span> <span style="color:#009999">.</span> <span style="color:#dd1144">"' => '"</span> <span style="color:#009999">.</span> <span style="color:teal">$config</span>[<span style="color:teal">$key</span>][<span style="color:#dd1144">'value'</span>] <span style="color:#009999">.</span> <span style="color:#dd1144">"',"</span>;
    }
    <span style="color:teal">$payments</span> <strong>=</strong> <span style="color:#0086b3">substr</span>(<span style="color:teal">$payments</span>, <span style="color:#009999">0</span>, <strong>-</strong><span style="color:#009999">1</span>);
    <span style="color:teal">$payment</span> <strong>=</strong> <span style="color:#dd1144">"</span><span style="color:#dd1144">\$</span><span style="color:#dd1144">payment=array("</span> <span style="color:#009999">.</span> <span style="color:teal">$payments</span> <span style="color:#009999">.</span> <span style="color:#dd1144">")"</span>;
    <span style="color:teal">$configstr</span> <strong>=</strong> <span style="color:#dd1144">"<"</span> <span style="color:#009999">.</span> <span style="color:#dd1144">"?php</span><span style="color:#dd1144">\r\n</span><span style="color:#dd1144">"</span> <span style="color:#009999">.</span> <span style="color:teal">$payment</span> <span style="color:#009999">.</span> <span style="color:#dd1144">"</span><span style="color:#dd1144">\r\n</span><span style="color:#dd1144">?"</span> <span style="color:#009999">.</span> <span style="color:#dd1144">"></span><span style="color:#dd1144">\r\n</span><span style="color:#dd1144">"</span>;
    <strong>if</strong> (<strong>!</strong><span style="color:#0086b3">empty</span>(<span style="color:teal">$payment</span>)) {
        <span style="color:teal">$m_file</span> <strong>=</strong> <span style="color:teal">DEDEDATA</span> <span style="color:#009999">.</span> <span style="color:#dd1144">"/payment/"</span> <span style="color:#009999">.</span> <span style="color:teal">$row</span>[<span style="color:#dd1144">'code'</span>] <span style="color:#009999">.</span> <span style="color:#dd1144">".php"</span>;
        <span style="color:teal">$fp</span> <strong>=</strong> <span style="color:#0086b3">fopen</span>(<span style="color:teal">$m_file</span>, <span style="color:#dd1144">"w"</span>) <strong>or</strong> <strong>die</strong>(<span style="color:#dd1144">"写入文件 </span><span style="color:teal">$safeconfigfile</span><span style="color:#dd1144"> 失败,请检查权限!"</span>);
        <span style="color:#0086b3">fwrite</span>(<span style="color:teal">$fp</span>, <span style="color:teal">$configstr</span>);
        <span style="color:#0086b3">fclose</span>(<span style="color:teal">$fp</span>);
    }
    <strong>if</strong> (<span style="color:teal">$cfg_soft_lang</span> <strong>==</strong> <span style="color:#dd1144">'utf-8'</span>) {
        <span style="color:teal">$config</span> <strong>=</strong> <span style="color:#990000"><strong>AutoCharset</strong></span>(<span style="color:teal">$config</span>, <span style="color:#dd1144">'utf-8'</span>, <span style="color:#dd1144">'gb2312'</span>);
        <span style="color:teal">$config</span> <strong>=</strong> <span style="color:#0086b3">serialize</span>(<span style="color:teal">$config</span>);
        <span style="color:teal">$config</span> <strong>=</strong> <span style="color:#990000"><strong>gb2utf8</strong></span>(<span style="color:teal">$config</span>);
    } <strong>else</strong> {
        <span style="color:teal">$config</span> <strong>=</strong> <span style="color:#0086b3">serialize</span>(<span style="color:teal">$config</span>);
    }
 
    <span style="color:teal">$query</span> <strong>=</strong> <span style="color:#dd1144">"UPDATE `#@__payment` SET name = '</span><span style="color:teal">$pay_name</span><span style="color:#dd1144">',fee='</span><span style="color:teal">$pay_fee</span><span style="color:#dd1144">',description='</span><span style="color:teal">$pay_desc</span><span style="color:#dd1144">',config='</span><span style="color:teal">$config</span><span style="color:#dd1144">',enabled='1' WHERE id='</span><span style="color:teal">$pid</span><span style="color:#dd1144">'"</span>; <span style="color:#999988"><em>// 7</em></span>
    <span style="color:teal">$dsql</span><strong>-></strong><span style="color:#990000"><strong>ExecuteNoneQuery</strong></span>(<span style="color:teal">$query</span>); <span style="color:#999988"><em>// 8</em></span>
</code></span></span></span></span>
在[5]和[6] 处,有一些检查$dopost设置为config和$pay_name,$pay_desc并且$pay_fee是从请求中设置的。稍后在[7]代码使用提供的攻击者构建原始 SQL 查询$pay_name,最后在[8]我认为触发了 SQL 注入......

纵深防御
过去,Dedecms 开发人员受到SQL 注入漏洞的严重打击(可能是由于register_globals在源代码级别启用)。在上面的例子中,我们得到了服务器的响应Safe Alert: Request Error step 2,当然我们的注入失败了。这是为什么?查看include/dedesqli.class.php以了解:
<span style="color:#111111"><span style="background-color:#ffffff"><span style="background-color:#eeeeff"><span style="background-color:#eeeeff"><code><span style="color:#999988"><em>//SQL语句过滤程序,由80sec提供,这里作了适当的修改</em></span>
<strong>function</strong> CheckSql(<span style="color:teal">$db_string</span>, <span style="color:teal">$querytype</span> <strong>=</strong> <span style="color:#dd1144">'select'</span>)
{
 
    <span style="color:#999988"><em>// ...more checks...</em></span>
 
    <span style="color:#999988"><em>//老版本的Mysql并不支持union,常用的程序里也不使用union,但是一些黑客使用它,所以检查它</em></span>
    <strong>if</strong> (<span style="color:#0086b3">strpos</span>(<span style="color:teal">$clean</span>, <span style="color:#dd1144">'union'</span>) <strong>!==</strong> <strong>false</strong> <strong>&&</strong> <span style="color:#0086b3">preg_match</span>(<span style="color:#dd1144">'~(^|[^a-z])union($|[^[a-z])~s'</span>, <span style="color:teal">$clean</span>) <strong>!=</strong> <span style="color:#009999">0</span>) {
        <span style="color:teal">$fail</span> <strong>=</strong> <strong>true</strong>;
        <span style="color:teal">$error</span> <strong>=</strong> <span style="color:#dd1144">"union detect"</span>;
    }
 
    <span style="color:#999988"><em>// ...more checks...</em></span>
 
    <span style="color:#999988"><em>//老版本的MYSQL不支持子查询,我们的程序里可能也用得少,但是黑客可以使用它来查询数据库敏感信息</em></span>
    <strong>elseif</strong> (<span style="color:#0086b3">preg_match</span>(<span style="color:#dd1144">'~\([^)]*?select~s'</span>, <span style="color:teal">$clean</span>) <strong>!=</strong> <span style="color:#009999">0</span>) {
        <span style="color:teal">$fail</span> <strong>=</strong> <strong>true</strong>;
        <span style="color:teal">$error</span> <strong>=</strong> <span style="color:#dd1144">"sub select detect"</span>;
    }
    <strong>if</strong> (<strong>!</strong><span style="color:#0086b3">empty</span>(<span style="color:teal">$fail</span>)) {
        <span style="color:#0086b3">fputs</span>(<span style="color:#0086b3">fopen</span>(<span style="color:teal">$log_file</span>, <span style="color:#dd1144">'a+'</span>), <span style="color:#dd1144">"</span><span style="color:teal">$userIP</span><span style="color:#dd1144">||</span><span style="color:teal">$getUrl</span><span style="color:#dd1144">||</span><span style="color:teal">$db_string</span><span style="color:#dd1144">||</span><span style="color:teal">$error</span><span style="color:#dd1144">\r\n</span><span style="color:#dd1144">"</span>);
        <strong>exit</strong>(<span style="color:#dd1144">"<font size='5' color='red'>Safe Alert: Request Error step 2!</font>"</span>);  <span style="color:#999988"><em>// 9</em></span>
    } <strong>else</strong> {
        <strong>return</strong> <span style="color:teal">$db_string</span>;
    }
</code></span></span></span></span>
现在我不知道80Sec是谁,但他们看起来很严肃。在CheckSql从被称为Execute
<span style="color:#111111"><span style="background-color:#ffffff"><span style="background-color:#eeeeff"><span style="background-color:#eeeeff"><code>    <span style="color:#999988"><em>//执行一个带返回结果的SQL语句,如SELECT,SHOW等</em></span>
    <strong>public</strong> <strong>function</strong> Execute(<span style="color:teal">$id</span> <strong>=</strong> <span style="color:#dd1144">"me"</span>, <span style="color:teal">$sql</span> <strong>=</strong> <span style="color:#dd1144">''</span>)
    {
 
        <span style="color:#999988"><em>//...</em></span>
 
        <span style="color:#999988"><em>//SQL语句安全检查</em></span>
        <strong>if</strong> (<span style="color:teal">$this</span><strong>-></strong>safeCheck) {
            <span style="color:#990000"><strong>CheckSql</strong></span>(<span style="color:teal">$this</span><strong>-></strong>queryString);
        }
</code></span></span></span></span>
和SetQuery:
<span style="color:#111111"><span style="background-color:#ffffff"><span style="background-color:#eeeeff"><span style="background-color:#eeeeff"><code>    <strong>public</strong> <strong>function</strong> SetQuery(<span style="color:teal">$sql</span>)
    {
        <span style="color:teal">$prefix</span> <strong>=</strong> <span style="color:#dd1144">"#@__"</span>;
        <span style="color:teal">$sql</span> <strong>=</strong> <span style="color:#0086b3">trim</span>(<span style="color:teal">$sql</span>);
        <strong>if</strong> (<span style="color:#0086b3">substr</span>(<span style="color:teal">$sql</span>, <strong>-</strong><span style="color:#009999">1</span>) <strong>!==</strong> <span style="color:#dd1144">";"</span>) {
            <span style="color:teal">$sql</span> <span style="color:#009999">.</span><strong>=</strong> <span style="color:#dd1144">";"</span>;
        }
        <span style="color:teal">$sql</span> <strong>=</strong> <span style="color:#0086b3">str_replace</span>(<span style="color:teal">$prefix</span>, <span style="color:teal">$GLOBALS</span>[<span style="color:#dd1144">'cfg_dbprefix'</span>], <span style="color:teal">$sql</span>);
 
        <span style="color:#990000"><strong>CheckSql</strong></span>(<span style="color:teal">$sql</span>, <span style="color:teal">$this</span><strong>-></strong><span style="color:#990000"><strong>getSQLType</strong></span>(<span style="color:teal">$sql</span>)); <span style="color:#999988"><em>// 5.7前版本仅做了SELECT的过滤,对UPDATE、INSERT、DELETE等语句并未过滤。</em></span>
         
        <span style="color:teal">$this</span><strong>-></strong>queryString <strong>=</strong> <span style="color:teal">$sql</span>;
    }
</code></span></span></span></span>
但是我们可以通过使用另一个同样调用的函数来避免这个函数,mysqli_query例如GetTableFields:
<span style="color:#111111"><span style="background-color:#ffffff"><span style="background-color:#eeeeff"><span style="background-color:#eeeeff"><code> <span style="color:#999988"><em>//获取特定表的信息</em></span>
    <strong>public</strong> <strong>function</strong> GetTableFields(<span style="color:teal">$tbname</span>, <span style="color:teal">$id</span> <strong>=</strong> <span style="color:#dd1144">"me"</span>)
    {
        <strong>global</strong> <span style="color:teal">$dsqli</span>;
        <strong>if</strong> (<strong>!</strong><span style="color:teal">$dsqli</span><strong>-></strong>isInit) {
            <span style="color:teal">$this</span><strong>-></strong><span style="color:#990000"><strong>Init</strong></span>(<span style="color:teal">$this</span><strong>-></strong>pconnect);
        }
        <span style="color:teal">$prefix</span> <strong>=</strong> <span style="color:#dd1144">"#@__"</span>;
        <span style="color:teal">$tbname</span> <strong>=</strong> <span style="color:#0086b3">str_replace</span>(<span style="color:teal">$prefix</span>, <span style="color:teal">$GLOBALS</span>[<span style="color:#dd1144">'cfg_dbprefix'</span>], <span style="color:teal">$tbname</span>);
        <span style="color:teal">$query</span> <strong>=</strong> <span style="color:#dd1144">"SELECT * FROM </span><span style="color:#dd1144">{</span><span style="color:teal">$tbname</span><span style="color:#dd1144">}</span><span style="color:#dd1144"> LIMIT 0,1"</span>;
        <span style="color:teal">$this</span><strong>-></strong>result[<span style="color:teal">$id</span>] <strong>=</strong> <span style="color:#990000"><strong>mysqli_query</strong></span>(<span style="color:teal">$this</span><strong>-></strong>linkID, <span style="color:teal">$query</span>);
    }
</code></span></span></span></span>
这不是,只是任何旧水槽。这个不使用引号,所以我们不需要打破带引号的字符串,这是必需的,因为我们的输入将流经_RunMagicQuotes函数。GetTableFields可以dede/sys_data_done.php在第[10]行的脚本中找到危险的用法:
<span style="color:#111111"><span style="background-color:#ffffff"><span style="background-color:#eeeeff"><span style="background-color:#eeeeff"><code><strong>if</strong> (<span style="color:teal">$dopost</span> <strong>==</strong> <span style="color:#dd1144">'bak'</span>) {
    <strong>if</strong> (<span style="color:#0086b3">empty</span>(<span style="color:teal">$tablearr</span>)) {
        <span style="color:#990000"><strong>ShowMsg</strong></span>(<span style="color:#dd1144">'你没选中任何表!'</span>, <span style="color:#dd1144">'javascript:;'</span>);
        <strong>exit</strong>();
    }
    <strong>if</strong> (<strong>!</strong><span style="color:#0086b3">is_dir</span>(<span style="color:teal">$bkdir</span>)) {
        <span style="color:#990000"><strong>MkdirAll</strong></span>(<span style="color:teal">$bkdir</span>, <span style="color:teal">$cfg_dir_purview</span>);
        <span style="color:#990000"><strong>CloseFtp</strong></span>();
    }
 
    <strong>if</strong> (<span style="color:#0086b3">empty</span>(<span style="color:teal">$nowtable</span>)) {
        <span style="color:teal">$nowtable</span> <strong>=</strong> <span style="color:#dd1144">''</span>;
    }
    <strong>if</strong> (<span style="color:#0086b3">empty</span>(<span style="color:teal">$fsize</span>)) {
        <span style="color:teal">$fsize</span> <strong>=</strong> <span style="color:#009999">20480</span>;
    }
    <span style="color:teal">$fsizeb</span> <strong>=</strong> <span style="color:teal">$fsize</span> <strong>*</strong> <span style="color:#009999">1024</span>;
    
    <span style="color:#999988"><em>//第一页的操作</em></span>
    <strong>if</strong> (<span style="color:teal">$nowtable</span> <strong>==</strong> <span style="color:#dd1144">''</span>) {
        <span style="color:#999988"><em>//...</em></span>
    }
    <span style="color:#999988"><em>//执行分页备份</em></span>
    <strong>else</strong> {
        <span style="color:teal">$j</span> <strong>=</strong> <span style="color:#009999">0</span>;
        <span style="color:teal">$fs</span> <strong>=</strong> <strong>array</strong>();
        <span style="color:teal">$bakStr</span> <strong>=</strong> <span style="color:#dd1144">''</span>;
 
        <span style="color:#999988"><em>//分析表里的字段信息</em></span>
        <span style="color:teal">$dsql</span><strong>-></strong><span style="color:#990000"><strong>GetTableFields</strong></span>(<span style="color:teal">$nowtable</span>); <span style="color:#999988"><em>// 10</em></span>
</code></span></span></span></span>
<span style="color:#111111"><span style="background-color:#ffffff"><span style="background-color:#eeeeff"><span style="background-color:#eeeeff"><code>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;
</code></span></span></span></span>
但是当然,这需要管理员权限,我们对此并不感兴趣(没有提升权限或绕过身份验证)。

查找预先验证的端点
如果我们尝试有点困难,虽然,我们可以找到一些更有趣的代码include/filter.inc.php在稍旧版本:DedeCMS-V5.7-UTF8-SP2.tar.gz。
<span style="color:#111111"><span style="background-color:#ffffff"><span style="background-color:#eeeeff"><span style="background-color:#eeeeff"><code><span style="color:teal">$magic_quotes_gpc</span> <strong>=</strong> <span style="color:#0086b3">ini_get</span>(<span style="color:#dd1144">'magic_quotes_gpc'</span>);
<strong>function</strong> _FilterAll(<span style="color:teal">$fk</span>, <strong>&</strong><span style="color:teal">$svar</span>)
{
    <strong>global</strong> <span style="color:teal">$cfg_notallowstr</span>, <span style="color:teal">$cfg_replacestr</span>, <span style="color:teal">$magic_quotes_gpc</span>;
    <strong>if</strong> (<span style="color:#0086b3">is_array</span>(<span style="color:teal">$svar</span>)) {
        <strong>foreach</strong> (<span style="color:teal">$svar</span> <strong>as</strong> <span style="color:teal">$_k</span> <strong>=></strong> <span style="color:teal">$_v</span>) {
            <span style="color:teal">$svar</span>[<span style="color:teal">$_k</span>] <strong>=</strong> <span style="color:#990000"><strong>_FilterAll</strong></span>(<span style="color:teal">$fk</span>, <span style="color:teal">$_v</span>);
        }
    } <strong>else</strong> {
        <strong>if</strong> (<span style="color:teal">$cfg_notallowstr</span> <strong>!=</strong> <span style="color:#dd1144">''</span> <strong>&&</strong> <span style="color:#0086b3">preg_match</span>(<span style="color:#dd1144">"#"</span> <span style="color:#009999">.</span> <span style="color:teal">$cfg_notallowstr</span> <span style="color:#009999">.</span> <span style="color:#dd1144">"#i"</span>, <span style="color:teal">$svar</span>)) {
            <span style="color:#990000"><strong>ShowMsg</strong></span>(<span style="color:#dd1144">" </span><span style="color:teal">$fk</span><span style="color:#dd1144"> has not allow words!"</span>, <span style="color:#dd1144">'-1'</span>);
            <strong>exit</strong>();
        }
        <strong>if</strong> (<span style="color:teal">$cfg_replacestr</span> <strong>!=</strong> <span style="color:#dd1144">''</span>) {
            <span style="color:teal">$svar</span> <strong>=</strong> <span style="color:#0086b3">preg_replace</span>(<span style="color:#dd1144">'/'</span> <span style="color:#009999">.</span> <span style="color:teal">$cfg_replacestr</span> <span style="color:#009999">.</span> <span style="color:#dd1144">'/i'</span>, <span style="color:#dd1144">"***"</span>, <span style="color:teal">$svar</span>);
        }
    }
    <strong>if</strong> (<strong>!</strong><span style="color:teal">$magic_quotes_gpc</span>) {
        <span style="color:teal">$svar</span> <strong>=</strong> <span style="color:#0086b3">addslashes</span>(<span style="color:teal">$svar</span>);
    }
    <strong>return</strong> <span style="color:teal">$svar</span>;
}
 
<span style="color:#999988"><em>/* 对_GET,_POST,_COOKIE进行过滤 */</em></span>
<strong>foreach</strong> (<strong>array</strong>(<span style="color:#dd1144">'_GET'</span>, <span style="color:#dd1144">'_POST'</span>, <span style="color:#dd1144">'_COOKIE'</span>) <strong>as</strong> <span style="color:teal">$_request</span>) {
    <strong>foreach</strong> (<span style="color:teal">$$_request</span> <strong>as</strong> <span style="color:teal">$_k</span> <strong>=></strong> <span style="color:teal">$_v</span>) {
        <span style="color:teal">${$_k}</span> <strong>=</strong> <span style="color:#990000"><strong>_FilterAll</strong></span>(<span style="color:teal">$_k</span>, <span style="color:teal">$_v</span>);
    }
}
</code></span></span></span></span>
你能看出这里有什么问题吗?$magic_quotes_gpc配置中的代码集。如果没有在php.inithen 中设置,则addslashes调用。但是我们可以通过$magic_quotes_gpc在请求中使用并重写该变量并避免addslashes!

此代码用于提交由未经身份验证的用户执行的反馈。我决定看一看,我发现以下沉没/plus/bookfeedback.php:
<span style="color:#111111"><span style="background-color:#ffffff"><span style="background-color:#eeeeff"><span style="background-color:#eeeeff"><code><strong>else</strong> <strong>if</strong>(<span style="color:teal">$action</span><strong>==</strong><span style="color:#dd1144">'send'</span>)
{
    <span style="color:#999988"><em>//...</em></span>
    <span style="color:#999988"><em>//检查验证码</em></span>
    <strong>if</strong>(<span style="color:teal">$cfg_feedback_ck</span><strong>==</strong><span style="color:#dd1144">'Y'</span>)
    {
        <span style="color:teal">$validate</span> <strong>=</strong> <strong>isset</strong>(<span style="color:teal">$validate</span>) <strong>?</strong> <span style="color:#0086b3">strtolower</span>(<span style="color:#0086b3">trim</span>(<span style="color:teal">$validate</span>)) <strong>:</strong> <span style="color:#dd1144">''</span>;
        <span style="color:teal">$svali</span> <strong>=</strong> <span style="color:#0086b3">strtolower</span>(<span style="color:#0086b3">trim</span>(<span style="color:#990000"><strong>GetCkVdValue</strong></span>()));
        <strong>if</strong>(<span style="color:teal">$validate</span> <strong>!=</strong> <span style="color:teal">$svali</span> <strong>||</strong> <span style="color:teal">$svali</span><strong>==</strong><span style="color:#dd1144">''</span>)
        {
            <span style="color:#990000"><strong>ResetVdValue</strong></span>();
            <span style="color:#990000"><strong>ShowMsg</strong></span>(<span style="color:#dd1144">'验证码错误!'</span>,<span style="color:#dd1144">'-1'</span>);
            <strong>exit</strong>();
        }
    }
 
    <span style="color:#999988"><em>//...</em></span>
    <strong>if</strong>(<span style="color:teal">$comtype</span> <strong>==</strong> <span style="color:#dd1144">'comments'</span>)
    {
        <span style="color:teal">$arctitle</span> <strong>=</strong> <span style="color:#0086b3">addslashes</span>(<span style="color:teal">$arcRow</span>[<span style="color:#dd1144">'arctitle'</span>]);
        <span style="color:teal">$arctitle</span> <strong>=</strong> <span style="color:teal">$arcRow</span>[<span style="color:#dd1144">'arctitle'</span>];
        <strong>if</strong>(<span style="color:teal">$msg</span><strong>!=</strong><span style="color:#dd1144">''</span>)
        {
            <span style="color:teal">$inquery</span> <strong>=</strong> <span style="color:#dd1144">"INSERT INTO `#@__bookfeedback`(`aid`,`catid`,`username`,`arctitle`,`ip`,`ischeck`,`dtime`, `mid`,`bad`,`good`,`ftype`,`face`,`msg`)
                   VALUES ('</span><span style="color:teal">$aid</span><span style="color:#dd1144">','</span><span style="color:teal">$catid</span><span style="color:#dd1144">','</span><span style="color:teal">$username</span><span style="color:#dd1144">','</span><span style="color:teal">$bookname</span><span style="color:#dd1144">','</span><span style="color:teal">$ip</span><span style="color:#dd1144">','</span><span style="color:teal">$ischeck</span><span style="color:#dd1144">','</span><span style="color:teal">$dtime</span><span style="color:#dd1144">', '</span><span style="color:#dd1144">{</span><span style="color:teal">$cfg_ml</span><strong>-></strong><span style="color:teal">M_ID</span><span style="color:#dd1144">}</span><span style="color:#dd1144">','0','0','</span><span style="color:teal">$feedbacktype</span><span style="color:#dd1144">','</span><span style="color:teal">$face</span><span style="color:#dd1144">','</span><span style="color:teal">$msg</span><span style="color:#dd1144">'); "</span>;  <span style="color:#999988"><em>// 11</em></span>
            <span style="color:teal">$rs</span> <strong>=</strong> <span style="color:teal">$dsql</span><strong>-></strong><span style="color:#990000"><strong>ExecuteNoneQuery</strong></span>(<span style="color:teal">$inquery</span>); <span style="color:#999988"><em>// 12</em></span>
            <strong>if</strong>(<strong>!</strong><span style="color:teal">$rs</span>)
            {
                <strong>echo</strong> <span style="color:teal">$dsql</span><strong>-></strong><span style="color:#990000"><strong>GetError</strong></span>();
                <strong>exit</strong>();
            }
        }
    }
</code></span></span></span></span>
在[11] 中,我们可以看到代码使用攻击者控制的输入(例如$catid和 )构建查询$bookname。有可能进入这个接收器并绕过addslashes触发未经身份验证的 SQL 注入:

我们设置了会话 cookie,因为它与存储在未经身份验证的会话中的验证码相关联:

CheckSql幸运的是,我无法绕过(不),但我可以绕过并从数据库中泄漏一些数据,因为我可以同时使用$catid和$bookname进行注入,然后(ab)使用第二个命令:
<span style="color:#111111"><span style="background-color:#ffffff"><span style="background-color:#eeeeff"><span style="background-color:#eeeeff"><code><strong>else</strong> <strong>if</strong>(<span style="color:teal">$action</span><strong>==</strong><span style="color:#dd1144">'quote'</span>)
{
    <span style="color:teal">$row</span> <strong>=</strong> <span style="color:teal">$dsql</span><strong>-></strong><span style="color:#990000"><strong>GetOne</strong></span>(<span style="color:#dd1144">"Select * from `#@__bookfeedback` where id ='</span><span style="color:teal">$fid</span><span style="color:#dd1144">'"</span>);
    <strong>require_once</strong>(<span style="color:teal">DEDEINC</span><span style="color:#009999">.</span><span style="color:#dd1144">'/dedetemplate.class.php'</span>);
    <span style="color:teal">$dtp</span> <strong>=</strong> <strong>new</strong> <span style="color:#445588"><strong>DedeTemplate</strong></span>();
    <span style="color:teal">$dtp</span><strong>-></strong><span style="color:#990000"><strong>LoadTemplate</strong></span>(<span style="color:teal">$cfg_basedir</span><span style="color:#009999">.</span><span style="color:teal">$cfg_templets_dir</span><span style="color:#009999">.</span><span style="color:#dd1144">'/plus/bookfeedback_quote.htm'</span>);
    <span style="color:teal">$dtp</span><strong>-></strong><span style="color:#990000"><strong>Display</strong></span>();
    <strong>exit</strong>();
}
</code></span></span></span></span>
我所要做的就是猜测$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脚本内部:
<span style="color:#111111"><span style="background-color:#ffffff"><span style="background-color:#eeeeff"><span style="background-color:#eeeeff"><code><strong>if</strong> (<span style="color:teal">$dopost</span> <strong>==</strong> <span style="color:#dd1144">'save'</span>) {
    <span style="color:teal">$validate</span> <strong>=</strong> <strong>isset</strong>(<span style="color:teal">$validate</span>) <strong>?</strong> <span style="color:#0086b3">strtolower</span>(<span style="color:#0086b3">trim</span>(<span style="color:teal">$validate</span>)) <strong>:</strong> <span style="color:#dd1144">''</span>;
    <span style="color:teal">$svali</span> <strong>=</strong> <span style="color:#990000"><strong>GetCkVdValue</strong></span>();
    <strong>if</strong> (<span style="color:teal">$validate</span> <strong>==</strong> <span style="color:#dd1144">''</span> <strong>||</strong> <span style="color:teal">$validate</span> <strong>!=</strong> <span style="color:teal">$svali</span>) {
        <span style="color:#990000"><strong>ShowMsg</strong></span>(<span style="color:#dd1144">'验证码不正确!'</span>, <span style="color:#dd1144">'-1'</span>); <span style="color:#999988"><em>// 1</em></span>
        <strong>exit</strong>();
    }
</code></span></span></span></span>
在[1] 处,我们可以观察到ShowMsg在 中定义的调用include/common.func.php:
<span style="color:#111111"><span style="background-color:#ffffff"><span style="background-color:#eeeeff"><span style="background-color:#eeeeff"><code><strong>function</strong> ShowMsg(<span style="color:teal">$msg</span>, <span style="color:teal">$gourl</span>, <span style="color:teal">$onlymsg</span> <strong>=</strong> <span style="color:#009999">0</span>, <span style="color:teal">$limittime</span> <strong>=</strong> <span style="color:#009999">0</span>)
{
    <strong>if</strong> (<span style="color:#0086b3">empty</span>(<span style="color:teal">$GLOBALS</span>[<span style="color:#dd1144">'cfg_plus_dir'</span>])) {
        <span style="color:teal">$GLOBALS</span>[<span style="color:#dd1144">'cfg_plus_dir'</span>] <strong>=</strong> <span style="color:#dd1144">'..'</span>;
    }
    <strong>if</strong> (<span style="color:teal">$gourl</span> <strong>==</strong> <strong>-</strong><span style="color:#009999">1</span>) { <span style="color:#999988"><em>// 2</em></span>
        <span style="color:teal">$gourl</span> <strong>=</strong> <strong>isset</strong>(<span style="color:teal">$_SERVER</span>[<span style="color:#dd1144">'HTTP_REFERER'</span>]) <strong>?</strong> <span style="color:teal">$_SERVER</span>[<span style="color:#dd1144">'HTTP_REFERER'</span>] <strong>:</strong> <span style="color:#dd1144">''</span>; <span style="color:#999988"><em>// 3</em></span>
        <strong>if</strong> (<span style="color:teal">$gourl</span> <strong>==</strong> <span style="color:#dd1144">""</span>) {
            <span style="color:teal">$gourl</span> <strong>=</strong> <strong>-</strong><span style="color:#009999">1</span>;
        }
    }
 
    <span style="color:teal">$htmlhead</span> <strong>=</strong> <span style="color:#dd1144">"
    <html></span><span style="color:#dd1144">\r\n</span><span style="color:#dd1144"><head></span><span style="color:#dd1144">\r\n</span><span style="color:#dd1144"><title>DedeCMS提示信息
    ...
    <script></span><span style="color:#dd1144">\r\n</span><span style="color:#dd1144">"</span>;
    <span style="color:teal">$htmlfoot</span> <strong>=</strong> <span style="color:#dd1144">"
    </script>
    ...
    </body></span><span style="color:#dd1144">\r\n</span><span style="color:#dd1144"></html></span><span style="color:#dd1144">\r\n</span><span style="color:#dd1144">"</span>;
 
    <span style="color:teal">$litime</span> <strong>=</strong> (<span style="color:teal">$limittime</span> <strong>==</strong> <span style="color:#009999">0</span> <strong>?</strong> <span style="color:#009999">1000</span> <strong>:</strong> <span style="color:teal">$limittime</span>);
    <span style="color:teal">$func</span> <strong>=</strong> <span style="color:#dd1144">''</span>;
 
    <span style="color:#999988"><em>//...</em></span>
 
    <strong>if</strong> (<span style="color:teal">$gourl</span> <strong>==</strong> <span style="color:#dd1144">''</span> <strong>||</strong> <span style="color:teal">$onlymsg</span> <strong>==</strong> <span style="color:#009999">1</span>) {
        <span style="color:#999988"><em>//...</em></span>
    } <strong>else</strong> {
        <span style="color:#999988"><em>//...</em></span>
        <span style="color:teal">$func</span> <span style="color:#009999">.</span><strong>=</strong> <span style="color:#dd1144">"var pgo=0;
      function JumpUrl(){
        if(pgo==0){ location='</span><span style="color:teal">$gourl</span><span style="color:#dd1144">'; pgo=1; }
      }</span><span style="color:#dd1144">\r\n</span><span style="color:#dd1144">"</span>;
        <span style="color:teal">$rmsg</span> <strong>=</strong> <span style="color:teal">$func</span>;
        <span style="color:#999988"><em>//...</em></span>
        <strong>if</strong> (<span style="color:teal">$onlymsg</span> <strong>==</strong> <span style="color:#009999">0</span>) {
            <strong>if</strong> (<span style="color:teal">$gourl</span> <strong>!=</strong> <span style="color:#dd1144">'javascript:;'</span> <strong>&&</strong> <span style="color:teal">$gourl</span> <strong>!=</strong> <span style="color:#dd1144">''</span>) {
                <span style="color:teal">$rmsg</span> <span style="color:#009999">.</span><strong>=</strong> <span style="color:#dd1144">"<br /><a href='</span><span style="color:#dd1144">{</span><span style="color:teal">$gourl</span><span style="color:#dd1144">}</span><span style="color:#dd1144">'>如果你的浏览器没反应,请点击这里...</a>"</span>;
                <span style="color:teal">$rmsg</span> <span style="color:#009999">.</span><strong>=</strong> <span style="color:#dd1144">"<br/></div></span><span style="color:#dd1144">\"</span><span style="color:#dd1144">);</span><span style="color:#dd1144">\r\n</span><span style="color:#dd1144">"</span>;
                <span style="color:teal">$rmsg</span> <span style="color:#009999">.</span><strong>=</strong> <span style="color:#dd1144">"setTimeout('JumpUrl()',</span><span style="color:teal">$litime</span><span style="color:#dd1144">);"</span>;
            } <strong>else</strong> {
                <span style="color:#999988"><em>//...</em></span>
            }
        } <strong>else</strong> {
            <span style="color:#999988"><em>//...</em></span>
        }
        <span style="color:teal">$msg</span> <strong>=</strong> <span style="color:teal">$htmlhead</span> <span style="color:#009999">.</span> <span style="color:teal">$rmsg</span> <span style="color:#009999">.</span> <span style="color:teal">$htmlfoot</span>;
    }
 
    <span style="color:teal">$tpl</span> <strong>=</strong> <strong>new</strong> <span style="color:#445588"><strong>DedeTemplate</strong></span>();
    <span style="color:teal">$tpl</span><strong>-></strong><span style="color:#990000"><strong>LoadString</strong></span>(<span style="color:teal">$msg</span>); <span style="color:#999988"><em>// 4</em></span>
    <span style="color:teal">$tpl</span><strong>-></strong><span style="color:#990000"><strong>Display</strong></span>(); <span style="color:#999988"><em>// 5</em></span>
}
</code></span></span></span></span>
我们可以在[2]中看到,如果$gourl设置为 -1,则攻击者可以通过引用标头控制[3]$gourl处的变量。该变量未过滤并嵌入到变量中,该变量由[4]处的调用加载并由[5]处的调用解析。在里面我们发现:$msgLoadStringDisplayinclude/dedetemplate.class.php
<span style="color:#111111"><span style="background-color:#ffffff"><span style="background-color:#eeeeff"><span style="background-color:#eeeeff"><code><strong>class</strong> <span style="color:#445588"><strong>DedeTemplate</strong></span>
{
    <span style="color:#999988"><em>//...</em></span>
    <strong>public</strong> <strong>function</strong> LoadString(<span style="color:teal">$str</span> <strong>=</strong> <span style="color:#dd1144">''</span>)
    {
        <span style="color:teal">$this</span><strong>-></strong>sourceString <strong>=</strong> <span style="color:teal">$str</span>; <span style="color:#999988"><em>// 6</em></span>
        <span style="color:teal">$hashcode</span> <strong>=</strong> <span style="color:#0086b3">md5</span>(<span style="color:teal">$this</span><strong>-></strong>sourceString);
        <span style="color:teal">$this</span><strong>-></strong>cacheFile <strong>=</strong> <span style="color:teal">$this</span><strong>-></strong>cacheDir <span style="color:#009999">.</span> <span style="color:#dd1144">"/string_"</span> <span style="color:#009999">.</span> <span style="color:teal">$hashcode</span> <span style="color:#009999">.</span> <span style="color:#dd1144">".inc"</span>;
        <span style="color:teal">$this</span><strong>-></strong>configFile <strong>=</strong> <span style="color:teal">$this</span><strong>-></strong>cacheDir <span style="color:#009999">.</span> <span style="color:#dd1144">"/string_"</span> <span style="color:#009999">.</span> <span style="color:teal">$hashcode</span> <span style="color:#009999">.</span> <span style="color:#dd1144">"_config.inc"</span>;
        <span style="color:teal">$this</span><strong>-></strong><span style="color:#990000"><strong>ParseTemplate</strong></span>();
    }
    
    <span style="color:#999988"><em>//...</em></span>
    <strong>public</strong> <strong>function</strong> Display()
    {
        <strong>global</strong> <span style="color:teal">$gtmpfile</span>;
        <span style="color:#0086b3">extract</span>(<span style="color:teal">$GLOBALS</span>, <span style="color:teal">EXTR_SKIP</span>);
        <span style="color:teal">$this</span><strong>-></strong><span style="color:#990000"><strong>WriteCache</strong></span>(); <span style="color:#999988"><em>// 7</em></span>
        <strong>include</strong> <span style="color:teal">$this</span><strong>-></strong>cacheFile; <span style="color:#999988"><em>// 9</em></span>
    }
</code></span></span></span></span>
在[6]的sourceString设置与攻击者控制的$msg。然后在[7] 处 WriteCache被调用:
<span style="color:#111111"><span style="background-color:#ffffff"><span style="background-color:#eeeeff"><span style="background-color:#eeeeff"><code>    <strong>public</strong> <strong>function</strong> WriteCache(<span style="color:teal">$ctype</span> <strong>=</strong> <span style="color:#dd1144">'all'</span>)
    {
        <strong>if</strong> (<strong>!</strong><span style="color:#0086b3">file_exists</span>(<span style="color:teal">$this</span><strong>-></strong>cacheFile) <strong>||</strong> <span style="color:teal">$this</span><strong>-></strong>isCache <strong>==</strong> <strong>false</strong>
            <strong>||</strong> (<span style="color:#0086b3">file_exists</span>(<span style="color:teal">$this</span><strong>-></strong>templateFile) <strong>&&</strong> (<span style="color:#0086b3">filemtime</span>(<span style="color:teal">$this</span><strong>-></strong>templateFile) <strong>></strong> <span style="color:#0086b3">filemtime</span>(<span style="color:teal">$this</span><strong>-></strong>cacheFile)))
        ) {
            <strong>if</strong> (<strong>!</strong><span style="color:teal">$this</span><strong>-></strong>isParse) {
                <span style="color:#999988"><em>//...</em></span>
            }
            <span style="color:teal">$fp</span> <strong>=</strong> <span style="color:#0086b3">fopen</span>(<span style="color:teal">$this</span><strong>-></strong>cacheFile, <span style="color:#dd1144">'w'</span>) <strong>or</strong> <span style="color:#0086b3">dir</span>(<span style="color:#dd1144">"Write Cache File Error! "</span>);
            <span style="color:#0086b3">flock</span>(<span style="color:teal">$fp</span>, <span style="color:#009999">3</span>);
            <span style="color:teal">$result</span> <strong>=</strong> <span style="color:#0086b3">trim</span>(<span style="color:teal">$this</span><strong>-></strong><span style="color:#990000"><strong>GetResult</strong></span>()); <span style="color:#999988"><em>// 8</em></span>
            <span style="color:teal">$errmsg</span> <strong>=</strong> <span style="color:#dd1144">''</span>;     
            <strong>if</strong> (<strong>!</strong><span style="color:teal">$this</span><strong>-></strong><span style="color:#990000"><strong>CheckDisabledFunctions</strong></span>(<span style="color:teal">$result</span>, <span style="color:teal">$errmsg</span>)) { <span style="color:#999988"><em>// 9</em></span>
                <span style="color:#0086b3">fclose</span>(<span style="color:teal">$fp</span>);
                <strong>@</strong><span style="color:#0086b3">unlink</span>(<span style="color:teal">$this</span><strong>-></strong>cacheFile);
                <strong>die</strong>(<span style="color:teal">$errmsg</span>);
            }
            <span style="color:#0086b3">fwrite</span>(<span style="color:teal">$fp</span>, <span style="color:teal">$result</span>);
            <span style="color:#0086b3">fclose</span>(<span style="color:teal">$fp</span>);
            <span style="color:#999988"><em>//...</em></span>
        }
</code></span></span></span></span>
在[8] 处,代码调用GetResult返回值sourceString来设置$result变量,该变量现在包含攻击者控制的输入。在[9] 处,该CheckDisabledFunctions函数在$result变量上被调用。让我们看看是什么CheckDisabledFunctions:
<span style="color:#111111"><span style="background-color:#ffffff"><span style="background-color:#eeeeff"><span style="background-color:#eeeeff"><code>    <strong>public</strong> <strong>function</strong> CheckDisabledFunctions(<span style="color:teal">$str</span>, <strong>&</strong><span style="color:teal">$errmsg</span> <strong>=</strong> <span style="color:#dd1144">''</span>)
    {
        <strong>global</strong> <span style="color:teal">$cfg_disable_funs</span>;
        <span style="color:teal">$cfg_disable_funs</span> <strong>=</strong> <strong>isset</strong>(<span style="color:teal">$cfg_disable_funs</span>) <strong>?</strong> <span style="color:teal">$cfg_disable_funs</span> <strong>:</strong> <span style="color:#dd1144">'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'</span>;
        <span style="color:#999988"><em>// 模板引擎增加disable_functions</em></span>
        <strong>if</strong> (<strong>!</strong><span style="color:#0086b3">defined</span>(<span style="color:#dd1144">'DEDEDISFUN'</span>)) {
            <span style="color:teal">$tokens</span> <strong>=</strong> <span style="color:#990000"><strong>token_get_all_nl</strong></span>(<span style="color:teal">$str</span>);
            <span style="color:teal">$disabled_functions</span> <strong>=</strong> <span style="color:#0086b3">explode</span>(<span style="color:#dd1144">','</span>, <span style="color:teal">$cfg_disable_funs</span>);
            <strong>foreach</strong> (<span style="color:teal">$tokens</span> <strong>as</strong> <span style="color:teal">$token</span>) {
                <strong>if</strong> (<span style="color:#0086b3">is_array</span>(<span style="color:teal">$token</span>)) {
                    <strong>if</strong> (<span style="color:teal">$token</span>[<span style="color:#009999">0</span>] <strong>=</strong> <span style="color:#dd1144">'306'</span> <strong>&&</strong> <span style="color:#0086b3">in_array</span>(<span style="color:teal">$token</span>[<span style="color:#009999">1</span>], <span style="color:teal">$disabled_functions</span>)) {
                        <span style="color:teal">$errmsg</span> <strong>=</strong> <span style="color:#dd1144">'DedeCMS Error:function disabled "'</span> <span style="color:#009999">.</span> <span style="color:teal">$token</span>[<span style="color:#009999">1</span>] <span style="color:#009999">.</span> <span style="color:#dd1144">'" <a href="http://help.dedecms.com/install-use/apply/2013/0711/2324.html" target="_blank">more...</a>'</span>;
                        <strong>return</strong> <strong>false</strong>;
                    }
                }
            }
        }
        <strong>return</strong> <strong>true</strong>;
    }
</code></span></span></span></span>
好。这是可能的攻击者绕过此否认与一些创意几种方式列表,写出恶意PHP到临时文件,并最终达到include在Display在[9]执行任意代码。

概念证明
可以借用自己的代码并调用危险函数,但无论如何都有几种通用方法可以绕过拒绝列表。不检查引用标头是否有双引号,因此以下有效负载将起作用:

以下(非详尽的)列表路径可以到达该漏洞:
  • /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

报告
我在 2021 年 4 月左右发现了这个漏洞,但决定继续使用它,因为它只影响pre-release发行版而不是发行版。在 repo 上几个月不活动后,我决定在 9 月 23 日向该错误报告,opensource@dedecms.com两天后发布了一个解决该错误的静默补丁:

由于开发人员的这种行为,我决定不报告影响发布版本的其余 RCE 漏洞。虽然我同意不需要 CVE,但我确实认为至少应该在提交中添加安全说明。

总结
我真的很喜欢审核中国软件,因为开发人员的思维方式往往与西方开发人员截然不同。逻辑流程更加流畅,作为安全审计员,它要求您在看到代码中出现的新模式时站起来思考并改变策略。

这是一个简单的提醒,即使产品被审计到死,也不要对自己失去信心。即使您不会说中文,您的下一个 RCE 也即将到来。
本博客所有文章如无特别注明均为原创。作者:渊龙Sec团队复制或转载请以超链接形式注明转自 渊龙Sec安全团队博客
原文地址《Dedecms v5.8 未授权RCE 漏洞
分享到:更多

相关推荐

网友评论(0)