Qual é a maneira mais segura de gravar programaticamente em um arquivo com privilégios de root?

35

Um aplicativo enorme precisa, em um momento específico, executar um pequeno número de gravações em um arquivo que requer permissões de root. Na verdade, não é um arquivo, mas uma interface de hardware que é exposta ao Linux como um arquivo.

Para evitar conceder privilégios de root a todo o aplicativo, escrevi um script bash que executa as tarefas críticas. Por exemplo, o seguinte script ativará a porta 17 da interface de hardware como saída:

echo "17" > /sys/class/gpio/export
echo "out" > /sys/class/gpio/gpio17/direction

No entanto, como suidestá desativado para scripts bash no meu sistema, pergunto-me qual é a melhor maneira de conseguir isso.

  1. Use algumas soluções alternativas apresentadas aqui

  2. Chame o script com a sudopartir do aplicativo principal e edite a lista de sudoers de acordo, para evitar exigir uma senha ao chamar o script. Estou um pouco desconfortável para dar privilégios ao sudo echo.

  3. Basta escrever um programa em C, com fprintfe defina-o como suid root. Codifique as strings e os nomes de arquivos e verifique se apenas o root pode editá-los. Ou leia as strings de um arquivo de texto, da mesma forma, garantindo que ninguém possa editar o arquivo.

  4. Alguma outra solução que não me ocorreu e é mais segura ou mais simples que as apresentadas acima?

vsz
fonte
10
Por que você não inicia seu programa com privilégios de root, abre o arquivo e descarta privilégios? É assim que todo servidor da Web faz isso para soquetes. Efetivamente, você não está executando como root, nem é necessário um ajudante.
21416 Damon

Respostas:

33

Você não precisa dar sudoacesso a echo. De fato, isso é inútil porque, por exemplo, com sudo echo foo > baro redirecionamento é feito como o usuário original, não como root.

Chame o script pequeno com sudo, permitindo NOPASSWD:acesso APENAS a esse script (e a qualquer outro script semelhante) pelo (s) usuário (s) que precisam acessar.

Essa é sempre a melhor / mais segura maneira de usar sudo. Isole o pequeno número de comandos que precisam de privilégios de root em seus próprios scripts separados e permita que o usuário não confiável ou parcialmente confiável execute apenas esse script como raiz.

O sudo(s) script (s) pequeno (s) de tabela não deve receber args (ou entrada) do usuário (ou seja, qualquer outro programa que ele chama deve ter opções e códigos codificados) ou deve ser muito cuidadoso para validar todos os argumentos / entradas necessários. aceite do usuário.

Seja paranóico na validação - em vez de procurar coisas "ruins" conhecidas a serem excluídas, permita apenas coisas "boas conhecidas" e aborte por qualquer incompatibilidade ou erro ou qualquer coisa remotamente suspeita.

A validação deve ocorrer o mais cedo possível no script (de preferência antes de fazer qualquer outra coisa como raiz).


Eu realmente deveria ter mencionado isso quando escrevi esta resposta, mas se o seu script for um shell, DEVE citar corretamente todas as variáveis. Seja especialmente cuidadoso ao citar variáveis ​​que contenham a entrada fornecida pelo usuário de qualquer forma, mas não assuma que algumas variáveis ​​são seguras, QUOTE ALL ALL .

Isso inclui variáveis de ambiente potencialmente controladas pelo usuário (por exemplo "$PATH", "$HOME", "$USER"etc. E, definitivamente, incluindo "$QUERY_STRING"e "HTTP_USER_AGENT"etc em um script CGI). De fato, apenas cite-os todos. Se você precisar construir uma linha de comando com vários argumentos, use uma matriz para criar a lista de argumentos e cite que - "${myarray[@]}".

Eu já disse "citá-los todos" com bastante frequência? lembre se. faça.

cas
fonte
18
Você esqueceu de mencionar que o próprio script deve ser possuído pelo root, com permissões 500.
Wildcard
13
Pelo menos remova as permissões de gravação, pelo amor de Deus. Esse foi realmente o meu ponto. O resto está apenas endurecendo.
Curinga
10
Um programa C cuidadosamente escrito terá uma superfície de ataque menor que um script de shell.
precisa saber é o seguinte
7
@immibis, possivelmente. Mas levará muito mais tempo para escrever e depurar do que um script de shell. E requer um compilador C (que é proibido em alguns servidores de produção para reduzir o risco de segurança, dificultando a compilação de explorações por invasores). Além disso, o IMO, um script de shell escrito por um administrador de sistema iniciante ou intermediário, ou um programador, tem menos probabilidade de ser explorável do que um programa C escrito por alguém com habilidade semelhante - especialmente se tiver que aceitar e validar dados fornecidos pelo usuário.
15556
3
@JonasWielicki - acho que agora estamos bem e verdadeiramente no campo da opinião, e não do fato. Você pode criar argumentos válidos para shell ou C (ou perl ou python ou awk etc), sendo mais ou menos propensos a erros exploráveis. Quando, na verdade, depende principalmente da habilidade do programador e da atenção aos detalhes (e cansaço, pressa, cautela, etc.). É um fato, porém, que linguagens de nível inferior tendem a exigir que mais código seja escrito para alcançar o que pode ser feito em muito menos linhas de código em linguagens de nível superior .... e cada LoC é outra oportunidade para um erro a ser cometido.
15556
16

Verifique o proprietário nos arquivos gpio:

ls -l /sys/class/gpio/

Provavelmente, você descobrirá que eles pertencem ao grupo gpio:

-rwxrwx--- 1 root     gpio     4096 Mar  8 10:50 export
...

Nesse caso, você pode simplesmente adicionar seu usuário ao gpiogrupo para conceder acesso sem o sudo:

sudo usermod -aG gpio myusername

Você precisará se desconectar e fazer login novamente para que a alteração entre em vigor.

jpa
fonte
Isso não funciona. De fato, o dono do grupo /sys/class/gpio/é o gpio, mas mesmo depois de me adicionar a esse grupo, ainda recebo a "permissão negada" sempre que tento escrever alguma coisa lá.
vsz
1
O problema é que os arquivos /sys/class/gpio/são na verdade apenas links simbólicos para /sys/devices/platform/soc/<some temporary name>/gpioonde o proprietário e o grupo são raiz.
vsz
4
@vsz Você tentou chgrp gpio /sys/devices/platform/soc/*/gpio? Talvez algo assim possa ser colocado em um arquivo de inicialização.
jpa
sim, mas não é assim tão simples. Como eles sempre são gerados de uma maneira diferente, eu tive que usar algo comochgrp gpio `readlink -f /sys/class/gpio/gpio18`/*
vsz
7

Uma solução para isso (usada principalmente na área de trabalho Linux, mas também aplicável em outros casos) é usar o D-Bus para ativar um pequeno serviço em execução como root e polkit para fazer a autorização. É basicamente para isso que o polkit foi projetado ; da documentação introdutória :

O polkit fornece uma API de autorização destinada a ser usada por programas privilegiados ("MECANISMOS"), oferecendo serviço a programas não privilegiados ("CLIENTES"). Consulte a página de manual do polkit para obter a arquitetura do sistema e o panorama geral.

Em vez de executar seu programa auxiliar, o programa grande e sem privilégios enviava uma solicitação no barramento. Seu ajudante pode estar em execução como um daemon iniciado na inicialização do sistema ou, melhor, ser ativado conforme necessário pelo systemd . Em seguida, esse auxiliar usaria o polkit para verificar se a solicitação é proveniente de um local autorizado. (Ou, nesse caso, se isso parecer exagero, você poderá usar outro mecanismo de autenticação / autorização codificado).

Encontrei um bom artigo básico sobre comunicação via D-Bus e, embora não o tenha testado, este parece ser um exemplo básico de adição de polkit à mistura .

Nesta abordagem, nada precisa ser marcado como setuid.

mattdm
fonte
5

Uma maneira de fazer isso é criar um programa raiz setuid escrito em C que faça apenas o necessário e nada mais. No seu caso, ele não precisa olhar para nenhuma entrada do usuário.

#include <unistd.h>
#include <string.h>
#include <stdio.h>  // for perror(3)
// #include ...  more stuff for open(2)

static void write_str_to_file(const char*fn, const char*str) {
    int fd = open(fn, O_WRONLY)
    if (-1 == fd) {
        perror("opening device file");  // make this a CPP macro instead of function so you can use string concat to get the filename into the error msg
        exit(1);
    }
    int err = write(fd, str, strlen(str));
    // ... error check
    err = close(fd);
    // ... error check
}

int main(int argc, char**argv) {
    write_string_to_file("/sys/class/gpio/export", "17");
    write_string_to_file("/sys/class/gpio/gpio17/direction", "out");
    return 0;
}

Não há como subverter isso através de variáveis ​​de ambiente ou qualquer coisa, porque tudo o que faz é fazer algumas chamadas de sistema.

Desvantagem: você deve verificar o valor de retorno de cada chamada do sistema.

De cabeça: a verificação de erros é realmente fácil: se houver algum erro, apenas perrore salve: saia com um status diferente de zero. Se houver um erro, investigue com strace. Você não precisa deste programa para fornecer mensagens de erro muito boas.

Peter Cordes
fonte
2
Eu posso ficar tentado a abrir os dois arquivos antes de escrever qualquer coisa para proteger contra a ausência do segundo. E eu posso executar este programa via, sudopara que ele próprio não precise ser configurado. Aliás, é <fcntl.h>para open().
Jonathan Leffler
3

Abençoar tee em vez de eco para sudo é uma maneira comum de abordar uma situação em que você precisa limitar as permissões de raiz. O redirecionamento para / dev / null é para interromper o vazamento de qualquer saída - o tee faz o que você deseja.

echo "17" | sudo tee /sys/class/gpio/export > /dev/null
echo "out" | sudo tee /sys/class/gpio/gpio17/direction > /dev/null
Miles Gillham
fonte
5
Se você permitir teea execução sudo, está permitindo que QUALQUER arquivo seja substituído ou anexado a (incluindo, por exemplo, /etc/passwd- basta acrescentar uma nova conta uid = 0). Você pode também permitir que todos os comandos para ser executado com sudo(BTW /etc/sudoerspertence ao conjunto de 'quaisquer arquivos' assim que pode ser substituído com sudo tee)
cas
2
Aparentemente, você pode limitar os arquivos nos quais teepode gravar, conforme descrito nesta resposta em uma pergunta separada. Certifique-se de ler os comentários também, pois algumas pessoas tiveram problemas com a sintaxe original usada e sugerem correções para esse problema.
Alex
1
@alex, sim, você pode fazer isso com qualquer comando no sudo - restrinja os argumentos permitidos. A configuração pode ficar muito longa e complicada se você quiser permitir teeou operar com muitos arquivos sudo.
15556
0

Você pode criar um script que execute sua tarefa desejada. Em seguida, crie uma nova conta de usuário que possa efetuar login apenas fornecendo uma chave usada pelo OpenSSH.

Nota: Qualquer pessoa poderá executar esse script se tiver o arquivo de chave, portanto, certifique-se de que o arquivo de chave OpenSSH não seja legível por alguém que você deseja impedir de executar a tarefa.

Na configuração do OpenSSH (no arquivo allowed_keys), antes de especificar a chave, especifique um comando (seguido por um espaço), conforme descrito neste texto "exemplo":

command="myscript" keydata optionalComment

Esta opção de configuração pode restringir essa chave OpenSSH para executar apenas um comando específico. Agora você tem o sudo concedendo as permissões, mas a configuração do OpenSSH é a parte da solução que realmente está sendo usada para restringir / limitar o que esse usuário é capaz de fazer, para que o usuário não esteja executando outros comandos. Isso também não tem um arquivo de configuração "sudo" tão complicado, portanto, se você usa o OpenBSD ou se os novos "doas" do OpenBSD ("faça como") começam a se tornar mais populares (com versões futuras de qualquer sistema operacional usado) , você não precisará ser desafiado por muita complexidade na configuração do sudo.

TOOGAM
fonte