Como forçar explicitamente e com segurança o uso de um comando interno no bash

19

Há uma pergunta semelhante que lida com o cenário de 'quebra automática', em que você deseja substituir, por exemplo, cdum comando que chama o builtin cd.

No entanto, à luz do shellshock et al e sabendo que o bash importa funções do ambiente, eu fiz alguns testes e não consigo encontrar uma maneira de chamar com segurança o builtin cddo meu script.

Considere isto

cd() { echo "muahaha"; }
export -f cd

Quaisquer scripts chamados neste ambiente usando cdserão interrompidos (considere os efeitos de algo como cd dir && rm -rf .).

Existem comandos para verificar o tipo de um comando (convenientemente chamado type) e comandos para executar a versão interna em vez de uma função ( builtine command). Mas eis que eles também podem ser substituídos usando funções

builtin() { "$@"; }
command() { "$@"; }
type() { echo "$1 is a shell builtin"; }

Produzirá o seguinte:

$ type cd
cd is a shell builtin
$ cd x
muahaha
$ builtin cd x
muahaha
$ command cd x
muahaha

Existe alguma maneira de forçar com segurança o bash a usar o comando embutido, ou pelo menos detectar que um comando não é um embutido, sem limpar o ambiente inteiro?

Sei que, se alguém controla seu ambiente, você provavelmente está ferrado de qualquer maneira, mas pelo menos para aliases, você tem a opção de não chamar o alias, inserindo um \antes dele.

falstro
fonte
você pode executar seu script usando o envcomando anterior, desta forma:env -i <SCRIPT.sh>
Arkadiusz Drabczyk 5/15/15
Somente se envnão for redefinido como uma função também. Isso é aterrorizante. Primeiro pensei que caracteres especiais ajudariam - chamando com o caminho completo que inclui /, usar .a fonte e assim por diante. Mas esses também podem ser usados ​​para nomes de funções! Você pode redefinir qualquer função que desejar, mas é difícil voltar a chamar o comando original.
orion
1
É verdade, mas se você deseja executar qualquer script em uma máquina comprometida, está ferrado de qualquer maneira. Como alternativa, escreva seu script #/bin/shse este não for o shell interativo padrão.
Arkadiusz Drabczyk

Respostas:

16

Olivier D está quase correto, mas você deve definir POSIXLY_CORRECT=1antes de executar unset. O POSIX possui uma noção de Built-ins especiais e o bash suporta isso . unseté um desses embutidos. Procurar SPECIAL_BUILTINem builtins/*.cna fonte do bash para uma lista, inclui set, unset, export, evale source.

$ unset() { echo muahaha-unset; }
$ unset unset
muahaha-unset
$ POSIXLY_CORRECT=1
$ unset unset

O ladino unsetfoi agora removido do ambiente, se você unset command, type, builtinentão você deve ser capaz de prosseguir, mas unset POSIXLY_CORRECTse você está confiando em comportamento não-POSIX ou recursos avançados festança.

No entanto, isso não resolve aliases, portanto, você deve usar \unsetpara garantir que ele funcione no shell interativo (ou sempre, caso expand_aliasesesteja em vigor).

Para os paranóicos, isso deve consertar tudo, acho:

POSIXLY_CORRECT=1
\unset -f help read unset
\unset POSIXLY_CORRECT
re='^([a-z:.\[]+):' # =~ is troublesome to escape
while \read cmd; do 
    [[ "$cmd" =~ $re ]] && \unset -f ${BASH_REMATCH[1]}; 
done < <( \help -s "*" )

( while, do, doneE [[são palavras reservadas e não precisam de precauções.) Note que estamos usando unset -fpara ter certeza de funções não definidas, embora as variáveis e funções compartilham o mesmo espaço de nomes é possível tanto para existir simultaneamente (graças a Etan Reisner), caso em que desarmar duas vezes também faria o truque. Você pode marcar uma função somente leitura, o bash não impede que você desative uma função somente leitura, incluindo o bash-4.2, o bash-4.3 impede você, mas ainda respeita os recursos especiais especiais quando POSIXLY_CORRECTdefinido.

Um readonly POSIXLY_CORRECTnão é um problema real, não é um booleano ou sinaliza que sua presença ativa o modo POSIX; portanto, se ele existir como um readonly, você poderá confiar nos recursos do POSIX, mesmo que o valor esteja vazio ou 0. Você simplesmente precisará funções problemáticas não configuradas de uma maneira diferente da anterior, talvez com alguns recortar e colar:

\help -s "*" | while IFS=": " read cmd junk; do echo \\unset -f $cmd; done

(e ignore quaisquer erros) ou participe de outros scripts .


Outras notas:

  • functioné uma palavra reservada, pode ser aliasada, mas não substituída por uma função. (Aliasing functioné levemente problemático porque \function não é aceitável como forma de contorná-lo)
  • [[, ]]são palavras reservadas, elas podem ter alias (que serão ignoradas), mas não substituídas por uma função (embora as funções possam ter esse nome)
  • (( não é um nome válido para uma função, nem um alias
mr.spuratic
fonte
Interessante, obrigado! Parece-me estranho que o bash tenha como padrão não proteger unsetet al de serem substituídos.
falstro
Isso precisa do -fargumento unset, não é? Caso contrário, se uma variável e uma função com o mesmo nome de um builtin forem definidas, unsetescolherá a variável para desabilitar primeiro. Além disso, isso falha se alguma das funções obscurecedoras estiver definida readonly, não é? (Embora seja detectável e possa ser cometido um erro fatal.) Isso também perde o [que parece estar embutido.
Etan Reisner
1
Eu não acho que \unset -f unsetfaz sentido, já que isso será feito no ciclo de leitura. Se unsetfor uma função, você \unsetnormalmente a resolveria em vez da incorporada, mas você pode usá-la \unsetcom segurança enquanto o modo POSIX estiver em vigor. Explicitamente chamando \unset -fon [, .e :pode ser uma boa idéia, porém, como seus exclui regex eles. Eu também adicionaria -fao \unsetloop e \unset POSIXLY_CORRECTdepois do loop, em vez de antes. \unalias -a(after \unset -f unalias) também permite renunciar com segurança à fuga em comandos subseqüentes.
Adrian Günter
1
@ AdrianGünter Try:, [[ ${POSIXLY_CORRECT?non-POSIX mode shell is untrusted}x ]]isso fará com que um script não interativo saia com o erro indicado se a variável estiver desabilitada ou continuará se estiver definido (vazio ou qualquer valor).
mr.spuratic
1
"você deve usar \unset" ... Note-se que a citação de qualquer caractere funcionaria para evitar a expansão do alias, não apenas o primeiro. un"se"tfuncionaria tão bem, com menos legibilidade. "A primeira palavra de cada comando simples, se não estiver entre aspas, é verificada para ver se há um apelido." - Bash Ref
Robin A. Meade
6

Sei que se alguém controla seu ambiente, você provavelmente está ferrado de qualquer maneira

Isso. Se você executar um script em um ambiente desconhecido, todos os tipos de coisas podem dar errado, começando com LD_PRELOADa execução do código do shell por um processo arbitrário antes mesmo de ele ler o script. Tentar se proteger contra um ambiente hostil de dentro do script é inútil.

O Sudo desinfeta o ambiente removendo qualquer coisa que se parece com uma definição de função do bash há mais de uma década. Desde o Shellshock , outros ambientes que executam o shell script em um ambiente não totalmente confiável seguiram o exemplo.

Você não pode executar com segurança um script em um ambiente que foi definido por uma entidade não confiável. Portanto, preocupar-se com as definições de função não é produtivo. Higienize seu ambiente e, ao fazer isso, variáveis ​​que o bash interpretaria como definições de função.

Gilles 'SO- parar de ser mau'
fonte
1
Eu discordo totalmente disso. Segurança não é uma proposição binária centrada em torno de "higienizado" ou "não higienizado". Trata-se de remover oportunidades de exploração. E, infelizmente, a complexidade dos sistemas modernos significa que existem muitas oportunidades de exploração que precisam ser bloqueadas adequadamente. Muitas oportunidades de exploração se concentram em ataques upstream aparentemente inócuos, como alterar a variável PATH, redefinir funções internas, etc. Ofereça a uma única pessoa ou programa a oportunidade de fazer isso, e todo o seu sistema está vulnerável.
DeJay Clayton
@DejayClayton Concordo plenamente com o seu comentário, exceto a primeira frase, porque não vejo como isso contradiz minha resposta de forma alguma. A remoção de uma oportunidade de exploração ajuda, mas não a remoção de apenas metade dela, onde um ajuste trivial no ataque permite que ele continue trabalhando.
Gilles 'SO- stop be evil'
Meu argumento é que você não pode simplesmente "higienizar seu ambiente" e parar de se preocupar com vários vetores de ataque. Às vezes vale a pena "proteger contra um ambiente hostil de dentro do script". Mesmo se você resolver o problema da "definição de função", ainda precisará se preocupar com coisas como ter um PATH em que alguém possa colocar um script personalizado chamado "cat" ou "ls" em um diretório e, em seguida, qualquer script que invoque "cat "ou" ls "em vez de" / bin / cat "e" / bin / ls "estão efetivamente executando uma exploração.
DeJay Clayton
@DejayClayton Obviamente, a higienização do ambiente não ajuda com vetores de ataque não relacionados ao ambiente. (Por exemplo, um script que chama /bin/catum chroot pode estar chamando qualquer coisa.) A higienização do ambiente dá a você uma sensatez PATH(supondo que você esteja fazendo o certo, é claro). Eu ainda não estou entendendo o seu ponto.
Gilles 'SO- stop be evil'
Considerando que PATH é uma das maiores oportunidades de exploração e que muitas práticas questionáveis ​​de PATH são documentadas como conselhos recomendados mesmo em blogs de segurança, e também considerando que os aplicativos instalados às vezes reordenam PATH para garantir que esses aplicativos funcionem, não consigo ver como codificação defensiva dentro de um script seria uma má idéia. O que você considera um PATH são, de fato, vulnerável a explorações que você não encontrou.
DeJay Clayton
1

Você pode usar unset -fpara remover as funções internas, comando e tipo.

odc
fonte
2
desculpe, unset() { true; }cuida disso.
falstro 5/03/2015
1
Droga! E listar funções definidas também depende de um builtin. Desisto!
odc