comment puis-je citer une expansion variable à l'intérieur d'une chaîne pour éviter le fractionnement de mots?

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

Dans ce cas, comment puis-je citer $myvarpour éviter la division des mots en raison des espaces blancs dans la valeur de myvar?

Tim
la source
4
Peu importe comment vous résolvez cela, cela ne fera toujours pas grand-chose. Le cdseul a un effet à l'intérieur de la bash -ccoque.
Kusalananda
Je ne donne qu'un exemple minimal pour la question seulement, ce n'est pas une solution de travail à mon vrai problème, qui est unix.stackexchange.com/a/269080/674 plus que dans la chaîne de commande donnée sudo bash -cj'ai une extension variable vers un chemin qui pourrait contenir des espaces blancs.
Tim

Réponses:

13

Il n'y a pas de division de mot (comme dans la fonctionnalité qui fractionne les variables lors des extensions non cotées) dans ce code car il $myvarn'est pas non cité.

Il existe cependant une vulnérabilité d'injection de commandes telle qu'elle $myvarest développée avant d'être transmise à bash. Son contenu est donc interprété comme du code bash!

Les espaces qui s'y trouvent entraîneront la transmission de plusieurs arguments cd, non pas à cause du fractionnement de mots , mais parce qu'ils seront analysés comme plusieurs jetons dans la syntaxe du shell. Avec une valeur de bye;reboot, cela redémarrera! ¹

Ici, vous voudriez:

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

(où vous passez le contenu de $myvarcomme premier argument de ce script en ligne; notez comment les deux $myvaret $1ont été cités pour leur shell respectif afin d'empêcher le fractionnement de mots IFS (et la globalisation)).

Ou:

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

(où vous passez le contenu de $myvardans une variable d'environnement).

Bien sûr, vous n'obtiendrez rien d'utile en exécutant uniquement cd dans ce script en ligne (à part vérifier si vous rootpouvez cdy entrer). Vraisemblablement, vous voulez que ce script soit cdlà et ensuite faire autre chose comme:

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

Si l'intention était d'utiliser sudopour pouvoir accéder à cdun répertoire auquel vous n'avez pas accès autrement, cela ne peut pas vraiment fonctionner.

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

lancera une interaction bashavec son répertoire actuel dans $myvar. Mais ce shell fonctionnera comme root.

Vous pourriez faire:

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

Pour obtenir une interaction non privilégiée bashavec le répertoire actuel $myvar, mais si vous n'aviez pas les autorisations pour cdy accéder en premier lieu, vous ne pourrez rien faire dans ce répertoire même s'il s'agit de votre répertoire de travail actuel.

$ 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

Une exception serait si vous disposez d'une autorisation de recherche sur le répertoire lui-même mais pas sur l'un des composants de répertoire de son chemin:

$ 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

¹ à proprement parler, pour des valeurs $myvarsimilaires $(seq 10)(littéralement), il y aurait bien sûr un fractionnement des mots lors de l'expansion de cette substitution de commandes par le bash shell commencé commeroot

Stéphane Chazelas
la source
4

Il y a une nouvelle (bash 4.4) citant la magie qui vous permet de faire plus directement ce que vous voulez. Selon le contexte plus large, l'utilisation de l'une des autres techniques pourrait être meilleure, mais cela fonctionne dans ce contexte limité.

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

Si vous ne pouvez pas faire confiance au contenu de $myvar, la seule approche raisonnable consiste à utiliser GNU printfpour en créer une version échappée:

#!/bin/bash

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

Comme le printfdit la page de manuel:

%q

ARGUMENT est imprimé dans un format qui peut être réutilisé comme entrée de shell, en échappant les caractères non imprimables avec la $''syntaxe POSIX proposée .

Toby Speight
la source
Voir ma réponse pour une version portable de ceci.
R .. GitHub STOP HELPING ICE
GNU printfn'y sera invoqué que sur les systèmes GNU et s'il $(printf '%q' "$myvar")est exécuté dans un shell où il printfn'est pas intégré. La plupart des obus ont printfintégré de nos jours. Tous ne prennent pas en charge un %qbien et quand ils le font, ils citeraient quelque chose dans un format pris en charge par le shell correspondant, qui n'est pas nécessairement le même que celui compris par bash. En fait, bash« s printfne parvient pas à citer correctement les choses pour lui - même , même pour certaines valeurs de $myvardans certains endroits.
Stéphane Chazelas
Avec bash-4.3au moins, c'est toujours une vulnérabilité d'injection de commande avec myvar=$'\xa3``reboot`\xa3`' dans une locale zh_HK.big5hkscs par exemple.
Stéphane Chazelas
3

Tout d'abord, comme d'autres l'ont remarqué, votre cdcommande est inutile car elle se produit dans le contexte d'un shell qui sort immédiatement. Mais en général, la question a du sens. Ce dont vous avez besoin est un moyen de shell citer une chaîne arbitraire , afin qu'elle puisse être utilisée dans un contexte où elle sera interprétée comme une chaîne entre shell. J'en ai un exemple dans ma page astuces sh :

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

Avec cette fonction, vous pouvez alors faire:

myvar="/path to/my directory"
sudo bash -c "cd $(quote "$myvar")"
R .. GitHub ARRÊTEZ D'AIDER LA GLACE
la source
3

Ce que Stéphane Chazelas suggère est certainement la meilleure façon de procéder. Je vais juste fournir une réponse sur la façon de citer en toute sécurité avec la syntaxe d'expansion des paramètres plutôt que d'utiliser un sedsous - processus, comme dans la réponse 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 sortie de ce script est:

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

Oui, il y a division des mots.
Eh bien, techniquement, la division de la ligne de commande lors de l'analyse de la ligne par bash.

Commençons par citer correctement:

La solution la plus simple (et non sécurisée) pour faire fonctionner la commande comme je pense que vous voudrez qu'elle soit:

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

Confirmons ce qui se passe (en supposant myvar="/path to/my directory"):

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

Comme vous pouvez le voir, la chaîne de chemin a été divisée en espaces. Et:

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

N'est pas divisé.

Cependant, la commande (telle qu'elle est écrite) n'est pas sécurisée. Meilleure utilisation:

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

Cela protégera le CD contre les fichiers commençant par un tiret (-) ou les liens suivants qui peuvent entraîner des problèmes.

Cela fonctionnera si vous pouvez avoir confiance que la chaîne $myvarest un chemin.
Cependant, "l'injection de code" est toujours possible car vous "exécutez une chaîne":

$ 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

Vous avez besoin d'une citation plus forte de la chaîne, comme:

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

Comme vous pouvez le voir ci-dessus, la valeur n'a pas été interprétée mais simplement utilisée comme "$1'.

Maintenant, nous pouvons (en toute sécurité) utiliser sudo:

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

S'il y a plusieurs arguments, vous pouvez utiliser:

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

Les guillemets simples ne fonctionneront pas ici, car votre variable ne sera pas développée. Si vous êtes sûr que la variable de votre chemin est filtrée, la solution la plus simple consiste à simplement ajouter des guillemets autour de l'expansion résultante, une fois qu'elle est dans le nouveau shell bash:

sudo bash -c "cd \"$myvar\""
cunninghamp3
la source
2
Encore une vulnérabilité d'injection de commandes, comme pour les valeurs de $myvarlike $(reboot)ou"; reboot; #
Stéphane Chazelas
1
l'utilisation sudo bash -c "cd -P -- '$myvar'"limiterait le problème aux seules valeurs $myvar qui contiennent des guillemets simples qui seraient plus faciles à nettoyer.
Stéphane Chazelas