Skip to main content

Command Palette

Search for a command to run...

IDOR in Government Ownership API Exposed Private Business Owner PII via CR Number Enumeration

Updated
6 min read

Severity: High
Bounty Awarded: $1,506
Program: Private Bug Bounty
Platform: Bugbounty.sa

Some IDORs are obvious immediately.

You change an ID.

Someone else’s data appears.

Easy.

Others look harmless at first because the application appears to return “public” information.

This bug started exactly like that.

At first glance, the endpoint looked like a normal commercial-registration lookup API tied to a government energy platform.

The request was simple:

GET /v2/public/profile/api/entity/owners/{crNumber}

The parameter looked predictable.

Commercial Registration numbers.

Nothing unusual.

But the response was not returning business metadata.

It was returning the people behind the business.

And that changed everything.


The Target

The affected platform belonged to a government-related service tied to regulated commercial activities and profile management workflows.

While browsing the application flow, I noticed several API calls tied to entity registration and ownership verification.

One endpoint stood out because it accepted a direct CR number inside the path:

/owners/{crNumber}

That immediately raises a common question during recon:

What happens if I replace the identifier?

Government systems frequently expose business lookup functionality, so the existence of a CR lookup itself was not suspicious.

The important question was:

What data is actually considered “public”?


Testing the Endpoint

Using an authenticated session, I intercepted a request similar to:

GET /v2/public/profile/api/entity/owners/40XXXXXXXX?api-version=2.0 HTTP/1.1
Host: api.redacted.gov.sa
Authorization: Bearer [redacted]
X-Requested-With: XMLHttpRequest
Origin: https://profile.redacted.gov.sa
Referer: https://profile.redacted.gov.sa/

The response returned ownership information tied to that commercial registration.

At first, it looked like standard registry data.

Then I looked closer.

The API response included:

  • full owner name

  • nationality

  • birth date

  • national ID

  • ownership relation

  • additional ownership metadata

Example response:

{
  "crNumber": "40XXXXXXXX",
  "ownersList": [
    {
      "ownerName": "Ahmed A. Al-Redacted",
      "ownerNationalityEN": "Saudi Arabia",
      "ownerBirthDate": "199X-XX-XX",
      "ownerId": "1101XXXXXX",
      "relationType": "Owner",
      "isAllowed": true
    }
  ]
}

That was no longer business information.

That was personally identifiable information tied to real individuals.

At this point, the next step was simple:

change the CR number.

And the system happily returned ownership information for entirely different entities.

No authorization validation.

No ownership checks.

No restriction ensuring the authenticated user was associated with the requested business.

Just:

supply CR number
        ↓
receive owner PII

Why This Was More Serious Than “Public Business Data”

This is where the report initially became contentious.

The first triage response closed the issue as Not Applicable with the reasoning that commercial registration data was already publicly accessible through another government portal.

Which, technically, was partially true.

Basic entity information was public.

Things like:

  • business name

  • registration status

  • entity details

  • commercial metadata

But that was not what this endpoint exposed.

The vulnerable API disclosed private owner-level information that was not available through the public registry lookup.

That distinction mattered.

A lot.

Because there is a major difference between:

"this company exists"

and

"here is the owner's personal information tied to that company"

The exposed data included sensitive attributes tied directly to individuals, not just organizations.

That changes the privacy impact entirely.


The Triage Dispute

This became one of those classic bounty moments where the technical vulnerability was correct, but the impact interpretation initially diverged.

The report was first closed as:

Status: Not Applicable

The reasoning:

"all information are public"

Instead of arguing emotionally, I focused on demonstrating the difference between:

  • public commercial entity data

  • private ownership PII

I compared the official public registry output against the vulnerable API response and showed that the public portal only exposed organizational details, while the API disclosed sensitive owner information that was not otherwise publicly accessible.

That clarification changed the outcome.

Shortly afterward:

Status: Approved

Later:

Status: Confirmed

Eventually:

Status: Resolved

One thing I’ve learned from government and enterprise programs:

Sometimes the hardest part is not finding the vulnerability.

It is explaining why the data actually matters.


Why Enumeration Matters

Commercial Registration numbers are not secret.

They are structured identifiers.

Predictable.

Often sequential or easily discoverable through public documents, invoices, company websites, procurement records, or open registries.

That means the attack surface was realistically enumerable.

An attacker could automate requests across large CR ranges and harvest ownership information at scale.

At that point, the issue stops being:

single-record exposure

and starts becoming:

bulk PII collection infrastructure

That dramatically changes risk.

Especially for:

  • government-linked systems

  • regulated industries

  • business ownership databases

  • identity-linked records

Even if certain fields appear individually “low sensitivity,” aggregation changes the impact.

At scale, the data becomes highly valuable for:

  • social engineering

  • target profiling

  • identity correlation

  • fraud campaigns

  • business-owner targeting


The Real Lesson

This bug reinforced something important:

“Public” is often misunderstood during triage.

Just because a system contains some public data does not mean every connected dataset becomes public too.

Applications frequently mix:

  • public entity data

  • private account data

  • internal ownership records

  • identity-linked metadata

And once those boundaries blur, IDORs become extremely dangerous.

Especially in government ecosystems where APIs are built around trusted internal assumptions.


Hunter Takeaways

1. Government APIs Frequently Trust Identifiers Too Much

Many internal systems assume identifiers like:

  • CR numbers

  • national IDs

  • license IDs

  • permit numbers

are only used by legitimate workflows.

Always test authorization boundaries around them.


2. Public Data ≠ Public PII

This matters constantly in triage.

Ask:

Is the exposed data already publicly accessible in the same form?

If not, explain the distinction clearly.


3. Aggregation Changes Severity

One leaked record may look minor.

Bulk enumerable access changes everything.

Always think about scale.


4. Triage Communication Matters

The vulnerability itself did not change between:

Not Applicable

and

Approved

The explanation did.

Good technical clarification can completely change report outcomes.


Final Thoughts

The actual vulnerability was simple:

replace CR number
        ↓
receive someone else's ownership data

No complex bypasses.

No race conditions.

No clever payloads.

Just missing authorization on a sensitive government API.

But the interesting part was not the bug itself.

It was the classification battle around what counts as “public.”

Because in many systems, the dangerous exposures are not obvious leaks.

They are trusted internal datasets accidentally exposed behind familiar-looking identifiers.