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:
- Nova festança é gerada
- Uma variável de ambiente é ingerida
- 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"