GHSA-985R-Q3QP-299H

Vulnerability from github – Published: 2026-06-26 21:23 – Updated: 2026-06-26 21:23
VLAI
Summary
phpMyFAQ has an incomplete fix for GHSA-xvp4-phqj-cjr3 — editUser() and updateUserRights() lack authorization guards
Details

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:

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.

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 4.1.3"
      },
      "package": {
        "ecosystem": "Packagist",
        "name": "thorsten/phpmyfaq"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "4.1.4"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 4.1.3"
      },
      "package": {
        "ecosystem": "Packagist",
        "name": "phpmyfaq/phpmyfaq"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "4.1.4"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [],
  "database_specific": {
    "cwe_ids": [
      "CWE-832"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-06-26T21:23:37Z",
    "nvd_published_at": null,
    "severity": "HIGH"
  },
  "details": "## Advisory / Disclosure\n\n# phpMyFAQ 4.1.3 \u2014 incomplete fix for the admin-API IDOR/privilege-escalation class\n\n**Target:** thorsten/phpMyFAQ (composer: `thorsten/phpmyfaq`, `phpmyfaq/phpmyfaq`)\n**Affected:** \u003c= 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 \u2014 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\u003e \"Only SuperAdmins may change other users\u0027 [attributes]. Self-service is\n\u003e always allowed.\" and \"a non-SuperAdmin must never be able to alter a\n\u003e 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` \u2192 `getUserById()` \u2192 privileged mutation\" primitive \u2014 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, \u2026)` | **none** (only `userHasPermission(USER_EDIT)`) |\n\nA logged-in administrator holding the delegable `edit_user` right \u2014 but  **not** SuperAdmin \u2014 can therefore:\n\n1. Set their own (or anyone\u0027s) `is_superadmin` flag to `true` via `admin/api/user/edit` \u2192 **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 \u003esetSuperAdmin((bool)$isSuperAdmin)` at **:463**. Only gate: `userHasPermission(PermissionType::USER_EDIT)` at :422.\n  - `updateUserRights()` lines 482-520; `userId` at :496, sink `grantUserRight($userId, \u2026)` at **:511**. Only gate at :485.\n  - `overwritePassword()` lines 419\u2026 \u2192 228-288; the **patched** guards at: 254-260 and: 269-273.\n- `phpmyfaq/src/phpMyFAQ/Controller/AbstractController.php` \u2014`userHasPermission()` :221-227 (checks one right only; non SuperAdmins can hold it).\n- `phpmyfaq/src/phpMyFAQ/User.php` \u2014 `setSuperAdmin()` :950-962 (`UPDATE faquser SET is_superadmin=\u2026 WHERE user_id=\u2026`, no guard); `isSuperAdmin()` :942-945.\n- `phpmyfaq/src/phpMyFAQ/Permission/BasicPermission.php` \u2014 `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\u0027s **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 \u2014`setSuperAdmin` (UPDATE), `grantUserRight` (INSERT), and the real `hasPermission` / `checkUserRight` / `getRightId` queries \u2014 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) \u2014 confirms the fix works *there*.\n- **Exploit 1:** `editor` (non-SuperAdmin, passes `userHasPermission(edit_user)`) flips their own `is_superadmin` 0\u21921 \u2192 SuperAdmin. `VULNERABLE`.\n- **Exploit 2:** `editor` grants the `editconfig` right via `updateUserRights`. `VULNERABLE`.\n\nRun:\n```sh\nphp poc/poc.php   # -\u003e 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 \u2014 the parent advisory\u0027s 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-\u003ecurrentUser-\u003eisSuperAdmin()`; 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 \u2014 apply the same guard.",
  "id": "GHSA-985r-q3qp-299h",
  "modified": "2026-06-26T21:23:37Z",
  "published": "2026-06-26T21:23:37Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/thorsten/phpMyFAQ/security/advisories/GHSA-985r-q3qp-299h"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/thorsten/phpMyFAQ"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "phpMyFAQ has an incomplete fix for GHSA-xvp4-phqj-cjr3 \u2014 editUser() and updateUserRights() lack authorization guards"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Loading…

Loading…

Related by attack behaviour

Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.


Loading…