Skip to main content

Command Palette

Search for a command to run...

The $1000 Ticket IDOR: One Number Exposed National IDs and Government Staff PII

Updated
10 min readView as Markdown
The $1000 Ticket IDOR: One Number Exposed National IDs and Government Staff PII

Severity: High
Bounty: ~$949
Program: Private Bug Bounty
Platform: Bugbounty.sa

This finding started with a very simple endpoint:

GET /api/tickets-management/portal/history-by-ticket/<ticket_id>

The endpoint returned the history of a support ticket.

Nothing unusual there.

The problem was that the backend trusted the supplied ticket_id without verifying whether the authenticated user was allowed to access that ticket.

Changing one number was enough to retrieve another user's support case.

And the response contained much more than a ticket status.

It exposed applicant information, internal case data, and detailed PII belonging to government support staff.

After testing a small sample of only 50 tickets, I was already able to collect dozens of unique staff records.

The system had roughly millions of ticket IDs.

That turned a basic IDOR into a much larger data-exposure problem.


Finding the Ticket History Endpoint

While testing the support portal, I came across an endpoint used to retrieve the history of a ticket:

GET /api/tickets-management/portal/history-by-ticket/[TICKET_ID] HTTP/1.1
Host: [REDACTED]
Cookie: [VALID SESSION]
Accept: application/json

The ticket identifier was directly embedded in the URL.

For example:

/history-by-ticket/2907020

The first thing I checked was whether the server tied that ticket ID to the authenticated user.

It did not.

Replacing my own ticket ID with another valid one returned the other ticket's history.

No additional authorization check prevented the access.

The attack was simply:

Authenticated user
       ↓
Change ticket_id
       ↓
Request another ticket
       ↓
Backend returns its full history

What the Response Contained

The response exposed considerably more than the ticket's current status.

A simplified portion looked like:

{
  "status": {
    "labelEn": "Under processing",
    "code": "[REDACTED]"
  },
  "assignedTo": {
    "id": "[STAFF_ID]",
    "email": "[GOVERNMENT_EMAIL]",
    "firstName": "[REDACTED]",
    "familyName": "[REDACTED]",
    "mobileNumber": "[REDACTED]",
    "nin": "[REDACTED]",
    "userName": "[REDACTED]",
    "fullName": "[REDACTED]",
    "userType": "HUMAN"
  },
  "case": {
    "id": "[TICKET_ID]",
    "applicantId": "[REDACTED]",
    "updatedBy": "[STAFF_ID]"
  }
}

The exposed staff information included:

  • full name

  • government email address

  • username

  • mobile number

  • National Identification Number

  • internal user ID

  • gender

  • staff assignment information

The ticket data also exposed information related to the applicant and the full case history.

So this was not only:

User A can see User B's ticket

It was also:

User A can use tickets to enumerate internal staff identities and sensitive data

Confirming It Was a Real IDOR

The vulnerable request required a valid authenticated session.

But once authenticated, the server trusted the ticket ID supplied by the user.

Conceptually, the backend behaved like:

$ticket = Ticket::find($ticketId);

return ticketHistory($ticket);

What was missing was an ownership or authorization check:

$ticket = Ticket::find($ticketId);

authorize(
    authenticatedUser(),
    $ticket
);

return ticketHistory($ticket);

The application knew who I was.

It simply failed to verify whether I should be able to access the requested ticket.


The Staff PII Made It More Interesting

At first, I treated this as a normal ticket IDOR.

Then I started looking more closely at the returned objects.

Each ticket contained information about the staff members who had handled or updated the case.

That included fields such as:

email
fullName
userName
mobileNumber
nin

The nin field was especially sensitive because it represented the employee's National Identification Number.

This meant every accessible ticket could potentially reveal another internal staff member.

And because many different staff members handled different cases, ticket enumeration could also become staff enumeration.


Testing the Scale

I wanted to understand whether this was limited to a few repeated support agents or whether the exposure scaled across the ticket system.

So I wrote a small script to retrieve a controlled sample of ticket histories and extract unique staff identities from the responses.

The script processed only 50 tickets.

It completed in less than a minute.

Even from that tiny sample, I collected a large number of unique staff records containing combinations of:

Internal ID
Government email
Full name
Username
Mobile number
National ID

The workflow was effectively:

List of ticket IDs
        ↓
Request ticket history
        ↓
Extract assignedTo / updatedBy users
        ↓
Deduplicate staff records
        ↓
Build internal employee dataset

I stopped after the small sample.

There was no need to enumerate the full dataset to demonstrate the impact.


Why the Number of Tickets Mattered

The ticket IDs were numeric and the system had approximately three million tickets.

That did not mean every possible ID would return useful data.

But it showed that the vulnerable object space was not small.

Even a tiny sample demonstrated that the endpoint could be used to gather staff information across unrelated support cases.

The difference between one exposed ticket and a broadly enumerable ticket system is important.

One successful request proves the IDOR.

A controlled sample helps show the potential scale.

The possible exposure looked like:

Millions of ticket objects
          ↓
Each ticket may reference staff
          ↓
Repeated IDOR requests
          ↓
Unique employee records accumulated

The Data Was Not Limited to the Applicant

One thing that made this issue unusual was who actually suffered the privacy impact.

The object being accessed was a citizen's support ticket.

But some of the most sensitive information inside the response belonged to the employees handling it.

So one authorization mistake crossed several privacy boundaries at once:

Unauthorized ticket access
          ↓
Applicant information
          +
Case history
          +
Government staff PII

This is something worth checking whenever an API returns nested objects.

The parent object may belong to one user, while embedded objects expose completely different people.


Demonstrating the Impact Without Mass Scraping

The script was useful for showing scalability, but there was no reason to dump millions of records.

A small sample was enough.

I collected the unique staff records from approximately 50 ticket requests and presented the result as a table.

The dataset clearly showed that different tickets exposed different government employees.

The columns included:

ID
Email
Full Name
Username
Mobile Number
National ID
Internal status

I redacted the actual employee information from the public writeup.

The important point was that these were not synthetic fields or empty values.

They contained real personal information belonging to internal staff.


Reporting and Triage

This report was actually a resubmission of an earlier report.

During triage, I was asked to provide either screenshots or a video demonstrating the impact.

I provided screenshots from the controlled enumeration test and explained that the sample covered only 50 tickets and completed in less than a minute.

I also pointed out that the available ticket space was in the millions, meaning the demonstrated sample represented only a tiny fraction of the potential exposure.

The organization verified the vulnerability and confirmed that it existed.

The report was rated High severity and awarded approximately $949.


What Made the Finding High Impact

A ticket IDOR by itself can range from minor to severe depending on what the ticket contains.

Here, the exposed data included sensitive identity information.

The demonstrated impact included:

  • unauthorized access to other users' support cases

  • National Identification Number exposure

  • government employee PII disclosure

  • internal government email addresses

  • mobile phone numbers

  • usernames and employee identifiers

  • full ticket action history

  • applicant-related case information

  • scalable enumeration across unrelated ticket IDs

The combination of PII sensitivity and enumeration potential was what made the issue significantly more serious than a simple ticket-status leak.


The Fix

After remediation, the organization asked me to verify whether the vulnerability was still reproducible.

The correct fix for this issue is straightforward:

Authenticated user
        ↓
Requests ticket
        ↓
Backend loads ticket
        ↓
Backend verifies ownership / authorization
        ↓
Only then returns history

The server should never rely on possession of a ticket ID as evidence that the requester is allowed to access it.

Sensitive nested fields should also be minimized.

Even authorized ticket responses should only contain the employee information actually required by the frontend.

Fields such as:

National ID
personal mobile number
internal identifiers

should not be returned unless there is a clear business requirement.


Hunter Takeaways

Look Beyond the Top-Level Object

The initial object was a support ticket.

But the most interesting data was inside nested user objects.

Whenever an API returns something like:

{
  "ticket": {},
  "assignedTo": {},
  "updatedByUser": {},
  "applicant": {}
}

inspect every nested object.

Sometimes the main object is harmless while one relationship exposes sensitive data.


A Small Sample Can Prove Large-Scale Risk

You do not need to enumerate an entire database.

In this case, roughly 50 controlled requests were enough to demonstrate that different tickets exposed many different employees.

That was enough to show the pattern:

One IDOR
   +
Large object space
   +
Different nested users
   =
Scalable PII exposure

Stop once the impact is proven.


Check Internal Staff Objects Carefully

Employee information is often accidentally treated as less sensitive than customer data.

Internal APIs may expose fields that were never meant to reach normal users:

employee ID
email
mobile
username
National ID
role
department

If those objects are nested inside user-facing APIs, an unrelated authorization issue can suddenly expose an internal directory.


Numeric IDs Still Deserve Attention

There is nothing complicated about:

/history-by-ticket/1234567

That simplicity is exactly why these endpoints are worth testing.

The important question is never whether an ID is predictable.

It is:

Does the backend verify that I am authorized to access the object represented by that ID?

Even if the identifier were completely random, missing object-level authorization would still be a vulnerability.

Predictability only affects how easily another valid object can be discovered.


Show Both Sensitivity and Scale

A strong IDOR report should answer two separate questions:

What does one unauthorized object expose?

In this case:

Ticket history + applicant data + staff PII

And:

How broadly can the issue be exercised?

Here, a small automated sample demonstrated that the exposure extended across many unrelated tickets and employees.

Both pieces helped communicate the real impact.


Final Thoughts

The vulnerability itself was simple:

Change ticket_id

The impact was not.

The complete chain was:

Authenticated portal account
          ↓
Change ticket ID
          ↓
Access another user's support case
          ↓
Read full ticket history
          ↓
Extract nested staff records
          ↓
Government emails, phone numbers and National IDs exposed
          ↓
Repeat across unrelated tickets

A single missing ownership check turned a support-ticket endpoint into a source of sensitive applicant and employee information.

The biggest lesson from this one was not about ticket IDs.

It was about looking at everything the unauthorized object brings back with it.

Sometimes the IDOR is only the door.

The nested data is the real finding.