O que significa env x = '() {:;}; comando 'bash do e por que é inseguro?

237

Aparentemente, existe uma vulnerabilidade (CVE-2014-6271) no bash: ataque de injeção de código por variáveis ​​de ambiente Bash especialmente criadas

Estou tentando descobrir o que está acontecendo, mas não tenho muita certeza de entendê-lo. Como pode echoser executado como está entre aspas simples?

$ env x='() { :;}; echo vulnerable' bash -c "echo this is a test"
vulnerable
this is a test

EDIT 1 : Um sistema corrigido fica assim:

$ env x='() { :;}; echo vulnerable' bash -c "echo this is a test"
bash: warning: x: ignoring function definition attempt
bash: error importing function definition for `x'
this is a test

EDIT 2 : Existe uma vulnerabilidade / correção relacionada: CVE-2014-7169 que usa um teste ligeiramente diferente:

$ env 'x=() { :;}; echo vulnerable' 'BASH_FUNC_x()=() { :;}; echo vulnerable' bash -c "echo test"

saída sem remendo :

vulnerable
bash: BASH_FUNC_x(): line 0: syntax error near unexpected token `)'
bash: BASH_FUNC_x(): line 0: `BASH_FUNC_x() () { :;}; echo vulnerable'
bash: error importing function definition for `BASH_FUNC_x'
test

saída parcialmente corrigida (versão inicial) :

bash: warning: x: ignoring function definition attempt
bash: error importing function definition for `x'
bash: error importing function definition for `BASH_FUNC_x()'
test

saída corrigida até e incluindo CVE-2014-7169:

bash: warning: x: ignoring function definition attempt
bash: error importing function definition for `BASH_FUNC_x'
test

EDIT 3 : a história continua com:

jippie
fonte
Não é o eco que é executado. é a definição da função de x. Se a função definida em x faz algum trabalho secreto, não há como o bash verificar o valor de retorno se a função x for real. Observe que a função está vazia no código de teste. Um valor de retorno não verificado pode levar à injeção de script. A injeção de script leva ao encaminhamento de privilégios e encaminhamento de privilégios ao acesso root. O remendo desactiva a criação de x como uma função
eyoung100
26
eyoung100, não, o eco está sendo executado. Você pode ver que está sendo executado porque a palavra vulnerableaparece na saída. O principal problema é que o bash também está analisando e executando o código após a definição da função. Veja a /bin/idparte de seclists.org/oss-sec/2014/q3/650 para outro exemplo.
Mikel
4
Apenas um comentário lateral rápido. A Red Hat informou que o patch lançado é apenas um patch parcial e deixa os sistemas ainda em risco.
Peter Peter
2
@ eyoung100 a diferença é que o código dentro da função é executado apenas quando a variável de ambiente é chamada explicitamente. O código após a definição da função é executado toda vez que um novo processo Bash é iniciado.
David Farrell
1
Veja stackoverflow.com/questions/26022248/… para obter detalhes adicionais
Barmar 1/14

Respostas:

204

O bash armazena definições de funções exportadas como variáveis ​​de ambiente. As funções exportadas são assim:

$ foo() { bar; }
$ export -f foo
$ env | grep -A1 foo
foo=() {  bar
}

Ou seja, a variável de ambiente foopossui o conteúdo literal:

() {  bar
}

Quando uma nova instância do bash é iniciada, ele procura essas variáveis ​​de ambiente especialmente criadas e as interpreta como definições de função. Você pode até escrever um você mesmo e ver que ele ainda funciona:

$ export foo='() { echo "Inside function"; }'
$ bash -c 'foo'
Inside function

Infelizmente, a análise das definições de função de strings (as variáveis ​​de ambiente) pode ter efeitos mais amplos do que o pretendido. Nas versões sem patch, ele também interpreta comandos arbitrários que ocorrem após o término da definição da função. Isso ocorre devido a restrições insuficientes na determinação de cadeias semelhantes a funções aceitáveis ​​no ambiente. Por exemplo:

$ export foo='() { echo "Inside function" ; }; echo "Executed echo"'
$ bash -c 'foo'
Executed echo
Inside function

Observe que o eco fora da definição da função foi executado inesperadamente durante a inicialização do bash. A definição da função é apenas um passo para que a avaliação e a exploração ocorram, a própria definição da função e a variável de ambiente usada são arbitrárias. O shell olha para as variáveis ​​de ambiente, vê foo, que parece atender às restrições que conhece sobre a aparência de uma definição de função, e avalia a linha, sem querer também executando o eco (que poderia ser qualquer comando, malicioso ou não).

Isso é considerado inseguro, porque normalmente não é permitido ou esperado que as variáveis ​​causem diretamente a invocação do código arbitrário nelas contido. Talvez seu programa defina variáveis ​​de ambiente a partir de entradas não confiáveis ​​do usuário. Seria altamente inesperado que essas variáveis ​​de ambiente pudessem ser manipuladas de forma que o usuário pudesse executar comandos arbitrários sem a sua intenção explícita de fazê-lo usando essa variável de ambiente por um motivo declarado no código.

Aqui está um exemplo de ataque viável. Você executa um servidor Web que executa um shell vulnerável, em algum lugar, como parte de sua vida útil. Esse servidor da Web passa variáveis ​​de ambiente para um script bash, por exemplo, se você estiver usando CGI, informações sobre a solicitação HTTP geralmente são incluídas como variáveis ​​de ambiente do servidor da Web. Por exemplo, HTTP_USER_AGENTpode estar definido para o conteúdo do seu agente de usuário. Isso significa que se você falsificar seu agente de usuário para algo como '() {:; }; echo foo ', quando esse script shell for executado, echo fooserá executado. Novamente, echo foopoderia ser qualquer coisa, maliciosa ou não.

Chris Down
fonte
3
Isso poderia afetar qualquer outro shell semelhante ao Bash, como o Zsh?
Amelio Vazquez-Reina
3
@ user815423426 Não, o zsh não possui esse recurso. Ksh tem isso, mas implementado de maneira diferente, acho que as funções só podem ser transmitidas em circunstâncias muito estreitas, apenas se o shell for bifurcar, não através do ambiente.
Gilles
20
@ user815423426 rc é o outro shell que transmite funções no ambiente, mas é variável com nomes prefixados com "fn_" e só são interpretados quando invocados.
Stéphane Chazelas
18
@ StéphaneChazelas - obrigado por relatar o bug.
Deer Hunter
13
@gnclmorais Você quer dizer que você corre export bar='() { echo "bar" ; }'; zsh -c bare é exibido em barvez de zsh:1: command not found: bar? Tem certeza de que não está confundindo o shell que está invocando com o shell que está usando para configurar o teste?
Gilles
85

Isso pode ajudar a demonstrar ainda mais o que está acontecendo:

$ export dummy='() { echo "hi"; }; echo "pwned"'
$ bash
pwned
$

Se você estiver executando um shell vulnerável, quando iniciar um novo subshell (aqui, simplesmente usando a instrução bash), verá que o código arbitrário ( echo "pwned") é imediatamente executado como parte de sua iniciação. Aparentemente, o shell vê que a variável de ambiente (fictícia) contém uma definição de função e avalia a definição para definir essa função em seu ambiente (observe que não está executando a função: isso imprimiria 'oi').

Infelizmente, ele não apenas avalia a definição da função, mas também o texto inteiro do valor da variável de ambiente, incluindo as declarações possivelmente maliciosas que seguem a definição da função. Observe que, sem a definição inicial da função, a variável de ambiente não seria avaliada, seria apenas adicionada ao ambiente como uma sequência de texto. Como Chris Down apontou, este é um mecanismo específico para implementar a importação de funções shell exportadas.

Podemos ver a função que foi definida no novo shell (e que foi marcada como exportada para lá) e podemos executá-la. Além disso, o manequim não foi importado como uma variável de texto:

$ declare -f
dummy ()
{
    echo "hi"
}
declare -fx dummy
$ dummy
hi
$echo $dummy
$

Nem a criação dessa função, nem qualquer coisa que ela faria se fosse executada, não faz parte da exploração - é apenas o veículo pelo qual a exploração é executada. O ponto é que, se um invasor puder fornecer código malicioso, precedido por uma definição de função mínima e sem importância, em uma sequência de texto inserida em uma variável de ambiente exportada, ela será executada quando um subshell for iniciado, o que é um evento comum. em muitos scripts. Além disso, será executado com os privilégios do script.

Sdenham
fonte
17
Embora a resposta aceita realmente diga isso se você a ler com cuidado, achei essa resposta ainda mais clara e mais útil no entendimento de que é o problema da avaliação da definição (em vez de executar a própria função).
Natevw 25/09/14
1
por que esse exemplo tem exportcomando enquanto os outros tinham env? Eu estava pensando que estava envsendo usado para definir as variáveis ​​ambientais que seriam chamadas quando outro shell bash for lançado. então como é este trabalho comexport
Haris
Até o momento, não houve uma resposta aceita. Provavelmente vou esperar mais alguns dias antes de aceitar um. A desvantagem desta resposta é que ela não divide o comando original, nem discute como passar do comando original na pergunta para os comandos nesta resposta, mostrando que são idênticos. Além disso, é uma boa explicação.
jippie
@ralph - ambos enve exportexportar definições de ambiente para que estejam disponíveis em um subshell. O problema está na maneira como essas definições exportadas são importadas para o ambiente de um subshell e, especificamente, no mecanismo que importa definições de funções.
precisa saber é o seguinte
1
@ralph - envexecuta um comando com algumas opções e variáveis ​​de ambiente definidas. Observe que, nos exemplos de perguntas originais, envdefine xcomo uma sequência e chama bash -ccom um comando para executar. Se você o fizer env x='foo' vim, o Vim será iniciado e, aí, você poderá chamar seu shell / ambiente contendo !echo $xe ele será impresso foo, mas se você sair e echo $xele não será definido, pois só existia enquanto o vim estava em execução. através do envcomando Em exportvez disso, o comando define valores persistentes no ambiente atual para que um subshell executado posteriormente os utilize.
Gary Fixler
72

Eu escrevi isso como uma reformulação em estilo tutorial da excelente resposta de Chris Down acima.


No bash, você pode ter variáveis ​​de shell como esta

$ t="hi there"
$ echo $t
hi there
$

Por padrão, essas variáveis ​​não são herdadas pelos processos filhos.

$ bash
$ echo $t

$ exit

Mas se você os marcar para exportação, o bash definirá um sinalizador que significa que eles entrarão no ambiente de subprocessos (embora o envpparâmetro não seja muito visto, o mainprograma C tem três parâmetros: main(int argc, char *argv[], char *envp[])onde a última matriz de ponteiros é uma matriz de variáveis ​​shell com suas definições).

Então, vamos exportar da tseguinte maneira:

$ echo $t
hi there
$ export t
$ bash
$ echo $t
hi there
$ exit

Considerando que acima tfoi indefinido no subshell, agora aparece após a exportação (use export -n tse você deseja parar de exportar).

Mas as funções no bash são um animal diferente. Você os declara assim:

$ fn() { echo "test"; }

E agora você pode simplesmente chamar a função chamando-a como se fosse outro comando shell:

$ fn
test
$

Mais uma vez, se você gerar um subshell, nossa função não será exportada:

$ bash
$ fn
fn: command not found
$ exit

Podemos exportar uma função com export -f:

$ export -f fn
$ bash
$ fn
test
$ exit

Aqui está a parte complicada: uma função exportada como fné convertida em uma variável de ambiente, assim como nossa exportação da variável shell testava acima. Isso não acontece quando fnera uma variável local, mas após a exportação podemos vê-la como uma variável do shell. No entanto, você também pode ter uma variável de shell regular (ou seja, sem função) com o mesmo nome. bash distingue com base no conteúdo da variável:

$ echo $fn

$ # See, nothing was there
$ export fn=regular
$ echo $fn
regular
$ 

Agora podemos usar envpara mostrar todas as variáveis ​​de shell marcadas para exportação fne a função regular e a função fnaparecem:

$ env
.
.
.
fn=regular
fn=() {  echo "test"
}
$

Um sub-shell ingerirá as duas definições: uma como variável regular e outra como função:

$ bash
$ echo $fn
regular
$ fn
test
$ exit

Você pode definir fncomo fizemos acima, ou diretamente como uma atribuição de variável regular:

$ fn='() { echo "direct" ; }'

Observe que isso é algo altamente incomum a se fazer! Normalmente, definiríamos a função fncomo fizemos acima com a fn() {...}sintaxe. Mas como o bash o exporta para o ambiente, podemos "atalhos" diretamente para a definição regular acima. Observe que (ao contrário da sua intuição, talvez) isso não resulta em uma nova função fndisponível no shell atual. Mas se você gerar uma concha ** sub **, ela será.

Vamos cancelar a exportação da função fne deixar o novo regular fn(como mostrado acima) intacto.

$ export -nf fn

Agora, a função fnnão é mais exportada, mas a variável regular fné, e ela contém () { echo "direct" ; }.

Agora, quando um subshell vê uma variável regular que começa com ()ele, interpreta o restante como uma definição de função. Mas isso é apenas quando um novo shell começa. Como vimos acima, apenas definir uma variável de shell comum começando com ()não faz com que ela se comporte como uma função. Você precisa iniciar um subshell.

E agora o bug "shellshock":

Como acabamos de ver, quando um novo shell ingere a definição de uma variável regular começando com ()ele, interpreta-o como uma função. No entanto, se houver mais dados após a chave de fechamento que define a função, ele executará o que estiver lá também.

Estes são os requisitos, mais uma vez:

  1. Nova festança é gerada
  2. Uma variável de ambiente é ingerida
  3. Essa variável de ambiente começa com "()" e, em seguida, contém um corpo de função entre chaves e, em seguida, possui comandos posteriormente

Nesse caso, um bash vulnerável executará os últimos comandos.

Exemplo:

$ export ex='() { echo "function ex" ; }; echo "this is bad"; '
$ bash
this is bad
$ ex
function ex
$

A variável exportada regular exfoi passada para o subshell, que foi interpretado como uma função, exmas os comandos à direita foram executados ( this is bad) à medida que o subshell era gerado.


Explicando o teste de uma linha liso

Uma linha popular para testar a vulnerabilidade do Shellshock é a citada na pergunta da @ jippie:

env x='() { :;}; echo vulnerable' bash -c "echo this is a test"

Aqui está um detalhamento: primeiro, o :in bash é apenas uma abreviação de true. truee :ambos avaliam como (você adivinhou) verdade, no bash:

$ if true; then echo yes; fi
yes
$ if :; then echo yes; fi
yes
$

Segundo, o envcomando (também incorporado ao bash) imprime as variáveis ​​de ambiente (como vimos acima), mas também pode ser usado para executar um único comando com uma variável (ou variáveis) exportada dada a esse comando e bash -cexecuta um único comando a partir de seu linha de comando:

$ bash -c 'echo hi'
hi
$ bash -c 'echo $t'

$ env t=exported bash -c 'echo $t'
exported
$

Então, costurando todas essas coisas juntas, podemos executar o bash como um comando, dar a ele alguma coisa fictícia para fazer (como bash -c echo this is a test) e exportar uma variável que começa com, ()para que o subshell a interprete como uma função. Se o shellshock estiver presente, ele também executará imediatamente qualquer comando à direita no subshell. Como a função que passamos é irrelevante para nós (mas devemos analisar!), Usamos a menor função válida imaginável:

$ f() { :;}
$ f
$ 

A função faqui apenas executa o :comando, que retorna true e sai. Agora acrescente a isso algum comando "maligno" e exporte uma variável regular para um subshell e você ganha. Aqui está o one-liner novamente:

$ env x='() { :;}; echo vulnerable' bash -c "echo this is a test"

Portanto, xé exportado como uma variável regular com uma função válida simples, com echo vulnerableaderência até o final. Isso é passado para o bash, e o bash interpreta xcomo uma função (da qual não nos importamos) e, em seguida, talvez execute o echo vulnerableshellshock se estiver presente.

Poderíamos encurtar um pouco a linha removendo a this is a testmensagem:

$ env x='() { :;}; echo vulnerable' bash -c :

Isso não se preocupa, this is a testmas executa o :comando silencioso mais uma vez. (Se você deixar de fora -c :, sente-se no subshell e precisará sair manualmente.) Talvez a versão mais amigável seja a seguinte:

$ env x='() { :;}; echo vulnerable' bash -c "echo If you see the word vulnerable above, you are vulnerable to shellshock"
Fixee
fonte
12
Boa explicação. Essa pergunta está recebendo muitas opiniões (provavelmente nem todos são tão proficientes em bash quanto os outros) e acredito que ninguém ainda tenha passado algumas palavras sobre o que { :;};realmente diz. Essa seria uma boa adição à sua resposta na minha opinião. Pode explicar como você obtém do seu exemplo o comando original na pergunta?
jippie
20

Se você pode alimentar variáveis ​​de ambiente arbitrárias em um programa, pode fazer com que ele faça praticamente tudo, carregando as bibliotecas de sua escolha. Na maioria dos casos, isso não é considerado uma vulnerabilidade no programa que recebe essas variáveis ​​de ambiente, mas no mecanismo pelo qual um estranho pode alimentar variáveis ​​de ambiente arbitrárias.

No entanto, o CVE-2014-6271 é diferente.

Não há nada errado em ter dados não confiáveis ​​em uma variável de ambiente. Basta garantir que ele não seja colocado em nenhuma daquelas variáveis ​​de ambiente que podem modificar o comportamento do programa. Em outras palavras, para uma chamada específica, você pode criar uma lista de permissões de nomes de variáveis ​​de ambiente, que podem ser especificadas diretamente por alguém de fora.

Um exemplo que foi apresentado no contexto do CVE-2014-6271 são os scripts usados ​​para analisar arquivos de log. Esses podem ter uma necessidade muito legítima de transmitir dados não confiáveis ​​em variáveis ​​de ambiente. Obviamente, o nome para essa variável de ambiente é escolhido de forma que não tenha efeitos adversos.

Mas aqui está o que há de ruim nessa vulnerabilidade específica do bash. Pode ser explorado através de qualquer nome de variável. Se você criar uma variável de ambiente chamada GET_REQUEST_TO_BE_PROCESSED_BY_MY_SCRIPT, não esperaria que nenhum outro programa, além do seu próprio script, interpretasse o conteúdo dessa variável de ambiente. Mas, ao explorar esse bug do bash, todas as variáveis ​​de ambiente se tornam um vetor de ataque.

Observe que isso não significa que os nomes das variáveis ​​de ambiente sejam secretos. O conhecimento dos nomes das variáveis ​​de ambiente envolvidas não facilita o ataque.

Se program1chamadas program2que, por sua vez program3, chamam , program1podem passar dados para program3através de variáveis ​​de ambiente. Cada programa possui uma lista específica de variáveis ​​de ambiente que define e uma lista específica em que atua. Se você escolher um nome não reconhecido program2, poderá passar dados de program1para program3sem se preocupar com isso program2.

Um invasor que conhece os nomes exatos das variáveis ​​exportadas program1e os nomes das variáveis ​​interpretadas por program2não pode explorar esse conhecimento para modificar o comportamento do 'programa2' se não houver sobreposição entre o conjunto de nomes.

Mas isso quebrou se program2fosse um bashscript, porque devido a esse erro bashinterpretaria todas as variáveis ​​de ambiente como código.

Kasperd
fonte
1
"toda variável de ambiente se torna um vetor de ataque" - essa é a parte que estava faltando. Obrigado.
wrschneider
9

É explicado no artigo que você vinculou ...

você pode criar variáveis ​​de ambiente com valores especialmente criados antes de chamar o shell bash. Essas variáveis ​​podem conter código, que é executado assim que o shell é chamado.

O que significa que o bash chamado com -c "echo this is a test"executa o código entre aspas simples quando é chamado.

O Bash possui funções, embora em uma implementação um pouco limitada, e é possível colocar essas funções bash em variáveis ​​de ambiente. Essa falha é acionada quando um código extra é adicionado ao final dessas definições de função (dentro da variável enivronment).

Significa que o exemplo de código que você postou explora o fato de que o bash invocado não para de avaliar essa sequência após executar a atribuição. Uma atribuição de função neste caso.

A coisa realmente especial sobre o trecho de código que você postou, como eu o entendo, é que, ao colocar uma definição de função antes do código que queremos executar, alguns mecanismos de segurança podem ser contornados.

Bananguin
fonte