
CVE-2026-39987 탐지 및 익스플로잇 툴킷으로, Marimo 노트북의 사전 인증 RCE(원격 코드 실행) 취약점입니다. WebSocket 엔드포인트 검사를 통해 취약한 인스턴스를 식별하는 Python 스캐너와 Nmap NSE 스크립트가 포함되어 있습니다.
Marimo(데이터 과학 및 AI/ML용 오픈소스 Python 노트북)에서 사전 인증 원격 코드 실행 취약점이 발견되었습니다. 터미널 WebSocket 엔드포인트(/terminal/ws)는 인증 검증을 완전히 건너뛰는 반면, 인접한 노트북 엔드포인트(/ws)는 이를 올바르게 강제합니다. 인증되지 않은 공격자는 /terminal/ws에 연결하여 자격 증명 없이 호스트 시스템에서 완전한 대화형 PTY 셸을 얻을 수 있습니다.
공개 후 10시간 이내에 실제 환경에서 악용되었습니다. 공격자는 3분 이내에 AWS 자격 증명을 탈취했습니다.
영향: Marimo <= 0.20.4. Marimo 0.23.0에서 수정됨.
| 필드 | 세부 내용 |
|---|---|
| CVE ID | CVE-2026-39987 |
| 공급업체 | Marimo 프로젝트 |
| 제품 | Marimo (Python 노트북) |
| 영향받는 버전 | <= 0.20.4 |
| CVSS v3.1 | 9.3 (치명적) |
| CWE | CWE-306 — 중요 기능에 대한 인증 누락 |
| 공격 경로 | 네트워크 |
| 인증 | 필요 없음 |
| 사용자 상호작용 | 없음 |
| 악용 성숙도 | 실제 환경에서 활발히 악용됨 |
| 악용까지 걸린 시간 | 공개 후 약 10시간 |
| 패치 버전 | Marimo 0.23.0 |
Marimo는 Jupyter의 현대적인 대안으로 설계된 오픈소스 반응형 Python 노트북입니다. 데이터 과학, AI/ML 실험, 대화형 데이터 분석을 위해 구축되었습니다. 주요 기능으로는 자동 종속성 추적, 재현 가능한 실행, 기존 노트북보다 깔끔한 개발자 경험이 있습니다.
Marimo는 Python 및 AI/ML 커뮤니티에서 빠르게 채택되고 있으며, 특히 Jupyter가 제공하는 것보다 더 구조화된 노트북 워크플로우를 원하는 실무자들 사이에서 인기를 얻고 있습니다.
모든 노트북 환경과 마찬가지로 Marimo 인스턴스는 일반적으로 민감한 리소스에 접근할 수 있습니다: 클라우드 자격 증명(AWS, GCP, Azure), 데이터베이스 연결 문자열, AI 서비스용 API 키(OpenAI, Anthropic 등), 내부 네트워크 접근 권한 등입니다. 기존 웹 애플리케이션과 달리 노트북은 임의 코드를 실행하도록 설계되었습니다. 그것이 핵심 목적입니다.
이러한 조합은 노트북 환경에서의 인증 우회를 특히 치명적으로 만듭니다.``` Typical Marimo Deployment:
┌──────────────┐ ┌────────────────────────────────┐ │ │ HTTP │ Marimo Server │ │ Browser │────────>│ │ │ (User) │ │ ┌──────────────────────────┐ │ │ │<────────│ │ /ws (Notebook) │ │ └──────────────┘ WS │ │ ✅ validate_auth() │ │ │ └──────────────────────────┘ │ │ │ │ ┌──────────────────────────┐ │ │ │ /terminal/ws │ │ │ │ ❌ NO AUTH CHECK │ │ │ └──────────────────────────┘ │ │ │ │ ┌──────────────────────────┐ │ │ │ Python Environment │ │ │ │ .env files │ │ │ │ AWS credentials │ │ │ │ API keys │ │ │ └──────────────────────────┘ │ └────────────────────────────────┘
---
## 취약점 심층 분석
### 두 개의 WebSocket 엔드포인트
Marimo 서버는 다양한 기능을 위해 여러 WebSocket 엔드포인트를 구현합니다. 두 주요 엔드포인트 간의 중요한 차이점은 인증 검사의 존재(또는 부재)입니다:```
Authentication Flow Comparison:
/ws (Notebook WebSocket):
┌─────────┐ ┌───────────────┐ ┌──────────┐ ┌───────────┐
│ Connect │───>│ validate_auth │───>│ Accept │───>│ Notebook │
└─────────┘ └───────┬───────┘ └──────────┘ └───────────┘
│
❌ Reject if
not authenticated
/terminal/ws (Terminal WebSocket):
┌─────────┐ ┌───────────────┐ ┌──────────┐ ┌───────────┐
│ Connect │───>│ Check mode & │───>│ Accept │───>│ PTY Shell │
└─────────┘ │ platform only │ └──────────┘ └───────────┘
└───────────────┘
⚠️ No auth check!
Anyone gets a shell!
노트북 엔드포인트(/ws)는 WebSocket 연결을 수락하기 전에 사용자 신원을 확인하기 위해 validate_auth()를 올바르게 호출합니다. 이는 예상되는 보안 동작입니다.
터미널 엔드포인트(/terminal/ws)는 서버가 실행 모드에 있는지와 플랫폼이 터미널 기능을 지원하는지만 확인합니다. validate_auth()를 절대 호출하지 않습니다. 이러한 기본 검사를 통과한 후에는 연결을 수락하고 완전한 PTY(의사 터미널) 세션을 생성합니다.```python
async def websocket_connect(self, message): await self.validate_auth() # ✅ Checks authentication await self.accept() # ... notebook communication
async def websocket_connect(self, message): if not self.is_running_mode(): # Only checks mode await self.close() return if not self.is_platform_supported(): # Only checks platform await self.close() return await self.accept() # ❌ No auth! Anyone gets a shell # ... PTY shell creation
이것은 **CWE-306: 중요 기능에 대한 인증 누락**입니다. 서버에서 가장 위험한 엔드포인트(대화형 셸을 제공하는 엔드포인트)에는 인증이 전혀 없습니다.
### 공격: 3분 만에 AWS 키 탈취
악용 타임라인은 현대 위협 행위자들이 얼마나 빠르게 움직이는지 보여줍니다:```
┌──────────────────────────────────────────────────────────────┐
│ CVE-2026-39987 Timeline │
├──────────────────────────────────────────────────────────────┤
│ │
│ T+0h Advisory published │
│ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │
│ T+9h First exploit built from advisory │
│ T+10h Exploitation in the wild confirmed │
│ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │
│ T+10h 0m Attacker connects to /terminal/ws │
│ T+10h 1m Full PTY shell obtained │
│ T+10h 2m .env file located and read │
│ T+10h 3m AWS keys exfiltrated │
│ Total attack time: ~3 minutes │
└──────────────────────────────────────────────────────────────┘
공격 자체는 매우 단순합니다:``` Step 1: Attacker opens WebSocket connection to /terminal/ws (No authentication needed, no special tools required)
Step 2: Server creates a PTY (pseudo-terminal) session Attacker now has an interactive shell
Step 3: Attacker runs commands: $ cat .env AWS_ACCESS_KEY_ID=AKIA... AWS_SECRET_ACCESS_KEY=... DATABASE_URL=postgres://... OPENAI_API_KEY=sk-...
Step 4: Credentials exfiltrated Attacker now has cloud access, database access, and API keys for AI services
Total time: under 3 minutes Authentication required: none Tools required: any WebSocket client
No exploit development needed. No shellcode. No memory corruption. Just a WebSocket client and a missing auth check.
---
## Impact Analysis
**Marimo 호스트에 대한 즉각적인 영향:**
- Marimo 프로세스의 권한을 가진 완전한 대화형 셸
- 프로세스가 읽을 수 있는 모든 파일에 대한 접근(소스 코드, 데이터, 자격 증명)
- API 키와 비밀번호가 포함된 환경 변수에 대한 접근
- 호스트 시스템에서 임의 명령 실행 능력
**자격 증명 노출(주요 공격 목표):**
- `.env` 파일 또는 환경 변수의 AWS 액세스 키와 시크릿 키
- GCP/Azure 서비스 계정 자격 증명
- 비밀번호가 포함된 데이터베이스 연결 문자열
- OpenAI, Anthropic 및 기타 AI 서비스 API 키
- SSH 키 및 기타 인증 자료
**다운스트림 영향(도난된 자격 증명을 통한):**
- 클라우드 인프라에 대한 무단 접근(EC2, S3, Lambda 등)
- 클라우드 스토리지 및 데이터베이스에서 데이터 유출
- 리소스 남용(암호화폐 채굴, AI API 크레딧 도용)
- 클라우드 및 온프레미스 네트워크로의 측면 이동
**위험 증폭 요인:**
- 노트북 환경은 임의 코드를 실행하도록 설계됨(그것이 목적임)
- 데이터 과학 환경은 일반적으로 학습 작업을 위해 광범위한 클라우드 접근 권한을 가짐
- 많은 Marimo 인스턴스가 협업 및 원격 작업을 위해 인터넷에 노출됨
- 연구/실험 환경에서는 보안 강화가 종종 후순위로 밀림
---
## 영향을 받는 버전
| 버전 | 상태 |
|---------|--------|
| Marimo 0.23.0+ | **패치됨** |
| Marimo 0.20.5 ~ 0.22.x | **취약할 가능성 높음**(권고 범위와 수정 사이) |
| Marimo <= 0.20.4 | **취약함**(확인된 범위) |
---
## 더 큰 그림: 공격받는 AI/ML 도구 체인
CVE-2026-39987은 고립된 사건이 아닙니다. 이는 2026년 4월에 나타난 명확한 패턴의 일부입니다:
| CVE | 제품 | 유형 | 상태 |
|:---|:---|:---|:---|
| **CVE-2026-39987** | Marimo | 사전 인증 RCE(WebSocket) | 10시간 만에 악용됨 |
| **CVE-2026-33017** | Langflow | RCE | CISA KEV(3월 26일) |
| **CVE-2026-5059** | aws-mcp-server | 명령 주입 RCE | 공개 권고 |
| **TorchGeo** | TorchGeo | eval() RCE | 공개 권고 |
단 한 달 만에 4개의 AI/ML 개발 도구가 심각한 RCE 취약점에 노출되었습니다. AI/ML 개발 파이프라인은 새로운 섀도우 IT가 되고 있습니다: 광범위한 접근 권한, 최소한의 보안 감독, 그리고 풍부한 자격 증명 저장소를 갖춘 채 배포되는 도구들입니다.```
┌─────────────────────────────────────────────────┐
│ Why AI/ML Tools Are Prime Targets │
├─────────────────────────────────────────────────┤
│ │
│ 1. DESIGNED to execute arbitrary code │
│ (that's literally what notebooks do) │
│ │
│ 2. Run with elevated privileges │
│ (GPU access, cloud SDKs, network access) │
│ │
│ 3. Contain high-value credentials │
│ (AWS keys, API tokens, DB connections) │
│ │
│ 4. Often exposed to the network │
│ (for collaboration and remote access) │
│ │
│ 5. Security hardening is an afterthought │
│ (focus on features and UX, not security) │
│ │
│ 6. Users are researchers, not security experts │
│ (default configs, weak passwords, no VPN) │
└─────────────────────────────────────────────────┘
Python 스크립트는 다단계 분석을 통해 취약한 Marimo 인스턴스를 감지합니다.
작동 방식:
/api/status, /api/health, /를 쿼리하여 응답 콘텐츠와 헤더에서 Marimo 표시를 확인합니다/terminal/ws에 안전한 HTTP 업그레이드 요청을 전송합니다(연결을 통해 데이터는 전송되지 않음)/terminal/ws(인증이 필요해야 함)와 /ws(인증이 필요한 것으로 알려짐)의 동작을 비교하여 불일치를 확인합니다대상 시스템에서 실행되는 명령은 없습니다. WebSocket 핸드셰이크는 테스트되지만 연결을 통해 데이터는 전송되지 않습니다. 이 확인은 완전히 수동적이며 프로덕션 환경에 안전합니다.
사용법:```bash
pip install -r requirements.txt
python CVE-2026-39987_Marimo_RCE_detector.py -t http://marimo-host:2718
python CVE-2026-39987_Marimo_RCE_detector.py -t https://marimo-host:443
python CVE-2026-39987_Marimo_RCE_detector.py -f targets.txt -o results.json -v
python CVE-2026-39987_Marimo_RCE_detector.py -t http://10.0.0.5:2718 --timeout 15
**옵션:**
| 플래그 | 설명 | 기본값 |
|------|-------------|---------|
| `-t`, `--target` | 단일 대상 URL (예: `http://host:2718`) | — |
| `-f`, `--file` | 대상 URL이 한 줄에 하나씩 포함된 파일 (`#` 주석 지원) | — |
| `-o`, `--output` | 결과를 JSON 파일로 저장 | — |
| `--timeout` | 연결 제한 시간(초) | `10` |
| `--verify-ssl` | SSL 인증서 검증 활성화 | 비활성화 |
| `-v`, `--verbose` | 전체 세부 정보가 포함된 자세한 출력 | 꺼짐 |
**출력 예시:**```
[*] CVE-2026-39987 Marimo Pre-Auth RCE Scanner
[*] Scanning 1 target(s)...
[*] NOTE: This scanner only checks for endpoint exposure.
[*] No commands are executed on target systems.
======================================================================
Target: http://10.0.0.5:2718
Scan Time: 2026-04-14T16:00:00Z
Risk Level: CRITICAL
======================================================================
Is Marimo: YES
Marimo Version: 0.19.2
/terminal/ws Open: YES — UNAUTHENTICATED
Vulnerable: YES
*** CRITICAL: Pre-authenticated RCE is possible! ***
*** An attacker can get a full PTY shell without any auth ***
Details:
- Marimo instance detected via /api/status
- Marimo version: 0.19.2
- WebSocket upgrade accepted — /terminal/ws accessible WITHOUT auth
- CONFIRMED: /terminal/ws accepts unauthenticated connections while
/ws requires auth — classic CVE-2026-39987 signature
- Version 0.19.2 <= 0.20.4 — VULNERABLE to pre-auth RCE
======================================================================
[*] Scan Complete: 1 targets scanned
[*] Marimo Instances: 1 | Vulnerable: 1 | Critical: 1
======================================================================
sudo cp CVE-2026-39987_Marimo_RCE.nse /usr/share/nmap/scripts/ sudo nmap --script-updatedb
nmap -p 2718 --script CVE-2026-39987_Marimo_RCE
nmap -p 2718,8080,8443,443 --script CVE-2026-39987_Marimo_RCE
nmap -p 2718 --script CVE-2026-39987_Marimo_RCE 10.0.0.0/24
nmap -p 2718 --script CVE-2026-39987_Marimo_RCE -iL targets.txt
nmap -sV -p 2718 --script CVE-2026-39987_Marimo_RCE
**Nmap 출력 예시:**```
PORT STATE SERVICE
2718/tcp open http
| CVE-2026-39987_Marimo_RCE:
| VULNERABLE:
| Marimo Pre-Auth RCE (CVE-2026-39987)
| State: VULNERABLE
| Risk level: CRITICAL
| Marimo Version: 0.19.2
| /terminal/ws: accessible without authentication
| Description:
| The Marimo /terminal/ws WebSocket endpoint accepts connections
| without authentication, enabling pre-authenticated RCE.
| An attacker can obtain a full PTY shell without any credentials.
| References:
|_ https://nvd.nist.gov/vuln/detail/CVE-2026-39987
Marimo 인스턴스에 접근할 수 있는 경우:```bash
curl -s http://:2718/api/status | python3 -m json.tool
curl -s -o /dev/null -w "%{http_code}"
-H "Upgrade: websocket"
-H "Connection: Upgrade"
-H "Sec-WebSocket-Key: dGVzdC1rZXktMTIzNDU2Nzg="
-H "Sec-WebSocket-Version: 13"
http://:2718/terminal/ws
curl -s -o /dev/null -w "%{http_code}"
-H "Upgrade: websocket"
-H "Connection: Upgrade"
-H "Sec-WebSocket-Key: dGVzdC1rZXktMTIzNDU2Nzg="
-H "Sec-WebSocket-Version: 13"
http://:2718/ws
If `/terminal/ws`가 101을 반환하고 `/ws`가 401/403을 반환한다면, 이는 전형적인 CVE-2026-39987 시그니처입니다.
---
## 침해 지표
사용자 환경에서 다음 징후를 확인하세요:
| 지표 | 확인 위치 | 찾아야 할 것 |
|:---|:---|:---|
| 승인되지 않은 WebSocket 연결 | 서버/프록시 로그 | 예상치 못한 IP에서 `/terminal/ws`로의 연결 |
| PTY 세션 생성 | 프로세스 모니터링 | Marimo 서버에 의해 생성된 예상치 못한 셸 프로세스 |
| 파일 접근 | 파일 감사 로그 | `.env`, 자격 증명 파일 또는 SSH 키 읽기 |
| 자격 증명 사용 | 클라우드 제공업체 감사 로그 | Marimo 환경에 저장된 키를 사용한 API 호출 |
| 외부 데이터 전송 | 네트워크 모니터링 | Marimo 호스트에서 발생하는 비정상적인 이그레스 트래픽 |
**조사 명령:**```bash
# Check for active WebSocket connections
ss -tnp | grep <MARIMO_PORT>
# Review process tree for unexpected shells
ps aux --forest | grep -A5 marimo
# Check if .env or credential files were recently accessed
stat .env
stat ~/.aws/credentials
# Review cloud provider activity logs for unauthorized access
aws cloudtrail lookup-events --lookup-attributes AttributeKey=AccessKeyId,AttributeValue=<KEY_ID>
# Check for unauthorized outbound connections
netstat -tnp | grep ESTABLISHED | grep -v 127.0.0.1
즉시 조치 (지금 바로 수행):
pip install --upgrade marimo)단기 조치 (이번 주 내):
.env 파일 및 환경 변수 검토 — 노출되었을 수 있는 민감한 데이터 확인장기 조치:
Kerem Oruç — 사이버보안 엔지니어