Skip to content

2 ROOTS 1 CAP | Linux Capabilities под капотом

Разбор Linux capabilities на примере двух root-процессов в контейнерах с одинаковыми UID, но разными правами доступа.

Ask about this video. Answers come from its transcript only — with the timestamp, so you can check them.

Generated from the transcript and can be wrong — check the timestamp.

Key Takeaways

  • UID root не гарантирует полный набор прав — важен набор capabilities.
  • Capabilities позволяют тонко настраивать права процессов, повышая безопасность.
  • CAP_DAC_OVERRIDE критична для обхода стандартных файловых прав.
  • Procfs и битовые маски помогают анализировать и сравнивать capabilities процессов.
  • В Docker и Linux важно контролировать capabilities для предотвращения излишних привилегий.

What the video covers

  • Два контейнера запущены из одного образа с root-процессами, но с разным результатом доступа к файлу.
  • В классической модели Unix root-процесс имел все права, но в Linux введены capabilities — отдельные полномочия.
  • Capabilities позволяют выдавать процессам только нужные привилегии без полного запуска от root.
  • Разница в правах доступа между двумя root-процессами объясняется разным набором effective capabilities.
  • Использование procfs позволяет посмотреть capabilities процесса в шестнадцатеричной битовой маске.
  • XOR-операция помогает выявить отличающиеся биты в наборах capabilities двух процессов.
  • Отличающийся бит оказался CAP_DAC_OVERRIDE, который позволяет обходить стандартные проверки доступа к файлам.
  • CAP_DAC_OVERRIDE позволяет читать файл с нулевыми правами, обходя discretionary access control (DAC).
  • В Docker один контейнер запущен со стандартным набором capabilities, у второго эта capability была удалена.
  • Видео объясняет важность управления capabilities для безопасности контейнеров и приложений.

Answers

Questions about this video

Почему два процесса с UID root имеют разные права доступа?

Потому что у них разный набор effective capabilities. UID root не гарантирует полный набор прав без соответствующих capabilities.

Что такое CAP_DAC_OVERRIDE и зачем она нужна?

CAP_DAC_OVERRIDE — это capability, позволяющая процессу обходить стандартные проверки доступа к файлам (DAC), например, читать файлы с нулевыми правами.

Как посмотреть capabilities процесса в Linux?

Capabilities процесса можно посмотреть в файловой системе procfs, в файле /proc/[pid]/status в строках, начинающихся с cap, где они представлены в виде шестнадцатеричных битовых масок.

Full Transcript — Download SRT & Markdown

00:00
Speaker A
Здорово, Гаяс. У нас тут один хост, два контейнера, запущенные из одного и того же имиджа. В обоих айдишник показывает одно и то же. User ID и группа ID равны нулю. То есть оба процесса запущены от root. File secret тоже один и тот же.
00:15
Speaker A
Владелец root, а права нуля, то есть отсутствуют вообще. Выполним в первом контейнере concatenate по этому файлу и спокойно его прочитаем. А во втором словим permission den. Образ одинаковый.
00:30
Speaker A
Пальзак одинаковый, права на файл одинаковые, операция тоже одна и та же, а результат почему-то разный. Выходит, что быть рутом недостаточно, чтобы быть всемогущим в Линуксе. Чтобы разобраться, где между этими двумя рутами вообще спряталась разница, откатимся более чем на полвека назад.
00:52
Speaker A
В раннем Юниксе модель привилегий была довольно простой. У каждого процесса есть учётные данные: юзер айдишник, группа айдишник. Когда процесс обращается к файлу, отправляет сигнал другому процессу или просит ядро ещё что-нибудь сделать, ядро сверяет эти данные и решает, можно это делать или
01:09
Speaker A
нет. Но процессы с юзер айдишником, равным нулю, стояли особняком. Они были привилегированными и обходили все проверки ядра, тогда как обычные процессы пользовательские проходили все проверки. Проблема в том, что иногда программе нужна всего одна привилегия, например, открыть порт ниже 1024,
01:28
Speaker A
зачитать файл или настроить сетевой интерфейс. Но отдельного разрешения на такую операцию в классической модели просто не было. Приходилось запускать весь процесс от рута. И выходит как бы не очень элегантно. Программе нужна одна привилегированная операция, а вместе с ней она получает почти все права в
01:47
Speaker A
системе. Ну а если в программе найдётся уязвимость, тот же набор получает и атакующий. Поэтому в девяносто девятом году, начиная с версии ядра Линукса 2.2, полномочия рута разделили на отдельные возможности, а по-английски capabilities.
02:03
Speaker A
Например, CAP_NET_BIND_SERVICE разрешает слушать привилегированные порты. CAP_CHOWN менять владельца файла. NET_ADMIN управляет сетевыми настройками, а SYS_PTRACE даёт полномочия для некоторых операций над другими процессами через, вы не поверите, PTRACE. Это, офк, не полный список, их там несколько десятков. И
02:24
Speaker A
превращать видео в аудиоверсию man capability 7 я всё-таки не планирую. Capability, а дальше иногда по тексту просто caps вообще не являются какой-то отдельной частью или фишкой докера. Это обычные полномочия Linux-процесса, которые лежат в его креденшилах и которые ядро проверяет при выполнении
02:44
Speaker A
привилегированных операций. Строго говоря, capability относится к треду, но дальше для простоты я всё-таки буду говорить у процесса, дабы не задушнить вас раньше времени. Сразу суперважная оговорка: capability сработает не только для рута. Обычному процессу с каким-нибудь юзер айдишником 1337 тоже
03:05
Speaker A
можно выдать отдельную капу, например, разрешить слушать тот же самый порт ниже 1024, не запуская всё приложение от рута. Мы взяли эти два рутовых процесса не потому, что капы бывают только у них.
03:18
Speaker A
Просто на таком примере лучше всего ломается привычная модель, что root может всё. И вот на этом моменте мы можем зафиксировать первую гипотезу.
03:28
Speaker A
Одинаковый юзер айдишник ещё не означает, что у процессов одинаковые полномочия. На этом моменте вернёмся к нашим баранам. Ещё раз выполним в контейнерах ID, увидим, что и там, и там root. То есть оба процесса привилегированы. Копаем дальше. И о
03:46
Speaker A
боги, в Линуксе всё есть файл, а значит, состояние процесса можно посмотреть напрямую без каких бы то ни было специальных докерных утилит. Перейдём к псевдофайловой системе procfs. В проке ядро отдаёт информацию о запущенных процессах, как вы все знаете. И если
04:02
Speaker A
открыть статус нужного процесса среди юзер айдишников, памяти и прочего добра, можно будет найти несколько строк, которые начинаются с cap. Для начала посмотрим на текущий шел в первом контейнере. Как вы помните, 2 доллара — это pid текущего шела. То есть мы
04:19
Speaker A
смотрим капы баша, из которого запускаются наши команды, получим примерно такой вывод. Выглядит как несколько случайных хэшей, но на самом деле это обычные битовые маски в шестнадцатиричном виде. И каждый бит соответствует отдельной capability.
04:36
Speaker A
Наборов здесь пять, но все вы можете изучить факультативно. Сегодня я вам расскажу про cap_eff, то есть effective. Это набор capabilities, который процесс может использовать прямо сейчас. Когда ядро проверяет, есть ли у текущего процесса нужные полномочия для конкретных операций, нас в первую очередь
04:55
Speaker A
интересуют именно эти наборы. Остальные отвечают за то, какие капсы процесс в принципе может иметь: сохранять или получить при execve. Если совсем коротко, да, permitted ограничивает то, что можно сделать effective, а bounding — то, что процесс в принципе сможет
05:10
Speaker A
получить дальше. inheritable и ambient и полные формулы перерасчёта. Повторюсь, изучите факультативно. Сегодня только про effective. В первом контейнере она имеет вот такой вид. Теперь давайте переключимся на второй и выполним ту же самую команду. А там у нас практически
05:26
Speaker A
то же самое, кроме того, что в конце FB превратилось в F9. Да, разница почти незаметна. Юзер айдишник у процессов одинаковый, а эффективный набор нет. И где-то в этих двух хекс-цифрах как раз и спрятана причина нашего permission den.
05:42
Speaker A
Осталось понять, какой именно бит отличается и что он вообще разрешает. Чтобы не сравнивать эти две портянки глазами, используем XOR. Побитовая исключающая или звучит страшнее, чем работает, гаяс. XOR сравнивает два числа, бит за битом. Если биты одинаковые, ставит ноль, если отличаются —
06:01
Speaker A
единицу. То есть все одинаковые части наших масок обнулятся, а в результате останется только отличающийся бит, ну или биты. Не пугайтесь также этой записи. Здесь 0x говорит башу, что перед нами шестнадцатиричные числа.
06:16
Speaker A
Знак крышечки выполняет XOR, а printf выводит результат снова в hex. Получаем двойку. Двойка — это не количество отличий, это ещё одна битовая маска. Просто она записана в шестнадцатиричном виде. Переводим двойку в двоичный вид. И выходит там куча нулей,
06:33
Speaker A
единичка, ноль. И вот теперь разницу нам видно. Во всей маске установлена только одна единица. Значит, между двумя исходными масками отличается только ровно один бит. Надеюсь, я вас окончательно не запутал, но просто поверьте на слово, если не поняли, один
06:53
Speaker A
бит отличается. Самому разбирать его, к счастью, не обязательно. Для этого есть Capabilities Shell, Caps Shell Decode 2, получаем название capability, которое всё это время стояло между нашими двумя рутами. И это CAP_DAC_OVERRIDE. Проверим через заголовочный файл ядра. Выполним
07:10
Speaker A
grep define CAP_DAC_OVERRIDE в usr/include/linux/capability.h. Увидим define, и здесь на первый взгляд можно запутаться, да?
07:19
Speaker A
XOR вернул двойку, а номер capability почему-то один. Но капсы нумеруются с нуля, и CAP_DAC_OVERRIDE имеет номер один, то есть занимает второй бит. А маска второго бита — это как раз два. Между нашими root в effective действительно отличается ровно один бит. У первого он
07:37
Speaker A
установлен, у второго сброшен. Теперь нам осталось понять, почему именно этот бит позволяет одному процессу прочитать файл с правами три нуля, а второму нет.
07:49
Speaker A
Кстати, DAC здесь расшифровывается как discretionary access control. Если сильно не усложнять, это те самые обычные файловые права владельца, группы и остальных, которых мы видим через ls -l. У нашего файла нет ни одного бита чтения, записи или исполнения, то есть
08:05
Speaker A
обычная DAC проверка запрещает прочитать его вообще кому угодно. А CAP_DAC_OVERRIDE даже из названия, да, понятно, что это override текущий DAC, позволяет процессу обходить обычные проверки доступа к файлам. В нашем конкретном случае открыть на чтение файл, у которого не
08:22
Speaker A
выставлен ни один бит на чтение. И вот теперь можно показать то, что я специально спрятал в начале. Первый контейнер был запущен со стандартным набором capabilities Docker, а у второго я убрал ровно одну капу. Как вы понимаете, это DAC override. Имидж остался тем же.
08:41
Speaker A
User айдишник тоже. Права никуда не делись, но в cap_eff, да, в cap_effective сбросился один бит. И когда SAT попытался открыть secret, у процесса больше нет полномочия, которое разрешало бы обойти обычную проверку файлов. И...
08:58
Speaker A
разница между двумя нашими рутами. У одного был cdite, у второго нет. То есть cд - это не просто параметр докера, понимаете? Хоть это и ключ докера, мы видим его результат непосредственно в криеншлах работающего Linux процесса.
09:12
Speaker A
Для управления этим набором у докера есть два основных влага. Это cap, который, как вы понимаете, добавляет капу. Мы можем, допустим, добавить cap adnet admin и cdrop, который, соответственно, убирает. Можно пойти ещё дальше и написать cdrop all. Возможно, вы встречались с использованием ключа в
09:31
Speaker A
таком виде. Тогда процесс всё ещё может стартовать с usедишником равным нулю, но исходный набор capabilities у него будет убран или ещё, я думаю, тоже хорошо знакомый всем, даже в большей степени.
09:43
Speaker A
Это минус-минус privilege. Но, кстати, считать его просто сокращением от C at all было бы не совсем правильно.
09:50
Speaker A
Privilge не только возвращает capabilities, он ещё расширяет доступ к устройствам хоста и ослабляет другие ограничения контейнера. Но нам сейчас интересно другое. Мы уже знаем, какой параметр запуска изменил поведение процесса. Но Docker - это всё ещё userspace программа. Он настроил процесс
10:07
Speaker A
при запуске. А когда КАТ выполнял системный вызов, докер ведь не сидит где-то между ним и файлом и не решает, разрешать чтение или нет. В конечном итоге решение принимает его величество ядро. Значит, где-то внутри ядра должен лежать тот самый бит, который мы только
10:23
Speaker A
что увидели через прог, и кто-то должен проверить его в момент открытия файла. Ну что ж, погружаемся ещё глубже.
10:30
Speaker A
Кста, если Linux вам интересно не только смотреть, но и трогать руками, я сейчас делаю тренажёр, который работает прямо в браузере. Открываете лабораторку, получаете полноценный терминал и пытаетесь решить таску без виртуалок, без дополнительного настройки окружения.
10:46
Speaker A
Скоро альфа-тест для Бусти сабов. Нужен будет фидбэк от моих самых преданных подписчиков, чтобы сделать крутой сервис. Сами задачи в сервисе после тестов будут абсолютно бесплатными, так что вы можете просто подождать. Но если вам интересно и вы хотите поучаствовать
11:01
Speaker A
во всей этой движухе, милости прошу, на бусте сможете даже повлиять на то, каким получится конечный результат.
11:10
Speaker A
Если вы были хорошими рыбопсами, то вы наверняка смотрели моё видео про пид, где я рассказывал про таскруктуры. Если нет, посмотрите и вернитесь. Тут рожёвывать повторно не буду. Вкратце, у каждого треда в ядре есть taskstстраct, но user айдишник, группа айдишник и
11:28
Speaker A
capabilitти лежат не прямо в таскруктуре. В ней есть ссылка на отдельную структуру кред, ну, криншлы.
11:35
Speaker A
Вот здесь рядом друг с другом и живут те самые usеerйдишники и capability set, который мы только что смотрели через прок. То есть современная картина выглядит уже не так, что user ID ноль, можно всё, а скорее так. криншлы процесса, где есть и user айдишник с
11:50
Speaker A
группа айдишником, есть как бы capabilities и есть usер space. Capability имеет смысл относительно конкретного usernрм спеaceйса, поэтому условный csis admin внутри отдельного юзернем сAйса не обязательно даёт те же полномочия, что и capsis admin в initial usernрном спейсе хоста, да, в первом
12:08
Speaker A
юзерном спейсе. И в эту кроличью нору мы тоже не полезем. Для нашего эксперимента достаточно понимать, что Docker при запуске контейнера настраивает cedential процесса, выставляет user ID, group ID, собирает capability set, да, набор привилегий, а потом запускает сам процесс. После этого докер уже не
12:26
Speaker A
участвует в каждом проверке доступа. Этим занято ядро. Вернёмся C Secret. CAT, да, ctinate сам по себе диски напрямую тоже, если что, не читает и даже не умеет. В какой-то момент он просит, очень вежливо просит ядро открыть secret, например, через
12:44
Speaker A
системный вызов Open Ad. Дальше запрос попадает в VFS, общий файловый слой Линкса. Если хотите отдельно видос про VFS, напишите в комментариях. Ядро находит нужный и начинает проверять, может ли текущий процесс открыть этот файл на чтение. Если сильно упростить
13:01
Speaker A
смысловой путь, получается примерно так: Cut Secret, Open AD, VFS, проверка доступа к аноди, потом обычные дакправа, о которых я говорил ранее, да, которые не разрешают в нашем случае чтения.
13:11
Speaker A
Потом ядро смотрит CDAC override, есть ли в эффектите в капсах. Если есть, то мы файл открываем, если нет, ловим permission den. То есть сначала ядро смотрит на обычные дак права и только потом переходит к битам, выставленным в мод. А у нас там все прочерты. То есть
13:27
Speaker A
обычная проверка чтения не разрешает. Дальше ядро смотрит на креды текущего процесса и проверяет, есть ли полномочия, которые позволяет эту проверку обойти. И вот здесь пути наших двух контейнеров окончательно расходятся. У первого нужный бит есть, поэтому открыть его можно, а у второго
13:45
Speaker A
его мы сбросили. Обходить дак проверку нечем. Ядро возвращает отказ и Permission denй. Но сразу ещё одна ремарка. Это не полный список, скажем так, проверок, которые нужно обойти файлу перед открытием, потому что capabilities - это не универсальный пропуск через все проверки. Кап оверай
14:02
Speaker A
позволяет обойти конкретный класс проверок, но это не значит, что процесс теперь автоматически обойдёт себе комп, up Armor или ваш любимый C Linux. Не забыли, кстати, его выключить на текущей системе. Все они работают отдельными слоями и могут запретить операцию
14:17
Speaker A
независимо от того, какие капы есть у процесса. И вот теперь, наконец-то, прослушав всю эту душнину, встаёт вопрос. Да, у меня встал вопрос: а зачем мне, крутому современному GOPS девоopпс, кубернетис рыбопсу, это вообще нужно? В чём прикладная польза?
14:38
Speaker A
Допустим, в вашем приложении нашли уязвимость на RCE, так называемую, и атакующий получил возможность выполнять внутри контейнера свои команды.
14:49
Speaker A
Никакого специального режима хакера после этого не включается. Код выполняется в контексте существующего процесса, а значит, получает его айдишник, намспейсы и капсы. И вот тут, как вы понимаете, очень важно, какие капсы мы этому процессу оставили. И хороший пример - это капс админ. Сис
15:10
Speaker A
админы, как говорится, плюс в чат. Название звучит довольно безобидно. Это же не кап def ops, да? В любом случае, как будто просто какая-то административная операция. Но исторически в неё сложили просто огромное количество совершенно разных системных действий, работу с маунтами,
15:28
Speaker A
части операций, с неймспейсами, много чего ещё. Она получилась настолько широкой, что в документации ядра её фактически предлагают воспринимать как что-то вроде нового рута. Поэтому взлом процесса с минимальным набором капов и процесса с капасиis ad админом, питрейсом и нетадмином - это две
15:46
Speaker A
совершенно разные стартовые позиции для атакующего. При этом Capsis adminн сам по себе не означает автоматический контейнер Escape. Это важно уточнить. И капа сама по себе не является уязвимостью. Но каждая лишняя capability открывает процессу дополнительные привилегированные операции, а значит,
16:04
Speaker A
увеличивает количество вариантов для дальнейшей атаки. До взлома этими правами пользуется приложение, а после взлома уже атакующий. Поэтому базовый подход обычно обратный. Мы сначала выкидываем всё, а потом по необходимости возвращаем только те привилегии, без которых это работать не будет. Следующая
16:23
Speaker A
прикладная тема ваша любимая, великий и могучий Шмубернетис. Никакой своей отдельной системы с капсами в кубе нет.
16:32
Speaker A
Тот же эксперимент можно собрать через обычный знакомый на более secюurity contтекст. Обратите внимание, в кубе название капсы указывается без префикса C, то есть CDC override превращается в DAC override. Процесс всё ещё может иметь userйдишник ноль, но нужного полномочия в его криншлах уже нет. С
16:51
Speaker A
нашим файлом трёхнулёвым результат будет тот же самый. Permission den. Kuber передаёт эту конфигурацию контейнер Runтайму. Runime настраивает обычный Linux процесс, а дальше решение снова принимает ядро. Более вменяемый базовый вариант обычно выглядит примерно так.
17:06
Speaker A
Здесь нас в первую очередь интересует блок с капсами. Сначала мы выкидываем весь исходный набор Cabilities, потом возвращаем только те полномочия, которые реально нужно приложению. Например, Netbind Survice, если ему зачем-то нужно слушать привергированный порт. Allow privilege escalation false просит runime
17:25
Speaker A
выставить процессу Linux Flag No News, чтобы через execвий, чем у его родителя. Run as nonroot true, как вы понимаете, требует, чтобы процесс внутри контейнера запускался не с usейдишником, равный нулю, да не с рутовым юзее айййдишником. Часто, кстати, чтобы поведение было
17:43
Speaker A
однозначным, добавляют run as s user и откуда начинаться будут usейдишники. Аom профайл включает стандартный security compilль. CCOM фильтрует системные вызовы, то есть даже если, как я до этого говорил, есть Caps необходимый CISC всё равно может быть запрещён нашим
18:04
Speaker A
профилем. И runtime default означает, что конкретный профиль предоставляет сам runtime, например, контейнер D или вернёмся к тому, с чего мы начали. Один хост, два контейнера, запущены из одного имиджа. В обоих мы под рутом один и тот же файл. Первый rootт смог его
18:23
Speaker A
прочитать, второй нет. В проке мы нашли два одинаковых эффектив набора, ну или почти одинаковых. Разница была всего лишь в один бит. Очень важный бит получается, потому что с помощью него процесс мог обойти обычную проверку файлов и прочитать файл с тремя нулями,
18:40
Speaker A
тогда как второй без этого бита получил Permission denй. И Docker, и Kuber здесь никакой собственной функционала не изобретают. Они просто настраивают обычные Linux криншлы процесса.
18:52
Speaker A
Окончательное решение открыть файл или вернуть отказ всё равно принимает ядро. Поэтому вопрос контейнер работает от рута или нет, сам по себе мало что говорит о его реальных возможностях.
19:04
Speaker A
Гораздо полезнее спросить, какие capпаilти у этого процесса есть на самом деле. Так что старое представление root может всё в современном Линуксе работает уже далеко не всегда. Иногда между двумя рутами может быть целая пропасть. Два рута, одна капа. Совершенно разные
19:22
Speaker A
последствия. Пишите, Гаяс, насколько был интересен и недушен видос. Часто ли вам приходится настраивать капсы?
19:28
Speaker A
Подписывайтесь на Бусте, чтобы получить доступ к альфа-тесту моего сервиса, а также записим стримам и приватку чатика, где мы с рыбопсами обитаем. Также приходите на стримы на Твиче. Недавно был просто невероятный кукинг стрим. Ну и на телегу тоже подписывайтесь. Канал с
19:45
Speaker A
меми и новостями. Ну а на сегодня всё. Всем б-б.
Topics:LinuxcapabilitiesCAP_DAC_OVERRIDErootконтейнерыDockerбезопасностьprocfsпривилегииUnix

Get More with the SozAI App

Transcribe recordings, audio files, and YouTube videos — with AI summaries, speaker detection, and unlimited transcriptions.

Or transcribe another YouTube video here →