phpMyFAQ exposed an unauthenticated API endpoint meant for setup backups. By sending a simple POST request, attackers can generate and download a ZIP file containing the `database.php` config, which holds cleartext database credentials. Patch to 4.0.16 immediately and rotate your database keys.
A critical failure in access control within phpMyFAQ allows unauthenticated attackers to trigger a configuration backup, resulting in the exfiltration of cleartext database credentials.
We all love convenience. Developers love it even more. In the world of web applications, nothing says "I love my users" quite like an automated setup wizard that handles the dirty work for you. phpMyFAQ, a popular open-source FAQ system, decided to be helpful during its update process. It included a feature to back up your critical configuration files before applying patches. A noble goal.
However, in the rush to be helpful, the developers committed the cardinal sin of API design: they assumed that just because a function is part of the "Setup" controller, nobody would call it unless they were supposed to. They built a mechanism to zip up the keys to the kingdom—specifically the database configuration—and put it on a silver platter.
CVE-2025-69200 isn't some complex heap overflow or a race condition that requires nanosecond precision. It is a logic flaw so simple it hurts. It’s the digital equivalent of locking your front door but installing a button on the outside labeled "Dispense Spare Key." And guess what? The internet loves pressing buttons.
The vulnerability lives inside src/phpMyFAQ/Controller/Api/SetupController.php. This controller is designed to handle installation and setup tasks. Historically, setup scripts are tricky because they often need to run before an admin user even exists or is authenticated. However, this specific endpoint, /api/setup/backup, was left exposed to the entire world without any access control checks.
The root cause is a classic Missing Function Level Access Control (CWE-262) combined with Sensitive Information Exposure (CWE-202). When the backup() method is invoked, it doesn't check if you are an admin. It doesn't check if you have a valid session token. It doesn't even check if you said "please."
Instead, it obediently executes a routine that targets content/core/config/. It grabs everything in there—most notably database.php—and compresses it into a ZIP archive. Depending on the version and configuration, it then either returns the path to this ZIP file in the API response or saves it to a predictable directory like data/ or attachments/ inside the web root. For an attacker, this is game over. The database.php file contains the host, database name, username, and—crucially—the plaintext password for the backend database.
Let’s look at the logic flow that makes this possible. While I won't paste the entire copyrighted source, the logic in the vulnerable SetupController effectively boiled down to this pseudocode:
// Vulnerable Logic in SetupController.php
public function backup(): Response
{
// Note the complete lack of:
// if (!$user->isAdmin()) { return new Response(403); }
$backupManager = new Backup($this->config);
$zipPath = $backupManager->createConfigBackup();
return new JsonResponse(['success' => true, 'file' => $zipPath]);
}See that? It just... runs. It assumes that if you can reach the API route, you are allowed to be there.
The fix, implemented in version 4.0.16, was two-fold, but the initial commit history tells a funny story. First, developers often try to fix these things by removing the UI element that calls the API. Commit b0e99ee3 removed the "Please make sure you have a copy of your configuration" link from the frontend templates.
[!ALERT] Developer Tip: Hiding a button does not delete the API endpoint. Security through obscurity is not security; it's just hide-and-seek with people who are better at seeking than you are at hiding.
Fortunately, the actual patch also secured the backend method. The fixed code enforces an authorization check, ensuring that only an authenticated session with administrative privileges can trigger the backup routine. If you try to hit it now without a session cookie, you get a 403 Forbidden (or a 401 Unauthorized), which is exactly what should have happened in the first place.
Exploiting this is trivially easy. You don't need Burp Suite Professional, you don't need Metasploit, and you certainly don't need a PhD in computer science. You just need curl.
Here is the attack chain visualized:
The Proof of Concept:
All an attacker needs to do is send a POST request to the endpoint. If the server is vulnerable, it will likely respond with the path to the file.
curl -X POST -H "Content-Type: application/json" \
http://target-phpmyfaq.com/api/setup/backupIf the response JSON contains a file path, the attacker simply downloads it. If the response is suppressed but the file is generated, the attacker can brute-force the filename (often based on a timestamp) or check standard directories like /attachments/ or /data/. Once the ZIP is downloaded, extracting it reveals database.php.
Inside that file, you'll find:
$DB['server'] = 'localhost';
$DB['user'] = 'root';
$DB['password'] = 'SuperSecretPassword123!';
$DB['db'] = 'phpmyfaq';Congratulations. You now own the database.
Why is this a "High" severity (CVSS 7.5) and not critical? Only because it doesn't immediately give you a shell. But let's be real: in many environments, database access is effectively Remote Code Execution (RCE).
With the database credentials, an attacker can:
FILE privileges (common in lazy setups), the attacker can use SELECT ... INTO OUTFILE to write a PHP web shell directly into the web root. At that point, the server belongs to them.Even without RCE, the loss of confidentiality is total. If this database server is reused for other applications (a common practice in shared hosting or budget VPS setups), the attacker might pivot to compromise other services.
If you are running phpMyFAQ, check your version immediately. If it is anything older than 4.0.16, you are vulnerable.
Remediation Steps:
SetupController.php.data/, attachments/, and content/core/config/ directories for any suspicious .zip files. If you find one, assume it was downloaded by an unauthorized party.database.php yesterday. Change your database password immediately. Update the database.php file with the new credentials.[!NOTE] If you cannot upgrade immediately, you can mitigate this by blocking access to
/api/setup/*via your web server configuration (Nginx/Apache) for all external IPs.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
phpMyFAQ phpMyFAQ | < 4.0.16 | 4.0.16 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-202 (Sensitive Information Exposure) |
| Attack Vector | Network (API) |
| CVSS | 7.5 (High) |
| Privileges Required | None |
| Impact | Confidentiality (High) |
| Affected Component | SetupController.php |
| Exploit Status | Trivial (Unauthenticated POST) |
Exposure of Sensitive Information Through Data Queries
Get the latest CVE analysis reports delivered to your inbox.