
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 키 및 기타 인증 자료