Estou escrevendo um daemon de servidor HTTP em C (há razões para isso), gerenciando-o com o arquivo de unidade systemd.
Estou reescrevendo um aplicativo desenvolvido há 20 anos, por volta de 1995. E o sistema que eles usam é que eles fazem o chroot e depois o setuid, e o procedimento padrão.
Agora, no meu trabalho anterior, a política usual era que você nunca executasse nenhum processo como root. Você cria um usuário / grupo para ele e executa a partir daí. Obviamente, o sistema executou algumas coisas como root, mas poderíamos obter todo o processamento da lógica de negócios sem ser root.
Agora, para o daemon HTTP, posso executá-lo sem raiz se não fizer chroot dentro do aplicativo. Portanto, não é mais seguro que o aplicativo nunca seja executado como root?
Não é mais seguro executá-lo como usuário mydaemon desde o início? Em vez de iniciá-lo com root, chrooting e setuid para mydaemon-user?
capabilities(7)
.Respostas:
Parece que outros não entenderam o seu argumento, o que não foi uma razão para usar raízes alteradas, o que obviamente você já sabe, nem o que mais pode fazer para impor limites aos daemons, quando você também sabe claramente sobre correr sob o égide de contas de usuário sem privilégios; mas por que fazer essas coisas dentro do aplicativo . Na verdade, há um exemplo bastante claro do porquê.
Considere o design do
httpd
programa daemon no pacote de arquivos públicos de Daniel J. Bernstein. A primeira coisa que ele faz é alterar a raiz para o diretório raiz que foi solicitado a usar com um argumento de comando e, em seguida, largar privilégios para o ID do usuário e o ID do grupo sem privilégios que são passados em duas variáveis de ambiente.Os conjuntos de ferramentas de gerenciamento da Daemon têm ferramentas dedicadas para coisas como alterar o diretório raiz e passar para IDs de usuários e grupos sem privilégios. A runit de Gerrit Pape tem
chpst
. Meu conjunto de ferramentas nosh temchroot
esetuidgid-fromenv
. O s6 de Laurent Bercot tems6-chroot
es6-setuidgid
. O Perp de Wayne Marshall temruntool
erunuid
. E assim por diante. De fato, todos eles têm o próprio conjunto de ferramentas daemontools do M. Bernsteinsetuidgid
como antecedente.Alguém poderia pensar que poderia extrair a funcionalidade
httpd
e usar essas ferramentas dedicadas. Então, como você imagina, nenhuma parte do programa do servidor é executada com privilégios de superusuário.O problema é que uma conseqüência direta precisa fazer muito mais trabalho para configurar a raiz alterada, e isso expõe novos problemas.
Com Bernstein
httpd
como está, os únicos arquivos e diretórios que estão na árvore de diretórios raiz são os que serão publicados no mundo. Não há mais nada na árvore. Além disso, não há razão para existir um arquivo de imagem de programa executável nessa árvore.Mas mover a mudança de diretório raiz em um programa da cadeia de carregamento (ou systemd), e de repente o arquivo de imagem programa
httpd
, qualquer biblioteca compartilhada que cargas e quaisquer arquivos especiais em/etc
,/run
e/dev
que o carregador de programa ou tempo de execução C acesso à biblioteca durante a inicialização do programa (que você pode achar bastante surpreendente se vocêtruss
/strace
um programa em C ou C ++), também deverá estar presente na raiz alterada. Caso contrário,httpd
não poderá ser acorrentado e não será carregado / executado.Lembre-se de que este é um servidor de conteúdo HTTP (S). Pode servir potencialmente qualquer arquivo (legível pelo mundo) na raiz alterada. Isso agora inclui coisas como suas bibliotecas compartilhadas, seu carregador de programas e cópias de vários arquivos de configuração do carregador / CRTL para o seu sistema operacional. E se, por algum motivo (acidental), o servidor de conteúdo tiver acesso a coisas de gravação , um servidor comprometido poderá obter acesso de gravação à imagem do programa por
httpd
si próprio ou até ao carregador de programas do seu sistema. (Lembre-se que você tem agora dois conjuntos paralelos de/usr
,/lib
,/etc
,/run
, e/dev
diretórios para manter seguro.)Nada disso é o caso em que
httpd
altera a raiz e descarta os privilégios.Portanto, você negociou com uma pequena quantidade de código privilegiado, que é bastante fácil de auditar e é executado logo no início do
httpd
programa, rodando com privilégios de superusuário; por ter uma superfície de ataque bastante expandida de arquivos e diretórios na raiz alterada.É por isso que não é tão simples quanto fazer tudo externamente ao programa de serviço.
Observe que, no entanto, isso é um mínimo de funcionalidade em
httpd
si. Todo o código que faz coisas como procurar no banco de dados da conta do sistema operacional o ID do usuário e o ID do grupo para colocar nessas variáveis de ambiente em primeiro lugar é externo aohttpd
programa, em comandos auditáveis independentes simples comoenvuidgid
. (E é claro que é uma ferramenta UCSPI, por isso não contém nenhum do código para escutar na porta TCP relevante (s) ou para aceitar conexões, sendo estes o domínio de comandos, comotcpserver
,tcp-socket-listen
,tcp-socket-accept
,s6-tcpserver4-socketbinder
,s6-tcpserver4d
, e assim por diante.)Leitura adicional
httpd
. publicfile . cr.yp.to.httpd
. Os softwares de Daniel J. Bernstein, tudo em um . Programas. Jonathan de Boyne Pollard. 2016.gopherd
. Os softwares de Daniel J. Bernstein, tudo em um . Programas. Jonathan de Boyne Pollard. 2017.fonte
Eu acho que muitos detalhes da sua pergunta poderiam ser aplicados igualmente
avahi-daemon
, os quais eu olhei recentemente. (Eu poderia ter perdido outro detalhe que difere). A execução do avahi-daemon em um chroot tem muitas vantagens, caso o avahi-daemon seja comprometido. Esses incluem:O ponto 3 pode ser particularmente interessante quando você não está usando o dbus ou similar ... Acho que o avahi-daemon usa o dbus, por isso garante o acesso ao dbus do sistema, mesmo de dentro do chroot. Se você não precisar enviar mensagens no dbus do sistema, negar essa capacidade pode ser um recurso de segurança bastante interessante.
Observe que, se o avahi-daemon fosse reescrito, ele poderia optar por confiar no systemd para segurança e usar, por exemplo
ProtectHome
. Propus uma alteração ao avahi-daemon para adicionar essas proteções como uma camada extra, juntamente com algumas proteções adicionais que não são garantidas pelo chroot. Você pode ver a lista completa de opções que propus aqui:https://github.com/lathiat/avahi/pull/181/commits/67a7b10049c58d6afeebdc64ffd2023c5a93d49a
Parece que existem mais restrições que eu poderia ter usado se o avahi-daemon não usasse o próprio chroot, algumas das quais são mencionadas na mensagem de confirmação. Não tenho certeza de quanto isso se aplica.
Observe que as proteções que eu usei não limitariam o daemon de abrir arquivos de soquete unix (ponto 3 acima).
Outra abordagem seria usar o SELinux. No entanto, você meio que amarraria seu aplicativo a esse subconjunto de distribuições Linux. A razão pela qual pensei positivamente no SELinux aqui, é que o SELinux restringe o acesso que os processos têm ao dbus, de maneira refinada. Por exemplo, acho que você poderia esperar que
systemd
isso não estivesse na lista de nomes de ônibus para os quais você precisava enviar mensagens para :-)."Gostaria de saber se o uso do sandbox systemd é mais seguro que chroot / setuid / umask / ..."
Resumo: por que não os dois? Vamos decodificar um pouco acima :-).
Se você pensa no ponto 3, usar chroot fornece mais confinamento. ProtectHome = e seus amigos nem tentam ser tão restritivos quanto chroot. (Por exemplo, nenhuma das opções nomeadas do systemd lista negra
/run
, onde tendemos a colocar arquivos de soquete unix).O chroot mostra que restringir o acesso ao sistema de arquivos pode ser muito poderoso, mas nem tudo no Linux é um arquivo :-). Existem opções systemd que podem restringir outras coisas, que não são arquivos. Isso é útil se o programa estiver comprometido, você pode reduzir os recursos do kernel disponíveis, o que pode tentar explorar uma vulnerabilidade. Por exemplo, o avahi-daemon não precisa de soquetes bluetooth e acho que seu servidor da web também não :-). Portanto, não lhe dê acesso à família de endereços AF_BLUETOOTH. Basta colocar a lista de permissões AF_INET, AF_INET6 e talvez AF_UNIX, usando a
RestrictAddressFamilies=
opçãoLeia os documentos para cada opção que você usa. Algumas opções são mais eficazes em combinação com outras e outras não estão disponíveis em todas as arquiteturas de CPU. (Não porque a CPU está ruim, mas porque a porta Linux para essa CPU não foi tão bem projetada. Eu acho).
(Existe um princípio geral aqui. É mais seguro se você pode escrever listas do que deseja permitir, e não do que deseja negar. Como definir um chroot, é exibida uma lista dos arquivos que você tem permissão para acessar, e isso é mais robusto. do que dizer que você deseja bloquear
/home
).Em princípio, você pode aplicar todas as mesmas restrições antes de setuid (). É tudo apenas código que você pode copiar do systemd. No entanto, as opções de unidade do systemd devem ser significativamente mais fáceis de escrever e, como estão em um formato padrão, devem ser mais fáceis de ler e revisar.
Por isso, recomendo apenas ler a seção de sandbox
man systemd.exec
na plataforma de destino. Mas se você quer o projeto mais seguro possível, eu não tenha medo de tentarchroot
(e depois cairroot
privilégios) em seu programa de bem . Há uma troca aqui. O usochroot
impõe algumas restrições ao seu design geral. Se você já possui um design que usa chroot e parece fazer o que você precisa, isso parece ótimo.fonte
Se você pode confiar no systemd, é realmente mais seguro (e mais simples!) Deixar o sandbox para o systemd. (Obviamente, o aplicativo também pode detectar se foi iniciado em área restrita pelo systemd ou não, e a própria área restrita se ainda for raiz.) O equivalente ao serviço que você descreve seria:
Mas não precisamos parar por aí. O systemd também pode fazer muitos outros sandboxs para você - aqui estão alguns exemplos:
Veja
man 5 systemd.exec
muito mais diretrizes e descrições mais detalhadas. Se você ativar o soquete daemon (man 5 systemd.socket
), poderá usar as opções relacionadas à rede: o único link do serviço para o mundo externo será o soquete de rede que recebeu do systemd, ele não poderá se conectar a mais nada. Se for um servidor simples que apenas escuta em algumas portas e não precisa se conectar a outros servidores, isso pode ser útil. (As opções relacionadas ao sistema de arquivos também podem tornarRootDirectory
obsoletas, na minha opinião, portanto, talvez você não precise se preocupar em configurar um novo diretório raiz com todos os binários e bibliotecas necessários.)As versões mais recentes do systemd (desde a v232) também são suportadas
DynamicUser=yes
, onde o systemd alocará automaticamente o usuário do serviço apenas para o tempo de execução do serviço. Isto significa que você não tem que registrar um usuário permanente para o serviço, e funciona bem, desde que o serviço não escreve para quaisquer outros locais do sistema de arquivos do que o seuStateDirectory
,LogsDirectory
eCacheDirectory
(que você também pode declarar no arquivo unidade - vejaman 5 systemd.exec
novamente - e qual systemd irá gerenciar, tendo o cuidado de atribuí-los corretamente ao usuário dinâmico).fonte