¿Es posible usar `find -exec sh -c` de forma segura?

30

Estoy tratando de utilizar finda echo 0en algunos archivos, pero al parecer esto sólo funciona con sh -c:

find /proc/sys/net/ipv6 -name accept_ra -exec sh -c 'echo 0 > {}' \;

Pero usar sh -ccon find -execme hace sentir muy incómodo porque sospecho que tengo problemas de citas. Jugueteé un poco y aparentemente mis sospechas estaban justificadas:

  • Mi configuración de prueba:

    martin@dogmeat ~ % cd findtest 
    martin@dogmeat ~/findtest % echo one > file\ with\ spaces
    martin@dogmeat ~/findtest % echo two > file\ with\ \'single\ quotes\'
    martin@dogmeat ~/findtest % echo three > file\ with\ \"double\ quotes\"
    martin@dogmeat ~/findtest % ll
    insgesamt 12K
    -rw-rw-r-- 1 martin martin 6 Sep 17 12:01 file with "double quotes"
    -rw-rw-r-- 1 martin martin 4 Sep 17 12:01 file with 'single quotes'
    -rw-rw-r-- 1 martin martin 4 Sep 17 12:01 file with spaces
  • Usar find -execsin sh -cparece funcionar sin problemas: no es necesario citar aquí:

    martin@dogmeat ~ % find findtest -type f -exec cat {} \;
    one
    two
    three
  • Pero cuando estoy usando sh -c {}parece requerir algún tipo de cita:

    martin@dogmeat ~ % LANG=C find findtest -type f -exec sh -c 'cat {}' \;
    cat: findtest/file: No such file or directory
    cat: with: No such file or directory
    cat: spaces: No such file or directory
    cat: findtest/file: No such file or directory
    cat: with: No such file or directory
    cat: single quotes: No such file or directory
    cat: findtest/file: No such file or directory
    cat: with: No such file or directory
    cat: double quotes: No such file or directory
  • Las comillas dobles funcionan siempre que ningún nombre de archivo contenga comillas dobles:

    martin@dogmeat ~ % LANG=C find findtest -type f -exec sh -c 'cat "{}"' \;
    one
    two
    cat: findtest/file with double: No such file or directory
    cat: quotes: No such file or directory
  • Las comillas simples funcionan siempre que ningún nombre de archivo contenga comillas simples:

    martin@dogmeat ~ % LANG=C find findtest -type f -exec sh -c "cat '{}'" \;
    one
    cat: findtest/file with single: No such file or directory
    cat: quotes: No such file or directory
    three

No he encontrado una solución que funcione en todos los casos. ¿Hay algo que estoy pasando por alto o que estoy usando sh -cde forma find -execinherentemente insegura?

Martin von Wittich
fuente

Respuestas:

41

¡Nunca incrustar {}en el código de shell! Eso crea una vulnerabilidad de inyección de comandos. Tenga en cuenta que para el cat "{}", no es sólo acerca de los "personajes, \, `, $son también un problema (por ejemplo, considere un archivo llamado ./$(reboot)/accept_ra).

(por cierto, algunas findimplementaciones no le permitirán hacer eso, y POSIX deja el comportamiento sin especificar cuando {}no está solo en un argumento para find)

Aquí, querrá pasar los nombres de archivo como argumentos separados a sh(no en el argumento de código ), y el shscript en línea (el argumento de código ) para referirse a ellos utilizando parámetros posicionales:

find . -name accept_ra -exec sh -c 'echo 0 > "$1"' sh {} \;

O, para evitar ejecutar uno shpor archivo:

find . -name accept_ra -exec sh -c 'for file do
  echo 0 > "$file"; done' sh {} +

Lo mismo se aplica a xargs -I{}o zsh's zargs -I{}. No escribas:

<list.txt xargs -I {} sh -c 'cmd> {}'

Lo que sería una vulnerabilidad de inyección de comandos de la misma manera que con el findanterior, pero:

<list.txt xargs sh -c 'for file do cmd > "$file"; done' sh

Lo que también tiene la ventaja de evitar ejecutar uno shpor archivo y el error cuando list.txtno contiene ningún archivo.

Con zsh's zargs, probablemente quieras usar una función en lugar de invocar sh -c:

do-it() cmd > $1
zargs ./*.txt -- do-it

Tenga en cuenta que en todos los ejemplos anteriores, el segundo shanterior va al guión en línea $0. Debe utilizar algo relevante allí (como sho find-sh), no cosas como _, -, --o la cadena vacía, como el valor en el $0que se utiliza para los mensajes de error del shell:

$ find . -name accept_ra -exec sh -c 'echo 0 > "$1"' inline-sh {} \;
inline-sh: ./accept_ra: Permission denied

GNU parallelfunciona de manera diferente. Con él, no desea usarlo, sh -cya parallelque ejecuta un shell e intenta reemplazarlo {}con el argumento citado en la sintaxis correcta para el shell .

<list.txt PARALLEL_SHELL=sh parallel 'cmd > {}'
Stéphane Chazelas
fuente
esa es la segunda shparece ser algún tipo de marcador de posición, también funciona si se sustituye por _, por ejemplo, - muy útil si se desea llamar internos de bash: find /tmp -name 'fil*' -exec bash -c 'printf "%q\n" "$1"' _ {} \;. ¿Pero alguien sabe dónde está documentado esto?
Florian Fida
1
@FlorianFida El primer argumento para el shell se convierte $0(por lo general, el nombre del shell. Debe omitirlo en este escenario para que no coma uno de sus argumentos posicionales normales. La documentación lo -cmenciona.
Etan Reisner
2
@EtanReisner inulm.de/~mascheck/various/find/#embedded
Gilles 'SO- deja de ser malvado'
1
@phk, eso no está argv[0]aquí, es solo $0el guión. La página de Sven es inexacta aquí, y rno hará que el shell ingrese a un modo restringido por lo que puede ver y zshno cambiará el modo según $0. (exec -a rksh ksh -c 'cd /')ejecutará un restringido ksh, pero no ksh -c 'cd /' rksh).
Stéphane Chazelas
1
Stéphane, si no le importa, ¿hay alguna respuesta suya que explique "por qué no incrustar {} en el código de shell"? Revisé todas sus respuestas en encontrar pero no pude encontrar una, aunque podría jurar que vi algunas cosas que había escrito sobre este tema, pero no puedo recordar si fue una respuesta, un comentario en el chat o en otro sitio como compunix ... Si / cuando se tiene el tiempo, ¿le importaría la expansión de la "Nunca incrustar} {" parte o qué cree que deberíamos tener un dedicado Q por ejemplo, "implicaciones de seguridad de la utilización de findla -exec sh -ce incrustar {}en el código shell" ?
don_crissti