dedecms v5.8 未授权RCE 漏洞
Posted 白昼小丑
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了dedecms v5.8 未授权RCE 漏洞相关的知识,希望对你有一定的参考价值。
在这篇博文中,我将分享对 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.ini
then 中设置,则addslashes
调用。但是我们可以通过$magic_quotes_gpc
在请求中使用并重写该变量并避免addslashes
!
此代码用于提交由未经身份验证的用户执行的反馈。我决定看一看,我发现以下沉没/plus/bookfeedback.php
:
<span style="color:#111111"><span style="background-color:#ffffff"><span style="background-以上是关于dedecms v5.8 未授权RCE 漏洞的主要内容,如果未能解决你的问题,请参考以下文章
Laravel v5.8 反序列化rce (CVE-2019-9081) 复现
X-ray捡洞中遇到的高频漏洞(Shiro默认key备份文件&敏感目录泄露Druid未授权访问phpstudy-nginx解析漏洞dedecms-cve-2018-6910)