¿Qué significa env x = '() {:;}; comando 'bash do y por qué es inseguro?


237

Aparentemente hay una vulnerabilidad (CVE-2014-6271) en bash: Bash ataque de inyección de código de variables de entorno especialmente diseñado

Estoy tratando de descubrir qué está sucediendo, pero no estoy completamente seguro de entenderlo. ¿Cómo se echopuede ejecutar como está entre comillas simples?

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

EDITAR 1 : Un sistema parcheado se ve así:

$ 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 : hay una vulnerabilidad / parche relacionado: CVE-2014-7169 que utiliza una prueba ligeramente diferente:

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

salida sin parchear :

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

salida parcheada parcialmente (versión anterior) :

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

salida parcheada hasta CVE-2014-7169 inclusive:

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

EDITAR 3 : la historia continúa con:


No es el eco lo que se ejecuta. es la definición de la función de x. Si la función definida en x hace algún trabajo disimulado, no hay forma de que bash pueda verificar el valor de retorno si la función x fuera real. Observe que la función está vacía en el código de prueba. Un valor de retorno no verificado puede conducir a la inyección de script. La inyección de scripts conduce a la escalada de privilegios y la escalada de privilegios conduce al acceso raíz. El parche deshabilita la creación de x como una función
eyoung100

26
eyoung100, no, el eco se está ejecutando. Puede ver que se está ejecutando porque la palabra vulnerableaparece en la salida. El principal problema es que bash también analiza y ejecuta el código después de la definición de la función. Vea la /bin/idparte de seclists.org/oss-sec/2014/q3/650 para otro ejemplo.
Mikel

44
Solo un comentario lateral rápido. Red Hat ha informado que el parche que se ha lanzado es solo un parche parcial y deja los sistemas aún en riesgo.
Peter

2
@ eyoung100 la diferencia es que el código dentro de la función solo se ejecuta cuando se llama explícitamente a la variable de entorno. El código después de la definición de la función se ejecuta cada vez que se inicia un nuevo proceso Bash.
David Farrell

1
Ver stackoverflow.com/questions/26022248/… para detalles adicionales
Barmar

Respuestas:


204

bash almacena las definiciones de funciones exportadas como variables de entorno. Las funciones exportadas se ven así:

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

Es decir, la variable de entorno footiene los contenidos literales:

() {  bar
}

Cuando se inicia una nueva instancia de bash, busca estas variables de entorno especialmente diseñadas y las interpreta como definiciones de funciones. Incluso puede escribir uno usted mismo y ver que todavía funciona:

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

Desafortunadamente, el análisis de definiciones de funciones a partir de cadenas (las variables de entorno) puede tener efectos más amplios de lo previsto. En versiones no parcheadas, también interpreta comandos arbitrarios que ocurren después de la terminación de la definición de la función. Esto se debe a limitaciones insuficientes en la determinación de cadenas de función aceptables en el entorno. Por ejemplo:

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

Tenga en cuenta que el eco fuera de la definición de la función se ejecutó inesperadamente durante el inicio de bash. La definición de la función es solo un paso para que la evaluación y la explotación sucedan, la definición de la función en sí y la variable de entorno utilizada son arbitrarias. El shell mira las variables de entorno, ve foo, lo que parece que cumple con las restricciones que conoce sobre cómo se ve una definición de función, y evalúa la línea, ejecutando involuntariamente también el eco (que podría ser cualquier comando, malicioso o no).

Esto se considera inseguro porque las variables no suelen permitirse o esperarse, por sí mismas, que provoquen directamente la invocación de código arbitrario contenido en ellas. Quizás su programa establezca variables de entorno a partir de la entrada del usuario no confiable. Sería muy inesperado que esas variables de entorno pudieran manipularse de tal manera que el usuario pudiera ejecutar comandos arbitrarios sin su intención explícita de hacerlo utilizando esa variable de entorno por tal razón declarada en el código.

Aquí hay un ejemplo de un ataque viable. Usted ejecuta un servidor web que ejecuta un shell vulnerable, en algún lugar, como parte de su vida útil. Este servidor web pasa las variables de entorno a un script bash, por ejemplo, si está utilizando CGI, la información sobre la solicitud HTTP a menudo se incluye como variables de entorno del servidor web. Por ejemplo, HTTP_USER_AGENTpuede configurarse según el contenido de su agente de usuario. Esto significa que si falsifica a su agente de usuario para que sea algo así como '() {:; }; echo foo ', cuando se ejecute ese script de shell, echo foose ejecutará. De nuevo, echo foopodría ser cualquier cosa, maliciosa o no.


3
¿Podría esto afectar a cualquier otro shell similar a Bash, como Zsh?
Amelio Vazquez-Reina

3
@ user815423426 No, zsh no tiene esta función. Ksh lo tiene implementado de manera diferente, creo que las funciones solo se pueden transmitir en circunstancias muy limitadas, solo si el shell se bifurca, no a través del entorno.
Gilles

20
@ user815423426 rc es el otro shell que pasa funciones en el entorno, pero tiene una variable con nombres con el prefijo "fn_" y solo se interpretan cuando se invocan.
Stéphane Chazelas

18
@ StéphaneChazelas: gracias por informar del error.
Deer Hunter

13
@gnclmorais ¿Quieres decir que corres export bar='() { echo "bar" ; }'; zsh -c bary se muestra en barlugar de zsh:1: command not found: bar? ¿Estás seguro de que no estás confundiendo el shell que estás invocando con el shell que estás utilizando para configurar la prueba?
Gilles

85

Esto puede ayudar a demostrar aún más lo que está sucediendo:

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

Si está ejecutando un shell vulnerable, cuando inicie un nuevo subshell (aquí, simplemente usando la instrucción bash), verá que el código arbitrario ( echo "pwned") se ejecuta inmediatamente como parte de su inicio. Aparentemente, el shell ve que la variable de entorno (ficticio) contiene una definición de función y evalúa la definición para definir esa función en su entorno (tenga en cuenta que no está ejecutando la función: eso imprimiría 'hola').

Desafortunadamente, no solo evalúa la definición de la función, sino que evalúa todo el texto del valor de la variable de entorno, incluidas las posibles declaraciones maliciosas que siguen a la definición de la función. Tenga en cuenta que sin la definición de la función inicial, la variable de entorno no se evaluaría, simplemente se agregaría al entorno como una cadena de texto. Como señaló Chris Down, este es un mecanismo específico para implementar la importación de funciones de shell exportadas.

Podemos ver la función que se ha definido en el nuevo shell (y que se ha marcado como exportada allí), y podemos ejecutarla. Además, el dummy no se ha importado como una variable de texto:

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

Ni la creación de esta función, ni nada de lo que haría si se ejecutara, es parte del exploit: es solo el vehículo mediante el cual se ejecuta el exploit. El punto es que si un atacante puede proporcionar código malicioso, precedido por una definición de función mínima y sin importancia, en una cadena de texto que se coloca en una variable de entorno exportada, entonces se ejecutará cuando se inicie una subshell, que es un evento común en muchos guiones Además, se ejecutará con los privilegios del script.


17
Si bien la respuesta aceptada en realidad dice esto si la lee detenidamente, encontré esta respuesta aún más clara y más útil para comprender que el problema es la evaluación de la definición (en lugar de ejecutar la función en sí misma).
natevw

1
¿Por qué este ejemplo tiene exportcomando mientras que los otros tenían env? Estaba pensando que envse está utilizando para definir las variables ambientales que se llamarían cuando se inicie otro bash shell. entonces, ¿cómo funciona esto?export
Haris

Hasta este momento no ha habido una respuesta aceptada. Probablemente esperaré otros dos días antes de aceptar uno. La desventaja de esta respuesta es que no desglosa el comando original, ni discute cómo pasar del comando original en la pregunta a los comandos en esta respuesta, mostrando que son idénticos. Aparte de eso, es una buena explicación.
jippie

@ralph - tanto envy exportdefiniciones de los entornos de exportación para que estén disponibles en un subnivel. El problema está en cómo se importan estas definiciones exportadas al entorno de una subshell, y específicamente en el mecanismo que importa las definiciones de funciones.
sdenham

1
@ralph: envejecuta un comando con algunas opciones y variables de entorno establecidas. Tenga en cuenta que en los ejemplos de preguntas originales, se envestablece xen una cadena y llama bash -ccon un comando para ejecutar. Si lo hace env x='foo' vim, Vim se iniciará, y allí puede llamar a su shell / entorno que contiene !echo $x, e imprimirá foo, pero si luego sale y echo $x, no se definirá, ya que solo existió mientras vim se estaba ejecutando a través del envcomando. En su exportlugar, el comando establece valores persistentes en el entorno actual para que una subshell ejecutada más tarde los use.
Gary Fixler

72

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 envpparámetro no se ve mucho, mainen 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 tsiguiente manera:

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

Mientras que lo anterior tno estaba definido en la subshell, ahora aparece después de que lo exportamos (úselo export -n tsi 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 fnse convierte en una variable de entorno al igual que nuestra exportación de la variable de shell testaba arriba. Esto no sucede cuando fnera 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 envpara mostrar todas las variables de shell marcados para la exportación y tanto la regularidad fny la función fnaparece:

$ 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 fncomo 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 fncomo 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 fndisponible en el shell actual. Pero si genera un shell ** sub **, entonces lo hará.

Cancelemos la exportación de la función fny dejemos el nuevo regular fn(como se muestra arriba) intacto.

$ export -nf fn

Ahora la función fnya 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:

  1. Nueva fiesta se genera
  2. Se ingiere una variable de entorno
  3. 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 exse pasó al subshell, que se interpretó como una función, expero 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. truey :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 envcomando (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 -cejecuta 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 faquí 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 xlo tanto, se exporta como una variable regular con una función válida simple con echo vulnerabletachuelas al final. Esto se pasa a bash, y bash interpreta xcomo una función (que no nos importa) y luego quizás ejecute echo vulnerableif shellshock.

Podríamos acortar un poco la línea eliminando el this is a testmensaje:

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

Esto no molesta this is a testpero 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"

12
Buena explicación Esta pregunta está recibiendo muchos puntos de vista (probablemente no todos sean tan hábiles en bash como los demás) y creo que nadie ha gastado un par de palabras en lo que { :;};realmente dice. Esa sería una buena adición a su respuesta en mi opinión. ¿Puede explicar cómo pasa de su ejemplo al comando original en la pregunta?
jippie

20

Si puede alimentar variables de entorno arbitrarias a un programa, puede hacer que haga casi cualquier cosa haciendo que cargue las bibliotecas de su elección. En la mayoría de los casos, esto no se considera una vulnerabilidad en el programa que recibe esas variables de entorno, sino más bien en el mecanismo por el cual un extraño puede alimentar variables de entorno arbitrarias.

Sin embargo, CVE-2014-6271 es diferente.

No hay nada de malo en tener datos no confiables en una variable de entorno. Uno solo tiene que asegurarse de que no se incluya en ninguna de esas variables de entorno que pueden modificar el comportamiento del programa. Ponga un poco más abstracto, para una invocación particular, puede crear una lista blanca de nombres de variables de entorno, que un extraño puede especificar directamente.

Un ejemplo que se ha presentado en el contexto de CVE-2014-6271 son los scripts utilizados para analizar archivos de registro. Esos pueden tener una necesidad muy legítima de pasar datos no confiables en las variables de entorno. Por supuesto, el nombre de dicha variable de entorno se elige de manera que no tenga ningún efecto adverso.

Pero esto es lo malo de esta vulnerabilidad de bash en particular. Se puede explotar a través de cualquier nombre de variable. Si crea una variable de entorno llamada GET_REQUEST_TO_BE_PROCESSED_BY_MY_SCRIPT, no esperaría que ningún otro programa además de su propio script interprete el contenido de esa variable de entorno. Pero al explotar este error bash, cada variable de entorno se convierte en un vector de ataque.

Tenga en cuenta que esto no significa que se espera que los nombres de las variables de entorno sean secretos. Conocer los nombres de las variables de entorno involucradas no hace que un ataque sea más fácil.

Si las program1llamadas program2que a su vez llaman program3, program1podrían pasar datos a program3través de variables de entorno. Cada programa tiene una lista específica de variables de entorno que establece y una lista específica sobre la que actúa. Si elige un nombre que no reconoce program2, puede pasar datos de program1a program3sin preocuparse de que esto tenga algún efecto adverso program2.

Un atacante que conozca los nombres exactos de las variables exportadas por program1y los nombres de las variables interpretadas por program2no puede explotar este conocimiento para modificar el comportamiento de 'programa2' si no hay superposición entre el conjunto de nombres.

Pero esto se rompió si se program2trataba de un bashscript, porque debido a este error bashinterpretaría cada variable de entorno como código.


1
"cada variable de entorno se convierte en un vector de ataque", esa es la parte que me faltaba. Gracias.
wrschneider

9

Se explica en el artículo que ha vinculado ...

puede crear variables de entorno con valores especialmente diseñados antes de llamar al shell bash. Estas variables pueden contener código, que se ejecuta tan pronto como se invoca el shell.

Lo que significa que el bash que se llama -c "echo this is a test"ejecuta el código en las comillas simples cuando se invoca.

Bash tiene funciones, aunque en una implementación algo limitada, y es posible poner estas funciones bash en variables de entorno. Esta falla se activa cuando se agrega código adicional al final de estas definiciones de funciones (dentro de la variable de entorno).

Significa que el ejemplo de código que publicó explota el hecho de que el bash invocado no deja de evaluar esta cadena después de realizar la asignación. Una asignación de función en este caso.

Lo realmente especial sobre el fragmento de código que publicó, según tengo entendido, es que al poner una definición de función antes del código que queremos ejecutar, se pueden eludir algunos mecanismos de seguridad.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.