
Я пишу демон HTTP-сервера на языке C (на то есть свои причины), управляя им с помощью юнит-файла systemd.
Я переписываю приложение, разработанное 20 лет назад, примерно в 1995 году. И используемая ими система заключается в том, что они делают chroot, а затем setuid и используют стандартную процедуру.
В моей предыдущей работе обычной политикой было то, что вы никогда не запускаете какой-либо процесс как root. Вы создаете пользователя/группу для него и запускаете его оттуда. Конечно, система запускала некоторые вещи как root, но мы могли бы достичь всей обработки бизнес-логики, не будучи root.
Теперь для HTTP-демона, я могу запустить его без root, если я не использую chroot внутри приложения. Так разве не безопаснее для приложения никогда не запускать его как root?
Разве не безопаснее запустить его как mydaemon-user с самого начала? Вместо того, чтобы запускать его как root, chroot, а затем setuid на mydaemon-user?
решение1
Похоже, другие не поняли вашу мысль, а именно не о причинах использования измененных корней, которые вы, конечно, уже знаете, и не о том, что еще вы можете сделать, чтобы наложить ограничения на демонов, когда вы также четко знаете о работе под эгидой непривилегированных учетных записей пользователей; а о том, зачем это делатьвнутри приложения. На самом деле есть довольно показательный пример, почему.
Рассмотрим дизайн программы httpd
dæmon в пакете publicfile Дэниела Дж. Бернстайна. Первое, что она делает, это меняет root на корневой каталог, который ей было сказано использовать с аргументом команды, затем сбрасывает привилегии на непривилегированный идентификатор пользователя и идентификатор группы, которые передаются в двух переменных среды.
Наборы инструментов управления Dæmon имеют специальные инструменты для таких вещей, как изменение корневого каталога и переход к непривилегированным идентификаторам пользователей и групп. Runit Геррита Пейпа имеетchpst
. Мой набор инструментов для еды включает в себяchroot
иsetuidgid-fromenv
. S6 Лорана Берко имеетs6-chroot
иs6-setuidgid
. Преступник Уэйна Маршалла имеетruntool
иrunuid
. И так далее. Действительно, у всех есть собственный набор инструментов daemontools М. Бернстайна сsetuidgid
как антецедент.
Можно было бы подумать, что можно извлечь функциональность из httpd
таких специализированных инструментов и использовать их. Затем, как вы себе представляете,нетчасть программы сервера когда-либо работала с привилегиями суперпользователя.
Проблема в том, что в результате приходится выполнять значительно больше работы по настройке измененного корня, а это создает новые проблемы.
С Бернштейном httpd
в его нынешнем виде,толькоФайлы и каталоги, которые находятся в корневом дереве каталогов, должны быть опубликованы в мире. Существуетничего большев дереве вообще. Более того, нет никаких оснований длялюбойисполняемый файл образа программы должен существовать в этом дереве.
Но переместите изменение корневого каталога в программу с цепочкой загрузки (или systemd), и внезапно файл образа программы для httpd
, любые общие библиотеки, которые он загружает, и любые специальные файлы в /etc
, /run
, и /dev
к которым загрузчик программы или библиотека времени выполнения C обращаются во время инициализации программы (что может показаться вам довольно удивительным, если вы truss
/ strace
программа на C или C++),такжедолжны присутствовать в измененном корне. В противном случае httpd
не может быть присоединен и не будет загружен/запущен.
Помните, что это HTTP(S)-сервер контента. Он может потенциально обслуживать любой (доступный для чтения всем) файл в измененном корне. Теперь это включает такие вещи, как ваши общие библиотеки, ваш загрузчик программ и копии различных файлов конфигурации загрузчика/CRTL для вашей операционной системы. И если каким-то (случайным) образом сервер контента имеет доступ кписатьstuff, скомпрометированный сервер может получить доступ на запись к образу программы для httpd
себя или даже к загрузчику программ вашей системы. (Помните, что теперь у вас есть два параллельных набора каталогов /usr
, /lib
, /etc
, /run
и /dev
, которые нужно хранить в безопасности.)
Ничего из этого не происходит, когда httpd
происходит смена root-прав и падение привилегий.
Таким образом, вы обменяли наличие небольшого количества привилегированного кода, который довольно легко проверить и который запускается сразу при запуске программы httpd
с привилегиями суперпользователя, на значительно расширенную поверхность атаки на файлы и каталоги в измененном корневом каталоге.
Вот почему не так просто сделать все это вне сервисной программы.
Обратите внимание, что это, тем не менее, является минимальным набором функций httpd
. Весь код, который выполняет такие действия, как поиск в базе данных учетных записей операционной системы идентификатора пользователя и идентификатора группы, чтобы в первую очередь поместить их в эти переменные средыявляетсявнешним по отношению к httpd
программе, в простых автономных проверяемых командах, таких как envuidgid
. (И, конечно, это инструмент UCSPI, поэтому он не содержит кода для прослушивания соответствующего порта(ов) TCP или для приема соединений, которые являются областью таких команд, какtcpserver
,tcp-socket-listen
,tcp-socket-accept
,s6-tcpserver4-socketbinder
,s6-tcpserver4d
, и так далее.)
дальнейшее чтение
- Дэниел Дж. Бернстайн (1996).
httpd
.публичныйфайл. кри.ип.то. httpd
.Программное обеспечение Дэниела Дж. Бернстайна в одном. Программное обеспечение. Джонатан де Бойн Поллард. 2016.gopherd
.Программное обеспечение Дэниела Дж. Бернстайна в одном. Программное обеспечение. Джонатан де Бойн Поллард. 2017.- https://unix.stackexchange.com/a/353698/5132
- https://github.com/janmojzis/httpfile/blob/master/droproot.c
решение2
Я думаю, что многие детали вашего вопроса можно применить и к avahi-daemon
, который я недавно просматривал. (Я мог упустить еще одну деталь, которая отличается). Запуск avahi-daemon в chroot имеет много преимуществ, в случае, если avahi-daemon скомпрометирован. К ним относятся:
- он не может читать домашние каталоги пользователей и извлекать личную информацию.
- он не может эксплуатировать ошибки в других программах, записывая в /tmp. Существует по крайней мере одна целая категория таких ошибок. Напримерhttps://www.google.co.uk/search?q=tmp+race+security+bug
- он не может открыть ни один файл сокета unix, находящийся за пределами chroot, который другие демоны могут прослушивать и читать сообщения.
Пункт 3 может быть особенно хорош, когда вынетиспользуя dbus или что-то подобное... Я думаю, avahi-daemon использует dbus, поэтому он гарантирует сохранение доступа к системному dbus даже изнутри chroot. Если вам не нужна возможность отправлять сообщения на системном dbus, запрет этой возможности может быть довольно хорошей функцией безопасности.
управление им с помощью файла модуля systemd
Обратите внимание, что если бы avahi-daemon был переписан, он мог бы потенциально полагаться на systemd для обеспечения безопасности и использовать eg ProtectHome
. Я предложил изменение avahi-daemon, чтобы добавить эти защиты в качестве дополнительного слоя, вместе с некоторыми дополнительными защитами, которые не гарантируются chroot. Полный список предложенных мной опций можно увидеть здесь:
https://github.com/lathiat/avahi/pull/181/commits/67a7b10049c58d6afeebdc64ffd2023c5a93d49a
Похоже, есть еще ограничения, которые я мог бы использовать, если бы avahi-daemon это сделалнетиспользуйте сам chroot, некоторые из которых упомянуты в сообщении коммита. Я не уверен, насколько это применимо.
Обратите внимание, что использованные мной средства защиты не ограничили бы открытие демоном файлов сокетов Unix (пункт 3 выше).
Другой подход — использовать SELinux. Однако вы как бы привязываете свое приложение к этому подмножеству дистрибутивов Linux. Причина, по которой я здесь положительно отношусь к SELinux, заключается в том, что SELinux ограничивает доступ процессов к dbus, в мелкозернистом виде. Например, я думаю, вы часто могли бы ожидать, что этого systemd
не будет в списке имен шин, на которые вам нужно отправлять сообщения :-).
«Мне было интересно, является ли использование песочницы systemd более безопасным, чем chroot/setuid/umask/...»
Резюме: почему не оба варианта? Давайте немного расшифруем вышесказанное :-).
Если вы подумаете о пункте 3, использование chroot обеспечивает больше ограничений. ProtectHome= и его друзья даже не пытаются быть такими же ограничивающими, как chroot. (Например, ни один из названных параметров systemd не заносит в черный список /run
, куда мы обычно помещаем файлы сокетов unix).
chroot показывает, что ограничение доступа к файловой системе может быть очень мощным, но невсев Linux это файл :-). Существуют параметры systemd, которые могут ограничивать другие вещи, которые не являются файлами. Это полезно, если программа скомпрометирована, вы можете ограничить доступные ей функции ядра, в которых она может попытаться использовать уязвимость. Например, avahi-daemon не нужны сокеты Bluetooth, и я полагаю, что вашему веб-серверу они тоже не нужны :-). Так что не предоставляйте ему доступ к семейству адресов AF_BLUETOOTH. Просто добавьте в белый список AF_INET, AF_INET6 и, возможно, AF_UNIX, используя параметр RestrictAddressFamilies=
.
Пожалуйста, прочтите документацию для каждой используемой вами опции. Некоторые опции более эффективны в сочетании с другими, а некоторые доступны не на всех архитектурах ЦП. (Не потому, что ЦП плохой, а потому, что порт Linux для этого ЦП не был так хорошо спроектирован. Я думаю).
(Здесь есть общий принцип. Будет безопаснее, если вы сможете написать списки того, что вы хотите разрешить, а не того, что вы хотите запретить. Например, определение chroot дает вам список файлов, к которым вам разрешен доступ, и это надежнее, чем указать, что вы хотите заблокировать /home
).
В принципе, вы можете применить все те же ограничения самостоятельно перед setuid(). Это всего лишь код, который вы можете скопировать из systemd. Однако параметры юнита systemd должны быть значительно проще в написании, и поскольку они находятся в стандартном формате, их должно быть легче читать и просматривать.
Поэтому я настоятельно рекомендую просто прочитать раздел «песочница» man systemd.exec
на вашей целевой платформе. Но если вы хотите максимально безопасную конструкцию, я бы не боялся попробовать chroot
(и затем отказаться от root
привилегий) в вашей программетакже. Здесь есть компромисс. Использование chroot
накладывает некоторые ограничения на ваш общий дизайн. Если у вас уже есть дизайн, использующий chroot, и он, кажется, делает то, что вам нужно, это звучит довольно здорово.
решение3
Если вы можете положиться на systemd, то действительно безопаснее (и проще!) предоставить песочницу systemd. (Конечно, приложение также может определить, было ли оно запущено в песочнице systemd или нет, и поместить себя в песочницу, если оно все еще является пользователем root.) Эквивалентом описанной вами службы будет:
[Service]
ExecStart=/usr/local/bin/mydaemon
User=mydaemon-user
RootDirectory=...
Но нам не обязательно останавливаться на достигнутом. systemd также может выполнять множество других задач по изоляции — вот несколько примеров:
[Service]
# allocate separate /tmp and /var/tmp for the service
PrivateTmp=yes
# mount / (except for some subdirectories) read-only
ProtectSystem=strict
# empty /home, /root
ProtectHome=yes
# disable setuid and other privilege escalation mechanisms
NoNewPrivileges=yes
# separate network namespace with only loopback device
PrivateNetwork=yes
# only unix domain sockets (no inet, inet6, netlink, …)
RestrictAddressFamilies=AF_UNIX
Смотрите man 5 systemd.exec
для большего количества директив и более подробных описаний. Если вы сделаете своего демона активируемым через сокет ( man 5 systemd.socket
), вы даже сможете использовать сетевые опции: единственной связью службы с внешним миром будет сетевой сокет, который она получила от systemd, она не сможет подключиться ни к чему другому. Если это простой сервер, который прослушивает только некоторые порты и не нуждается в подключении к другим серверам, это может быть полезно. (Опции, связанные с файловой системой, также могут сделать устаревшим, RootDirectory
по моему мнению, так что, возможно, вам больше не нужно беспокоиться о настройке нового корневого каталога со всеми необходимыми двоичными файлами и библиотеками.)
Более новые версии systemd (начиная с v232) также поддерживают DynamicUser=yes
, где systemd автоматически выделяет вам пользователя службы только на время выполнения службы. Это означает, что вам не нужно регистрировать постоянного пользователя для службы, и работает нормально, пока служба не записывает данные в какие-либо расположения файловой системы, кроме своих StateDirectory
, LogsDirectory
, и CacheDirectory
(которые вы также можете объявить в файле модуля — см. man 5 systemd.exec
снова — и которыми systemd затем будет управлять, заботясь о том, чтобы правильно назначить их динамическому пользователю).