{"schema":"threatline.record/2","record":{"id":"GHSA-h76j-35m7-3mwq","aliases":["GHSA-h76j-35m7-3mwq"],"summary":"User with the download attachments permission can access draft (unpublished) FAQs records","details":"### Summary\r\nThe attachment download endpoint does not verify whether the attachment’s parent FAQ is published or otherwise visible to the requester. \r\nAn ordinary authenticated user with the normal dlattachment  (\"Download attachments\" ) permission can download the complete contents of an attachment belonging to a draft FAQ even though the same user receives HTTP 404 for the parent FAQ through both the page and API paths.\r\n\r\n### Details\r\nThe download is handled by the 4.1 implementation in `phpmyfaq/attachment.php`. Its decision checks that the attachment exists, evaluates record ACL values, and requires either `dlattachment` or the guest-download configuration. It does not load the parent FAQ and apply the parent’s publication-state and visibility checks.\r\n\r\nThe normal FAQ page and API do apply that visibility boundary. This creates inconsistent authorisation: the parent record is hidden while its child file remains retrievable through `index.php?action=attachment&id=<ID>`.\r\n\r\nThe later 4.1 commit `b3654e5590066693488f71f6482dbe3d3c9a1c3c` improves attachment record-ACL enforcement but does not add the missing parent-publication check. The refactored `AttachmentService::canDownloadAttachment()` on the tested `main` revision likewise lacks that check.\r\n\r\nThis appears to be a residual path related to CVE-2026-76215. That issue added parent-record checks to comments and attachment-metadata APIs, but the separate file-download path was not covered. The present path exposes complete file contents, remains present in 4.1.8, and is independently fixable. \r\n\r\nThis is distinct from CVE-2026-24420. That vulnerability allowed a user without dlattachment to bypass a defective permission check. In this reproduction, User A legitimately possesses dlattachment, while User B without that right is correctly denied. The missing authorisation decision here is whether the attachment’s parent FAQ is published and visible.\r\n\r\nI believe this is an independently fixable residual path related to CVE-2026-76215. Please determine whether the existing CVE should be updated or whether the distinct download endpoint, full-file impact, and affected fixed versions warrant a separate CVE. I defer to the maintainer and CNA on whether this warrants a separate CVE or an update to CVE-2026-76215.\r\n\r\n### PoC\r\n1. Through the administrator interface, create:\r\n    - An active ordinary user, User A.\r\n    - An active ordinary user, User B.\r\n    - A public category.\r\n    - FAQ ID 2 with `active='no'` and the default all-users/all-groups ACL rows.\r\n2. Grant User A only `right_id=36` `dlattachment`(`Download attachments`). Leave User B with no rights.\r\n3. Create a draft FAQ (`/admin/faq/add`). Through `/admin/api/content/attachments/upload`, upload a text file named `canary-c.txt` to FAQ 2. The reproduced body was:\r\n    \r\n    ```\r\n    PMF-PROOF-LAB-CANARY-20260907-12345678\r\n    ```\r\n    \r\nConfirm that the response contains `attachmentId: 1`, the `faq_faqattachment` row exists, and the file exists on disk.\r\n    \r\n4. Log in as User A and request the draft FAQ:\r\n\r\n    ```http\r\n    GET /index.php?action=faq&cat=1&id=2&artlang=en HTTP/1.1\r\n    Host: <TEST_HOST>\r\n    Cookie: <USER_A_SESSION>\r\n    ```\r\n    \r\n    The response is HTTP 404. `/api/v3.1/faq/1/2` also returns 404.\r\n    \r\n5. Using the same User A session, request the child attachment:\r\n    \r\n    ```http\r\n    GET /index.php?action=attachment&id=1 HTTP/1.1\r\n    Host: <TEST_HOST>\r\n    Cookie: <USER_A_SESSION>\r\n    ```\r\n    \r\n6. Observe HTTP 200, `Content-Disposition: attachment; filename=\"canary-c.txt\"`, `Content-Length: 39`, and the exact canary body.\r\n9. Repeat the download and confirm the same result.\r\n10. As User B and as an anonymous visitor, request the same attachment. Both receive the application’s attachment-error page and no canary bytes, confirming that the right gate works and isolating the missing publication check.\r\n11. Publish FAQ 2. User A now receives HTTP 200 for both the FAQ and attachment.\r\n12. Return FAQ 2 to draft status without changing its ACLs. The parent returns HTTP 404 again, but the attachment remains downloadable with identical bytes.\r\n\r\nSanitised request/response pairs, the upgrade record, four matching download hashes, negative controls, and screenshots are available privately.\r\n \r\nFor version phpMyFAQ 4.2.0-beta (main/4.2 development revision):\r\nRepeat above steps 1-4, instead of calling the \"/index.php?action=attachment&id=1\" endpoint, simply call \"/attachment/1\".(Its corresponding download route is /attachment/X.) \r\n\r\n### Impact\r\nAn authenticated user with ordinary attachment download permission can bypass the publication boundary of a draft FAQ and obtain its complete attached files. Draft records may contain internal documentation, confidential files, or material not intended for release.\r\n\r\nThe primary reproduction requires a low privileged authenticated user with `dlattachment`; it does not require admin or draft-view authority. Attachment identifiers are sequential and appear in ordinary links for published attachments, making neighboring identifiers reasonably discoverable.\r\n\r\nWhen guest downloads are enabled, anonymous exposure may also be possible, but that configuration-dependent variant is not required for the primary finding and is not used for the suggested severity.","categories":["Unclassified"],"product":"phpmyfaq/phpmyfaq","vendor":"","ecosystem":null,"affected_ranges":[{"source":"GitHub Advisory Database","source_url":"https://github.com/thorsten/phpMyFAQ/security/advisories/GHSA-h76j-35m7-3mwq","source_modified_at":"2026-10-03T11:35:25Z","fetched_at":"2026-10-26T23:45:56.352924Z","package":{"ecosystem":"composer","name":"phpmyfaq/phpmyfaq"},"range":">= 4.1.7"}],"fixed_versions":[{"source":"GitHub Advisory Database","source_url":"https://github.com/thorsten/phpMyFAQ/security/advisories/GHSA-h76j-35m7-3mwq","source_modified_at":"2026-10-03T11:35:25Z","fetched_at":"2026-10-26T23:45:56.352924Z","package":{"ecosystem":"composer","name":"phpmyfaq/phpmyfaq"},"version":"4.1.9"}],"cvss":null,"severity":"Medium","kev":{"listed":false},"epss":{"probability":null,"percentile":null,"score_date":null},"published_at":"2026-10-03T11:35:25Z","modified_at":"2026-10-03T11:35:25Z","provenance":[{"source":"GitHub Advisory Database","url":"https://github.com/thorsten/phpMyFAQ/security/advisories/GHSA-h76j-35m7-3mwq","fetched_at":"2026-10-26T23:45:56.352924Z","source_modified_at":"2026-10-03T11:35:25Z"}],"sources":["GitHub Advisory Database"],"references":[],"locations":[],"motoko_assessment":{"status":"requires_architecture_review","automatic_remediation":false},"classification":{"method":"product-first software-layer rules","rules_version":"2.1","matched_on":null,"confidence":"unknown","reviewed":false},"change_timestamp":"2026-10-03T11:35:25Z","content_hash":"b6ff9e92127ad3d0abc1db36c8dbf43260bd7e3f5725a1766dd403dad6960a3b"}}
