Discuz:A “different” real penetration test case analysis

Category: Tags: ,

0x00 Preface

This article is a technical analysis article derived from a real authorized penetration case. In the article, we will first briefly introduce the overall penetration process of this case and demonstrate part of it, but we will not describe it in detail. We will only briefly introduce the infiltration process, and then extract the highlights of the entire infiltration process, and then conduct technical analysis and discussion, hoping that different people will have different gains.

0x01 Brief description of penetration process

After receiving the project, the technical analysis team carried out project analysis and information collection and sorting, and sorted out a batch of domain names and some key sites. Among them, a combination of phpmyadmin and discuz was built, and both were exposed to the Internet, which is also very common A situation. Due to the parsing configuration problem of a certain web port of the website, php was not parsed and formed an arbitrary file download vulnerability. Through this vulnerability, we obtained the root account and password of MySQL. Due to the strict permission settings of the Linux server, it is impossible to log in to mysql directly with phpmyadmin and get the discuz webshell by escalation. After many attempts, we used phpmyadmin to replace the administrator hash to log in to the discuz backend. In the discuz backend, we used the vulnerability to modify the ucenter configuration file to write into the webshell.

After entering the intranet, I found a service containing java webdav (intra-domain windows, which will be called A server in the following text) when detecting the intranet web through simple 80 and 443. Use the xxe of java webdav to execute NTLM Relay. At the same time, collect the user names in the discuz database, use kerberos AS_REQ and password injection (a combination of a password and different user names and then KDC enumerate) fortunately to obtain a set of user accounts and passwords in the domain, and use this user to add a machine account. Combining NTLM Relay and this machine account, and using resource-based constrained delegation, the machine account has the authority to control server A. Log in to server A and bypass Kaspersky anti-virus software to grab the domain management password, and this task of tackling the problem ended. The sample picture is as follows:

In this penetration process, we believe that the Discuz x3 series and xxe to domain control are worthy of analysis and discussion.

0x02 Discuz X3 series

This section is divided into 3 parts. First, I will make a brief summary of the main vulnerabilities in the later versions of Discuz X3, then do some analysis on several keys of discuz, and finally release the latest discuz getshell.

Summary of Vulnerabilities in Discuz X3

At present, it is basically Discuz programs above x3, and only the vulnerabilities above x3 are summarized here. The summary is not to re-analyze every vulnerability. It is unnecessary. There are already many excellent analysis articles on the Internet. Then why should we summarize it? If you are a first-line penetration tester or a classmate who is evaluated by a red team, you should often encounter discuz. Often most classmates give up when they look at the program version and search for vulnerabilities. In everyone’s impression, discuz is difficult, and there is no need to spend too much time on it, but in fact discuz is not as safe as you think. This section will summarize the various small loopholes of discuz and put forward some ideas based on our own breakthroughs in discuz goals.

Types Applicable version Applicable conditions Utilization analysis Article
SSRF <= x3.4 Repair patch 1.windows<br/>2.php>5.3+php-curl<=7.54<br/>3.DZ is open on port 80 The use of SSRF is mainly to attack other services. In most cases, you need to use the gopher protocol.<br/>In DZ, you need to use the cache (redis, memcache) getshell. Of course, the tcp protocol simulated by gopher. If there are other in the server or intranet Available services, then you can also construct a data table to use. 1. Discuz x3.4 front SSRF analysis<br/>

2. DiscuzX Discovery and Utilization of Two SSRFs<br/>

3. Discuz! RCE caused by unauthorized access to Memcached

Arbitrary file deletion <= x3.4<br/>Repair patch Front user permissions After the installation of Discuz is successful, the installation files will be deleted after logging in to the background, so reinstallation and utilization cannot be realized. <br/>The main use in reality is to delete the index.htm file, and then use the directory traversal to obtain the backup file, through the various sensitive information (various KEY, hash) in the backup file, and then further use. Discuz!X ≤3.4 arbitrary file deletion vulnerability analysis
Short file name vulnerability Not repaired windows It seems to be a rather tasteless trick, but it is very useful when guessing the file name of some random commands. For example, we can download the database backup file by using the short file name (the file name contains random characters). Using the backup file, we can try to crack the user password. https://gitee.com/ComsenzDiscuz/DiscuzX/issues/I10NG9
authkey prediction <x 3.4 No The essence of the problem is that mt_rand() shares the random number seed in the same process. Using the guessed authkey, we can crack the encryption verification process of discuz theme function Discuz_X authkey security vulnerability analysis
Background SQL injection <=3.4<br/>Repair patch Background permissions The discuz backend already has the database backup function, so the effect of select injection will be reduced a lot. The biggest significance of this vulnerability lies in the write file getshell of the lower version of mysql (writing to the discuz backup directory is not possible here, because the mkdir of discuz is set to 0777, However, due to the influence of umask, the actual write is 0755, so it is more difficult to write files), but because the x3 backend does not have the function of directly executing SQL, if there is an injection, we can cross-database query to get other websites with the same mysql . Discuz! X series full version background Sql injection vulnerability
Background injection vulnerability <=3.4<br/>Repair patch Background permissions Because it is an update injection, we can already use database backups to obtain data in the background. This is of little significance to this website, but if there are other websites with the same mysql, if the authority is not strict, cross-database query to get other websites with the same mysql. SQL injection
Set mysql arbitrary file reading in the background <=3.4<br/>Repair patch Background permissions After reading through the file, we can combine the use of keys such as uc_key and authkey. Mysql arbitrary file reading attack chain expansion
Background command execution 1.5-2.5<br/>Repair patch Background permissions This vulnerability is a command injection vulnerability, but due to the error of the developer, 3.x is not available. The vulnerability itself was only fixed in x3.4 CVE-2018-14729
RCE caused by unauthorized access to Memcached <=3.4 memcached permissions Need to modify the permissions of memcached, this permission can come from ssrf, can also come from unauthorized Discuz! RCE caused by unauthorized access to MemcachedDiscuz!因Memcached未授权访问导致的RCE
Discuz! X3.1 background arbitrary code execution <=x3.1 Background permissions The getshell method of the x3.1 intermediate version,just for reference Discuz! X3.1 background arbitrary code execution
Background uc_center code execution < 3.4<br/>Repair patch Background permissions Please see the following article content for use and analysis This article will analyze

To sum up:

For discuz’s ssrf vulnerability, access to the intranet IP is restricted in the patch, which makes it difficult to exploit.

In the background getshell, it is recommended to use uc_center rce to be more convenient, and to kill all versions including the latest version, as analyzed later.

UC_KEY direct getshell has been fixed in the latest version above x3, but it may be used in some versions before 3.2.

The above vulnerabilities should not be comprehensive, but the combination of a few humble small vulnerabilities will find great power. Careful readers should find that the greatest harm that most of the above vulnerabilities can cause is information leakage. What is the use of information leakage? Below we will analyze several Discuz keys. You should have understood here. Through information leakage, you can obtain relevant keys, break through Discuz’s encryption system, and gain higher authority.

Discuz several key analysis

Through analysis, in discuz, there are mainly the following kinds of keys, which together constitute the encryption and decryption system of discuz, the naming here is repeated, and I have marked the corresponding key value and the location of the key. As shown in the following table:

In fact, the main discussion is only authkey, UC_KEY(dz), UC_KEY(uc_server), UC_MYKEY, authkey(uc_server). Let’s first look at how these keys came from, and where they ended up.

Key generation

authkey, UC_KEY(dz), UC_KEY(uc_server), UC_MYKEY are all generated during installation. The generation of authkey (uc_server) is closely related to UC_MYKEY, which will be described in detail later. The generated code is as follows:

                $uid = DZUCFULL ? 1 : $adminuser['uid'];
                $authkey = md5($_SERVER['SERVER_ADDR'].$_SERVER['HTTP_USER_AGENT'].$dbhost.$dbuser.$dbpw.$dbname.$username.$password.$pconnect.substr($timestamp, 0, 8)).random(18);
                $_config['db'][1]['dbhost'] = $dbhost;
                $_config['db'][1]['dbname'] = $dbname;
                $_config['db'][1]['dbpw'] = $dbpw;
                $_config['db'][1]['dbuser'] = $dbuser;
                $_config['db'][1]['tablepre'] = $tablepre;
                $_config['admincp']['founder'] = (string)$uid;
                $_config['security']['authkey'] = $authkey;
                $_config['cookie']['cookiepre'] = random(4).'_';
                $_config['memory']['prefix'] = random(6).'_';

                save_config_file(ROOT_PATH.CONFIG, $_config, $default_config);

                $db = new dbstuff;

                $db->connect($dbhost, $dbuser, $dbpw, $dbname, DBCHARSET);

                if(!VIEW_OFF) {

                if(DZUCFULL) {

                $db->query("REPLACE INTO {$tablepre}common_setting (skey, svalue) VALUES ('authkey', '$authkey')");

We see that the key generation depends on the random function customized by discuz, and the authkey blasting problem that has occurred has also arisen. During the installation, due to the same cgi process, mt_rand() only sowed the seed once, so the problem of random number seed blasting and key guessing occurred. In version 3.4, the authkey is generated by splicing a complete 32-bit string, which makes it impossible to blast and calculate the first half of the authkey, so this problem has been fixed. But the principle of this vulnerability is worth learning. At the end of the code, it can be seen that the authkey is also placed in the database after it is generated, and finally the authkey exists in the database pre_common_setting table and the /config/config_global.php configuration file. The instal_uc_server() function in the code realizes the generation of UC_KEY(dz), UC_KEY(uc_server), using the same generation function _generate_key(), the code is as follows:

function _generate_key() {
 $random = random(32);
 $return = array();
 for($i=0; $i<32; $i++) {
  $return[$i] = $random[$i].$info[$i];
 return implode('', $return);

The generated algorithm involves the http header information of the installation environment and the installation process, causing the blasting to basically fail, which is unpredictable. Finally, UC_KEY(dz) is saved in /config/config_ucenter.php, and UC_KEY(uc_server) is saved in /uc_server/data/config.inc.php.

Thoughts on Discuz Key

We look at the source code to analyze the functions affected by each key. Through these function points, we can get more information. The integration and utilization of information is often the key to our penetration. Below we will do some thinking and give some examples.

1. authkey
The use of authkey occupies a heavy proportion in the main discuz program, mainly used for the encrypted storage and decryption of user data, such as the storage and use of alipay related payment data, the storage of FTP passwords, etc.; it is also used for verification of some functions , Such as verification code verification, upload hash verification, etc.; user permission verification also uses authkey, such as source/class/discuz/discuz_application.php in _init_user() using authkey to decode the auth field in the cookie , And use the unlocked uid and pw for permission verification, but only knowing that authkey cannot complete the permission verification, we also need to know the user’s “password hash” (the password field in the database pre_common_member table, here is only one The random value of md5, the real user password hash is in pre_ucenter_members), when we can read the database data through other methods, we can forge the login information to log in. For example, the authkey in source/include/misc/misc_emailcheck.php participates in the generation of the verification hash. When we know the authkey, by forging the hash, we can modify the user’s registered email address, and then use the password to retrieve the front-end user ( The administrator cannot use the password retrieval function).

2. UC_KEY(dz)
UC_KEY (dz) is also the protagonist of the frequently mentioned UC_KEY GetWebShell. It is mainly used in two places: one is for database backup api/db/dbbak.php; the other is for operations related to users and login and cache files. The main function is located in the uc_note class in api/uc.php.

Regarding the use of UC_KEY(dz), basically GetWebShell is through uc.php, but this vulnerability has been fixed in the new version. The use of UC_KEY(dz) is not limited to this. If you read the dbbak.php code, you will find that with UC_KEY(dz) we can directly back up the database, download the database, and find relevant information from the database for further penetration.

Another place is the uc_note class. For example, the synlogin() function inside can fake login to any front-end user. Of course, there are other functions, which are not analyzed here.

3. UC_KEY(uc_server)
UC_KEY (uc_server) is often overlooked by everyone, it is actually more used than UC_KEY (dz). First of all, he can also back up the database. Students who are familiar with the discuz code should know that there are two dbbak.php files, one is the api/db/dbbak.php mentioned above; the other is uc_server/api/dbbak.php, Their code can be said to be almost the same. The only difference is that there are two more constant definitions in api/db/dbbak.php, which basically does not affect much. These two files can be controlled by UC_KEY (dz) and UC_KEY (uc_server).

UC_KEY (uc_server) almost controls all Ucenter functions related to authorization authentication. For example, the permission verification function sid_decode(), in which UC_KEY (uc_server) and the user-controllable http header jointly generate the sid for permission authentication, so we can forge the sid to bypass some permission checks. There are also related uses of seccode, which will not be introduced here.

The entire discuz program actually includes the discuz main program and Ucenter, and Ucenter relies more on a fixed key system. I feel that the vulnerability of Ucenter may be easier to discover than the vulnerability of the discuz main program, you can try.

UC_MYKEY is mainly used to encrypt and decrypt UC_KEY (discuz), as shown below:

UC_KEY(dz) ----- authcode(UC_MYKEY, ENCODE) ----> authkey(uc_server)

authkey(uc_server) ----- authcode(UC_MYKEY, DECODE) ----> UC_KEY(dz)

authkey (uc_server) is stored in the authkey field in pre_ucenter_applications of the database. The code generated by authkey (uc_server) is as follows:

                        $authkey = getgpc('authkey', 'P');
                        $authkey = $this->authcode($authkey, 'ENCODE', UC_MYKEY);
                        $synlogin = getgpc('synlogin', 'P');
                        $app = $this->db->result_first("SELECT COUNT(*) FROM ".UC_DBTABLEPRE."applications WHERE name='$name'");
                        if($app) {
                                $this->message('app_add_name_invalid', 'BACK');
                        } else {
                                $extra = serialize(array('apppath'=> getgpc('apppath', 'P')));
                                $this->db->query("INSERT INTO ".UC_DBTABLEPRE."applications SET name='$name', url='$url', ip='$ip',
                                        viewprourl='$viewprourl', apifilename='$apifilename', authkey='$authkey', synlogin='$synlogin',
                                        type='$type', recvnote='$recvnote', extra='$extra',
                                $appid = $this->db->insert_id();

Now we can know that UC_KEY(dz) can be obtained from two places, one is the configuration file and the other is the database. Those who are familiar with discuz will find a problem. The authkey (uc_server) obtained by injection can sometimes be used directly as UC_KEY (dz). But sometimes it is found to be a string greater than 64 bits or a string less than 64 bits. This is because, if it is the default discuz main program and Ucenter installation, at this time the authkey field in the database pre_ucenter_applications is stored as UC_KEY(dz). If you modify UC_KEY(dz) through the ucenter background, the authkey field in the database pre_ucenter_applications is stored Is the result calculated by the algorithm mentioned above. The length of this result is variable, it is a string of 40 bits or more.

to sum up

For getshell, in the low version before x3 and some unupdated versions before x3.2, we can directly use discuz’s uc_key(dz) combined with api/uc.php front-end getshell to obtain uc_key(dz). :

The authkey (uc_server) in the database is combined with UC_MYKEY, which can also be seen in the UCenter background, without using * to display.
Obtain uc_key(dz) for file leakage and other issues
After the x3 version, the use of keys is mainly focused on operating the database and UCenter functions, using various methods to enter the discuz background, combined with the background GetWebShell method described next to obtain the final permissions.

GetWebShell patch bypass in the background
In the version less than x3.4, the use method that has been announced on the Internet is to modify the Ucenter database connection information in the background. Since the writing is not escaped, the one-sentence Trojan is directly written into the config/config_ucenter.php file, causing the code to execute.

But this vulnerability has been fixed in the new version of x3.4, the code is as follows:

// https://gitee.com/ComsenzDiscuz/DiscuzX/blob/v3.4-20191201/upload/source/admincp/admincp_setting.php  x3.4
  if($operation == 'uc' && is_writeable('./config/config_ucenter.php') && $isfounder) {
    require_once './config/config_ucenter.php';

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

    if(function_exists("mysql_connect") && ini_get("mysql.allow_local_infile")=="1" && constant("UC_DBHOST") != $settingnew['uc']['dbhost']){
      cpmsg('uc_config_load_data_local_infile_error', '', 'error');

    if($settingnew['uc']['connect']) {
      $uc_dblink = function_exists("mysql_connect") ? @mysql_connect($settingnew['uc']['dbhost'], $settingnew['uc']['dbuser'], $ucdbpassnew, 1) : new mysqli($settingnew['uc']['dbhost'], $settingnew['uc']['dbuser'], $ucdbpassnew);
      if(!$uc_dblink) {
        cpmsg('uc_database_connect_error', '', 'error');
      } else {
        if(function_exists("mysql_connect")) {
        } else {

    $fp = fopen('./config/config_ucenter.php', 'r');
    $configfile = fread($fp, filesize('./config/config_ucenter.php'));
    $configfile = trim($configfile);
    $configfile = substr($configfile, -2) == '?>' ? substr($configfile, 0, -2) : $configfile;

    $connect = '';
    $settingnew['uc'] = daddslashes($settingnew['uc']);
    if($settingnew['uc']['connect']) {
      $connect = 'mysql';
      $samelink = ($dbhost == $settingnew['uc']['dbhost'] && $dbuser == $settingnew['uc']['dbuser'] && $dbpw == $ucdbpassnew);
      $samecharset = !($dbcharset == 'gbk' && UC_DBCHARSET == 'latin1' || $dbcharset == 'latin1' && UC_DBCHARSET == 'gbk');
      $configfile = str_replace("define('UC_DBHOST', '".addslashes(UC_DBHOST)."')", "define('UC_DBHOST', '".$settingnew['uc']['dbhost']."')", $configfile);
      $configfile = str_replace("define('UC_DBUSER', '".addslashes(UC_DBUSER)."')", "define('UC_DBUSER', '".$settingnew['uc']['dbuser']."')", $configfile);
      $configfile = str_replace("define('UC_DBPW', '".addslashes(UC_DBPW)."')", "define('UC_DBPW', '".$ucdbpassnew."')", $configfile);

The patch escapes $ucdbpassnew, and if(function_exists(“mysql_connect”) && ini_get(“mysql.allow_local_infile”)==”1″ && constant(“UC_DBHOST”) != $settingnew[‘uc’][‘ dbhost’]), this patch also solves the problem of malicious mysql file reading.

Bypass patch
Through the patch, we know that all Ucenter configuration parameters will be escaped, but I found that the configuration file changes of discuz are all done by replacing characters. It is easy to have problems in the replacement characters, so look for the relevant code for configuration modification in the source code, and finally find the utilization point in api/uc.php.


if(!defined('IN_UC')) {
  require_once '../source/class/class_core.php';

  $discuz = C::app();

  require DISCUZ_ROOT.'./config/config_ucenter.php';

  $get = $post = array();

  $code = @$_GET['code'];
  parse_str(authcode($code, 'DECODE', UC_KEY), $get);

  if(time() - $get['time'] > 3600) {
    exit('Authracation has expiried');
  if(empty($get)) {
    exit('Invalid Request');

  include_once DISCUZ_ROOT.'./uc_client/lib/xml.class.php';
  $post = xml_unserialize(file_get_contents('php://input'));

  if(in_array($get['action'], array('test', 'deleteuser', 'renameuser', 'gettag', 'synlogin', 'synlogout', 'updatepw', 'updatebadwords', 'updatehosts', 'updateapps', 'updateclient', 'updatecredit', 'getcredit', 'getcreditsettings', 'updatecreditsettings', 'addfeed'))) {
    $uc_note = new uc_note();
    echo call_user_func(array($uc_note, $get['action']), $get, $post);
  } else {
} else {
  function updateapps($get, $post) {
    global $_G;


    $UC_API = '';
    if($post['UC_API']) {
      $UC_API = str_replace(array('\'', '"', '\\', "\0", "\n", "\r"), '', $post['UC_API']);

    $cachefile = DISCUZ_ROOT.'./uc_client/data/cache/apps.php';
    $fp = fopen($cachefile, 'w');
    $s = "<?php\r\n";
    $s .= '$_CACHE[\'apps\'] = '.var_export($post, TRUE).";\r\n";
    fwrite($fp, $s);

    if($UC_API && is_writeable(DISCUZ_ROOT.'./config/config_ucenter.php')) {
      if(preg_match('/^https?:\/\//is', $UC_API)) {
        $configfile = trim(file_get_contents(DISCUZ_ROOT.'./config/config_ucenter.php'));
        $configfile = substr($configfile, -2) == '?>' ? substr($configfile, 0, -2) : $configfile;
        $configfile = preg_replace("/define\('UC_API',\s*'.*?'\);/i", "define('UC_API', '".addslashes($UC_API)."');", $configfile);
        if($fp = @fopen(DISCUZ_ROOT.'./config/config_ucenter.php', 'w')) {
          @fwrite($fp, trim($configfile));

The update of uc_api is completed in the updateapps function. The regularity here is non-greedy when matching. There will be a problem here. When uc_api is define(‘UC_API’,’\ ‘);phpinfo();//’); When we execute the updateapps function to update uc_api, phpinfo(); will be released. To use updateapps to update uc_api, we need to know the value of UC_KEY(dz), and the value of UC_KEY(dz) happens to be set by our background.


Utilization analysis

The following content is visible to members

You do not have permission to read this content, click here to become a member and refresh this page to read it


There are no reviews yet.

Be the first to review “Discuz:A “different” real penetration test case analysis”

Your email address will not be published. Required fields are marked *