Forums Bug Reports Thread

Broken access control: any logged-in user can read/reply/change-status/assign ANY ticket via /help/api/tickets/*

Patrick Bass · Jun 6 · 19 · 1 Locked
[Critical] [Urgent] [Bug Fixed] [Always Reproduces]
🚀 OP Jun 6, 2026 6:25pm

Area: mobieusHelp (audit p7) · Surface: mobieusHelp /help/api/tickets/* (TicketsApiController) · Dimension: security · Severity: critical

OWASP A01:2021 Broken Access Control / IDOR. The /help/api/tickets/* JSON API gates only on platform login. There is no agent check, no per-queue permission check (unlike AgentController which enforces visible_queue_ids + HelpdeskPermissions::can), and no requester-ownership check (unlike PortalController which verifies $owns). Any authenticated tenant user can read arbitrary tickets (PII: requester email, subject, full public thread), change ticket status (e.g. mass-close), and reassign tickets to arbitrary agents.

Evidence

src/Controllers/Helpdesk/TicketsApiController.php only calls $this->requireAuth() — never checks the user is an agent, owns the ticket, or has queue permission. showByReference (L63-80): `$this->requireAuth(); $ticket = Ticket::findByReference($reference); ... $full = TicketService::getWithThread($this->ctx, (int)$ticket['id'], $includeInternal);` returns ANY ticket's subject/body/requester email to any authenticated user. changeStatus (L108-121) and assign (L124-139) call TicketService::changeStatus/assign with no role/ownership gate. TicketService::changeStatus (src/Services/Helpdesk/TicketService.php L254-323) and assign (L326-382) perform NO ownership/agent/queue check — only the state machine. These routes are registered inside the authenticated group (routes.php L1150-1154, group opened L775 closed L1470) so AuthMiddleware applies but nothing more. A role-2 registered member on any Pro+ tenant can enumerate references and read/mutate every ticket.

Suggested fix. Resolve the caller to an agent (AgentResolver) and enforce HelpdeskPermissions::can(perms, action, queueId) for every reply/status/assign, and gate showByReference on agent-on-queue OR requester-ownership. Mirror the checks already present in AgentController/PortalController. Agent-reply already throws when agentId is null, but status/assign/read do not — close those.

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


Patrick Bass
@mobieus

🚀 Jun 6, 2026 9:00pm

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

TicketsApiController (/help/api/tickets/*) now resolves the caller to a helpdesk agent and enforces per-queue HelpdeskPermissions (view / reply_public / change_status / assign_other), with schema-correct requester-ownership (helpdesk_requesters FK, not users.id) for read and reply. Previously every action gated only on requireAuth(), so any logged-in tenant user could read any ticket (requester PII + full thread), mass-close, or reassign tickets.

Status: fixed. Thread closed and locked.


Patrick Bass
@mobieus

Log in or register to reply to this thread.