Раздел 12. Практические задания
Задания выполняются только на собственной учебной машине. Часть из них демонстрирует векторы атаки; цель каждого — понять механизм и уметь его закрыть.
Проверка результата описана в каждом задании. Формулировка «показать, что…» означает: выполнить команду и привести её вывод, а не пересказать ожидаемое.
Как проверять себя
| Формулировка | Что требуется |
|---|---|
| «показать, что…» | Команда и её фактический вывод |
| «обосновать» | Сценарий риска, а не ссылка на правило |
| «исправить» | Работающая конфигурация плюс проверка, что уязвимость закрыта |
Задание 1. Docker socket равен root на host
Тип: обязательное
Материал: 12.2
Показать на учебной машине, что доступ к Docker socket позволяет прочитать файл, недоступный вашему пользователю.
Требования:
- Убедиться, что ваш пользователь не может прочитать
/etc/shadowнапрямую. - Прочитать первую строку
/etc/shadowчерез container, имеющий доступ к socket. - Объяснить, почему при этом не потребовался
sudo. - Показать, что членство в группе
dockerдаёт тот же результат, что иsudo. - Назвать три способа сократить риск и указать, что каждый не решает.
Проверка
# 1. Прямое чтение недоступно
cat /etc/shadow 2>&1 | head -1
# 2. Через socket — доступно
docker run --rm -v /:/host:ro alpine:3.21 head -1 /host/etc/shadow
# 3. Права вашего пользователя
id | tr ' ' '\n' | grep -o 'docker' || echo "не в группе docker"
Критерий
Обе команды выполнены, вывод второй содержит строку из /etc/shadow. Дан ответ, почему sudo не понадобился, и перечислены три меры с указанием их границ.
Ориентир для самопроверки
Ключевые пункты:
- монтирование
/в container даёт доступ ко всей файловой системе host; - daemon работает от root, и всё, что он делает по вашей команде, делается от root;
- членство в группе
docker— это право отдавать команды этому daemon, то есть эквивалент root без записи в журналsudo; - меры: rootless mode (меняет модель, но не решает для того, кто в группе), прокси к API с фильтрацией команд (сложно настроить полно), отказ от монтирования socket в container'ы (не влияет на доступ с host).
Ни одна мера не отменяет главного: если пользователь может отдавать команды daemon, он равен root.
Задание 2. Извлечение секрета из образа
Тип: обязательное
Материал: 12.6
Извлечь секрет, переданный через --build-arg, из готового образа.
Требования:
- Собрать образ, передав секрет через
--build-arg. - Извлечь его из готового образа, не имея команды сборки.
- Собрать второй образ с секретом через
ENVи извлечь его другим способом. - Собрать третий с
COPYсекрета и последующимRUN rm; извлечь третьим способом. - Составить таблицу: способ передачи → канал утечки → команда извлечения.
Проверка
docker history --no-trunc ОБРАЗ | grep -i token
docker inspect ОБРАЗ --format '{{range .Config.Env}}{{println .}}{{end}}'
docker save ОБРАЗ -o img.tar && tar -xf img.tar -C unpacked && grep -r СЕКРЕТ unpacked
Критерий
Все три секрета извлечены, для каждого приведена команда. Таблица содержит три строки и правильно сопоставляет способ передачи каналу.
Задание 3. Сборка без утечки секрета
Тип: обязательное
Материал: 12.6, 5.9
Переделать сборку из задания 2 на RUN --mount=type=secret и доказать отсутствие секрета.
Требования:
- Переписать
Dockerfileна secret mount. - Показать в журнале сборки, что секрет был использован (например, вывести его длину).
- Доказать отсутствие секрета во всех трёх каналах:
ENV,history, слои. - Показать, что копирование
/run/secrets/xв обычный путь возвращает утечку. - Сформулировать правило одним предложением.
Проверка
docker build --secret id=token,src=./token.txt -t safe:1 .
for check in \
"docker inspect safe:1 --format '{{range .Config.Env}}{{println .}}{{end}}'" \
"docker history --no-trunc safe:1"; do
eval "$check" | grep -c СЕКРЕТ
done
docker save safe:1 | tar -t > /dev/null && echo "слои проверить распаковкой"
Критерий
Ноль совпадений по всем трём каналам. Пункт 4 воспроизведён: показан образ, где секрет присутствует несмотря на secret mount. Правило сформулировано.
Ориентир для самопроверки
Правило: секрет используют внутри RUN, но не сохраняют — механизм защищает точку монтирования, а не значение.
Задание 4. Минимальный набор capabilities
Тип: обязательное
Материал: 12.4, 11.2
Запустить приложение с --cap-drop=ALL и определить минимально необходимый набор.
Требования:
- Взять приложение из раздела 6 или написать простое на FastAPI.
- Запустить с
--cap-drop=ALLи зафиксировать результат. - Если что-то не работает — определить, какая операция отказала.
- Добавлять capabilities по одной, проверяя после каждой.
- Показать, что часть потребностей снимается изменением операции, а не выдачей capability.
- Записать итоговый набор и обоснование каждой capability в нём.
Проверка
docker run --rm --cap-drop=ALL ОБРАЗ
docker run --rm --cap-drop=ALL python:3.13-slim sh -c 'grep CapEff /proc/self/status'
Критерий
Для типичного веб-приложения итоговый набор — пустой. Если получилось иначе, для каждой capability приведено обоснование сценарием, а не «иначе не работает».
Ориентир для самопроверки
Самая частая «нужная» capability — NET_BIND_SERVICE для порта 80. Она не нужна: слушайте 8000 и публикуйте -p 80:8000. Порты ниже 1024 привилегированы на стороне процесса, а не на стороне публикации.
Задание 5. Сравнение обычного и privileged container
Тип: обязательное
Материал: 12.4
Сравнить привилегии обычного и --privileged container по всем признакам.
Требования:
- Сравнить количество capabilities.
- Сравнить состояние seccomp.
- Сравнить число узлов в
/devи доступность диска host. - Сравнить доступ к
/sysна запись. - Добавить третью колонку —
--cap-add=ALL— и объяснить, что она показывает. - Назвать четыре независимых ослабления, которые вносит
--privileged.
Проверка
for cfg in "" "--cap-add=ALL" "--privileged"; do
docker run --rm $cfg python:3.13-slim sh -c '
grep CapEff /proc/self/status
grep Seccomp: /proc/self/status
ls /dev | wc -l
test -e /dev/sda && echo "sda: есть" || echo "sda: нет"
'
done
Критерий
Таблица из трёх колонок заполнена. Объяснено, зачем нужна колонка --cap-add=ALL: она отделяет вклад capabilities от остальных ослаблений --privileged.
Задание 6. Настройка userns-remap
Тип: дополнительное
Материал: 12.3
Настроить userns-remap и показать, что root в container отображается на непривилегированный UID host.
Задание меняет конфигурацию daemon и требует его перезапуска. Выполняйте только на учебной машине. Существующие образы и container'ы после включения станут недоступны — они хранятся в отдельном каталоге.
Требования:
- Зафиксировать состояние до: содержимое
/proc/self/uid_mapв container. - Включить
userns-remapв/etc/docker/daemon.json. - Перезапустить daemon, убедиться, что он запустился.
- Показать
uid_mapпосле и объяснить разницу. - Создать файл в смонтированном каталоге и показать его владельца на host до и после.
- Назвать три ограничения
userns-remap. - Вернуть исходную конфигурацию.
Проверка
docker run --rm alpine:3.21 cat /proc/self/uid_map
cat /etc/subuid | grep dockremap
ls -ln СМОНТИРОВАННЫЙ_КАТАЛОГ
Критерий
uid_map до включения содержит 0 0 4294967295; после — начинается с не-нулевого второго поля. Файл, созданный от root в container, принадлежит на host непривилегированному UID. Названы три ограничения.
Ориентир для самопроверки
Ограничения: несовместимость с --pid=host и --network=host; невозможность использовать --privileged в полном объёме; сложности с bind mount, где нужны конкретные UID; отдельное хранилище образов, требующее повторного скачивания.
Задание 7. Свой профиль seccomp
Тип: дополнительное
Материал: 12.5
Написать профиль, блокирующий конкретный системный вызов, и проверить его.
Требования:
- Выбрать вызов, разрешённый профилем по умолчанию, — например
chownилиsocket. - Написать профиль на основе профиля Docker с запретом только этого вызова.
- Показать, что операция работает без профиля и не работает с ним.
- Показать, что остальное приложение продолжает работать.
- Оценить, что произойдёт с профилем при обновлении базового образа.
- Обосновать, стоит ли применять этот профиль в вашем проекте.
Проверка
docker run --rm --security-opt seccomp=./profile.json ОБРАЗ КОМАНДА
docker run --rm ОБРАЗ КОМАНДА # для сравнения
Критерий
Профиль блокирует ровно выбранный вызов. Пункт 6 содержит вывод, а не описание — для большинства проектов обоснованный ответ отрицательный.
Задание 8. Приоритизация находок сканера
Тип: дополнительное
Материал: 12.7, 11.6
Проверить образ сканером и обосновать приоритет каждой находки.
Если сканер (Trivy, Grype,
docker scout) недоступен — отметьте это явно и выполните доступную часть по метаданным образа. Не имитируйте вывод сканера.
Требования:
- Просканировать образ вашего проекта.
- Для находок уровня critical и high определить: есть ли исправление, достижим ли код, применим ли обходной путь.
- Обновить базовый образ и просканировать повторно.
- Показать, сколько находок закрылось одним этим действием.
- Для оставшихся указать конкретное действие или обоснование бездействия.
Проверка
trivy image ОБРАЗ --severity CRITICAL,HIGH 2>/dev/null || echo "trivy недоступен"
docker scout cves ОБРАЗ 2>/dev/null || echo "docker scout недоступен"
Критерий
Каждая находка critical/high получила решение с обоснованием. Показано, что обновление базового образа закрывает большинство. Недоступность инструментов, если она есть, отмечена явно.
Задание 9. Диагностика: пять нарушений в compose.yaml
Тип: диагностическое
Материал: весь раздел
Дан файл с пятью нарушениями безопасности. Найти все, обосновать каждое сценарием риска, исправить.
name: vulnerable-stack
services:
api:
build: .
privileged: true
environment:
DATABASE_PASSWORD: суперсекрет123
API_TOKEN: ghp_EXAMPLEnotArealTokenAAAAAAAAAAAAAAAAAA
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /:/host
ports:
- "8000:8000"
db:
image: postgress/postgres:17
environment:
POSTGRES_PASSWORD: пароль
volumes:
- dbdata:/var/lib/postgresql/data
volumes:
dbdata:
Требования:
- Найти все нарушения — их больше пяти.
- Для каждого описать конкретный сценарий: что сможет сделать атакующий.
- Расставить приоритеты: что исправлять первым и почему.
- Написать исправленный файл.
- Показать, что исправленный стек работает.
Критерий
Найдено не менее пяти нарушений. Для каждого приведён сценарий, а не ссылка на правило. Приоритет обоснован.
Ориентир: что искать
Порядок соответствует убыванию тяжести:
| Нарушение | Почему первое |
|---|---|
| Монтирование Docker socket | Прямой root на host: атакующий запускает container с -v /:/host |
privileged: true | Все capabilities, все устройства, seccomp отключён |
Монтирование / в /host | Чтение и запись любого файла host |
image: postgress/postgres:17 | Опечатка в пространстве имён: сторонний образ вместо официального |
Секреты в environment | Видны в docker inspect, наследуются потомками |
| Токен, похожий на настоящий | Если он действующий — утёк в репозиторий |
| Слабый пароль БД | пароль подбирается мгновенно |
Нет cap_drop, read_only, no-new-privileges | Отсутствие базового hardening |
Нет user | Работа от root внутри container |
Первые три образуют цепочку: любой из них по отдельности даёт root на host. Их исправляют первыми — остальное без этого не имеет значения.
Задание 10. Аудит собственного проекта ★
Тип: итоговое
Материал: весь раздел
Провести аудит проекта из раздела 10 по security checklist.
Требования:
- Пройти чек-лист по всем пунктам, зафиксировав фактическое состояние.
- Для каждого невыполненного пункта: сценарий риска, стоимость исправления, решение.
- Исправить всё, что относится к первым трём звеньям цепочки поставок.
- Написать проверку, автоматизирующую не менее пяти пунктов чек-листа.
- Составить сводку по звеньям с указанием слабейшего.
- Записать решение по каждому неисправленному пункту с условием пересмотра.
Проверка
Проверка из пункта 4 должна:
- возвращать ненулевой код при нарушениях;
- не выводить значения найденных секретов;
- отличать «проверка не выполнена» от «проверка пройдена».
Критерий
Все пункты чек-листа имеют статус. Невыполненные — с обоснованным решением, а не пропущены. Проверка работает на вашем проекте и даёт разные коды возврата на нарушенной и исправленной конфигурации.
Ориентир для самопроверки
Признак хорошего аудита — наличие пунктов со статусом «не исправляем, потому что…». Аудит, где всё исправлено, обычно означает, что чек-лист прошли формально.
Признак хорошей автоматической проверки — она находит нарушение, которое вы внесли специально, и молчит на исправленной конфигурации. Проверьте оба случая.
Сводная таблица
| № | Задание | Тип | Основной материал |
|---|---|---|---|
| 1 | Docker socket равен root | обяз. | 12.2 |
| 2 | Извлечение секрета из образа | обяз. | 12.6 |
| 3 | Сборка без утечки секрета | обяз. | 12.6 |
| 4 | Минимальный набор capabilities | обяз. | 12.4 |
| 5 | Обычный против privileged | обяз. | 12.4 |
| 6 | Настройка userns-remap | доп. | 12.3 |
| 7 | Свой профиль seccomp | доп. | 12.5 |
| 8 | Приоритизация находок сканера | доп. | 12.7 |
| 9 | Пять нарушений в compose.yaml | диаг. | весь раздел |
| 10 | Аудит собственного проекта | ★ | весь раздел |
Что должно получиться
После заданий 1–5 у вас есть практическое подтверждение четырёх утверждений раздела:
- Доступ к Docker socket равен root на host — проверено на своей машине.
- Секрет, попавший в образ, извлекается тремя командами.
- Secret mount закрывает все три канала — при условии, что секрет не копируют.
- Типичному веб-приложению не нужна ни одна capability.
Задания 6–8 добавляют механизмы, применяемые по обоснованию, а не по умолчанию.
Задание 9 проверяет умение находить нарушения в чужой конфигурации; задание 10 — в своей.
Навигация
← Предыдущий материал
Вернуться к разделу
Следующий раздел: Observability и диагностика →
Главное оглавление