
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 é o chroot e depois o setuid, e o procedimento padrão.
Agora, em meu trabalho anterior, a política usual era nunca executar nenhum processo como root. Você cria um usuário/grupo para ele e executa a partir daí. É claro que o sistema executou algumas coisas como root, mas poderíamos realizar todo o processamento da lógica de negócios sem ser root.
Agora, para o daemon HTTP, posso executá-lo sem root se não fizer chroot dentro do aplicativo. Então não é mais seguro que o aplicativo nunca seja executado como root?
Não é mais seguro executá-lo como mydaemon-user desde o início? Em vez de iniciá-lo com root, chrooting e então setuid para mydaemon-user?
Responder1
Parece que outros não entenderam o que você entendeu, o que não é motivo para usar raízes alteradas, o que você claramente já sabe, nem o que mais você pode fazer para colocar limites aos daemons, quando você também sabe claramente como correr sob os égides de contas de usuários sem privilégios; mas por que fazer essas coisasdentro do aplicativo. Na verdade, há um exemplo bastante preciso do porquê.
Considere o design do httpd
programa daemon no pacote publicfile de Daniel J. Bernstein. A primeira coisa que ele faz é alterar root para o diretório raiz que foi instruído a usar com um argumento de comando e, em seguida, descartar privilégios para o ID de usuário sem privilégios e o ID de grupo que são passados em duas variáveis de ambiente.
Os conjuntos de ferramentas de gerenciamento Dæmon têm ferramentas dedicadas para coisas como alterar o diretório raiz e passar para IDs de usuários e grupos sem privilégios. O runit de Gerrit Pape temchpst
. Meu conjunto de ferramentas nosh temchroot
esetuidgid-fromenv
. O s6 de Laurent Bercot tems6-chroot
es6-setuidgid
. O criminoso de Wayne Marshall temruntool
erunuid
. E assim por diante. Na verdade, todos eles têm o próprio conjunto de ferramentas daemontools de M. Bernstein comsetuidgid
como antecedente.
Alguém poderia pensar que seria possível extrair a funcionalidade httpd
e usar essas ferramentas dedicadas. Então, como você imagina,nãoparte do programa do servidor é executada com privilégios de superusuário.
O problema é que, como consequência direta, é necessário trabalhar significativamente mais para configurar a raiz alterada, e isso expõe novos problemas.
Com Bernstein httpd
tal como está, oapenasarquivos e diretórios que estão na árvore de diretórios raiz são aqueles que serão publicados para o mundo. Hánada maisna árvore. Além disso, não há razão paraqualquerarquivo de imagem do programa executável exista nessa árvore.
Mas mova o diretório raiz para um programa de carregamento em cadeia (ou systemd) e, de repente, o arquivo de imagem do programa para httpd
, quaisquer bibliotecas compartilhadas que ele carrega e quaisquer arquivos especiais em /etc
, /run
e /dev
que o carregador de programa ou a biblioteca de tempo de execução C acesse durante a inicialização do programa (o que você pode achar bastante surpreendente se você truss
for strace
um programa C ou C++),tambémdeve estar presente na raiz alterada. Caso contrário, httpd
não poderá ser encadeado e não será carregado/executado.
Lembre-se de que este é um servidor de conteúdo HTTP(S). Ele pode potencialmente servir qualquer arquivo (legível por todos) na raiz alterada. Isso agora inclui coisas como suas bibliotecas compartilhadas, seu carregador de programa e cópias de vários arquivos de configuração do carregador/CRTL para seu sistema operacional. E se por algum meio (acidental) o servidor de conteúdo tiver acesso aescreveroutras coisas, um servidor comprometido pode obter acesso de gravação à imagem do programa para httpd
si mesmo ou até mesmo ao carregador de programas do seu sistema. (Lembre-se de que agora você tem dois conjuntos paralelos de diretórios /usr
, /lib
, /etc
, /run
e /dev
para manter a segurança.)
Nada disso é o caso quando httpd
altera o root e elimina os próprios privilégios.
Então você negociou com uma pequena quantidade de código privilegiado, que é bastante fácil de auditar e que roda 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 dentro da raiz alterada.
É por isso que não é tão simples como fazer tudo externamente ao programa de serviço.
Observe que isso ainda é um mínimo de funcionalidade em httpd
si. Todo o código que faz coisas como procurar no banco de dados de contas do sistema operacional o ID do usuário e o ID do grupo para colocar nessas variáveis de ambiente em primeiro lugaréexterno ao httpd
programa, em comandos simples e auditáveis independentes, como envuidgid
. (E é claro que é uma ferramenta UCSPI, portanto não contém nenhum código para escutar nas portas TCP relevantes ou para aceitar conexões, sendo essas o domínio de comandos comotcpserver
,tcp-socket-listen
,tcp-socket-accept
,s6-tcpserver4-socketbinder
,s6-tcpserver4d
, e assim por diante.)
Leitura adicional
- Daniel J. Bernstein (1996).
httpd
.arquivo público. cr.yp.to. httpd
.Softwares de Daniel J. Bernstein em um só lugar. Programas. Jonathan de Boyne Pollard. 2016.gopherd
.Softwares de Daniel J. Bernstein em um só lugar. Programas. Jonathan de Boyne Pollard. 2017.- https://unix.stackexchange.com/a/353698/5132
- https://github.com/janmojzis/httpfile/blob/master/droproot.c
Responder2
Acho que muitos detalhes da sua pergunta poderiam ser aplicados igualmente ao avahi-daemon
, que analisei recentemente. (Posso ter perdido outro detalhe diferente). Executar o avahi-daemon em um chroot tem muitas vantagens, caso o avahi-daemon seja comprometido. Esses incluem:
- ele não pode ler o diretório inicial de nenhum usuário e exfiltrar informações privadas.
- ele não pode explorar bugs em outros programas escrevendo em /tmp. Existe pelo menos uma categoria inteira desses bugs. Por exemplohttps://www.google.co.uk/search?q=tmp+race+security+bug
- ele não pode abrir nenhum arquivo de soquete unix que esteja fora do chroot, no qual outros daemons possam estar ouvindo e lendo mensagens.
O ponto 3 pode ser particularmente interessante quando você estánãousando dbus ou similar... Acho que o avahi-daemon usa dbus, então ele garante manter o acesso ao dbus do sistema mesmo de dentro do chroot. Se você não precisa da capacidade de enviar mensagens no dbus do sistema, negar essa capacidade pode ser um recurso de segurança bastante interessante.
gerenciando-o com arquivo de unidade systemd
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 mudança no avahi-daemon para adicionar essas proteções como uma camada extra, junto 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 há mais restrições que eu poderia ter usado se o avahi-daemon o fizessenãouse o próprio chroot, alguns dos quais são mencionados na mensagem de commit. Não tenho certeza de quanto isso se aplica.
Observe que as proteções que usei não teriam limitado o daemon de abrir arquivos de soquete unix (ponto 3 acima).
Outra abordagem seria usar o SELinux. No entanto, você estaria vinculando 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 no dbus, de uma forma refinada. Por exemplo, acho que muitas vezes você poderia esperar que isso systemd
não estivesse na lista de nomes de barramentos para os quais você precisava enviar mensagens :-).
"Eu queria saber se usar o sandbox do systemd é mais seguro do que chroot/setuid/umask/..."
Resumo: por que não os dois? Vamos decodificar um pouco o que foi dito acima :-).
Se você pensar no ponto 3, usar chroot fornece mais confinamento. ProtectHome= e seus amigos nem tentam ser tão restritivos quanto o chroot. (Por exemplo, nenhuma das listas negras de opções do systemd nomeadas /run
, onde tendemos a colocar arquivos de soquete unix).
chroot mostra que restringir o acesso ao sistema de arquivos pode ser uma ferramenta muito poderosa, mas nãotudono Linux é um arquivo :-). Existem opções do 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 para ele, nos quais ele pode tentar explorar uma vulnerabilidade. Por exemplo, o avahi-daemon não precisa de soquetes bluetooth e acho que seu servidor web também não. :-). Portanto, não conceda acesso à família de endereços AF_BLUETOOTH. Apenas coloque AF_INET, AF_INET6 e talvez AF_UNIX na lista de permissões, usando a RestrictAddressFamilies=
opção.
Por favor, leia os documentos para cada opção que você usa. Algumas opções são mais eficazes em combinação com outras e algumas não estão disponíveis em todas as arquiteturas de CPU. (Não porque a CPU seja ruim, mas porque a porta Linux para essa CPU não foi tão bem projetada. Eu acho).
(Há um princípio geral aqui. É mais seguro se você puder escrever listas do que deseja permitir, não do que deseja negar. Assim como definir um chroot fornece uma lista de arquivos que você tem permissão para acessar, e isso é mais robusto do que dizer que deseja bloquear /home
).
Em princípio, você mesmo poderia 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 unidades 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.
Portanto, recomendo fortemente apenas a leitura da seção de sandbox man systemd.exec
em sua plataforma de destino. Mas se você quiser o design mais seguro possível, não teria medo de tentar chroot
(e depois abandonar root
os privilégios) no seu programatambém. Há uma compensação aqui. O uso chroot
impõe algumas restrições ao seu design geral. Se você já tem um design que usa chroot e parece fazer o que você precisa, isso parece ótimo.
Responder3
Se você pode confiar no systemd, então é realmente mais seguro (e mais simples!) Deixar o sandbox para o systemd. (É claro que o aplicativo também pode detectar se foi iniciado no sandbox pelo systemd ou não, e no próprio sandbox se ainda for root.) O equivalente ao serviço que você descreve seria:
[Service]
ExecStart=/usr/local/bin/mydaemon
User=mydaemon-user
RootDirectory=...
Mas não precisamos parar por aí. O systemd também pode fazer muitos outros sandboxing para você – aqui estão alguns exemplos:
[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
Veja man 5 systemd.exec
muito mais diretivas e descrições mais detalhadas. Se você tornar seu daemon ativável por soquete ( man 5 systemd.socket
), você pode até usar as opções relacionadas à rede: o único link do serviço para o mundo externo será o soquete de rede que ele recebeu do systemd, ele não será capaz de se conectar a mais nada. Se for um servidor simples que escuta apenas 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 tornar o RootDirectory
obsoleto, na minha opinião, então talvez você não precise mais se preocupar em configurar um novo diretório raiz com todos os binários e bibliotecas necessários.)
Versões mais recentes do systemd (desde v232) também suportam DynamicUser=yes
, onde o systemd alocará automaticamente o usuário do serviço para você apenas durante o tempo de execução do serviço. Isso significa que você não precisa registrar um usuário permanente para o serviço e funciona bem, desde que o serviço não grave em nenhum local do sistema de arquivos que não seja StateDirectory
, LogsDirectory
, e CacheDirectory
(que você também pode declarar no arquivo da unidade – veja man 5 systemd.exec
, novamente – e qual systemd irá então gerenciar, tomando cuidado para atribuí-los corretamente ao usuário dinâmico).