"Exécutez n'importe quelle commande qui passera des données non fiables à des commandes qui interprètent les arguments comme des commandes"

18

Du manuel de findutils:

Par exemple, des constructions telles que ces deux commandes

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

sont très dangereux. La raison en est que le '{}' est développé en un nom de fichier qui peut contenir un point-virgule ou d'autres caractères spéciaux pour le shell. Si par exemple quelqu'un crée le fichier, /tmp/foo; rm -rf $HOMEles deux commandes ci-dessus pourraient supprimer le répertoire personnel de quelqu'un.

Donc, pour cette raison, n'exécutez aucune commande qui transmettra des données non fiables (telles que les noms de fichiers) à des commandes qui interprètent des arguments comme des commandes à interpréter davantage (par exemple «sh»).

Dans le cas du shell, il existe une solution de contournement intelligente pour ce problème:

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

Cette approche n'est pas garantie pour éviter tous les problèmes, mais elle est beaucoup plus sûre que de remplacer les données du choix d'un attaquant dans le texte d'une commande shell.

  1. La cause du problème est-elle find -exec sh -c "something {}" \;{} est-il dû que le remplacement de est sans guillemets et n'est donc pas traité comme une chaîne unique?
  2. Dans la solution find -exec sh -c 'something "$@"' sh {} \; ,

    • le premier {}est remplacé, mais comme il {}n'est pas cité, n'a-t-il pas "$@"également le même problème que la commande d'origine? Par exemple, "$@"sera étendu à "/tmp/foo;", "rm", "-rf"et "$HOME" ?

    • pourquoi n'est-il {}pas échappé ou cité?

  3. Pourriez-vous donner d'autres exemples (toujours avec sh -cou sans celui-ci le cas échéant; avec ou sans findqui peuvent ne pas être nécessaires) où le même type de problème et de solution s'appliquent, et qui sont des exemples minimaux afin que nous puissions nous concentrer sur le problème et la solution avec peu de distraction possible? Voir Façons de fournir des arguments à une commande exécutée par `bash -c`

Merci.

Tim
la source

Réponses:

29

Ce n'est pas vraiment lié à la citation, mais plutôt au traitement des arguments.

Prenons l'exemple risqué:

find -exec sh -c "something {}" \;
  • Ceci est analysé par la coquille, et divisée en six mots: find, -exec, sh, -c, something {}(sans guillemets plus), ;. Il n'y a rien à développer. Le shell s'exécute findavec ces six mots comme arguments.

  • Quand findtrouve quelque chose à traiter, par exemple foo; rm -rf $HOME, il remplace {}avec foo; rm -rf $HOME, et fonctionne shavec les arguments sh, -c, et something foo; rm -rf $HOME.

  • shvoit maintenant -c, et en conséquence analyse something foo; rm -rf $HOME( le premier argument sans option ) et exécute le résultat.

Considérez maintenant la variante la plus sûre:

find -exec sh -c 'something "$@"' sh {} \;
  • Le shell fonctionne findavec les arguments find, -exec, sh, -c, something "$@", sh, {}, ;.

  • Maintenant , quand findtrouve foo; rm -rf $HOME, il remplace à {}nouveau, et fonctionne shavec les arguments sh, -c, something "$@", sh, foo; rm -rf $HOME.

  • shvoit -c, et analyse something "$@"que la commande à exécuter, et shet foo; rm -rf $HOMEque les paramètres de position ( à partir de$0 ), se dilate "$@"à foo; rm -rf $HOME une valeur unique , et fonctionne somethingavec l'argument unique foo; rm -rf $HOME.

Vous pouvez le voir en utilisant printf. Créez un nouveau répertoire, entrez-le et exécutez

touch "hello; echo pwned"

Exécution de la première variante comme suit

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

produit

Argument: .
Argument: ./hello
pwned

alors que la deuxième variante,

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

produit

Argument: .
Argument: ./hello; echo pwned
Stephen Kitt
la source
Dans la variante plus sûre, pourquoi n'est-il {}pas échappé ou cité?
Tim
1
Il n'a généralement pas besoin d'être cité; il suffirait de le citer dans un shell où il a une autre signification, et je ne pense pas que ce soit le cas avec les principaux shells utilisés de nos jours.
Stephen Kitt
De gnu.org/software/findutils/manual/html_mono/… : "Ces deux constructions ( ;et {}) doivent être échappées (avec un '\') ou citées pour les protéger de l'expansion par le shell." Voulez-vous dire que c'est incorrect pour bash?
Tim
3
Je veux dire que c'est incorrect pour les obus en général. Voir POSIX . Cette déclaration particulière dans le findutilsmanuel remonte à au moins 1996 ... Bien sûr, cela ne fait pas de mal de citer {}, soit comme '{}'ou "{}", mais ce n'est pas nécessaire.
Stephen Kitt
@Tim Vous rencontrez une situation courante où votre intuition ne tient pas encore compte du fait qu'il existe deux types très différents de traitement d'arguments en cours ici: quand / comment le shell analyse les arguments d'une ligne de texte (qui est l'endroit où les guillemets comptent) est différent du passage brut des arguments du système d'exploitation (où les guillemets ne sont que des caractères normaux). Dans la commande shell, find some/path -exec sh -c 'something "$@"' {} \;il y a en fait trois couches de traitement / passage d'arguments, deux de la variété shell et une de la variété de base du système d'exploitation brut.
mtraceur
3

Partie 1:

find utilise simplement le remplacement de texte.

Oui, si vous l'avez fait sans guillemets, comme ceci:

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

et un attaquant a réussi à créer un fichier appelé ; echo owned, puis il exécuterait

sh -c "echo ; echo owned"

ce qui entraînerait l'exécution du shell echopuisecho owned .

Mais si vous avez ajouté des guillemets, l'attaquant pourrait simplement mettre fin à vos guillemets puis mettre la commande malveillante après lui en créant un fichier appelé '; echo owned:

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

ce qui entraînerait l'exécution du shell echo '',echo owned .

(si vous échangiez les guillemets doubles contre des guillemets simples, l'attaquant pourrait aussi utiliser l'autre type de guillemets.)


Partie 2:

Dans find -exec sh -c 'something "$@"' sh {} \;, le {}n'est pas initialement interprété par le shell, il est exécuté directement avec execve, donc l'ajout de guillemets au shell n'aiderait pas.

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

n'a aucun effet, car le shell supprime les guillemets doubles avant de s'exécuter find.

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

ajoute des guillemets que le shell ne traite pas spécialement, donc dans la plupart des cas, cela signifie simplement que la commande ne fera pas ce que vous voulez.

Après l' avoir à étendre /tmp/foo;, rm, -rf, $HOMEne devrait pas être un problème, car ce sont des arguments à something, et somethingprobablement ne traite pas ses arguments comme des commandes à exécuter.


Partie 3:

Je suppose que des considérations similaires s'appliquent à tout ce qui prend une entrée non fiable et l'exécute en tant que (partie d'une) commande, par exemple xargset parallel.

Mikel
la source
xargsn'est dangereux que si -Iou -Jest utilisé. En fonctionnement normal, il ajoute uniquement des arguments à la fin de la liste, tout comme le -exec ... {} +fait.
Charles Duffy
1
( parallelen revanche, exécute un shell par défaut sans demande explicite de l'utilisateur, augmentant la surface vulnérable; c'est une question qui a fait l'objet de nombreuses discussions; voir unix.stackexchange.com/questions/349483/… pour une explication, et le lien inclus vers lists.gnu.org/archive/html/bug-parallel/2015-05/msg00005.html ).
Charles Duffy
@CharlesDuffy Vous voulez dire " diminuer la surface vulnérable". L'attaque illustrée par OP ne fonctionne en effet pas par défaut avec GNU Parallel.
Ole Tange
1
Oui, je sais que vous en avez besoin pour la parité sur SSH - si vous n'avez pas généré un parallelprocessus de "récepteur" distant à l'autre extrémité de n'importe quel socket, capable de faire un shell direct sans shell execv. C'est exactement ce que j'aurais fait si vous étiez à la place de l'outil. Un comportement non interactif basé sur la valeur actuelle de SHELLn'est guère étonnant.
Charles Duffy
1
Oui - je considère que les canaux et les redirections devraient être impossibles à moins que l'utilisateur ne démarre explicitement un shell, auquel cas ils n'obtiennent que le comportement explicite du shell qu'ils ont explicitement démarré. Si l'on ne peut s'attendre qu'à ce que execvfournit un , c'est plus simple et moins étonnant, et une règle qui ne change pas entre les emplacements / environnements d'exécution.
Charles Duffy
3

1. Le problème est-il dû au fait find -exec sh -c "something {}" \;que le remplacement de {}est sans guillemets et n'est donc pas traité comme une chaîne unique?

Dans un sens, mais citer ne peut pas aider ici . Le nom de fichier qui est remplacé à la place de {}peut contenir tous les caractères, y compris les guillemets . Quelle que soit la forme de citation utilisée, le nom de fichier peut contenir la même chose et "éclater" de la citation.

2. ... mais puisque {}n'est pas cité, n'a-t-il pas "$@"aussi le même problème que la commande d'origine? Par exemple, "$@"sera étendu à "/tmp/foo;", "rm", "-rf", and "$HOME"?

Non se "$@"développe en paramètres positionnels, en tant que mots séparés, et ne les divise pas davantage. Ici, {}est un argument à findlui-même et findpasse le nom de fichier actuel également comme argument distinct à sh. Il est directement disponible en tant que variable dans le script shell, il n'est pas traité comme une commande shell elle-même.

... pourquoi n'est-il {}pas échappé ou cité?

Ce n'est pas nécessaire, dans la plupart des coquilles. Si vous exécutez fish, cela doit être: fish -c 'echo {}'imprime une ligne vide. Mais peu importe si vous le citez, le shell supprimera simplement les guillemets.

3. Pourriez-vous donner d'autres exemples ...

Chaque fois que vous développez un nom de fichier (ou une autre chaîne non contrôlée) tel quel dans une chaîne qui est considérée comme une sorte de code (*) , il existe une possibilité d'exécution de commande arbitraire.

Par exemple, cela développe $fdirectement le code Perl et causera des problèmes si un nom de fichier contient un guillemet double. La citation dans le nom de fichier mettra fin à la citation dans le code Perl, et le reste du nom de fichier peut contenir n'importe quel code Perl:

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

(Le nom de fichier doit être un peu étrange car Perl analyse tout le code à l'avance, avant d'exécuter tout cela. Nous devons donc éviter une erreur d'analyse.)

Bien que cela passe en toute sécurité à travers un argument:

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

(Cela n'a pas de sens d'exécuter un autre shell directement à partir d'un shell, mais si vous le faites, c'est similaire au find -exec sh ... cas)

(* une sorte de code inclut SQL, donc XKCD obligatoire: https://xkcd.com/327/ plus explication: https://www.explainxkcd.com/wiki/index.php/Little_Bobby_Tables )

ilkkachu
la source
1

Votre préoccupation est précisément la raison pour laquelle GNU Parallel cite une entrée:

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

Cela ne fonctionnera pas echo pwned.

Il exécutera un shell, de sorte que si vous étendez votre commande, vous n'aurez pas soudainement une surprise:

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

Pour plus de détails sur le problème de la génération d'un shell, voir: https://www.gnu.org/software/parallel/parallel_design.html#Always-running-commands-in-a-shell

Ole Tange
la source
1
... cela dit, find . -print0 | xargs -0 printf "Argument: %s\n"est tout aussi sûr (ou plutôt, moreso, car avec -print0un gère correctement les noms de fichiers avec des retours à la ligne); la citation parallèle est une solution de contournement pour un problème qui n'existe pas du tout quand aucun shell n'est présent.
Charles Duffy
Mais dès que vous ajoutez | wcà la commande, vous devez sauter à travers des cerceaux pour la rendre sûre. GNU Parallel est sécurisé par défaut.
Ole Tange
ITYM: il essaie de couvrir toutes les bizarreries du langage shell par le processus compliqué de tout citer soigneusement. Ce n'est pas aussi sûr que de stocker correctement les données dans des variables, pas mélangées avec du code. Maintenant, s'il devait convertir automatiquement cela foo {} | wcen sh -c 'foo "$1" | wcsh {} `, cela pourrait être différent.
ilkkachu du