GHSA-985r-q3qp-299h: Thorsten/phpmyfaq PhpMyFAQ Has An Incomplete Fix Vulnerability

HIGH
CVSS 8.1
CWE-832
Published
Advisory Summary

GHSA-985r-q3qp-299h is a HIGH (CVSS 8.1) phpmyfaq has an incomplete fix vulnerability (CWE-832: Unlock of a Resource that is not Locked) affecting Thorsten/phpmyfaq libraries in the Packagist ecosystem. The issue involves phpmyfaq has an incomplete fix for ghsa-xvp4-phqj-cjr3 — edituser() and updateuserrights() lack authorization guards. Remediation is available by upgrading affected packages to version 4.1.4.

GHSA-985r-q3qp-299h at a Glance

CVE Identifier N/A
GHSA Identifier GHSA-985r-q3qp-299h
Severity HIGH
CVSS v3.1 Base Score 8.1
CWE Classification CWE-832: Unlock of a Resource that is not Locked
Affected Product Thorsten/phpmyfaq
Package Ecosystem Packagist
Published Date
Last Updated

Security Impact

CVSS: 8.1

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N).

Attack Vector Network (AV:N)
Attack Complexity Low (AC:L)
Privileges Required Low (PR:L)
User Interaction None (UI:N)
Scope Unchanged (S:U)
Confidentiality High (C:H)
Integrity High (I:H)
Availability None (A:N)

How to Fix GHSA-985r-q3qp-299h

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.

thorsten/phpmyfaq fixed: 4.1.4
Upgrade thorsten/phpmyfaq to 4.1.4
phpmyfaq/phpmyfaq fixed: 4.1.4
Upgrade phpmyfaq/phpmyfaq to 4.1.4
Verify Direct and Transitive Dependencies

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 GHSA-985r-q3qp-299h, 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.4), 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-832: Unlock of a Resource that is not Locked, where the product attempts to unlock a resource that is not locked. 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

## Advisory / Disclosure # phpMyFAQ 4.1.3 — incomplete fix for the admin-API IDOR/privilege-escalation class **Target:** thorsten/phpMyFAQ (composer: `thorsten/phpmyfaq`, `phpmyfaq/phpmyfaq`) **Affected:** <= 4.1.3 (the 4.1.3 security fix is incomplete; siblings remain) **Class:** CWE-862 Missing Authorization / CWE-269 Improper Privilege Management / CWE-639 Authorization Bypass Through User-Controlled Key **Methodology:** M1 incomplete-fix audit (sibling-walk of the 4.1.3 fix for GHSA-xvp4-phqj-cjr3) **Severity:** High — CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H = 8.8 (same class as the parent CVE) ## Summary phpMyFAQ 4.1.3 fixed **GHSA-xvp4-phqj-cjr3** ("IDOR Account Takeover") by adding actor-authorization guards to `UserController::overwritePassword()`. The patch establishes a new invariant, stated in its own code comments: > "Only SuperAdmins may change other users' [attributes]. Self-service is > always allowed." and "a non-SuperAdmin must never be able to alter a > SuperAdmin or protected account." That invariant is **not enforced** on two sibling endpoints in the *same file*, which the 4.1.3 fix left **unchanged**, and which carry the identical "user-controlled `userId` → `getUserById()` → privileged mutation" primitive — but with a strictly more dangerous sink: | Endpoint | Route | Sink | Guard in 4.1.3 | |----------|-------|------|----------------| | `overwritePassword()` | `admin/api/user/overwrite-password` | `changePassword()` | **isSelf + isSuperAdmin + target-protection** (patched) | | `editUser()` | `admin/api/user/edit` | `setSuperAdmin((bool)$req.is_superadmin)` | **none** (only `userHasPermission(USER_EDIT)`) | | `updateUserRights()` | `admin/api/user/update-rights` | `grantUserRight($req.userId, …)` | **none** (only `userHasPermission(USER_EDIT)`) | A logged-in administrator holding the delegable `edit_user` right — but **not** SuperAdmin — can therefore: 1. Set their own (or anyone's) `is_superadmin` flag to `true` via `admin/api/user/edit` → **full privilege escalation to SuperAdmin**. 2. Grant arbitrary rights to any account via `admin/api/user/update-rights`. This is exactly the threat model the parent advisory (GHSA-xvp4) calls out: "organizations with multiple admin users where not all should have SuperAdmin access." ## Anchors (upstream tag 4.1.3) - `phpmyfaq/src/phpMyFAQ/Controller/Administration/Api/UserController.php` - `editUser()` lines 419-476; user-controlled `userId` at :433, user-controlled `is_superadmin` at :443, sink `$user >setSuperAdmin((bool)$isSuperAdmin)` at **:463**. Only gate: `userHasPermission(PermissionType::USER_EDIT)` at :422. - `updateUserRights()` lines 482-520; `userId` at :496, sink `grantUserRight($userId, …)` at **:511**. Only gate at :485. - `overwritePassword()` lines 419… → 228-288; the **patched** guards at: 254-260 and: 269-273. - `phpmyfaq/src/phpMyFAQ/Controller/AbstractController.php` —`userHasPermission()` :221-227 (checks one right only; non SuperAdmins can hold it). - `phpmyfaq/src/phpMyFAQ/User.php` — `setSuperAdmin()` :950-962 (`UPDATE faquser SET is_superadmin=… WHERE user_id=…`, no guard); `isSuperAdmin()` :942-945. - `phpmyfaq/src/phpMyFAQ/Permission/BasicPermission.php` — `hasPermission()` :95-112 (SuperAdmin short-circuits true, else `checkUserRight`). Evidence snapshots in this folder: `advisory/fix-diff-4.1.2-to-4.1.3.txt` (proves the fix touched only `overwritePassword`/`deleteUser`) and `advisory/vulnerable-siblings-4.1.3.txt` (the two unguarded methods as shipped). `git diff 4.1.2 4.1.3` shows **no change** to `editUser`, `updateUserRights`, `setSuperAdmin`, or `grantUserRight`. ## Proof of Concept `poc/poc.php` (run log: `poc/run-log.txt`). Dependency-free: it builds phpMyFAQ's **real schema** (copied verbatim from `src/phpMyFAQ/Instance/Database/Sqlite3.php`) and executes the **verbatim SQL** that the shipped 4.1.3 methods run —`setSuperAdmin` (UPDATE), `grantUserRight` (INSERT), and the real `hasPermission` / `checkUserRight` / `getRightId` queries — to prove the primitive: - Seeds a SuperAdmin (`admin`, id=1) and a non-SuperAdmin admin (`editor`, id=2) granted only `add_user`/`edit_user`/`delete_user`. - **Control:** the patched `overwritePassword` guard blocks `editor` changing the SuperAdmin (id=1) — confirms the fix works *there*. - **Exploit 1:** `editor` (non-SuperAdmin, passes `userHasPermission(edit_user)`) flips their own `is_superadmin` 0→1 → SuperAdmin. `VULNERABLE`. - **Exploit 2:** `editor` grants the `editconfig` right via `updateUserRights`. `VULNERABLE`. Run: ```sh php poc/poc.php # -> RESULT: VULNERABLE ... EXIT 0 ``` ### PoC scope (honest) The PoC exercises the **privilege-escalation primitive** (the unguarded sinks + the real authorization-resolution logic) against the real schema. The full HTTP exploit additionally requires an authenticated admin session and a CSRF token (`editUser` verifies `update-user-data`, `updateUserRights` verifies `update-user-rights`); both are available to the authenticated admin attacker — the parent advisory's own PoC shows reading the CSRF token from admin pages. The controller-level **absence of an authorization guard** is established by source citation (the only gate is `userHasPermission(USER_EDIT)`), corroborated by the fix diff showing these methods were not modified. ## Recommended fix Apply the `overwritePassword` invariant to the siblings: - `editUser()`: reject `is_superadmin`/status/2FA changes unless `$this->currentUser->isSuperAdmin()`; never allow a non-SuperAdmin to edit a SuperAdmin or `protected` target. Treat `is_superadmin` as a SuperAdmin-only field (defeat the mass-assignment at :443/:463). - `updateUserRights()`: require `isSuperAdmin()` (or a privilege-level comparison) before `grantUserRight`; forbid granting rights the actor does not itself hold, and forbid targeting SuperAdmin/protected users. - `activate()` (`admin/api/user/activate`, :194-221) is a lower-impact sibling with the same shape — apply the same guard.

GHSA-985r-q3qp-299h Affected Thorsten/phpmyfaq Versions

2 component(s)
Fixed Versions:
4.1.4
Product / Package Ecosystem Affected Versions Fixed Version
thorsten/phpmyfaq
Packagist
< 4.1.4
4.1.4
phpmyfaq/phpmyfaq
Packagist
< 4.1.4
4.1.4

References

Frequently Asked Questions

What is GHSA-985r-q3qp-299h?
GHSA-985r-q3qp-299h is a security vulnerability in Thorsten/phpmyfaq involving phpMyFAQ has an incomplete fix for GHSA-xvp4-phqj-cjr3 — editUser() and updateUserRights() lack authorization guards under CWE-832: Unlock of a Resource that is not Locked.
Which Thorsten/phpmyfaq versions are affected?
Affected versions include releases prior to 4.1.4 across affected packages: thorsten/phpmyfaq, phpmyfaq/phpmyfaq.
What version fixes GHSA-985r-q3qp-299h?
Upgrading to 4.1.4 (or the latest patched release for your respective branch) resolves the vulnerability.
Is thorsten/phpmyfaq affected?
Yes, thorsten/phpmyfaq is affected in releases prior to 4.1.4. Upgrade your dependency to 4.1.4 or later.
Is phpmyfaq/phpmyfaq affected?
Yes, phpmyfaq/phpmyfaq is affected in releases prior to 4.1.4. Upgrade your dependency to 4.1.4 or later.
What is the CVSS score of GHSA-985r-q3qp-299h?
GHSA-985r-q3qp-299h has a CVSS v3.1 base score of 8.1 (HIGH) with vector string CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N.
What CWE is associated with GHSA-985r-q3qp-299h?
GHSA-985r-q3qp-299h is classified under CWE-832: Unlock of a Resource that is not Locked (The product attempts to unlock a resource that is not locked.).
How can I check Packagist dependencies for GHSA-985r-q3qp-299h?
Run the dependency tree command: 'inspect dependencies for thorsten/phpmyfaq'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-985r-q3qp-299h",
  "modified" : "2026-06-26T21:23:37Z",
  "published" : "2026-06-26T21:23:37Z",
  "aliases" : [ ],
  "summary" : "phpMyFAQ has an incomplete fix for GHSA-xvp4-phqj-cjr3 — editUser() and updateUserRights() lack authorization guards",
  "details" : "## Advisory / Disclosure\n\n# phpMyFAQ 4.1.3 — incomplete fix for the admin-API IDOR/privilege-escalation class\n\n**Target:** thorsten/phpMyFAQ (composer: `thorsten/phpmyfaq`, `phpmyfaq/phpmyfaq`)\n**Affected:** <= 4.1.3 (the 4.1.3 security fix is incomplete; siblings remain)\n**Class:** CWE-862 Missing Authorization / CWE-269 Improper Privilege Management / CWE-639 Authorization Bypass Through User-Controlled Key\n**Methodology:** M1 incomplete-fix audit (sibling-walk of the 4.1.3 fix for GHSA-xvp4-phqj-cjr3)\n**Severity:** High — CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H = 8.8 (same class as the parent CVE)\n\n## Summary\n\nphpMyFAQ 4.1.3 fixed **GHSA-xvp4-phqj-cjr3** (\"IDOR Account Takeover\") by adding actor-authorization guards to `UserController::overwritePassword()`. The patch establishes a new invariant, stated in its own code comments:\n\n> \"Only SuperAdmins may change other users' [attributes]. Self-service is\n> always allowed.\" and \"a non-SuperAdmin must never be able to alter a\n> SuperAdmin or protected account.\"\n\nThat invariant is **not enforced** on two sibling endpoints in the *same file*, which the 4.1.3 fix left **unchanged**, and which carry the identical \"user-controlled `userId` → `getUserById()` → privileged mutation\" primitive — but with a strictly more dangerous sink:\n\n| Endpoint | Route | Sink | Guard in 4.1.3 |\n|----------|-------|------|----------------|\n| `overwritePassword()` | `admin/api/user/overwrite-password` | `changePassword()` | **isSelf + isSuperAdmin + target-protection** (patched) |\n| `editUser()` | `admin/api/user/edit` | `setSuperAdmin((bool)$req.is_superadmin)` | **none** (only `userHasPermission(USER_EDIT)`) |\n| `updateUserRights()` | `admin/api/user/update-rights` | `grantUserRight($req.userId, …)` | **none** (only `userHasPermission(USER_EDIT)`) |\n\nA logged-in administrator holding the delegable `edit_user` right — but  **not** SuperAdmin — can therefore:\n\n1. Set their own (or anyone's) `is_superadmin` flag to `true` via `admin/api/user/edit` → **full privilege escalation to SuperAdmin**.\n2. Grant arbitrary rights to any account via `admin/api/user/update-rights`.\n\nThis is exactly the threat model the parent advisory (GHSA-xvp4) calls out: \"organizations with multiple admin users where not all should have SuperAdmin access.\"\n\n## Anchors (upstream tag 4.1.3)\n\n- `phpmyfaq/src/phpMyFAQ/Controller/Administration/Api/UserController.php`\n  - `editUser()` lines 419-476; user-controlled `userId` at :433, user-controlled `is_superadmin` at :443, sink \n`$user >setSuperAdmin((bool)$isSuperAdmin)` at **:463**. Only gate: `userHasPermission(PermissionType::USER_EDIT)` at :422.\n  - `updateUserRights()` lines 482-520; `userId` at :496, sink `grantUserRight($userId, …)` at **:511**. Only gate at :485.\n  - `overwritePassword()` lines 419… → 228-288; the **patched** guards at: 254-260 and: 269-273.\n- `phpmyfaq/src/phpMyFAQ/Controller/AbstractController.php` —`userHasPermission()` :221-227 (checks one right only; non SuperAdmins can hold it).\n- `phpmyfaq/src/phpMyFAQ/User.php` — `setSuperAdmin()` :950-962 (`UPDATE faquser SET is_superadmin=… WHERE user_id=…`, no guard); `isSuperAdmin()` :942-945.\n- `phpmyfaq/src/phpMyFAQ/Permission/BasicPermission.php` — `hasPermission()` :95-112 (SuperAdmin short-circuits true, else  `checkUserRight`).\n\nEvidence snapshots in this folder:\n`advisory/fix-diff-4.1.2-to-4.1.3.txt` (proves the fix touched only `overwritePassword`/`deleteUser`) and `advisory/vulnerable-siblings-4.1.3.txt` (the two unguarded methods as shipped). `git diff 4.1.2 4.1.3` shows **no change** to `editUser`, `updateUserRights`, `setSuperAdmin`, or `grantUserRight`.\n\n## Proof of Concept\n\n`poc/poc.php` (run log: `poc/run-log.txt`). Dependency-free: it builds phpMyFAQ's **real schema** (copied verbatim from\n`src/phpMyFAQ/Instance/Database/Sqlite3.php`) and executes the **verbatim SQL** that the shipped 4.1.3 methods run —`setSuperAdmin` (UPDATE), `grantUserRight` (INSERT), and the real `hasPermission` / `checkUserRight` / `getRightId` queries — to prove the primitive:\n\n- Seeds a SuperAdmin (`admin`, id=1) and a non-SuperAdmin admin (`editor`, id=2) granted only `add_user`/`edit_user`/`delete_user`.\n- **Control:** the patched `overwritePassword` guard blocks `editor` changing the SuperAdmin (id=1) — confirms the fix works *there*.\n- **Exploit 1:** `editor` (non-SuperAdmin, passes `userHasPermission(edit_user)`) flips their own `is_superadmin` 0→1 → SuperAdmin. `VULNERABLE`.\n- **Exploit 2:** `editor` grants the `editconfig` right via `updateUserRights`. `VULNERABLE`.\n\nRun:\n```sh\nphp poc/poc.php   # -> RESULT: VULNERABLE ... EXIT 0\n```\n\n### PoC scope (honest)\n\nThe PoC exercises the **privilege-escalation primitive** (the unguarded sinks + the real authorization-resolution logic) against the real schema. The full HTTP exploit additionally requires an authenticated admin session and a CSRF token (`editUser` verifies `update-user-data`, `updateUserRights` verifies `update-user-rights`); both are available to the authenticated admin attacker — the parent advisory's own PoC shows reading the CSRF token from admin pages. The controller-level **absence of an authorization guard** is established by source citation (the only gate is `userHasPermission(USER_EDIT)`), corroborated by the fix diff showing these methods were not modified.\n\n## Recommended fix\n\nApply the `overwritePassword` invariant to the siblings:\n- `editUser()`: reject `is_superadmin`/status/2FA changes unless `$this->currentUser->isSuperAdmin()`; never allow a non-SuperAdmin to edit a SuperAdmin or `protected` target. Treat `is_superadmin` as a SuperAdmin-only field (defeat the mass-assignment at :443/:463).\n- `updateUserRights()`: require `isSuperAdmin()` (or a privilege-level comparison) before `grantUserRight`; forbid granting rights the actor does not itself hold, and forbid targeting SuperAdmin/protected users.\n- `activate()` (`admin/api/user/activate`, :194-221) is a lower-impact sibling with the same shape — apply the same guard.",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "Packagist",
      "name" : "thorsten/phpmyfaq"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "4.1.4"
      } ]
    } ],
    "database_specific" : {
      "last_known_affected_version_range" : "<= 4.1.3"
    }
  }, {
    "package" : {
      "ecosystem" : "Packagist",
      "name" : "phpmyfaq/phpmyfaq"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "4.1.4"
      } ]
    } ],
    "database_specific" : {
      "last_known_affected_version_range" : "<= 4.1.3"
    }
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/thorsten/phpMyFAQ/security/advisories/GHSA-985r-q3qp-299h"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/thorsten/phpMyFAQ"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-832" ],
    "severity" : "HIGH",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-06-26T21:23:37Z",
    "nvd_published_at" : null
  }
}