Skip to content

[FIX] helpdesk_mgmt: enforce access control on /ticket/close (portal can change arbitrary tickets) - #1089

Open
gmaOCR wants to merge 1 commit into
OCA:19.0from
gmaOCR:fix-ticket-close-acl
Open

[FIX] helpdesk_mgmt: enforce access control on /ticket/close (portal can change arbitrary tickets)#1089
gmaOCR wants to merge 1 commit into
OCA:19.0from
gmaOCR:fix-ticket-close-acl

Conversation

@gmaOCR

@gmaOCR gmaOCR commented Aug 17, 2026

Copy link
Copy Markdown

[FIX] helpdesk_mgmt: enforce access control on /ticket/close

Security impact

Broken access control / IDOR (CWE-639) in the /ticket/close portal controller.

The support_ticket_close controller fetches the ticket with sudo(), which
bypasses the helpdesk.ticket record rules (portal "own tickets" and the
multi-company rule), and then gates the write only on the target stage's
close_from_portal flag - not on whether the caller may act on that ticket:

ticket = (
    request.env["helpdesk.ticket"].sudo()
    .search([("id", "=", values["ticket_id"])])   # ticket_id is attacker-controlled
)
stage = request.env["helpdesk.ticket.stage"].browse(values.get("stage_id"))
if stage.close_from_portal:                        # checks the stage, not the caller
    ticket.stage_id = values.get("stage_id")

As a result, any authenticated portal or internal user can change the stage
of any other tenant's ticket
by passing an arbitrary ticket_id together
with a stage_id whose close_from_portal is True (the default Done,
Cancelled and Rejected stages qualify), even for tickets they cannot read.
The exploit is a plain GET /ticket/close?ticket_id=<any>&stage_id=<close>.

Fix

Fetch the ticket as the current user so record rules constrain which tickets
the caller can act on; keep sudo() only for the stage write, once access to
the record has been established (so a portal user without write permission can
still close a ticket they legitimately own).

Tests

Adds test_helpdesk_ticket_close_acl.py:

  • a portal user cannot close a ticket it cannot access (stage stays unchanged);
  • a portal user can still close its own ticket (legitimate flow preserved).

Affected versions

Same vulnerable route on 16.0, 17.0, 18.0, 19.0. This PR targets 19.0; I am
happy to port it to the other branches.

The support_ticket_close controller fetched the ticket with sudo() and only
checked the target stage's close_from_portal flag, not the caller's right on
the ticket. Any portal or internal user could change the stage of arbitrary
tickets (cross-tenant) by passing another ticket_id, even for tickets they
cannot read.

Fetch the ticket as the current user so record rules apply; keep sudo() only
for the stage write, after access to the record has been established. Add a
regression test covering both the blocked cross-tenant close and the preserved
legitimate 'close my own ticket' flow.
@gmaOCR
gmaOCR force-pushed the fix-ticket-close-acl branch from 8a1d7bb to 1042d7a Compare August 17, 2026 21:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants