OTP EXPOSED IN PLAINTEXT: How a Single API Response Breaks Two-Factor Authentication + Video

Listen to this Post

Featured Image

Introduction:

One-Time Passwords (OTPs) are designed to add a second layer of security, but that guarantee vanishes the moment the OTP is returned in an API response. A penetration tester recently discovered an Android application that exposed the same OTP sent via SMS directly in the API’s JSON payload – turning two-factor authentication into a single, easily intercepted factor. This critical flaw enables attackers to bypass the entire OTP verification process without ever touching the user’s phone.

Learning Objectives:

  • Identify OTP exposure vulnerabilities in API responses during mobile and web application testing.
  • Execute man-in-the-middle (MITM) attacks to capture API traffic from Android emulators and real devices.
  • Implement secure OTP handling patterns, server-side verification, and complementary rate limiting controls.

You Should Know:

  1. Understanding the Vulnerability – OTP in API Response

Returning the OTP in the API response completely defeats its purpose. The typical flow should be: user requests OTP → server generates secret, sends it via SMS/email, stores a hash → user enters received OTP → server verifies entered value against stored hash. When the API returns the OTP in the response (e.g., {"otp": "123456", "message": "sent"}), an attacker intercepting the HTTP/HTTPS traffic instantly obtains the valid OTP.

Why developers make this mistake: They often use the same internal function to generate and log the OTP for debugging, then forget to strip it from the response. Or they mistakenly believe that HTTPS alone protects the payload.

Example vulnerable API response:

POST /api/request-otp HTTP/1.1
Host: vulnerable-app.com
{
"phone": "+1234567890"
}

Response:
{
"status": "success",
"otp": "482039",
"expires_in": 300
}

Mitigation: Never include the OTP or any secret token in the response body, headers, or URL parameters. Only return a generic success message like {"status": "otp_sent"}.

  1. How Attackers Exploit This on Android – Step-by-Step Interception

Attackers intercept API traffic using a proxy (Burp Suite or mitmproxy) on an emulator or rooted device. Below is a complete guide using mitmproxy and Nox Player (the emulator mentioned in the original post).

Step 1: Set up mitmproxy on Linux/Windows

 Install mitmproxy (Linux)
sudo apt update && sudo apt install mitmproxy -y

Or on Windows (using chocolatey)
choco install mitmproxy

Run mitmproxy on port 8080
mitmproxy --listen-port 8080

Step 2: Configure Nox Player (Android emulator)

  • Open Nox Player, go to Settings → Network → Enable Proxy.
  • Set proxy IP to your host machine’s IP (e.g., 192.168.1.100) and port 8080.

Step 3: Install mitmproxy CA certificate on Android

 Start mitmweb (web interface) for easier certificate download
mitmweb --listen-port 8080

On the emulator browser, navigate to http://mitm.it
 Choose Android, download the .pem certificate, install it via Settings → Security → Install from storage

Step 4: Capture traffic and spot OTP leakage

  • Open the target Android app, trigger OTP request.
  • In mitmproxy, look for the `/api/request-otp` endpoint. If you see a JSON response containing `”otp”` field, the vulnerability is confirmed.

Alternative using ADB and tcpdump (rooted device):

adb shell tcpdump -i wlan0 -s 0 -w /sdcard/capture.pcap
adb pull /sdcard/capture.pcap
 Analyze with Wireshark, filter http.response
  1. Server-Side Verification Best Practices – Secure OTP Flow

Never trust the client. The server must store a non-reversible representation of the OTP. Here’s a secure implementation in Node.js/Express and Python.

Node.js (Express) secure flow:

const crypto = require('crypto');
const bcrypt = require('bcrypt');

// Step 1: Request OTP
app.post('/api/request-otp', async (req, res) => {
const { phone } = req.body;
const otp = Math.floor(100000 + Math.random()  900000).toString();
const hashedOtp = await bcrypt.hash(otp, 10);

// Store hashed OTP with expiry (e.g., Redis with TTL 300s)
await redis.setex(<code>otp:${phone}</code>, 300, hashedOtp);

// Send OTP via SMS (Twilio example) – NEVER in API response
await twilio.messages.create({ body: <code>Your OTP: ${otp}</code>, to: phone, from: '+1234567890' });

// Return only generic success
res.json({ status: 'otp_sent' });
});

// Step 2: Verify OTP
app.post('/api/verify-otp', async (req, res) => {
const { phone, enteredOtp } = req.body;
const storedHash = await redis.get(<code>otp:${phone}</code>);

if (!storedHash) return res.status(400).json({ error: 'OTP expired' });

const isValid = await bcrypt.compare(enteredOtp, storedHash);
if (isValid) {
await redis.del(<code>otp:${phone}</code>);
res.json({ status: 'verified' });
} else {
res.status(401).json({ error: 'Invalid OTP' });
}
});

Python (Flask) example:

import hashlib, secrets
from flask import Flask, request, jsonify

app = Flask(<strong>name</strong>)
otp_store = {}  Use Redis in production

@app.route('/request-otp', methods=['POST'])
def request_otp():
phone = request.json['phone']
otp = str(secrets.randbelow(900000) + 100000)
hashed = hashlib.sha256(otp.encode()).hexdigest()
otp_store[bash] = {'hash': hashed, 'expiry': time.time() + 300}
 Send via SMS (integration omitted)
return jsonify({'status': 'otp_sent'})  NO OTP HERE
  1. API Security Hardening – Rate Limiting and Monitoring

Even if the OTP isn’t leaked, missing rate limits allows brute-force attacks. Implement layered defenses.

Nginx rate limiting (Linux commands):

 Edit nginx.conf
sudo nano /etc/nginx/nginx.conf

Add in http block:
limit_req_zone $binary_remote_addr zone=otp_limit:10m rate=3r/m;
limit_req_zone $binary_remote_addr zone=verify_limit:10m rate=10r/m;

Apply in location block:
location /api/request-otp {
limit_req zone=otp_limit burst=2 nodelay;
proxy_pass http://backend;
}

Using iptables to block brute-force (per IP):

 Allow 5 new connections per minute from same IP to port 443
sudo iptables -A INPUT -p tcp --dport 443 -m recent --name OTP_ATTACK --set
sudo iptables -A INPUT -p tcp --dport 443 -m recent --name OTP_ATTACK --update --seconds 60 --hitcount 5 -j DROP

Add CAPTCHA after 3 failed attempts: Integrate hCaptcha or reCAPTCHA on the verification endpoint. Monitor logs for spikes in OTP requests from same IP or fingerprint.

5. Mobile App Security Testing Commands (Android)

Test for OTP exposure using ADB, Frida, and Objection without needing source code.

List installed packages:

adb shell pm list packages | grep -i companyname

Extract APK for static analysis:

adb shell pm path com.vulnerable.app
adb pull /data/app/com.vulnerable.app/base.apk
 Decompile with jadx
jadx base.apk -d decompiled/
 Search for "otp" in decompiled code
grep -r "otp" decompiled/

Dynamic instrumentation with Frida to intercept API calls:

// frida_script.js - Hook OkHttp client
Java.perform(function() {
var Builder = Java.use('okhttp3.Request$Builder');
Builder.build.implementation = function() {
var request = this.build();
console.log("[] URL: " + request.url());
return request;
};
});
 Inject script into running app
frida -U -l frida_script.js com.vulnerable.app

Using Objection for runtime security assessment:

objection -g com.vulnerable.app explore
 Monitor HTTP traffic
ios monitoring http
 Disable SSL pinning if present
android sslpinning disable
  1. Web Application Firewall (WAF) Rules to Block OTP Leakage

Deploy ModSecurity with OWASP Core Rule Set (CRS) to detect and block responses containing sensitive patterns.

Custom ModSecurity rule to block OTP in response:

SecRule RESPONSE_BODY "@rx {\s\"otp\"\s:\s\"[0-9]{6}\"\s}" \
"id:100001,phase:4,deny,status:403,msg:'OTP leaked in API response',\
severity:CRITICAL,tag:LEAKAGE"

For cloud WAF (AWS WAF or Cloudflare): Create a regex pattern to match `”otp”:”\d{6}”` in response body and set rule action to block or log. Combine with rate-based rules to automatically blacklist offending IPs.

7. Real-World Impact – Account Takeover (ATO) Chain

This OTP exposure flaw directly enables ATO. Attackers can automate the process:

  1. Enumerate valid phone numbers/emails (often through sign-up or password reset endpoints).
  2. Trigger OTP request – the API returns the OTP in response (or attacker intercepts it).
  3. Use the leaked OTP to verify – immediately login or reset password.

4. Change credentials, lock out user, extract data.

Why it’s worse than SMS interception: Traditional SMS interception requires SIM swapping or SS7 exploits – high effort. Here, the attacker only needs to sniff HTTPS traffic on the same network (public Wi-Fi) or compromise the device with a basic malware that reads API responses. The comment in the original post highlighted: “If an attacker intercepts the API response, they never even need to touch the SMS. The response payload becomes the easier attack surface.”

What Undercode Say:

  • Never return secrets in API responses – OTPs, reset tokens, or API keys belong in out-of-band channels only, not in HTTP bodies.
  • Server-side verification is non-negotiable – hash the OTP, store with expiry, compare on verify endpoint. The client should only see success/failure.
  • Rate limiting + monitoring stops abuse – even if OTP is secure, without limits attackers can brute-force 6-digit codes. Combine with CAPTCHA and anomaly detection.
  • Test with MITM proxies regularly – include intercepting proxy testing in your mobile CI/CD pipeline. Use tools like mitmproxy, Burp Suite, and Frida to catch leaks before production.
  • Secure coding education for mobile/API devs – the trap is thinking “we need to send OTP” means “the API should return it.” Your API’s job stops at triggering delivery.

Prediction:

OTP-based 2FA will gradually decline as passkeys (WebAuthn) and device-bound tokens become mainstream, but legacy systems will retain OTPs for years. Attackers will shift focus to API misconfigurations like the one described – easier than exploiting SMS infrastructure. Expect regulatory fines under GDPR/PCI-DSS for exposing OTPs in responses, and a surge in bug bounty payouts for such findings (from $1,000 to $10,000+). Developers who fail to separate OTP generation from API response serialization will continue to be the weakest link. The fix is simple: delete the line of code that adds the OTP to the JSON response.

▶️ Related Video (84% Match):

🎯Let’s Practice For Free:

IT/Security Reporter URL:

Reported By: Https: – Hackers Feeds
Extra Hub: Undercode MoN
Basic Verification: Pass ✅

🔐JOIN OUR CYBER WORLD [ CVE News • HackMonitor • UndercodeNews ]

💬 Whatsapp | 💬 Telegram

📢 Follow UndercodeTesting & Stay Tuned:

𝕏 formerly Twitter 🐦 | @ Threads | 🔗 Linkedin | 🦋BlueSky