GNU/Linux >> Tutoriales Linux >  >> Linux

¿Implicaciones de seguridad de olvidar citar una variable en Bash/posix Shells?

Si ha estado siguiendo unix.stackexchange.com por un tiempo,
debería saber ahora que dejar una variable
sin comillas en el contexto de la lista (como en echo $var ) en shells Bourne/POSIX
(siendo zsh la excepción) tiene un significado muy especial y
no debe hacerse a menos que tenga una muy buena razón para hacerlo.

Se discute extensamente en una serie de preguntas y respuestas aquí (Ejemplos:¿Por qué mi script de shell se ahoga con espacios en blanco u otros caracteres especiales?, ¿Cuándo son necesarias las comillas dobles?, Expansión de una variable de shell y efecto de glob y split en ella, Citado frente a la expansión de cadenas sin comillas )

Ese ha sido el caso desde el lanzamiento inicial del shell Bourne
a finales de los años 70 y no ha cambiado con el shell Korn
(uno de los mayores
arrepentimientos de David Korn (pregunta n.º 7 )) o bash que en su mayoría
copió el shell de Korn, y así es como ha sido especificado por POSIX/Unix.

Ahora, todavía estamos viendo una serie de respuestas aquí e incluso
código de shell lanzado públicamente ocasionalmente donde
las variables no se citan. Habrías pensado que la gente ya habría
aprendido.

En mi experiencia, existen principalmente 3 tipos de personas que omiten
citar sus variables:

  • principiantes Esos pueden disculparse ya que es cierto que es una sintaxis completamente poco intuitiva. Y es nuestro papel en este sitio
    educarlos.

  • gente olvidadiza.

  • personas que no están convencidas incluso después de martillazos repetidos,
    que piensan que seguramente el autor de la concha de Bourne no
    pretendía que citáramos todas nuestras variables
    .

Tal vez podamos convencerlos si exponemos el riesgo asociado con
este tipo de comportamientos.

¿Qué es lo peor que puede pasar si
olvida citar sus variables? ¿Es realmente que malo?

¿De qué tipo de vulnerabilidad estamos hablando aquí?

¿En qué contextos puede ser un problema?

Respuesta aceptada:

Preámbulo

Primero, diría que no es la forma correcta de abordar el problema.
Es un poco como decir "no deberías asesinar a la gente porque
de lo contrario irás a la cárcel
“.

Del mismo modo, no cita su variable porque de lo contrario
está introduciendo vulnerabilidades de seguridad. Citas tus
variables porque está mal no hacerlo (pero si el miedo a la cárcel puede ayudar, por qué no).

Un pequeño resumen para aquellos que acaban de subirse al tren.

En la mayoría de los shells, dejar una expansión variable sin comillas (aunque
eso (y el resto de esta respuesta) también se aplica a la sustitución del comando
(`...` o $(...) ) y expansión aritmética ($((...)) o $[...] )) tiene un significado
muy especial. La mejor forma de describirlo es que es como
invocar algún tipo de split+glob implícito. operador¹.

cmd $var

en otro idioma se escribiría algo como:

cmd(glob(split($var)))

$var primero se divide en una lista de palabras de acuerdo con reglas
complejas que involucran el $IFS parámetro especial (el split parte)
y luego cada palabra resultante de esa división se considera como
un patrón que se expande a una lista de archivos que coinciden
(el glob parte).

Como ejemplo, si $var contiene *.txt,/var/*.xml y $IFS contiene , , cmd sería llamado con una serie de argumentos,
el primero de ellos sería cmd y los siguientes son el txt archivos en el directorio actual y el xml archivos en /var .

Si quisieras llamar a cmd con solo los dos argumentos literales cmd y *.txt,/var/*.xml , escribirías:

cmd "$var"

que estaría en su otro idioma más familiar:

cmd($var)

¿Qué entendemos por vulnerabilidad en un shell? ?

Después de todo, se sabe desde el principio que los scripts de shell
no deben usarse en contextos sensibles a la seguridad.
Seguramente, está bien, dejar una variable sin comillas es un error, pero eso no puede
hacer tanto daño, ¿verdad?

Bueno, a pesar del hecho de que cualquiera le diría que los scripts de shell
nunca deben usarse para CGI web, o que afortunadamente
la mayoría de los sistemas no permiten scripts de shell setuid/setgid hoy en día,
uno Lo que reveló shellshock (el error de bash explotable de forma remota
que apareció en los titulares en septiembre de 2014) es que
los shells todavía se usan ampliamente donde probablemente no deberían:
en CGI, en cliente DHCP secuencias de comandos de enlace, en comandos sudoers,
invocados por (si no es como ) comandos setuid…

A veces sin saberlo. Por ejemplo, system('cmd $PATH_INFO') en un php /perl /python El script CGI invoca un shell para interpretar esa línea de comando (sin mencionar
el hecho de que cmd en sí mismo puede ser un script de shell y es posible que su autor
nunca haya esperado que fuera llamado desde un CGI).

Tienes una vulnerabilidad cuando hay un camino para la escalada de privilegios
, eso es cuando alguien (llamémosle el atacante )
es capaz de hacer algo que no debe hacer.

Invariablemente eso significa el atacante proporcionando datos, esos datos
siendo procesados ​​por un usuario/proceso privilegiado que sin darse cuenta
hace algo que no debería estar haciendo, en la mayoría de los casos debido
a un error.

Básicamente, tienes un problema cuando tu código con errores procesa
datos bajo el control de el atacante .

Ahora bien, no siempre es obvio dónde están esos datos. puede provenir,
y, a menudo, es difícil saber si su código llegará a
procesar datos que no son de confianza.

En lo que respecta a las variables, en el caso de un script CGI,
es bastante obvio, los datos son los parámetros CGI GET/POST y
cosas como cookies, ruta, host... parámetros.

Para un script setuid (que se ejecuta como un usuario cuando es invocado por
otro), son los argumentos o las variables de entorno.

Otro vector muy común son los nombres de archivo. Si está obteniendo una
lista de archivos de un directorio, es posible que los archivos hayan sido
plantados allí por el atacante .

En ese sentido, incluso ante la indicación de un shell interactivo, usted
podría ser vulnerable (al procesar archivos en /tmp o ~/tmp por ejemplo).

Incluso un ~/.bashrc puede ser vulnerable (por ejemplo, bash
lo interpretará cuando se invoque sobre ssh para ejecutar un ForcedCommand como en git implementaciones de servidor con algunas variables bajo el control
del cliente).

Ahora, un script no puede ser llamado directamente para procesar datos que no son de confianza, pero puede ser llamado por otro comando que lo haga. O su
código incorrecto puede ser copiado y pegado en secuencias de comandos que lo hacen (por usted 3
años después o por uno de sus colegas). Un lugar donde es
particularmente crítico está en las respuestas en los sitios de preguntas y respuestas, ya que
nunca sabrá dónde pueden terminar las copias de su código.

Pongan manos a la obra; ¿Qué tan malo es?

Dejar una variable (o sustitución de comando) sin comillas es, con diferencia,
la principal fuente de vulnerabilidades de seguridad asociadas
con el código shell. En parte porque esos errores a menudo se traducen en
vulnerabilidades, pero también porque es muy común ver
variables sin comillas.

Relacionado:¿Por qué usar install en lugar de cp y mkdir?

En realidad, al buscar vulnerabilidades en el código de shell,
lo primero que debe hacer es buscar variables sin comillas. Es fácil de
detectar, a menudo un buen candidato, generalmente fácil de rastrear hasta
los datos controlados por el atacante.

Hay un número infinito de formas en que una variable sin comillas puede convertirse
en una vulnerabilidad. Solo daré algunas tendencias comunes aquí.

Divulgación de información

La mayoría de las personas se topará con errores asociados con variables sin comillas
debido a la división part (por ejemplo, es
común que los archivos tengan espacios en sus nombres hoy en día y el espacio
está en el valor predeterminado de IFS). Mucha gente pasará por alto el glob parte. El globo parte es al menos tan peligrosa como la split parte.

Globbing hecho sobre una entrada externa sin desinfectar significa el
atacante
puede hacerte leer el contenido de cualquier directorio.

en:

echo You entered: $unsanitised_external_input

si $unsanitised_external_input contiene /* , eso significa el
atacante
puede ver el contenido de / . No es gran cosa. Sin embargo, se vuelve
más interesante con /home/* que le da una lista de
nombres de usuario en la máquina, /tmp/* , /home/*/.forward para
pistas sobre otras prácticas peligrosas, /etc/rc*/* para servicios habilitados
... No es necesario nombrarlos individualmente. Un valor de /* /*/* /*/*/*... simplemente enumerará todo el sistema de archivos.

Vulnerabilidades de denegación de servicio.

Llevando el caso anterior un poco demasiado lejos, tenemos un DoS.

En realidad, cualquier variable sin comillas en el contexto de la lista con una entrada
sin desinfectar es al menos una vulnerabilidad DoS.

Incluso los scripters de shell expertos suelen olvidarse de citar cosas
como:

#! /bin/sh -
: ${QUERYSTRING=$1}

: es el comando no operativo. ¿Qué podría salir mal?

Eso está destinado a asignar $1 a $QUERYSTRING si $QUERYSTRING estaba desarmado. Esa es una forma rápida de hacer que un script CGI también se pueda llamar desde
la línea de comando.

Ese $QUERYSTRING sin embargo, todavía está expandido y debido a que
no se cita, el split+glob se invoca el operador.

Ahora, hay algunos globs que son particularmente caros de
expandir. El /*/*/*/* uno es bastante malo ya que significa listar directorios hasta 4 niveles hacia abajo. Además de la actividad del disco y la CPU
, eso significa almacenar decenas de miles de rutas de archivo
(40k aquí en una máquina virtual de servidor mínima, 10k de los cuales son directorios).

Ahora /*/*/*/*/../../../../*/*/*/* significa 40k x 10k y /*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/* es suficiente para
poner de rodillas incluso a la máquina más poderosa.

Pruébelo usted mismo (aunque esté preparado para que su máquina
se bloquee o se cuelgue):

a='/*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/*' sh -c ': ${a=foo}'

Por supuesto, si el código es:

echo $QUERYSTRING > /some/file

Entonces puedes llenar el disco.

Simplemente haga una búsqueda en Google en shell
cgi o bash
cgi o ksh
cgi, y encontrará
algunas páginas que le muestran cómo escribir CGI en shells. Observe
cómo la mitad de los que procesan parámetros son vulnerables.

Incluso el
propio
de David Korn es vulnerable (mira el manejo de cookies).

hasta vulnerabilidades de ejecución de código arbitrario

La ejecución de código arbitrario es el peor tipo de vulnerabilidad,
ya que si el atacante puede ejecutar cualquier comando, no hay límite en
lo que puede hacer.

Esa es generalmente la división parte que lleva a esos. Esa
división da como resultado que se pasen varios argumentos a los comandos
cuando solo se espera uno. Mientras que el primero de ellos se usará
en el contexto esperado, los otros estarán en un contexto diferente
por lo que potencialmente se interpretarán de manera diferente. Mejor con un ejemplo:

awk -v foo=$external_input '$2 == foo'

Aquí, la intención era asignar el contenido del $external_input variable de shell al foo awk variables.

Ahora:

$ external_input='x BEGIN{system("uname")}'
$ awk -v foo=$external_input '$2 == foo'
Linux

La segunda palabra resultante de la división de $external_input no está asignado a foo pero considerado como awk código (aquí que
ejecuta un comando arbitrario:uname ).

Eso es especialmente un problema para los comandos que pueden ejecutar otros comandos
(awk , env , sed (GNU uno), perl , find …) especialmente
con las variantes de GNU (que aceptan opciones después de los argumentos).
A veces, no sospecharías que los comandos puedan ejecutarse
otros como ksh , bash o zsh 's [ o printf

for file in *; do
  [ -f $file ] || continue
  something-that-would-be-dangerous-if-$file-were-a-directory
done

Si creamos un directorio llamado x -o yes , entonces la prueba
se vuelve positiva, porque es una expresión condicional
completamente diferente que estamos evaluando.

Peor aún, si creamos un archivo llamado x -a a[0$(uname>&2)] -gt 1 ,
con todas las implementaciones de ksh al menos (lo que incluye el sh de la mayoría de Unices comerciales y algunos BSD), que ejecuta uname porque esos shells realizan una evaluación aritmética en los
operadores de comparación numérica del [ comando.

$ touch x 'x -a a[0$(uname>&2)] -gt 1'
$ ksh -c 'for f in *; do [ -f $f ]; done'
Linux

Lo mismo con bash para un nombre de archivo como x -a -v a[0$(uname>&2)] .

Por supuesto, si no pueden obtener una ejecución arbitraria, el atacante puede
conformarse con un daño menor (lo que puede ayudar a conseguir una ejecución
arbitraria). Cualquier comando que pueda escribir archivos o cambiar
permisos, propiedad o tener algún efecto principal o secundario podría ser explotado.

Se pueden hacer todo tipo de cosas con los nombres de archivo.

$ touch -- '-R ..'
$ for file in *; do [ -f "$file" ] && chmod +w $file; done

Y terminas haciendo .. escribible (recursivamente con GNU chmod ).

Scripts que realizan el procesamiento automático de archivos en áreas de escritura pública como /tmp deben escribirse con mucho cuidado.

¿Qué pasa con [ $# -gt 1 ]

Eso es algo que encuentro exasperante. Algunas personas se toman la molestia de
preguntarse si una expansión en particular puede ser
problemática para decidir si pueden omitir las comillas.

es como decir Oye, parece $# no puede estar sujeto a
el operador split+glob, pidamos al shell que lo divida+glob
.
O Oiga, escribamos un código incorrecto solo porque es poco probable
que se produzca el error
.

Ahora, ¿qué tan improbable es? Bien, $# (o $! , $? o cualquier
sustitución aritmética) solo puede contener dígitos (o - para
algunos²) por lo que el glob parte está fuera. Para la división parte para hacer
algo, sin embargo, todo lo que necesitamos es $IFS para contener dígitos (o - ).

Con algunas conchas, $IFS puede ser heredado del entorno,
pero si el entorno no es seguro, se acabó el juego de todos modos.

Relacionado:¿Falla la redirección a un nombre de archivo globbed?

Ahora, si escribes una función como:

my_function() {
  [ $# -eq 2 ] || return
  ...
}

Lo que eso significa es que el comportamiento de su función depende
del contexto en el que se llama. O en otras palabras, $IFS se convierte en una de sus entradas. Estrictamente hablando, cuando
escribes la documentación de la API para tu función, debería ser
algo como:

# my_function
#   inputs:
#     $1: source directory
#     $2: destination directory
#   $IFS: used to split $#, expected not to contain digits...

Y el código que llama a su función debe asegurarse de que $IFS no
contiene dígitos. Todo eso porque no tenía ganas de escribir
esos 2 caracteres de comillas dobles.

Ahora, para eso [ $# -eq 2 ] error para convertirse en una vulnerabilidad,
necesitaría de alguna manera el valor de $IFS estar bajo
control del atacante . Posiblemente, eso no sucedería normalmente
a menos que el atacante logró explotar otro error.

Sin embargo, eso no es inaudito. Un caso común es cuando las personas
olvidan desinfectar los datos antes de usarlos en expresiones aritméticas
. Ya hemos visto anteriormente que puede permitir
la ejecución de código arbitrario en algunos shells, pero en todos ellos permite que el atacante para dar a cualquier variable un valor entero.

Por ejemplo:

n=$(($1 + 1))
if [ $# -gt 2 ]; then
  echo >&2 "Too many arguments"
  exit 1
fi

Y con un $1 con valor (IFS=-1234567890) , esa evaluación aritmética
tiene el efecto secundario de configurar IFS y el siguiente [ el comando falla, lo que significa que la comprobación de demasiados argumentos está
anulado.

¿Qué pasa cuando el split+glob no se invoca el operador?

Hay otro caso en el que se necesitan comillas alrededor de variables y otras expansiones:cuando se usa como patrón.

[[ $a = $b ]]   # a `ksh` construct also supported by `bash`
case $a in ($b) ...; esac

no probar si $a y $b son iguales (excepto con zsh ) pero si $a coincide con el patrón en $b . Y necesitas citar $b si desea comparar como cadenas (lo mismo en "${a#$b}" o "${a%$b}" o "${a##*$b*}" donde $b debe citarse si no debe tomarse como un patrón).

Lo que eso significa es que [[ $a = $b ]] puede devolver verdadero en los casos en que $a es diferente de $b (por ejemplo, cuando $a es anything y $b es * ) o puede devolver falso cuando son idénticos (por ejemplo, cuando ambos $a y $b son [a] ).

¿Eso puede representar una vulnerabilidad de seguridad? Sí, como cualquier bicho. Aquí, el atacante puede alterar el flujo de código lógico de su secuencia de comandos y/o romper las suposiciones que está haciendo su secuencia de comandos. Por ejemplo, con un código como:

if [[ $1 = $2 ]]; then
   echo >&2 '$1 and $2 cannot be the same or damage will incur'
   exit 1
fi

El atacante puede omitir la verificación pasando '[a]' '[a]' .

Ahora bien, si ni ese patrón coincide ni el split+glob ¿Cuál es el peligro de dejar una variable sin comillas?

Tengo que admitir que escribo:

a=$b
case $a in...

Ahí, citar no hace daño pero no es estrictamente necesario.

Sin embargo, un efecto secundario de omitir las comillas en esos casos (por ejemplo, en las respuestas de preguntas y respuestas) es que puede enviar un mensaje erróneo a los principiantes:que puede estar bien no citar variables .

Por ejemplo, pueden empezar a pensar que si a=$b está bien, entonces export a=$b también lo sería (que no está en muchos shells, ya que está en los argumentos de export comando so en contexto de lista) o env a=$b .

¿Qué pasa con zsh? ?

zsh arregló la mayoría de esas incomodidades de diseño. En zsh (al menos cuando no está en el modo de emulación sh/ksh), si quiere dividir , o globos o coincidencia de patrones , tienes que solicitarlo explícitamente:$=var para dividir, y $~var para glob o para que el contenido de la variable sea tratado como un patrón.

Sin embargo, la división (pero no el globbing) aún se realiza implícitamente al sustituir el comando sin comillas (como en echo $(cmd) ).

Además, un efecto secundario a veces no deseado de no citar la variable es la eliminación de vacíos . El zsh el comportamiento es similar a lo que puede lograr en otros shells al deshabilitar el globbing por completo (con set -f ) y dividir (con IFS='' ). Aún así, en:

cmd $var

No habrá split+glob , pero si $var está vacío, en lugar de recibir un argumento vacío, cmd no recibirá ningún argumento.

Eso puede causar errores (como el obvio [ -n $var ] ). Eso posiblemente puede romper las expectativas y suposiciones de un script y causar vulnerabilidades.

Como la variable vacía puede hacer que un argumento sea simplemente eliminado , eso significa que el siguiente argumento podría interpretarse en el contexto incorrecto.

Como ejemplo,

printf '[%d] <%s>n' 1 $attacker_supplied1 2 $attacker_supplied2

Si $attacker_supplied1 está vacío, entonces $attacker_supplied2 se interpretará como una expresión aritmética (para %d ) en lugar de una cadena (para %s ) y cualquier dato sin desinfectar utilizado en una expresión aritmética es una vulnerabilidad de inyección de comandos en shells similares a Korn, como zsh.

$ attacker_supplied1='x y' attacker_supplied2='*'
$ printf '[%d] <%s>n' 1 $attacker_supplied1 2 $attacker_supplied2
[1] <x y>
[2] <*>

bien, pero:

$ attacker_supplied1='' attacker_supplied2='psvar[$(uname>&2)0]'
$ printf '[%d] <%s>n' 1 $attacker_supplied1 2 $attacker_supplied2
Linux
[1] <2>
[0] <>

El uname comando arbitrario fue ejecutado.

¿Qué pasa cuando haces necesito el split+glob operador?

Sí, normalmente es cuando desea dejar su variable sin comillas. Pero luego debe asegurarse de ajustar su split y globo operadores correctamente antes de usarlo. Si solo desea la división parte y no el glob parte (que es el caso la mayor parte del tiempo), entonces necesita deshabilitar globbing (set -o noglob /set -f ) y arreglar $IFS . De lo contrario, también provocará vulnerabilidades (como el ejemplo CGI de David Korn mencionado anteriormente).

Conclusión

En resumen, dejar una variable (o sustitución de comando o
expansión aritmética) sin comillas en shells puede ser muy peligroso
de hecho, especialmente cuando se hace en contextos incorrectos, y es muy
difícil saber cuáles son esos contextos equivocados.

Esa es una de las razones por las que se considera mala práctica .

Gracias por leer hasta ahora. Si se te pasa por la cabeza, no
te preocupes. No se puede esperar que todos entiendan todas las implicaciones de
escribir su código de la forma en que lo escriben. Por eso tenemos recomendaciones de buenas prácticas , por lo que pueden seguirse sin
necesariamente entender por qué.

Relacionado:¿Cómo calcular el promedio de valores en una columna considerando la información de otra columna?

(y en caso de que aún no sea obvio, evite escribir
código sensible a la seguridad en shells).

Y por favor, ¡cite sus variables en sus respuestas en este sitio!


Linux
  1. Personalización del shell Bash

  2. [ :Operador inesperado en programación shell

  3. Alias ​​con variable en bash

  4. Cómo:¿Historial ilimitado de Bash/shell?

  5. ¿Implicaciones de seguridad del uso de rutas relativas en la variable ambiental PATH?

.bashrc frente a .bash_profile

Cómo establecer la variable de entorno en Bash

Bash Beginner Series #2:Comprender las variables en Bash Shell Scripting

¿Qué es Subshell en Linux?

Diferencia entre comillas simples y dobles en Bash Shell

8 tipos de shells de Linux