Container Runtimes, Engines, Builders, And Sandboxes

Tip

Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Вивчайте та практикуйте Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Підтримайте HackTricks

Одне з найбільших джерел плутанини в безпеці контейнерів — це те, що кілька цілком різних компонентів часто зводяться до одного слова. “Docker” може означати формат образу, CLI, демон, систему збірки, стек рантайму або просто ідею контейнерів загалом. Для роботи з безпекою така неоднозначність є проблемою, оскільки різні шари відповідають за різні механізми захисту. Вихід із контейнера через невірно змонтований bind mount — не те саме, що вихід через низькорівневу вразливість рантайму, і ні те, ні інше не дорівнює помилці політики кластера в Kubernetes.

Ця сторінка розділяє екосистему за роллю, щоб решта секції могла точно говорити про те, де насправді мешкає захист або слабкість.

OCI As The Common Language

Сучасні Linux-контейнерні стеки часто взаємодіють, тому що вони говорять набором специфікацій OCI. The OCI Image Specification описує, як представлені образи і шари. The OCI Runtime Specification описує, як рантайм має запускати процес, включаючи namespaces, mounts, cgroups і налаштування безпеки. The OCI Distribution Specification стандартизує, як реєстри надають вміст.

Це важливо, бо пояснює, чому образ, побудований одним інструментом, часто можна запустити іншим, і чому кілька двигунів можуть використовувати один і той самий низькорівневий runtime. Це також пояснює, чому поведінка безпеки може виглядати схожою в різних продуктах: багато з них конструюють ту ж OCI runtime-конфігурацію і передають її тому ж невеликому набору рантаймів.

Low-Level OCI Runtimes

Низькорівневий runtime — це компонент, що найближче до межі ядра. Це та частина, яка фактично створює namespaces, записує налаштування cgroup, застосовує capabilities і seccomp-фільтри, і нарешті execve()-ить процес контейнера. Коли люди обговорюють “ізоляцію контейнера” на механічному рівні, саме про цей шар вони зазвичай говорять, навіть якщо не кажуть це явно.

runc

runc — референсний OCI runtime і залишається найвідомішою реалізацією. Він широко використовується під Docker, containerd і в багатьох розгортаннях Kubernetes. Багато публічних досліджень і матеріалів з експлуатації спрямовані на середовища типу runc, просто тому що вони поширені і тому що runc визначає базу, яку багато людей уявляють, коли думають про Linux-контейнер. Розуміння runc дає читачу сильну ментальну модель класичної ізоляції контейнера.

crun

crun — ще один OCI runtime, написаний на C і широко використовуваний у сучасних Podman-середовищах. Його часто хвалять за хорошу підтримку cgroup v2, сильну ергономіку для rootless сценаріїв і менші накладні витрати. З точки зору безпеки важливо не те, що він написаний іншою мовою, а те, що він все одно виконує ту саму роль: перетворює OCI-конфігурацію на запущене дерево процесів під ядром. Rootless Podman workflow часто здається безпечнішим не тому, що crun магічно вирішує всі проблеми, а тому, що навколишній стек частіше використовує user namespaces і принцип найменших привілеїв.

runsc From gVisor

runsc — runtime, який використовує gVisor. Тут межа набуває суттєво іншого значення. Замість того, щоб передавати більшість syscall-ів безпосередньо до хост-ядра звичним способом, gVisor вставляє шар userspace-ядра, який емулює або опосередковує значну частину Linux-інтерфейсу. Результат — це не звичайний runc-контейнер з кількома додатковими прапорами; це інша конструкція санбоксингу, призначена зменшити attack surface хост-ядра. Суміщення сумісності та продуктивності — частина цього дизайну, тому середовища з runsc слід документувати інакше, ніж звичайні OCI-runtime середовища.

kata-runtime

Kata Containers просувають межу далі, запускаючи робоче навантаження всередині легковагової віртуальної машини. Адміністративно це може все ще виглядати як розгортання контейнера, і оркестраційні шари можуть ставитися до нього так само, але підлягаюча межа ізоляції ближча до віртуалізації, ніж до класичного контейнера з спільним хост-ядром. Це робить Kata корисними, коли потрібна сильніша ізоляція орендарів без відмови від контейнерно-орієнтованих робочих процесів.

Engines And Container Managers

Якщо низькорівневий runtime — це компонент, що говорить безпосередньо з ядром, то engine або manager — це компонент, з яким зазвичай взаємодіють користувачі та оператори. Він обробляє pulls образів, метадані, логи, мережі, volumes, операції життєвого циклу та API-виставлення. Цей шар має величезне значення, оскільки багато реальних компромісів відбуваються саме тут: доступ до runtime socket або daemon API може дорівнювати компромісу хоста, навіть якщо сам низькорівневий runtime повністю здоровий.

Docker Engine

Docker Engine — найпізнаваніша контейнерна платформа для розробників і одна з причин, чому словник контейнерів став таким Docker-орієнтованим. Типовий шлях — docker CLI до dockerd, який координує нижчі компоненти, такі як containerd і OCI runtime. Історично Docker-розгортання часто були з root-правами, і тому доступ до Docker socket був дуже потужним примітивом. Саме тому так багато практичного матеріалу з privilege-escalation зосереджено на docker.sock: якщо процес може попросити dockerd створити привілейований контейнер, змонтувати host-шляхи або приєднатися до host namespaces, то йому може не знадобитися kernel exploit взагалі.

Podman

Podman спроектований навколо більш daemonless моделі. Операційно це допомагає підкреслити ідею, що контейнери — це просто процеси, керовані стандартними механізмами Linux, а не через один довгоживучий привілейований демон. Podman також має значно сильніший rootless сценарій порівняно з класичними Docker-розгортаннями, з якими багато хто знайомий. Це не робить Podman автоматично безпечним, але значно змінює профіль ризику за замовчуванням, особливо у поєднанні з user namespaces, SELinux і crun.

containerd

containerd — основний компонент управління runtime у багатьох сучасних стеках. Він використовується під Docker і також є одним з домінантних бекендів runtime у Kubernetes. Він відкриває потужні APIs, керує образами та снапшотами та делегує остаточне створення процесу низькорівневому runtime. Обговорення безпеки навколо containerd повинні підкреслювати, що доступ до containerd socket або до функціоналу ctr/nerdctl може бути настільки ж небезпечним, як доступ до Docker API, навіть якщо інтерфейс і робочий процес відчуваються менш “орієнтованими на розробника”.

CRI-O

CRI-O більш сфокусований, ніж Docker Engine. Замість того, щоб бути платформою загального призначення для розробників, він побудований навколо чистої реалізації Kubernetes Container Runtime Interface. Це робить його особливо поширеним у дистрибутивах Kubernetes і екосистемах з сильним SELinux, таких як OpenShift. З точки зору безпеки, ця вужча спрямованість корисна, оскільки зменшує концептуальний хаос: CRI-O переважно належить до шару “запуск контейнерів для Kubernetes”, а не до універсальної платформи.

Incus, LXD, And LXC

Системи Incus/LXD/LXC варто відокремити від Docker-подібних application контейнерів, тому що їх часто використовують як system containers. System container зазвичай очікують бачити як легковагову машину з повнішим userspace, довготривалими сервісами, ширшим доступом до пристроїв і більш глибокою інтеграцією з хостом. Механізми ізоляції все ще базуються на kernel-примітивах, але операційні очікування відрізняються. Внаслідок цього невірні налаштування тут часто виглядають менше як “погані дефолти app-container” і більше як помилки в легковаговій віртуалізації або делегуванні хоста.

systemd-nspawn

systemd-nspawn займає цікаве місце, оскільки є systemd-нативним і дуже корисним для тестування, налагодження і запуску OS-подібних середовищ. Це не домінуючий cloud-native production runtime, але він зустрічається достатньо часто в лабораторіях і орієнтованих на дистрибутив середовищах, що робить його згадування виправданим. Для аналізу безпеки це ще одне нагадування, що поняття “контейнер” охоплює кілька екосистем і операційних стилів.

Apptainer / Singularity

Apptainer (раніше Singularity) поширений у науково-дослідних та HPC-середовищах. Його припущення про довіру, робочі процеси користувачів і модель виконання суттєво відрізняються від стеків, орієнтованих на Docker/Kubernetes. Зокрема, такі середовища часто дуже дбають про те, щоб дозволити користувачам запускати запаковані робочі навантаження без надання їм широких привілей для керування контейнерами. Якщо рев’ювер вважає, що кожне контейнерне середовище — це по суті “Docker на сервері”, він серйозно неправильно зрозуміє такі розгортання.

Build-Time Tooling

Багато обговорень безпеки говорять лише про runtime, але інструменти під час збірки також важливі, оскільки вони визначають вміст образів, експозицію секретів у часі збірки і скільки довіреного контексту вбудовується у фінальний артефакт.

BuildKit і docker buildx — сучасні бекенди збірки, які підтримують такі можливості, як кешування, секрет-монтування, SSH-форвардинг і мультиплатформні збірки. Це корисні функції, але з погляду безпеки вони також створюють місця, де секрети можуть leak into image layers або де надто широкий build context може виявити файли, які ніколи не мали бути включені. Buildah відіграє схожу роль в OCI-native екосистемах, особливо навколо Podman, тоді як Kaniko часто використовують у CI-середовищах, які не хочуть надавати привілейований Docker daemon пайплайну збірки.

Ключовий висновок: створення образу і виконання образу — це різні фази, але слабкий pipeline збірки може створити слабку runtime-позицію задовго до того, як контейнер буде запущено.

Orchestration Is Another Layer, Not The Runtime

Kubernetes не потрібно ототожнювати з самим runtime. Kubernetes — це оркестратор. Він планує Pods, зберігає бажаний стан і виражає політику безпеки через конфігурацію робочих навантажень. Потім kubelet говорить з реалізацією CRI, такою як containerd або CRI-O, які в свою чергу викликають низькорівневий runtime, наприклад runc, crun, runsc або kata-runtime.

Це розділення важливе, бо багато людей помилково приписують захист “Kubernetes”, коли він насправді забезпечується node runtime, або ж звинувачують “containerd defaults” за поведінку, що походить від Pod spec. На практиці остаточна позиція безпеки — це композиція: оркестратор просить щось, runtime-стек це транслює, а ядро в кінці кінців це примушує виконувати.

Why Runtime Identification Matters During Assessment

Якщо ви ідентифікуєте engine і runtime рано, багато подальших спостережень стають легше інтерпретованими. Rootless Podman контейнер підказує, що user namespaces ймовірно є частиною історії. Docker socket, змонтований у робочому навантаженні, свідчить, що API-driven privilege escalation є реалістичним шляхом. CRI-O/OpenShift вузол має миттєво навести на думку про SELinux labels і restricted workload policy. gVisor або Kata середовище має зробити вас обережнішими стосовно припущення, що класичний runc breakout PoC спрацює так само.

Ось чому одним із перших кроків при оцінці контейнера завжди має бути відповідь на два прості питання: який компонент управляє контейнером і який runtime фактично запустив процес. Коли ці відповіді зрозумілі, решта середовища зазвичай стає набагато простішою для розуміння.

Runtime Vulnerabilities

Не кожен вихід із контейнера виникає через помилку оператора. Іноді вразливим є сам runtime. Це важливо, бо робоче навантаження може виконуватися з виглядом уважної конфігурації і все одно бути підданим через низькорівневу ваду runtime.

Класичний приклад — CVE-2019-5736 у runc, коли зловмисний контейнер міг перезаписати бінарний runc на хості, а потім чекати наступного docker exec або подібного виклику runtime, щоб запустити код, керований атакуючим. Шлях експлуатації дуже відрізняється від простого bind-mount чи помилки з capabilities, бо він зловживає тим, як runtime знову входить у простір процесів контейнера під час обробки exec.

A minimal reproduction workflow from a red-team perspective is:

go build main.go
./main

Тоді, з хоста:

docker exec -it <container-name> /bin/sh

Головний урок — не точна історична реалізація експлойту, а висновок для оцінки: якщо версія runtime вразлива, звичайне виконання коду всередині контейнера може бути достатнім для компрометації хоста навіть тоді, коли видима конфігурація контейнера не виглядає явно слабкою.

Недавні runtime CVE, такі як CVE-2024-21626 у runc, BuildKit mount races та containerd parsing bugs підсилюють ту саму думку. Версія runtime та рівень патчу є частиною межі безпеки, а не просто питанням обслуговування.

Tip

Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Вивчайте та практикуйте Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Підтримайте HackTricks