"Ejecuta cualquier comando que pasará datos no confiables a comandos que interpretan argumentos como comandos"

18

Del manual de findutils:

Por ejemplo construcciones como estos dos comandos

# risky
find -exec sh -c "something {}" \;
find -execdir sh -c "something {}" \;

son muy peligrosos La razón de esto es que el '{}' se expande a un nombre de archivo que puede contener un punto y coma u otros caracteres especiales para el shell. Si, por ejemplo, alguien crea el archivo /tmp/foo; rm -rf $HOME, los dos comandos anteriores podrían eliminar el directorio de inicio de alguien.

Por esta razón, no ejecute ningún comando que pasará datos no confiables (como los nombres de archivos) a comandos que interpreten argumentos como comandos para ser interpretados más a fondo (por ejemplo, 'sh').

En el caso del shell, hay una solución inteligente para este problema:

# safer
find -exec sh -c 'something "$@"' sh {} \;
find -execdir sh -c 'something "$@"' sh {} \;

No se garantiza que este enfoque evite todos los problemas, pero es mucho más seguro que sustituir los datos de la elección de un atacante en el texto de un comando de shell.

  1. ¿La causa del problema es find -exec sh -c "something {}" \;que el reemplazo de {}no está entre comillas y, por lo tanto, no se trata como una sola cadena?
  2. En la solución find -exec sh -c 'something "$@"' sh {} \;,

    • primero {}se reemplaza, pero como {}no se cita, ¿no "$@"tiene el mismo problema que el comando original? Por ejemplo, "$@"se ampliará a "/tmp/foo;", "rm", "-rf", y "$HOME"?

    • ¿Por qué {}no se escapa o se cita?

  3. ¿Podría dar otros ejemplos (aún con sh -co sin él si corresponde; con o sin los findcuales pueden no ser necesarios) en los que se aplica el mismo tipo de problema y solución, y que son ejemplos mínimos para que podamos centrarnos en el problema y la solución con poca distracción posible? Consulte Formas de proporcionar argumentos a un comando ejecutado por `bash -c`

Gracias.

Tim
fuente
Relacionado: unix.stackexchange.com/questions/156008
Kusalananda

Respuestas:

29

Esto no está realmente relacionado con las citas, sino más bien con el procesamiento de argumentos.

Considere el ejemplo arriesgado:

find -exec sh -c "something {}" \;
  • Esta es analizada por el caparazón, y dividido en seis palabras: find, -exec, sh, -c, something {}(sin comillas ya), ;. No hay nada que ampliar. El shell se ejecuta findcon esas seis palabras como argumentos.

  • Cuando findencuentra algo para procesar, por ejemplo foo; rm -rf $HOME, reemplaza {}con foo; rm -rf $HOME, y se ejecuta shcon los argumentos sh, -cy something foo; rm -rf $HOME.

  • shahora ve -cy, como resultado, analiza something foo; rm -rf $HOME( el primer argumento sin opción ) y ejecuta el resultado.

Ahora considere la variante más segura:

find -exec sh -c 'something "$@"' sh {} \;
  • Las carreras de concha findcon los argumentos find, -exec, sh, -c, something "$@", sh, {}, ;.

  • Ahora, cuando findlos hallazgos foo; rm -rf $HOME, al que sustituye {}de nuevo, y se ejecuta shcon los argumentos sh, -c, something "$@", sh, foo; rm -rf $HOME.

  • shve -c, y analiza something "$@"como el comando para ejecutar, y shy foo; rm -rf $HOMEcomo los parámetros posicionales ( a partir de$0 ), se expande "$@"a foo; rm -rf $HOME como un valor único , y se ejecuta somethingcon el solo argumento foo; rm -rf $HOME.

Puedes ver esto usando printf. Crea un nuevo directorio, ingrésalo y ejecuta

touch "hello; echo pwned"

Ejecutando la primera variante de la siguiente manera

find -exec sh -c "printf \"Argument: %s\n\" {}" \;

produce

Argument: .
Argument: ./hello
pwned

mientras que la segunda variante, corre como

find -exec sh -c 'printf "Argument: %s\n" "$@"' sh {} \;

produce

Argument: .
Argument: ./hello; echo pwned
Stephen Kitt
fuente
En la variante más segura, ¿por qué {}no se escapa o se cita?
Tim
1
Generalmente no necesita ser citado; solo necesitaría ser citado en un shell donde tenga algún otro significado, y no creo que ese sea el caso con ninguno de los shells principales en uso hoy en día.
Stephen Kitt
De gnu.org/software/findutils/manual/html_mono/… : "Ambas construcciones ( ;y {}) necesitan ser escapadas (con un '\') o citadas para protegerlas de la expansión del shell". ¿Quieres decir que es incorrecto para bash?
Tim
3
Quiero decir que es incorrecto para los depósitos en general. Ver POSIX . Esa declaración en particular en el findutilsmanual se remonta al menos a 1996 ... Por supuesto, no hace daño citar {}, como '{}'o "{}", pero no es necesario.
Stephen Kitt
@Tim Estás experimentando una situación común en la que tu intuición aún no explica el hecho de que hay dos tipos muy diferentes de procesamiento de argumentos aquí: cuándo / cómo el shell analiza los argumentos a partir de una línea de texto (que es donde las citas importan) es diferente del paso de argumentos sin procesar del sistema operativo (donde las comillas son solo caracteres regulares). En el comando de shell find some/path -exec sh -c 'something "$@"' {} \;en realidad hay tres capas de procesamiento / paso de argumentos, dos de la variedad de shell y una de la variedad básica del sistema operativo sin procesar.
mtraceur
3

Parte 1:

find solo usa reemplazo de texto.

Sí, si lo hiciste sin comillas, así:

find . -type f -exec sh -c "echo {}" \;

y un atacante pudo crear un archivo llamado ; echo owned, entonces se ejecutaría

sh -c "echo ; echo owned"

lo que resultaría en la ejecución de la shell en echoese momento echo owned.

Pero si agregaste comillas, el atacante podría terminar tus comillas y luego colocar el comando malicioso después de crear un archivo llamado '; echo owned:

find . -type f -exec sh -c "echo '{}'" \;

que daría lugar a la cáscara de funcionamiento echo '', echo owned.

(si intercambió las comillas dobles por comillas simples, el atacante también podría usar el otro tipo de comillas).


Parte 2:

En find -exec sh -c 'something "$@"' sh {} \;, el {}shell no lo interpreta inicialmente, se ejecuta directamente con execve, por lo que agregar comillas de shell no ayudaría.

find -exec sh -c 'something "$@"' sh "{}" \;

no tiene ningún efecto, ya que el shell elimina las comillas dobles antes de ejecutarlo find.

find -exec sh -c 'something "$@"' sh "'{}'" \;

agrega citas que el shell no trata especialmente, por lo que en la mayoría de los casos solo significa que el comando no hará lo que desea.

Tener que se expanda a /tmp/foo;, rm, -rf, $HOMEno debería ser un problema, porque esos son argumentos a something, y somethingprobablemente no trata a sus argumentos como comandos para su ejecución.


Parte 3:

Supongo que se aplican consideraciones similares para cualquier cosa que tome una entrada no confiable y la ejecute como (parte de) un comando, por ejemplo, xargsy parallel.

Mikel
fuente
xargssolo es peligroso si -Io -Jse usa. En el funcionamiento normal, solo agrega argumentos al final de la lista, al igual que lo -exec ... {} +hace.
Charles Duffy
1
( parallel, por el contrario, ejecuta un shell de forma predeterminada sin una solicitud explícita del usuario, lo que aumenta la superficie vulnerable; este es un tema que ha sido objeto de mucha discusión; consulte unix.stackexchange.com/questions/349483/… para obtener una explicación, y el enlace incluido a lists.gnu.org/archive/html/bug-parallel/2015-05/msg00005.html ).
Charles Duffy
@CharlesDuffy Te refieres a " disminuir la superficie vulnerable". El ataque ilustrado por OP no funciona de manera predeterminada con GNU Parallel.
Ole Tange
1
Sí, sé que lo necesita para la paridad a través de SSH, si no generó un parallelproceso de "receptor" remoto en el otro extremo de cualquier zócalo, puede hacer un proceso directo sin shell execv. Que es exactamente lo que hubiera hecho, si estuviera en su lugar implementando la herramienta. Tener un programa no interactivo se comporta de manera diferente en función del valor actual de SHELLes apenas el menor asombro.
Charles Duffy
1
Sí, considero que las canalizaciones y las redirecciones deberían ser imposibles a menos que el usuario inicie explícitamente un shell, momento en el que solo obtiene el comportamiento explícito del shell que inició explícitamente. Si uno puede esperar solo lo que execvproporciona, eso es más simple y menos sorprendente, y una regla que no cambia en ubicaciones / entornos de tiempo de ejecución.
Charles Duffy
3

1. ¿La causa del problema es find -exec sh -c "something {}" \;que el reemplazo de {}no está entre comillas y, por lo tanto, no se trata como una sola cadena?

En cierto sentido, pero las citas no pueden ayudar aquí . El nombre de archivo que se reemplaza en lugar de {}puede contener cualquier carácter, incluidas las comillas . Cualquiera sea la forma de cita utilizada, el nombre de archivo puede contener el mismo y "romper" la cita.

2. ... pero como {}no se cita, ¿no "$@"tiene el mismo problema que el comando original? Por ejemplo, "$@"se ampliará a "/tmp/foo;", "rm", "-rf", and "$HOME"?

No. se "$@"expande a los parámetros posicionales, como palabras separadas, y no los divide más. Aquí, {}es un argumento finden sí mismo, y findpasa el nombre de archivo actual también como un argumento distinto a sh. Está directamente disponible como una variable en el script de shell, no se procesa como un comando de shell en sí.

... ¿por qué {}no se escapa o se cita?

No necesita ser, en la mayoría de los depósitos. Si ejecuta fish, debe ser: fish -c 'echo {}'imprime una línea vacía. Pero no importa si lo cita, el shell simplemente eliminará las comillas.

3. ¿Podría dar otros ejemplos ...

Cada vez que expande un nombre de archivo (u otra cadena no controlada) como está dentro de una cadena que se toma como algún tipo de código (*) , existe la posibilidad de ejecución de comandos arbitrarios.

Por ejemplo, esto expande $fdirectamente el código Perl y causará problemas si un nombre de archivo contiene una comilla doble. La cita en el nombre del archivo finalizará la cita en el código Perl, y el resto del nombre del archivo puede contener cualquier código Perl:

touch '"; print "HELLO";"'
for f in ./*; do
    perl -le "print \"size: \" . -s \"$f\""
done

(El nombre del archivo debe ser un poco extraño ya que Perl analiza el código completo por adelantado, antes de ejecutarlo. Por lo tanto, tendremos que evitar un error de análisis).

Si bien esto lo pasa de manera segura a través de un argumento:

for f in ./*; do
    perl -le 'print "size: " . -s $ARGV[0]' "$f"
done

(No tiene sentido ejecutar otro shell directamente desde un shell, pero si lo hace, es similar al find -exec sh ...caso)

(* algún tipo de código incluye SQL, XKCD obligatorio: https://xkcd.com/327/ más explicación: https://www.explainxkcd.com/wiki/index.php/Little_Bobby_Tables )

ilkkachu
fuente
1

Su preocupación es precisamente la razón por la cual GNU Parallel cita entradas:

touch "hello; echo pwned"
find . -print0 | parallel -0 printf \"Argument: %s\\n\" {}

Esto no se ejecutará echo pwned.

Ejecutará un shell, de modo que si extiende su comando, no se sorprenderá de repente:

# This _could_ be run without spawining a shell
parallel "grep -E 'a|bc' {}" ::: foo
# This cannot
parallel "grep -E 'a|bc' {} | wc" ::: foo

Para obtener más detalles sobre el problema de spawning-a-shell, consulte: https://www.gnu.org/software/parallel/parallel_design.html#Always-running-commands-in-a-shell

Ole Tange
fuente
1
... Dicho esto, find . -print0 | xargs -0 printf "Argument: %s\n"es igual de seguro (o más bien, más, ya que con -print0uno maneja los nombres de archivo con nuevas líneas correctamente); Las citas paralelas son una solución para un problema que no existe cuando no hay ningún shell.
Charles Duffy
Pero tan pronto como agregue | wcal comando, debe saltar a través de los aros para que sea seguro. GNU Parallel es seguro por defecto.
Ole Tange
ITYM: trata de cubrir cada peculiaridad del lenguaje shell mediante el complicado proceso de citar todo cuidadosamente. Eso no es tan seguro como almacenar adecuadamente los datos en variables, no entremezclado con el código. Ahora, si se convirtiera automáticamente foo {} | wcen sh -c 'foo "$1" | wcsh {} `, eso podría ser diferente.
ilkkachu