GHSA-9JJ8-V89V-XJVW
Vulnerability from github – Published: 2026-07-20 19:06 – Updated: 2026-07-20 19:06Summary
The /static/<group>/<filename> route accepts group="..", which causes send_from_directory("static/..", filename) to execute. This moves the base directory up to /app/changedetectionio, enabling unauthenticated local file read of application source files (e.g., flask_app.py). Severity is low information disclosure (C:L).
Details
The vulnerable code is in changedetectionio/flask_app.py inside static_content():
group = re.sub(r'[^\w.-]+', '', group.lower())
filename = re.sub(r'[^\w.-]+', '', filename.lower())
...
return send_from_directory(f"static/{group}", path=filename)
The group sanitization allows dots, so group=".." passes validation.
This results in send_from_directory("static/..", filename), effectively shifting the base directory to /app/changedetectionio and allowing reads of files in that directory.
The route is unauthenticated, so any user can retrieve source files without logging in.
Limitation: the route only matches
/static/<group>/<filename>and rejects slashes insidefilename, so it cannot traverse further to arbitrary system paths like/etc/passwd. It is limited to files inside the application package directory.
PoC
1) Start an instance (example: Docker on port 5050)
docker run -d --name cdio -p 127.0.0.1:5050:5000 -v cdio-data:/datastore cdio-local
2) Reproduce
(URL-encoded traversal)
curl -i http://127.0.0.1:5050/static/%2e%2e/flask_app.py
(curl path passthrough)
curl --path-as-is -i http://127.0.0.1:5050/static/../flask_app.py
3) Observe that the response body contains Python source code from flask_app.py.
Impact
- Vulnerability type: Directory Traversal / Local File Read
- Affected users: Anyone with network access (no authentication required)
- Scope: Source files under
/app/changedetectionio - Security impact: Internal logic exposure can aid further exploitation (Confidentiality: Low)
{
"affected": [
{
"package": {
"ecosystem": "PyPI",
"name": "changedetection.io"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "0.53.2"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-25527"
],
"database_specific": {
"cwe_ids": [
"CWE-22"
],
"github_reviewed": true,
"github_reviewed_at": "2026-07-20T19:06:55Z",
"nvd_published_at": "2026-02-19T15:16:11Z",
"severity": "MODERATE"
},
"details": "## Summary\nThe `/static/\u003cgroup\u003e/\u003cfilename\u003e` route accepts `group=\"..\"`, which causes `send_from_directory(\"static/..\", filename)` to execute. This moves the base directory up to `/app/changedetectionio`, enabling **unauthenticated local file read** of application source files (e.g., `flask_app.py`). Severity is **low information disclosure (C:L)**.\n\n### Details\nThe vulnerable code is in `changedetectionio/flask_app.py` inside `static_content()`:\n\n```\ngroup = re.sub(r\u0027[^\\w.-]+\u0027, \u0027\u0027, group.lower())\nfilename = re.sub(r\u0027[^\\w.-]+\u0027, \u0027\u0027, filename.lower())\n...\nreturn send_from_directory(f\"static/{group}\", path=filename)\n```\n\nThe `group` sanitization allows dots, so `group=\"..\"` passes validation. \nThis results in `send_from_directory(\"static/..\", filename)`, effectively shifting the base directory to `/app/changedetectionio` and allowing reads of files in that directory. \nThe route is unauthenticated, so **any user can retrieve source files** without logging in.\n\n\u003e Limitation: the route only matches `/static/\u003cgroup\u003e/\u003cfilename\u003e` and rejects slashes inside `filename`, so it cannot traverse further to arbitrary system paths like `/etc/passwd`. It is limited to files inside the application package directory.\n\n### PoC\n1) Start an instance (example: Docker on port 5050)\n```\ndocker run -d --name cdio -p 127.0.0.1:5050:5000 -v cdio-data:/datastore cdio-local\n```\n\n2) Reproduce \n**(URL-encoded traversal)**\n```\ncurl -i http://127.0.0.1:5050/static/%2e%2e/flask_app.py\n```\n\n**(curl path passthrough)**\n```\ncurl --path-as-is -i http://127.0.0.1:5050/static/../flask_app.py\n```\n\n3) Observe that the response body contains Python source code from `flask_app.py`.\n\n### Impact\n- **Vulnerability type:** Directory Traversal / Local File Read\n- **Affected users:** Anyone with network access (no authentication required)\n- **Scope:** Source files under `/app/changedetectionio`\n- **Security impact:** Internal logic exposure can aid further exploitation (Confidentiality: Low)",
"id": "GHSA-9jj8-v89v-xjvw",
"modified": "2026-07-20T19:06:55Z",
"published": "2026-07-20T19:06:55Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/dgtlmoon/changedetection.io/security/advisories/GHSA-9jj8-v89v-xjvw"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-25527"
},
{
"type": "WEB",
"url": "https://github.com/dgtlmoon/changedetection.io/commit/9d38b4517364831889b5b0d7b3465fd060403fd4"
},
{
"type": "PACKAGE",
"url": "https://github.com/dgtlmoon/changedetection.io"
},
{
"type": "WEB",
"url": "https://github.com/pypa/advisory-database/tree/main/vulns/changedetection-io/PYSEC-2026-2124.yaml"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N",
"type": "CVSS_V3"
}
],
"summary": "changedetection.io is vulnerable to unauthenticated static path traversal"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.