Gửi #896575: dgtlmoon changedetection.io 0.55.8 Race Conditionthông tin

tiêu đềdgtlmoon changedetection.io 0.55.8 Race Condition
Mô tả# Timing Attack Vulnerability in Password Verification (CWE-208) **BUG_Author:** herantong **Affected Version:** changedetection.io ≤ 0.55.8 **Vendor:** [changedetection.io GitHub Repository](https://github.com/dgtlmoon/changedetection.io) **Software:** [changedetection.io](https://github.com/dgtlmoon/changedetection.io) **Vulnerability Files:** - `changedetectionio/flask_app.py` --- ## Description ### 1. Timing Side-Channel in Password Hash Comparison In `flask_app.py`, the `check_password()` method compares a PBKDF2-derived key against a stored salted hash using Python's standard `==` operator. This operator performs a lexicographic, short-circuit byte comparison that returns `False` as soon as the first mismatched byte is encountered. Consequently, the response time of a failed login attempt is proportional to the number of leading bytes that match the correct hash, enabling a timing side-channel attack (CWE-208). ### 2. Exploiting the Timing Side-Channel An attacker with network access to the application can repeatedly send crafted login requests while measuring the response latency of each attempt. By brute-forcing one byte position at a time and observing which candidate produces the longest response time, the attacker can iteratively recover the full password hash. Once the hash is known, it can be used to construct valid authentication material. ### 3. Vulnerable Code Location The vulnerability resides in the `check_password` method of the `User` class: ```python # changedetectionio/flask_app.py:482-504 def check_password(self, password): import base64 import hashlib raw_salt_pass = os.getenv("SALTED_PASS", False) if not raw_salt_pass: raw_salt_pass = datastore.data['settings']['application'].get('password') raw_salt_pass = base64.b64decode(raw_salt_pass) salt_from_storage = raw_salt_pass[:32] new_key = hashlib.pbkdf2_hmac( 'sha256', password.encode('utf-8'), salt_from_storage, 100000 ) new_key = salt_from_storage + new_key return new_key == raw_salt_pass # <-- Vulnerable comparison on line 504 ``` ### 4. Reachability via HTTP Endpoint The vulnerable method is directly invoked by the `/login` POST handler, which accepts user-controlled input: ```python # changedetectionio/flask_app.py:742-775 @app.route('/login', methods=['GET', 'POST']) def login(): # ... password = request.form.get('password') if (user.check_password(password)): # <-- User input reaches vulnerable comparison flask_login.login_user(user, remember=True) return redirect(validated_redirect) else: flash(gettext('Incorrect password'), 'error') return redirect(url_for('login', redirect=redirect_url if redirect_url else None)) ``` ### 5. Absence of Mitigations - No usage of `hmac.compare_digest` or `secrets.compare_digest` was found anywhere in the codebase. - No rate-limiting, account lockout, or network-level defenses are in place for the `/login` endpoint. - The project requires Python ≥ 3.10, making both `hmac.compare_digest` (since Python 3.3) and `secrets.compare_digest` (since Python 3.6) readily available. --- ## Proof of Concept 1. Identify the login endpoint of the target changedetection.io instance: ``` POST http://<target-host>/login ``` 2. Send repeated login requests with a fixed candidate password while measuring the response time of each attempt. A higher response time for a particular byte value indicates a correct prefix match against the stored hash. 3. Example timing measurement approach (pseudocode): ```python import time import requests target = "http://<target-host>/login" recovered_prefix = b"" hash_length = 64 # PBKDF2-HMAC-SHA256 output + 32-byte salt for position in range(hash_length): best_byte = None best_time = 0 for candidate in range(256): password = b"placeholder" start = time.perf_counter() requests.post(target, data={"password": password}) elapsed = time.perf_counter() - start if elapsed > best_time: best_time = elapsed best_byte = candidate recovered_prefix += bytes([best_byte]) ``` 4. After iterating through all byte positions, the attacker fully recovers the stored salted hash, allowing authentication bypass. --- ## Root Cause Analysis | Question | Answer | |---|---| | Does the code perform a cryptographic comparison on a security-sensitive value? | Yes — `new_key == raw_salt_pass` on line 504 compares a PBKDF2-derived password hash. | | Is the comparison performed using a non-constant-time operator? | Yes — Python's `==` short-circuits on the first mismatched byte. | | Does the compared data originate from user input? | Yes — the `password` parameter comes from `request.form.get('password')` on line 765. | | Is the vulnerable code reachable from an external HTTP endpoint? | Yes — it is called directly by the `/login` POST handler on line 767. | | Are any mitigations already in place? | No — no `compare_digest`, rate limiting, or account lockout exists. | | Is the code in a test, demo, or dead-code context? | No — it is the live production authentication path. | **Verdict: Confirmed vulnerability (CWE-208 — Observable Timing Discrepancy).** --- ## Fix Recommendations 1. **Primary Fix**: Replace the `==` comparison on line 504 with a constant-time comparison function: ```python import hmac # ... return hmac.compare_digest(new_key, raw_salt_pass) ``` Alternatively, `secrets.compare_digest(new_key, raw_salt_pass)` (available since Python 3.6). 2. **Verification**: Run the existing test suite (e.g., `test_security.py`) to confirm that the login flow still passes. Add a unit test that asserts `check_password` returns `False` for an incorrect password without raising exceptions. 3. **Defense in Depth** (optional but recommended): Add rate limiting or account lockout on the `/login` endpoint to further reduce the feasibility of network-based timing attacks. --- ## References - [CWE-208: Observable Timing Discrepancy](https://cwe.mitre.org/data/definitions/208.html) - [Python `hmac.compare_digest` documentation](https://docs.python.org/3/library/hmac.html#hmac.compare_digest) - [Python `secrets.compare_digest` documentation](https://docs.python.org/3/library/secrets.html#secrets.compare_digest)
Nguồn⚠️ https://github.com/herantong/cve/blob/main/Timing%20Attack%20Vulnerability%20in%20Password%20Verification%20(CWE-208)
Người dùng
 herantong (UID 97028)
Đệ trình20/07/2026 04:01 (cách đây 2 các tháng)
Kiểm duyệt22/09/2026 07:03 (2 months later)
Trạng tháiđược chấp nhận
Mục VulDB408338 [dgtlmoon changedetection.io đến 0.60.7 Hash Comparison flask_app.py check_password mật khẩu tiết lộ thông tin]
điểm20

Do you need the next level of professionalism?

Upgrade your account now!