¿Cómo puedo citar una expansión variable dentro de una cadena para evitar la división de palabras?

9
$ myvar="/path to/my directory"
$ sudo bash -c "cd $myvar"

En tal caso, ¿cómo puedo citar $myvarpara evitar la división de palabras debido a los espacios en blanco en el valor de myvar?

Tim
fuente
44
No importa cómo resuelva esto, todavía no servirá de mucho. El cdúnico tiene efecto dentro del bash -ccaparazón.
Kusalananda
Solo estoy dando un ejemplo mínimo para la pregunta, no es una solución que funcione para mi problema real, que es unix.stackexchange.com/a/269080/674 más que en la cadena de comandos dada sudo bash -ctengo una expansión variable para un nombre de ruta que puede contener espacios en blanco.
Tim

Respuestas:

13

No hay división de palabras (como en la función que divide las variables en expansiones sin comillas) en ese código, ya $myvarque no está sin comillas.

Sin embargo, existe una vulnerabilidad de inyección de comandos que $myvarse expande antes de pasar a bash. ¡Entonces su contenido se interpreta como código bash!

Los espacios allí causarán que se pasen varios argumentos cd, no debido a la división de palabras , sino porque se analizarán como varios tokens en la sintaxis del shell. ¡Con un valor de bye;reboot, eso se reiniciará! ¹

Aquí te gustaría:

sudo bash -c 'cd -P -- "$1"' bash "$myvar"

(donde pasa el contenido de $myvarcomo el primer argumento de esa secuencia de comandos en línea; tenga en cuenta cómo ambos $myvary $1fueron citados por su respectivo shell para evitar la división de palabras IFS (y globbing)).

O:

sudo MYVAR="$myvar" bash -c 'cd -P -- "$MYVAR"'

(donde pasa el contenido de $myvaren una variable de entorno).

Por supuesto, no logrará nada útil ejecutando solo cd en ese script en línea (aparte de verificar si rootpuede hacerlo cdallí). Presumiblemente, desea que ese script esté cdallí y luego haga algo más como:

sudo bash -c 'cd -P -- "$1" && do-something' bash "$myvar"

Si la intención era usar sudopara poder cdingresar a un directorio al que de otra manera no tendría acceso, entonces eso realmente no puede funcionar.

sudo sh -c 'cd -P -- "$1" && exec bash' sh "$myvar"

iniciará un interactivo bashcon su directorio actual en $myvar. Pero ese shell se ejecutará como root.

Podrías hacerlo:

sudo sh -c 'cd -P -- "$1" && exec sudo -u "$SUDO_USER" bash' sh "$myvar"

Para obtener una interacción no privilegiada bashcon el directorio actual $myvar, pero si no tenía los permisos para cdingresar a ese directorio en primer lugar, no podrá hacer nada en ese directorio, incluso si es su directorio de trabajo actual.

$ myvar=/var/spool/cron/crontabs
$ sudo sh -c 'cd -P -- "$1" && exec sudo -u "$SUDO_USER" bash' sh "$myvar"
bash-4.4$ ls
ls: cannot open directory '.': Permission denied

Una excepción sería si tiene permiso de búsqueda para el directorio en sí pero no para uno de los componentes del directorio de su ruta:

$ myvar=1/2
$ mkdir -p "$myvar"
$ chmod 0 1
$ cd 1/2
cd: permission denied: 1/2
$ sudo sh -c 'cd -P -- "$1" && exec sudo -u "$SUDO_USER" bash' sh "$myvar"
bash-4.4$ pwd
/home/stephane/1/2
bash-4.4$ mkdir 3
bash-4.4$ ls
3
bash-4.4$ cd "$PWD"
bash: cd: /home/stephane/1/2: Permission denied

¹ estrictamente hablando, para valores de $myvarlike $(seq 10)(literalmente), habría división de palabras, por supuesto, al expandir la sustitución de ese comando por el bash shell iniciado comoroot

Stéphane Chazelas
fuente
4

Hay una nueva magia de citas (bash 4.4) que te permite hacer más directamente lo que quieres. Dependiendo del contexto más amplio, usar una de las otras técnicas podría ser mejor, pero funciona en este contexto limitado.

sudo bash -c "cd ${myvar@Q}; pwd"
Seth Robertson
fuente
4

Si no puede confiar en el contenido de $myvar, el único enfoque razonable es usar GNU printfpara crear una versión escapada:

#!/bin/bash

myvar=$(printf '%q' "$myvar")
bash -c "echo $myvar"

Como printfdice la página del manual:

%q

ARGUMENTO se imprime en un formato que se puede reutilizar como entrada de shell, escapando los caracteres no imprimibles con la $''sintaxis POSIX propuesta .

Toby Speight
fuente
Vea mi respuesta para una versión portátil de esto.
R .. GitHub DEJA DE AYUDAR AL HIELO
GNU printfsolo se invocará allí en los sistemas GNU y si $(printf '%q' "$myvar")se ejecuta en un shell donde printfno está integrado. La mayoría de las conchas se han printfincorporado hoy en día. No todos admiten un %qpensamiento y cuando lo hacen, citarían las cosas en un formato compatible con el shell correspondiente, que puede no ser necesariamente el mismo que el entendido bash. En realidad, bash's printffalla citar cosas correctamente, incluso para sí mismo para algunos valores de $myvaren algunos lugares.
Stéphane Chazelas
Con bash-4.3al menos, eso es todavía una vulnerabilidad de inyección de comandos con myvar=$'\xa3``reboot`\xa3`' en un zh_HK.big5hkscs locale por ejemplo.
Stéphane Chazelas
3

En primer lugar, como otros han notado, su cdcomando es inútil ya que ocurre en el contexto de un shell que se cierra inmediatamente. Pero en general la pregunta tiene sentido. Lo que necesita es una forma de citar shell una cadena arbitraria , de modo que pueda usarse en un contexto donde se interpretará como una cadena citada por shell. Tengo un ejemplo de esto en mi página de trucos sh :

quote () { printf %s\\n "$1" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/'/" ; }

Con esta función, puede hacer:

myvar="/path to/my directory"
sudo bash -c "cd $(quote "$myvar")"
R .. GitHub DEJA DE AYUDAR AL HIELO
fuente
3

Lo que sugiere Stéphane Chazelas es definitivamente la mejor manera de hacer esto. Solo proporcionaré una respuesta sobre cómo cotizar de manera segura con la sintaxis de expansión de parámetros en lugar de usar un sedsubproceso, como en la respuesta de R ...

myvar='foo \'\''"bar'

quote() {
  printf "'%s'" "${1//\'/\'\\\'\'}"
}

printf "value: %s\n" "$myvar"
printf "quoted: %s\n" "$(quote "$myvar")"
bash -c "printf 'interpolated in subshell script: %s\n' $(quote "$myvar")"

La salida de ese script es:

value: foo \'"bar
quoted: 'foo \'\''"bar'
interpolated in subshell script: foo \'"bar
JoL
fuente
1

Sí, hay división de palabras.
Bueno, técnicamente, la división de la línea de comando en bash analiza la línea.

Primero comencemos citando bien:

La solución más simple (e insegura) para que el comando funcione como creo que querrá que sea es:

bash -c "cd \"$myvar\""

Confirmemos lo que sucede (suponiendo myvar="/path to/my directory"):

$ bash -c "printf '<%s>\n' $myvar"
<./path>
<to/my>
<directory>

Como puede ver, la cadena de ruta se dividió en espacios. Y:

$ bash -c "printf '<%s>\n' \"$myvar\""
<./path to/my directory>

No está dividido

Sin embargo, el comando (como está escrito) es inseguro. Mejor uso:

$ bash -c "cd -P -- \"$myvar\"; pwd"
/home/user/temp/path to/my directory

Eso protegerá el CD contra archivos que comienzan con un guión (-) o que siguen enlaces que pueden generar problemas.

Eso funcionará si puede confiar en que la cadena $myvares una ruta.
Sin embargo, la "inyección de código" todavía es posible ya que está "ejecutando una cadena":

$ myvar='./path to/my directory";date;"true'
$ bash -c "printf '<%s> ' \"$myvar\"; echo"
<./path to/my directory> Tue May  8 12:25:51 UTC 2018

Necesita una cita más fuerte de la cadena, como:

$ bash -c 'printf "<%s> " "$1"; echo' _ "$myvar"
<./path to/my directory";date -u;"true>

Como puede ver arriba, el valor no se interpretó sino que se usó como "$1'.

Ahora podemos (con seguridad) usar sudo:

$ sudo bash -c 'cd -P -- "$1"; pwd' _ "$myvar"
_: line 0: cd: ./path to/my directory";date -u;"true: No such file or directory

Si hay varios argumentos, puede usar:

$ sudo bash -c 'printf "<%s>" "$@"' _ "$val1" "$val2" "$val3"
Isaac
fuente
-2

Las comillas simples no funcionarán aquí, ya que su variable no se expandirá. Si está seguro de que la variable para su ruta está desinfectada, la solución más simple es simplemente agregar comillas alrededor de la expansión resultante, una vez que esté en el nuevo shell bash:

sudo bash -c "cd \"$myvar\""
cunninghamp3
fuente
2
Sigue siendo una vulnerabilidad de inyección de comandos, como para valores de $myvarigual $(reboot)o"; reboot; #
Stéphane Chazelas
1
usar sudo bash -c "cd -P -- '$myvar'"limitaría el problema a solo valores $myvar que contengan comillas simples que serían más fáciles de desinfectar.
Stéphane Chazelas