Forums Bug Reports Thread

Bookmark toggle verifies existence but not viewability — leaks titles/body of threads & posts in forums the user cannot access

Patrick Bass · Jun 6 · 19 · 1 Locked
[Minor] [Normal Priority] [Bug Fixed] [Always Reproduces]
🚀 OP Jun 6, 2026 5:27pm

Area: Forums (audit p2) · Surface: POST /api/bookmark/toggle (BookmarkController@toggle) + GET /bookmarks (BookmarkController@index) · Dimension: Broken Access Control / IDOR / Information Disclosure (OWASP A01:2021) · Severity: minor

Any authenticated user can bookmark an arbitrary thread or post by guessing/iterating its integer ID — even one inside a hidden or listed_private forum or a paid forum they have not paid for — and then read its title (threads) or the first 160 characters of its body (posts) back on their /bookmarks page. This is an IDOR that turns the bookmark feature into an oracle for content in forums the user is not authorized to view. Severity is limited to title + body prefix disclosure, but it bypasses the Forum::canView gate that protects the actual thread/post pages.

Evidence

platform/src/Controllers/BookmarkController.php:41-54 — existence is checked but visibility is not: for a thread `$exists = $this->db->fetchOne('SELECT id FROM forum_threads WHERE id = :id', ['id'=>$targetId]);` (no Forum::canView). If it exists, Bookmark::toggle() stores it (line 56). The /bookmarks list then SELECTs and renders the bookmarked thread title / first 160 chars of post body: BookmarkController.php:131-161 (`CASE WHEN b.target_type='thread' THEN t.title ... THEN SUBSTRING(p.body,1,160) END AS target_title`) with no forum-visibility join/filter.

Suggested fix. In toggle(), after confirming the target exists, resolve its forum_id and enforce Forum::canView()/isPreviewOnly() (and Forum::hasPaidAccess for paid forums) before allowing the bookmark; reject with 404 otherwise. As defense in depth, also filter the /bookmarks index query by the viewer's forum visibility so previously-stored bookmarks to now-inaccessible forums don't leak their titles.

Filed by the automated tenant-app audit and adversarially evidence-verified. Status: verified. Open — not yet actioned.


Patrick Bass
@mobieus

🚀 Jun 7, 2026 5:38am

Resolved — fixed and deployed. Commit 89b2334003a7, shipped dev-first then to all tenants on 2026-06-06.

toggle() now resolves forum_id (thread directly, post via JOIN forum_threads) and enforces Forum::canView()/!isPreviewOnly()/hasPaidAccess() after the existence check, rejecting 404 via a shared $notFound closure so the target isn't revealed. Added use App\Models\Forum. index() query now selects COALESCE(t.forum_id, pt.forum_id) AS resolved_forum_id and PHP filters the result set (memoized per forum) to drop bookmarks whose parent forum the viewer can no longer see, as defense in depth.

Status: fixed. Thread closed and locked.


Patrick Bass
@mobieus

Log in or register to reply to this thread.