# 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:

```plaintext
/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:

```plaintext
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:

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

Response (redacted):

```html
<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:

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

Response:

```html
<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:

```python
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:

```plaintext
056XXXXXXX ?
059XXXXXXX ?
050XXXXXXX ?
054XXXXXXX ?
```

Everything is guesswork.

With enumeration:

```plaintext
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:

```plaintext
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:

```text
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:

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

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

Response:

```json
{
  "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:

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

Response shape:

```json
{
  "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:

```text
generate OTP
      ↓
wait for victim SMS
      ↓
manually receive code
```

The flow became:

```text
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:

```http
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:

```json
{
  "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:

```text
random numbers
      ↓
poor targeting
      ↓
low reliability
```

Without OTP exposure:

```text
confirmed subscriber
      ↓
blocked by SMS possession
```

Combined:

```text
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:

```text
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.
