Skip to main content

Command Palette

Search for a command to run...

From Username Enumeration to Subscriber PII: Chaining 3 Weak Findings in a Telecom Ecosystem

Updated
8 min readView as Markdown
From Username Enumeration to Subscriber PII: Chaining 3 Weak Findings in a Telecom Ecosystem

Severity: Medium
Bounty Awarded: $560
Program: Private Bug Bounty

Most hunters have encountered login enumeration and immediately deprioritized it.

Different response.

Different redirect.

Different wording.

You confirm the bug, think “low at best”, maybe submit it, maybe do not.

This write-up is about one of those findings.

In isolation, it was weak.

A username enumeration issue on a telecom login flow that realistically deserved low severity, or an arguable medium if triage felt generous.

No authentication bypass.

No direct account access.

No PII exposure.

No password reset abuse.

Just subscriber validation.

The interesting part came from chaining.

Not into an in-scope ATO.

Into an ecosystem.

The final chain looked like this:

/auth/validate-login
        ↓
Confirmed subscriber
        ↓
Voucher OTP generation
        ↓
Leaked OTP retrieval
        ↓
Voucher authentication
        ↓
/api/v1/integrator/login_redirect/{id}
        ↓
Subscriber-linked PII exposure

Recon: Following the Authentication Flow

The initial signal came from the authentication workflow.

During normal login interaction, the application performed a validation step before continuing authentication.

Instead of immediately requesting credentials, the flow first validated whether the supplied phone number existed.

The request looked roughly like this:

POST /auth/validate-login HTTP/2
Host: [REDACTED]
Content-Type: application/x-www-form-urlencoded
Cookie: laravel_session=[REDACTED]

_token=[REDACTED]&username=05XXXXXXXX

Nothing unusual at first glance.

Typical Laravel auth flow.

CSRF token.

Session cookie.

Phone number passed through username.

The interesting part was the response.


Finding #1: Username Enumeration via /auth/validate-login

The endpoint behaved differently depending on whether the subscriber existed.

Existing Subscriber

Submitting a valid number:

HTTP/2 200 OK
Content-Type: text/html

Response (redacted):

<div class="auth-container">
    <form id="login-flow">
        ...
    </form>
</div>

Observed behavior:

  • authentication flow continued

  • subscriber recognized

  • password/OTP state rendered


Non-Existing Subscriber

Submitting an invalid number:

HTTP/2 200 OK
Content-Type: text/html

Response:

<div class="register-container">
    <h1>[REDACTED]</h1>
    <p>[REDACTED]</p>
</div>

Observed behavior:

  • registration flow rendered

  • distinct UI state

  • predictable content difference

The signal was deterministic.

No timing attack.

No statistical guessing.

No race condition.

Simple response diffing.

Pseudo-logic:

if "register-state-marker" in response:
    exists = False
else:
    exists = True

That alone was enough to validate telecom subscribers reliably.

At this stage:

Impact = weak

Realistically:

Low severity.

If I had reported only this:

“User enumeration exists”

It probably ends there.


Why Telecom Enumeration Is Different

Enumeration on telecom assets is easy to underestimate.

Phone numbers are not disposable usernames.

They often act as shared identity keys across:

  • customer portals

  • vouchers

  • loyalty systems

  • rewards platforms

  • billing

  • partner services

  • mobile APIs

Once you can answer:

“Does this subscriber exist?”

You remove uncertainty.

That matters more than people think.

Without enumeration:

056XXXXXXX ?
059XXXXXXX ?
050XXXXXXX ?
054XXXXXXX ?

Everything is guesswork.

With enumeration:

056XXXXXXX → valid subscriber
059XXXXXXX → invalid
050XXXXXXX → valid subscriber
054XXXXXXX → invalid

Now targeting becomes deterministic.

This changed my next step.

Instead of escalating the enumeration directly, I started mapping adjacent assets.

Same branding.

Shared auth assumptions.

Similar phone-number workflows.

Typical telecom ecosystem sprawl.

That led to a connected voucher platform.

Importantly:

It was out of scope.

Many hunters stop here.

I usually do not.

Not for exploitation.

For impact modeling.

Because OOS weaknesses often explain why an in-scope bug matters.


Pivot: Mapping Identity Reuse

The first question was simple:

Does the same subscriber identity exist in the voucher ecosystem?

The answer was yes.

The voucher platform accepted the same subscriber phone numbers during authentication flows.

That immediately made the enumeration bug more interesting.

Because now:

validated telecom subscriber
            =
validated voucher user

The enumeration finding had quietly become a targeting oracle.

Still not severe.

But now useful.

The next step was checking how authentication worked inside the voucher ecosystem.

The login flow relied on OTP verification.

Typical process:

  1. submit subscriber phone number

  2. generate OTP

  3. validate OTP

  4. establish authenticated session

Normally, this would still be annoying to abuse at scale.

You would need:

  • valid numbers

  • working OTP delivery

  • timing

  • access to SMS

But this is where the second finding appeared.


Finding #2: OTP Exposure via /api/v1/otp

While testing the voucher authentication flow, I noticed the platform relied entirely on SMS OTPs for login.

Typical flow:

phone number
    ↓
OTP generated
    ↓
SMS delivery
    ↓
OTP verification
    ↓
authenticated session

At first, this looked standard.

The important detail was that the same subscriber phone numbers validated through the telecom login flow also existed inside the voucher ecosystem.

Meaning the first bug already solved a major problem:

target validation

Instead of guessing random numbers, I now had confirmed subscribers only.

The voucher authentication flow started with OTP generation:

POST /api/v1/otp/send HTTP/2
Host: voucher.[REDACTED]
Content-Type: application/json

{
  "channel": "sms",
  "receiver": "05XXXXXXXX",
  "action": "LOGIN_OTP"
}

Response:

{
  "is_successful": true,
  "data": {
    "identifier": "[REDACTED]"
  },
  "message": {
    "title": "Success",
    "message": "OTP generated successfully."
  }
}

Nothing unusual yet.

The problem appeared during OTP handling.

A separate endpoint exposed OTP-related data in a way that broke the trust assumption behind SMS verification.

Redacted request structure:

GET /api/v1/otp HTTP/2
Host: voucher.[REDACTED]
Authorization: Bearer [REDACTED]

Response shape:

{
  "is_successful": true,
  "data": {
    "data": [
      {
        "identifier": "[REDACTED]",
        "otp_code": "[REDACTED]",
        "action": "LOGIN_OTP",
        "expiration_time": "[REDACTED]"
      }
    ]
  }
}

The key issue:

The platform exposed active OTP context that should never have been retrievable in this manner.

In practice, this collapsed the SMS trust boundary.

Instead of:

generate OTP
      ↓
wait for victim SMS
      ↓
manually receive code

The flow became:

generate OTP
      ↓
retrieve OTP context
      ↓
complete auth

At this stage, the voucher compromise became practical.

But there was still a limitation.

Authenticated access alone did not necessarily expose meaningful telecom subscriber context.

The next question became:

What does the voucher platform trust downstream?

That led to the third finding.


Finding #3: Subscriber Context Exposure via /api/v1/integrator/login_redirect/{id}

After authentication, I continued tracing authenticated requests and integration behavior.

One endpoint immediately stood out:

POST /api/v1/integrator/login_redirect/{id} HTTP/2
Host: voucher.[REDACTED]
Authorization: Bearer [REDACTED]

The endpoint appeared to broker authentication between systems.

The naming alone was interesting:

login_redirect

That usually means one system trusts another to establish user context.

Redacted response structure:

{
  "status": "ok",
  "data": {
    "access_token": "[REDACTED]",
    "refresh_token": "[REDACTED]",
    "customer": {
      "id": "[REDACTED]",
      "name": "[REDACTED]",
      "email": "[REDACTED]",
      "phone": "05XXXXXXXX",
      "customer_identifier": "[REDACTED]",
      "reference": "[REDACTED]",
      "subscription_metadata": "[REDACTED]"
    }
  }
}

This was the moment the chain became interesting.

The voucher ecosystem was not isolated.

It trusted subscriber context from the telecom platform.

Which meant successful authentication in vouchers exposed subscriber-linked information originating from the larger ecosystem.

The important part here was not the endpoint itself.

It was what the earlier findings changed.

Without enumeration:

random numbers
      ↓
poor targeting
      ↓
low reliability

Without OTP exposure:

confirmed subscriber
      ↓
blocked by SMS possession

Combined:

confirmed subscriber
      ↓
voucher auth
      ↓
subscriber context
      ↓
PII exposure

This transformed the original finding.

The in-scope bug stopped being:

“You can tell if an account exists.”

And became:

“You can deterministically identify real subscribers and materially improve downstream compromise leading to subscriber PII exposure.”

That is a very different risk discussion.


Why the Medium Felt Unfair

I understand why the report landed as Medium.

Strictly speaking:

  • the enumeration itself was weak

  • the downstream issues were out of scope

  • no direct compromise occurred on the primary platform

From a program perspective, that makes sense.

But from an attacker perspective, the chain mattered more than the individual bugs.

The in-scope issue solved the hardest problem:

certainty

Instead of wasting effort on random telecom ranges, an attacker could focus entirely on verified subscribers.

That dramatically increased reliability.

And reliability is often what separates:

theoretical abuse

from

practical abuse


Hunter Takeaways

1. Enumeration Is Often More Valuable Than It Looks

Especially in:

  • telecom

  • fintech

  • loyalty systems

  • identity-heavy ecosystems

Because identity is shared everywhere.


2. OOS Findings Still Matter

Not for payout arguments.

For impact modeling.

Ask:

Does this make my in-scope finding more dangerous?

Sometimes the answer upgrades the whole story.


3. Stop Thinking in Single Bugs

The best findings are often:

weak bug
    +
weak bug
    +
weak assumption
    =
real impact

This chain was exactly that.

None of the findings looked especially impressive alone.

Together, they became something much harder to dismiss.


Final Thoughts

If I had reported /auth/validate-login immediately, this would have probably ended as a low-quality Medium and disappeared into the backlog.

The useful part was continuing to dig.

Not because I expected a critical.

Because telecom ecosystems tend to reuse identity everywhere.

That assumption paid off.

The biggest lesson from this case:

Sometimes the bug is not the vulnerability itself.

Sometimes the bug is simply the thing that makes the next vulnerability finally practical.

More from this blog

A

Abdulaziz's Writeups

6 posts

Technical bug bounty writeups and security research from real-world targets.