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 envp
parâmetro não seja muito visto, o main
programa 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 t
seguinte maneira:
$ echo $t
hi there
$ export t
$ bash
$ echo $t
hi there
$ exit
Considerando que acima t
foi indefinido no subshell, agora aparece após a exportação (use export -n t
se 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 t
estava acima. Isso não acontece quando fn
era 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 env
para mostrar todas as variáveis de shell marcadas para exportação fn
e a função regular e a função fn
aparecem:
$ 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 fn
como 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 fn
como 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 fn
disponível no shell atual. Mas se você gerar uma concha ** sub **, ela será.
Vamos cancelar a exportação da função fn
e deixar o novo regular fn
(como mostrado acima) intacto.
$ export -nf fn
Agora, a função fn
nã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 ex
foi passada para o subshell, que foi interpretado como uma função, ex
mas 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
. true
e :
ambos avaliam como (você adivinhou) verdade, no bash:
$ if true; then echo yes; fi
yes
$ if :; then echo yes; fi
yes
$
Segundo, o env
comando (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 -c
executa 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 f
aqui 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 vulnerable
aderência até o final. Isso é passado para o bash, e o bash interpreta x
como uma função (da qual não nos importamos) e, em seguida, talvez execute o echo vulnerable
shellshock se estiver presente.
Poderíamos encurtar um pouco a linha removendo a this is a test
mensagem:
$ env x='() { :;}; echo vulnerable' bash -c :
Isso não se preocupa, this is a test
mas 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"