Est-il dangereux d'exécuter l'écho sans guillemets?

11

J'ai vu quelques sujets similaires, mais ils se réfèrent à ne pas citer de variables, ce qui, je le sais, pourrait conduire à des résultats indésirables.

J'ai vu ce code et je me demandais s'il serait possible d'injecter quelque chose à exécuter lorsque cette ligne de code s'exécutera:

echo run after_bundle

Viktor Fonic
la source
Je suis tombé sur cela quand j'avais: target = "*** LIVE SERVER ***"; écho cible: $ cible; et le *** développé dans une liste de dossiers ... 😬
Matt Parkins

Réponses:

17

Pour le cas spécifique

echo run after_bundle

la citation n'est pas nécessaire. Aucune citation n'est nécessaire car l'argument to echoest des chaînes statiques qui ne contiennent aucune extension de variable ou substitution de commande, etc. Ce ne sont que "deux mots" (et comme le souligne Stéphane , elles sont en outre construites à partir du jeu de caractères portable ).

Le "danger" survient lorsque vous traitez des données variables que le shell peut développer ou interpréter. Dans de tels cas, il faut veiller à ce que la coque fasse la bonne chose et que le résultat soit ce qui est prévu.

Les deux questions suivantes contiennent des informations pertinentes à ce sujet:


echoest parfois utilisé pour "protéger" les commandes potentiellement nuisibles dans les réponses sur ce site. Par exemple, je peux montrer comment supprimer des fichiers ou déplacer des fichiers vers une nouvelle destination en utilisant

echo rm "${name##*/}.txt"

ou

echo mv "$name" "/new_dir/$newname"

Cela produirait des commandes sur le terminal au lieu de supprimer ou de renommer des fichiers. L'utilisateur pourrait alors inspecter les commandes, décider qu'elles semblent correctes, les supprimer echoet recommencer.

Votre commande echo run after_bundlepeut être une instruction pour l'utilisateur, ou peut être un morceau de code "mis en commentaire" qui est trop dangereux pour être exécuté sans en connaître les conséquences.

En utilisant echocomme ça, il faut savoir ce que fait la commande modifiée et il faut garantir que la commande modifiée est réellement sûre (ce ne serait potentiellement pas le cas si elle contenait des redirections, et son utilisation sur un pipeline ne fonctionne pas, etc.)

Kusalananda
la source
Cependant, ajouter des guillemets ne suffit pas pour savoir ce que ferait un shell - tout comme vous ne pouvez pas dire que cela echo rm "first file.txt" "second file.txt"est en aucune façon différent echo rm "first" "file.txt" "second" "file.txt", la sortie des deux étant la même. Si vous souhaitez générer une commande shell en sortie, vous devez utiliser printf '%q ' rm "first file.txt" "second file.txt"; echoou quelque chose d'équivalent qui régénère les guillemets syntaxiques qui évaluent le argvpassé.
Charles Duffy
@CharlesDuffy J'espère vraiment que personne ne copiera-copiera la sortie de débogage et l'exécutera dans le shell!
Kusalananda
1
Générer des commandes shell et les rediriger shn'est pas exactement un modèle inhabituel, et voir les gens se demander "pourquoi ça foomarche quand je l'exécute sur une ligne de commande, mais ce script qui émet cette chaîne exacte avec echodevant la ligne ne fonctionne pas? " arrive tout le temps ici. Plus précisément, la sortie de débogage n'est pas utile si elle masque vos bogues - et si vos bogues sont liés à des citations, echoils ne les révéleront pas.
Charles Duffy
27

Juste une note supplémentaire en plus de la bonne réponse de @ Kusalananda .

echo run after_bundle

est correct car aucun des caractères de ces 3 arguments¹ n'est passé pour echocontenir des caractères spéciaux pour le shell.

Et (le point supplémentaire que je veux faire ici), il n'y a pas de paramètres régionaux du système où ces octets pourraient se traduire par des caractères spéciaux pour le shell.

Tous ces caractères se trouvent dans ce que POSIX appelle le jeu de caractères portable . Ces caractères doivent être présents et codés de la même manière dans tous les jeux de caractères d'un système POSIX².

Ainsi, cette ligne de commande sera interprétée de la même manière, indépendamment des paramètres régionaux.

Maintenant, si nous commençons à utiliser des caractères en dehors de ce jeu de caractères portable, c'est une bonne idée de les citer même s'ils ne sont pas spéciaux pour le shell, car dans un autre endroit, les octets qui les constituent peuvent être interprétés comme des caractères différents qui pourraient devenir spécial à la coquille. Notez que c'est que vous utilisiez echoou toute autre commande, le problème n'est pas avec echomais avec la façon dont le shell analyse son code.

Par exemple dans un UTF-8:

echo voilà | iconv -f UTF-8 -t //TRANSLIT

Cela àest codé comme 0xc3 0xa0. Maintenant, si vous avez cette ligne de code dans un script shell et que le script shell est invoqué par un utilisateur qui utilise un paramètre régional dont le jeu de caractères n'est pas UTF-8, ces deux octets pourraient faire des caractères très différents.

Par exemple, dans un fr_FR.ISO8859-15environnement local, un environnement local français typique utilisant le jeu de caractères standard à un octet qui couvre la langue française (le même que celui utilisé pour la plupart des langues d'Europe occidentale, y compris l'anglais), cet octet 0xc3 est interprété comme le Ãcaractère et 0xa0 comme le non briser le caractère de l'espace.

Et sur quelques systèmes comme NetBSD³, cet espace insécable est considéré comme un caractère vide ( isblank()sur il retourne vrai, il est assorti de [[:blank:]]) et les shells comme le bashtraitent donc comme un délimiteur de jeton dans leur syntaxe.

Cela signifie qu'au lieu de s'exécuter echoavec $'voil\xc3\xa0'comme argument, ils l'exécutent avec $'voil\xc3'comme argument, ce qui signifie qu'il ne s'imprimera pas voilàcorrectement.

Il obtient bien pire avec les jeux de caractères chinois comme BIG5, BIG5-HKSCS, GB18030, GBK qui ont beaucoup de caractères dont le codage contient le même encodage que le |, `, \(pour nommer le pire) (que SJIS risible, alias Microsoft Kanji, à l' exception que c'est au ¥lieu de \, mais toujours traité comme \par la plupart des outils car il est encodé en 0x5c).

Par exemple, si dans un zh_CN.gb18030environnement chinois, vous écrivez un script comme:

echo  reboot

Ce script sortira 詜 rebootdans un environnement local utilisant GB18030 ou GBK, 唰 rebootdans un environnement local utilisant BIG5 ou BIG5-HKSCS, mais dans un environnement local C utilisant ASCII ou un environnement local utilisant ISO8859-15 ou UTF-8, entraînera rebootl'exécution parce que l'encodage GB18030 de est 0xd4 0x7c et 0x7c est l'encodage |en ASCII nous finissons donc en cours d' exécution:

 echo �| reboot

(ce représentant cependant l'octet 0xd4 est rendu dans les paramètres régionaux). Exemple utilisant le moins nocif unameau lieu de reboot:

$ echo $'echo \u8a5c uname' | iconv -t gb18030 > myscript
$ LC_ALL=zh_CN.gb18030 bash ./myscript | sed -n l
\324| uname$
$ LC_ALL=C bash ./myscript | sed -n l
Linux$

(a unameété exécuté).

Donc, mon conseil serait de citer toutes les chaînes qui contiennent des caractères en dehors du jeu de caractères portable.

Cependant, notez que puisque l'encodage de \et `se trouve dans l'encodage de certains de ces caractères, il est préférable de ne pas utiliser \ou "..."ou $'...'(à l'intérieur duquel `et / ou \sont toujours spéciaux), mais '...'plutôt de citer des caractères en dehors du jeu de caractères portable.

Je ne connais aucun système qui a une locale où le jeu de caractères a un caractère (autre que 'lui bien sûr) dont l'encodage contient l'encodage de ', donc ceux-ci '...'devraient certainement être les plus sûrs.

Notez que plusieurs shells prennent également en charge une $'\uXXXX'notation pour exprimer des caractères en fonction de leur point de code Unicode. Dans les shells comme zshet bash, le caractère est inséré encodé dans le jeu de caractères de la locale (bien que cela puisse provoquer des comportements inattendus si ce jeu de caractères n'a pas ce caractère). Cela vous permet d'éviter d'insérer des caractères non ASCII dans votre code shell.

Donc ci-dessus:

echo 'voilà' | iconv -f UTF-8 -t //TRANSLIT
echo '詜 reboot'

Ou:

echo $'voil\u00e0'
echo $'\u8a5c reboot'

(avec la mise en garde, il pourrait casser le script lorsqu'il est exécuté dans des locales qui n'ont pas ces caractères).

Ou mieux, car \est également spécial pour echo(ou au moins certaines echo implémentations, au moins celles compatibles Unix):

printf '%s\n' 'voilà' | iconv -f UTF-8 -t //TRANSLIT
printf '%s\n' '詜 reboot'

(notez que cela \est également spécial dans le premier argument de printf, il est donc préférable d'éviter les caractères non ASCII au cas où ils pourraient contenir le codage de \).

Notez que vous pouvez également faire:

'echo' 'voilà' | 'iconv' '-f' 'UTF-8' '-t' '//TRANSLIT'

(ce serait exagéré mais pourrait vous donner une certaine tranquillité d'esprit si vous n'êtes pas sûr des caractères dans le jeu de caractères portable)

Assurez-vous également de ne jamais utiliser l'ancienne `...`forme de substitution de commandes (qui introduit un autre niveau de traitement de barre oblique inverse), mais utilisez-la à la $(...)place.


¹ techniquement, echoest également passé comme argument à l' echoutilitaire (pour lui dire comment il a été invoqué), c'est le argv[0]et argcest 3, bien que dans la plupart des shells de nos jours il echosoit intégré, de sorte que exec()d'un /bin/echofichier avec une liste de 3 arguments est simulé par le coquille. Il est également courant de considérer la liste d'arguments comme commençant par le second ( argv[1]to argv[argc - 1]) car ce sont ceux sur lesquels les commandes agissent principalement.

² une exception notable à cela étant le ja_JP.SJISlieu ridicule des systèmes FreeBSD dont le jeu de caractères n'a \ni ~caractère ni !

³ notez que bien que de nombreux systèmes (FreeBSD, Solaris, pas GNU cependant) considèrent U + 00A0 comme un [[:blank:]]dans les paramètres régionaux UTF-8, peu le font dans d'autres paramètres régionaux comme ceux utilisant ISO8859-15, peut-être pour éviter ce genre de problème.

Stéphane Chazelas
la source
Dans votre premier paragraphe, vous nous dites "... des caractères de ces 3 arguments passés à echo...", je ne compte que 2 arguments passés à la commande echo, les arguments que je peux compter sont runet after_bundle, attention à expliquer comment vous compté et obtenu à 3 arguments?
Ferrybig
1
@ViktorFonic, voir éditer sur le nombre d'arguments (et que le problème principal n'est pas avec echo). Voir (exec -a foo /bin/echo --help)sur un système GNU et avec le shell GNU pour savoir comment passer un premier argument arbitraire de l' /bin/echoutilité.
Stéphane Chazelas
@Ferrybig Voir l'édition de Stephane, note de bas de page 1. Les arguments à commander dans le style C habituel sont un tableau d'arguments, argv [0] étant le nom de l'exécutable lui-même. Un peu similaire aux $0paramètres de position et dans les coquilles.
Sergiy Kolodyazhnyy
Il y a 373 encodage iconvdans lequel ESCest converti en '. Essayez (à titre d'exemple):printf '\x1b'|iconv -f utf8 -t IBM-937|xxd
Isaac
Il existe 173 codages dans lesquels certains points de code (autres que ESC) sont convertis en a '. Essayez printf '\u2804' | iconv -f utf8 -t BRF | xxd. Il y a des encodages dans lesquels il y a beaucoup de points de code qui deviennent '. Environ 8695 codepoints dans UCS-4 deviennent '. Essayez printf '\U627' | iconv -cf utf-8 -t UCS-4. Plusieurs (37) encodages convertissent le caractère 0x127 en a '. Essayezprintf '\U127' | iconv -cf utf8 -t UCS2 |xxd
Isaac