This Is How I Hacked an OTP Verification System

spyboy's avatarPosted by

(Authentication Bypass via Logic & Timing – Educational Case Study)

Disclaimer
This article is written strictly for educational and defensive purposes.
All applications, endpoints, OTP formats, identifiers, and timings are fully anonymized.
No real user accounts or systems were harmed.
The goal is to explain why OTP ≠ security when logic is flawed.


Why OTP Systems Fail More Than Passwords

OTP systems are trusted blindly.

Developers think:

  • “OTP means secure”
  • “It expires quickly”
  • “It’s random”

Attackers know better.

🔥 OTP systems fail not because of weak randomness,
but because of state, timing, and verification logic mistakes.

This post shows how I bypassed OTP verification entirely
➡️ no brute force
➡️ no guessing
➡️ no SMS interception

Just logic abuse.


Target Overview (Anonymized)

  • Type: Web + mobile application
  • OTP type: 6-digit numeric
  • Use cases:
    • Login
    • Password reset
  • Flow:
    1. Enter email / phone
    2. OTP sent
    3. Submit OTP
    4. Access granted

On paper: secure
In reality: fragile


Phase 1: Mapping the OTP Flow (This Is Everything)

I triggered OTP login.

Observed requests:

POST /api/auth/request-otp
POST /api/auth/verify-otp

Response from OTP request:

{
  "status": "OTP_SENT",
  "otp_id": "f3a9c2"
}

🚩 Red Flag #1
OTP verification tied to a client-controlled identifier (otp_id).


Phase 2: Understanding How Verification Worked

Verification request:

POST /api/auth/verify-otp
Content-Type: application/json

{
  "otp_id": "f3a9c2",
  "otp": "123456"
}

Normal behavior:

  • Correct OTP → login
  • Wrong OTP → error

But what mattered was what happens after verification, not before.


Phase 3: Testing OTP State Enforcement

After a successful OTP login, I noticed something odd.

The server returned:

{
  "status": "VERIFIED",
  "session_token": "eyJhbGciOi..."
}

But the OTP itself was not immediately invalidated.

That’s critical.


Phase 4: OTP Reuse (The Silent Killer)

I reused the same OTP request context.

Sent again:

POST /api/auth/verify-otp

Same payload.

Response:

{
  "status": "VERIFIED",
  "session_token": "new_token_here"
}

😐
Same OTP.
Same otp_id.
Multiple valid sessions.


Why This Is Dangerous

This means:

  • OTP was treated as stateless
  • Verification did not invalidate it
  • Backend trusted that “OTP already used” wasn’t needed

OTP became:

A reusable password


Phase 5: Cross-Account OTP Abuse

Here’s where it got worse.

I requested OTP for Account A, got:

"otp_id": "f3a9c2"

Then I modified the verification request:

{
  "otp_id": "f3a9c2",
  "email": "victim@example.com",
  "otp": "same_otp"
}

Response:

{
  "status": "VERIFIED"
}

🚨
OTP was not bound to a specific user.

This enabled:

Login into other accounts using a valid OTP issued elsewhere


Phase 6: Race Condition Amplification (Advanced)

go through last post..

I combined both.

I sent multiple OTP verification requests in parallel.

Result:

  • OTP accepted multiple times
  • Sessions created simultaneously
  • Rate limiting bypassed

This wasn’t brute force.

This was timing abuse.


Real-World Attack Chain

An attacker can:

1️⃣ Request OTP for their own account
2️⃣ Capture otp_id
3️⃣ Reuse OTP
4️⃣ Bind it to victim
5️⃣ Login as victim
6️⃣ Create persistent sessions

No alerts.
No password resets.
No failed attempts.


Why Rate Limiting Didn’t Help

DefenseWhy It Failed
Rate limitOTP was valid
CAPTCHACorrect flow
ExpiryNot enforced on use
OTP lengthIrrelevant

OTP security depends on state, not digits.


Root Cause Analysis

❌ Developer Mistakes

  • OTP not invalidated after use
  • OTP not bound to user identity
  • OTP verification not atomic
  • Relied on frontend flow order
  • No replay protection

This is logic failure, not crypto failure.


✅ How OTP Systems Must Be Built

OTP rules (non-negotiable):

  • OTP must be single-use
  • OTP must be bound to user + action
  • OTP must expire on:
    • success
    • failure threshold
  • Verification must be atomic
  • State must live server-side only

Anything less is bypassable.


How I Hunt OTP Bugs (Exact Playbook)

🔍 Checklist

1️⃣ Reuse OTP
2️⃣ Reuse otp_id
3️⃣ Swap users
4️⃣ Parallel verification
5️⃣ Skip verification step
6️⃣ Replay old OTPs

OTP bugs are everywhere because:

Devs test correct OTP only once

Attackers test everything else.


Tools Used

ToolPurpose
Burp SuiteFlow manipulation
RepeaterOTP replay
IntruderParallel verification
TimingRace exploitation

Severity Assessment

ImpactLevel
Account takeoverCritical
MFA bypassCritical
DetectionVery low
Abuse scaleHigh

Severity:

Critical – Authentication Bypass


Why Companies Miss This

Because:

  • OTP “feels secure”
  • QA tests happy path only
  • Logs show “successful login”
  • No alerts triggered

By the time it’s noticed — it’s too late.


Final Thoughts

OTP is not security.

Correct OTP logic is.

And logic bugs don’t care how many digits you use.


Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.