Préambule
Premièrement, je dirais que ce n'est pas la bonne façon de résoudre le problème. C'est un peu comme dire " tu ne devrais pas assassiner des gens, sinon tu iras en prison ".
De même, vous ne citez pas votre variable car sinon, vous introduisez des vulnérabilités de sécurité. Vous citez vos variables car il est faux de ne pas le faire (mais si la peur de la prison peut aider, pourquoi pas).
Un petit résumé pour ceux qui viennent de sauter dans le train.
Dans la plupart des shells, laisser un développement variable sans guillemets (bien que cela (et le reste de cette réponse) s'applique également à la substitution de commande ( `...`
ou $(...)
) et au développement arithmétique ( $((...))
ou $[...]
)) a une signification toute particulière. La meilleure façon de le décrire est que cela revient à invoquer une sorte d’ opérateur implicite scindé + glob¹ .
cmd $var
dans une autre langue serait écrit quelque chose comme:
cmd(glob(split($var)))
$var
est d'abord divisé en une liste de mots selon des règles complexes impliquant le $IFS
paramètre spécial (la partie divisée ), puis chaque mot résultant de cette division est considéré comme un motif qui est développé en une liste de fichiers qui lui correspond (la partie globale ) .
Par exemple, si $var
contient *.txt,/var/*.xml
et $IFS
contient ,
, cmd
serait appelé avec un certain nombre d'arguments, le premier cmd
et les suivants étant les txt
fichiers du répertoire en cours et les xml
fichiers du répertoire /var
.
Si vous vouliez appeler cmd
avec seulement deux arguments littéraux cmd
et *.txt,/var/*.xml
écrire, vous écririez:
cmd "$var"
qui serait dans votre autre langue plus familière:
cmd($var)
Qu'entendons-nous par vulnérabilité dans un shell ?
Après tout, on sait depuis la nuit des temps que les scripts shell ne doivent pas être utilisés dans des contextes sensibles à la sécurité. OK, laisser une variable sans guillemets est un bogue, mais cela ne fait pas beaucoup de mal, n'est-ce pas?
Bien que quiconque vous dise que les scripts shell ne doivent jamais être utilisés pour les CGI Web, ou que, heureusement, la plupart des systèmes n’autorisent plus les scripts shell setuid / setgid de nos jours, le shellshock (le bogue bash exploitable à distance titre en septembre 2014) a révélé que les shells sont encore largement utilisés là où ils ne devraient probablement pas: dans les CGI, dans les scripts de hook client DHCP, dans les commandes sudoers, invoqués par (sinon comme ) les commandes setuid ...
Parfois sans le savoir. Par exemple, system('cmd $PATH_INFO')
dans un script php
/ perl
/ python
CGI, un shell est interprété pour interpréter cette ligne de commande (sans parler du fait qu’il cmd
peut s’agir d’un script shell et que son auteur ne s’est jamais attendu à ce qu’il soit appelé depuis un CGI).
Vous avez une vulnérabilité quand il y a un chemin pour l'ascension des privilèges, c'est-à-dire quand quelqu'un (appelons-le l'attaquant ) est capable de faire quelque chose qu'il n'est pas censé faire.
Invariablement, cela signifie que l'attaquant fournit des données, ces données étant traitées par un utilisateur / processus privilégié qui fait par inadvertance quelque chose qu'il ne devrait pas faire, dans la plupart des cas à cause d'un bogue.
En gros, vous rencontrez un problème lorsque votre code de bogue traite des données sous le contrôle de l'attaquant .
À présent, la provenance de ces données n’est pas toujours évidente , et il est souvent difficile de dire si votre code parviendra à traiter des données non fiables.
En ce qui concerne les variables, dans le cas d'un script CGI, il est assez évident que les données sont les paramètres CGI GET / POST et des éléments tels que les cookies, le chemin d'accès, les paramètres de l'hôte ....
Pour un script setuid (exécuté en tant qu'utilisateur lorsqu'il est appelé par un autre), il s'agit des arguments ou des variables d'environnement.
Les noms de fichiers sont un autre vecteur très courant. Si vous obtenez une liste de fichiers à partir d’un répertoire, il est possible que l’attaquant y ait planté des fichiers .
À cet égard, même à l'invite d'un shell interactif, vous pourriez être vulnérable (lors du traitement de fichiers dans /tmp
ou ~/tmp
par exemple).
Même un ~/.bashrc
peut être vulnérable (par exemple, bash
l'interprète lorsqu'il est appelé ssh
pour exécuter un processus ForcedCommand
similaire à celui d' git
un déploiement de serveur avec certaines variables sous le contrôle du client).
Désormais, un script ne peut pas être appelé directement pour traiter des données non fiables, mais il peut être appelé par une autre commande. Ou votre code incorrect peut être copié-collé dans des scripts (par vous 3 ans en arrière ou l'un de vos collègues). Les réponses dans les sites de questions-réponses sont particulièrement critiques , car vous ne saurez jamais où les copies de votre code pourraient se retrouver.
Aux affaires; c'est comment?
Laisser une variable (ou une substitution de commande) sans guillemets est de loin la source de vulnérabilités en matière de sécurité associée au code shell. En partie parce que ces bogues se traduisent souvent par des vulnérabilités, mais aussi parce qu'il est si courant de voir des variables non citées.
En fait, lorsque vous recherchez des vulnérabilités dans le code shell, la première chose à faire est de rechercher des variables non citées. Il est facile de repérer, souvent un bon candidat, généralement facile de retrouver des données contrôlées par des attaquants.
Il existe un nombre infini de façons dont une variable non citée peut se transformer en vulnérabilité. Je vais juste donner quelques tendances communes ici.
Divulgation d'information
La plupart des gens se heurteront à des bogues associés à des variables sans guillemets à cause de la partie scindée (par exemple, il est courant que les fichiers comportent des espaces dans leurs noms et que la valeur par défaut est IFS). Beaucoup de gens vont négliger la
partie glob . La partie glob est au moins aussi dangereuse que la
partie scindée .
Globbing sur une entrée externe non authentifiée signifie que l'attaquant peut vous faire lire le contenu de n'importe quel répertoire.
Dans:
echo You entered: $unsanitised_external_input
si $unsanitised_external_input
contient /*
, cela signifie que l'attaquant peut voir le contenu de /
. Pas grand chose. Cela devient cependant plus intéressant, avec la /home/*
liste des noms d’utilisateur de la machine /tmp/*
, /home/*/.forward
des astuces sur d’autres pratiques dangereuses, /etc/rc*/*
des services activés ... Inutile de les nommer individuellement. Une valeur de /*
/*/* /*/*/*...
va simplement lister tout le système de fichiers.
Vulnérabilités de déni de service.
Prenant le cas précédent un peu trop loin et nous avons un DoS.
En fait, toute variable non citée dans le contexte de liste avec une entrée non authentifiée est au moins une vulnérabilité de type DoS.
Même les scripteurs experts oublient souvent de citer des choses telles que:
#! /bin/sh -
: ${QUERYSTRING=$1}
:
est la commande no-op. Qu'est ce qui pourrait aller mal?
Cela est destiné à affecter $1
à $QUERYSTRING
si $QUERYSTRING
était non défini. C'est un moyen rapide de rendre un script CGI appelable à partir de la ligne de commande.
Cela $QUERYSTRING
reste cependant étendu et comme il n’est pas cité, l’ opérateur split + glob est appelé.
Maintenant, il y a des globs qui sont particulièrement coûteux à développer. Celui- /*/*/*/*
ci est déjà assez mauvais car cela signifie qu’il faut lister les répertoires jusqu’à 4 niveaux. En plus de l'activité du disque et du processeur, cela signifie stocker des dizaines de milliers de chemins de fichiers (40k ici sur une VM serveur minimale, dont 10k répertoires).
Désormais, cela /*/*/*/*/../../../../*/*/*/*
signifie 40k x 10k et
/*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/*
est suffisant pour amener même la machine la plus puissante à ses genoux.
Essayez-le vous-même (préparez-vous à ce que votre machine tombe en panne ou se bloque):
a='/*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/*' sh -c ': ${a=foo}'
Bien sûr, si le code est:
echo $QUERYSTRING > /some/file
Ensuite, vous pouvez remplir le disque.
Il suffit de faire une recherche google sur shell cgi ou bash cgi ou ksh cgi et vous trouverez quelques pages qui vous montreront comment écrire des CGI dans des shells. Notez que la moitié de ceux qui traitent les paramètres sont vulnérables.
Même celui de David Korn
est vulnérable (regardez la gestion des cookies).
jusqu'à vulnérabilités d'exécution de code arbitraire
L'exécution de code arbitraire est le pire type de vulnérabilité, car si l'attaquant peut exécuter n'importe quelle commande, il n'y a aucune limite à ce qu'il peut faire.
C'est généralement la partie divisée qui mène à ceux-ci. Cette division entraîne la transmission de plusieurs arguments aux commandes lorsqu'un seul est attendu. Tandis que le premier de ceux-ci sera utilisé dans le contexte attendu, les autres seront dans un contexte différent, donc potentiellement interprété différemment. Mieux avec un exemple:
awk -v foo=$external_input '$2 == foo'
Ici, l’intention était d’affecter le contenu de la
$external_input
variable shell à la foo
awk
variable.
Maintenant:
$ external_input='x BEGIN{system("uname")}'
$ awk -v foo=$external_input '$2 == foo'
Linux
Le second mot résultant du fractionnement de $external_input
n'est pas affecté à, foo
mais considéré comme un awk
code (ici, il exécute une commande arbitraire:) uname
.
Cela est particulièrement un problème pour les commandes qui peuvent exécuter d' autres commandes ( awk
, env
, sed
(GNU un), perl
, find
...) en particulier avec les variantes GNU (qui acceptent les options après arguments). Parfois, vous ne soupçonneriez pas que des commandes soient capables d'exécuter des tâches telles que ksh
, bash
ou zsh
est [
ou printf
...
for file in *; do
[ -f $file ] || continue
something-that-would-be-dangerous-if-$file-were-a-directory
done
Si nous créons un répertoire appelé x -o yes
, alors le test devient positif, car nous évaluons une expression conditionnelle complètement différente.
Pire, si nous créons un fichier appelé x -a a[0$(uname>&2)] -gt 1
, avec au moins toutes les implémentations de ksh (qui inclut les sh
Unices les plus commerciaux et certains BSD), s’exécute uname
parce que ces shells effectuent une évaluation arithmétique sur les opérateurs de comparaison numériques de la [
commande.
$ touch x 'x -a a[0$(uname>&2)] -gt 1'
$ ksh -c 'for f in *; do [ -f $f ]; done'
Linux
Même chose bash
pour un nom de fichier comme x -a -v a[0$(uname>&2)]
.
Bien sûr, s’ils ne peuvent pas exécuter une exécution arbitraire, l’attaquant peut s’attendre à des dommages moindres (ce qui peut aider à une exécution arbitraire). Toute commande pouvant écrire des fichiers ou modifier les autorisations, la propriété ou avoir un effet principal ou secondaire pourrait être exploitée.
Toutes sortes de choses peuvent être faites avec les noms de fichiers.
$ touch -- '-R ..'
$ for file in *; do [ -f "$file" ] && chmod +w $file; done
Et vous ..
finissez par rendre l’ écriture (récursive avec GNU
chmod
).
Les scripts effectuant un traitement automatique des fichiers dans des zones accessibles en écriture publique /tmp
doivent être écrits avec beaucoup de soin.
Qu'en est-il de [ $# -gt 1 ]
C'est quelque chose que je trouve exaspérant. Certaines personnes ont la peine de se demander si une expansion donnée peut être problématique pour décider si elles peuvent ou non omettre les guillemets.
C'est comme dire. Hé, il semble que $#
ne peut pas être soumis à l'opérateur split + glob, demandons au shell de le scinder + glob . Ou alors , écrivons un code incorrect simplement parce qu'il est peu probable que le bogue soit touché .
Maintenant, à quel point est-ce improbable? OK, $#
(ou $!
, $?
ou toute substitution arithmétique) ne peut contenir que des chiffres (ou -
pour certains), de sorte que la partie glob est sortie. Pour que la partie divisée fasse quelque chose, tout ce dont nous avons besoin est $IFS
de contenir des chiffres (ou -
).
Avec certains coquillages, $IFS
peut être hérité de l'environnement, mais si l'environnement n'est pas sûr, la partie est terminée de toute façon.
Maintenant, si vous écrivez une fonction comme:
my_function() {
[ $# -eq 2 ] || return
...
}
Cela signifie que le comportement de votre fonction dépend du contexte dans lequel elle est appelée. Ou en d'autres termes, $IFS
devient l'un des intrants. Strictement parlant, lorsque vous écrivez la documentation de l'API pour votre fonction, cela devrait ressembler à ceci:
# my_function
# inputs:
# $1: source directory
# $2: destination directory
# $IFS: used to split $#, expected not to contain digits...
Et le code appelant votre fonction doit s’assurer que $IFS
ne contient pas de chiffres. Tout cela parce que vous n'aviez pas envie de taper ces 2 caractères à double guillemet.
Maintenant, pour que ce [ $# -eq 2 ]
bogue devienne une vulnérabilité, il vous faudrait en quelque sorte que la valeur de $IFS
devienne sous le contrôle de l'attaquant . En théorie, cela ne se produirait normalement que si l'attaquant réussissait à exploiter un autre bogue.
Ce n'est pas du jamais vu. Il arrive souvent que des personnes oublient de nettoyer leurs données avant de les utiliser dans une expression arithmétique. Nous avons déjà vu plus haut que cela peut permettre l'exécution de code arbitraire dans certains shells, mais permet à
l'attaquant de donner à toute variable une valeur entière.
Par exemple:
n=$(($1 + 1))
if [ $# -gt 2 ]; then
echo >&2 "Too many arguments"
exit 1
fi
Et avec une $1
valeur with (IFS=-1234567890)
, cette évaluation arithmétique a pour effet secondaire les paramètres IFS et la [
commande suivante échoue, ce qui signifie que la vérification de trop d'arguments est ignorée.
Qu'en est-il lorsque l' opérateur split + glob n'est pas appelé?
Il existe un autre cas où des guillemets sont nécessaires autour des variables et autres développements: quand il est utilisé comme motif.
[[ $a = $b ]] # a `ksh` construct also supported by `bash`
case $a in ($b) ...; esac
ne testez pas si $a
et $b
sont les mêmes (sauf avec zsh
) mais si $a
correspond au modèle dans $b
. Et vous devez citer $b
si vous voulez comparer en tant que chaînes (la même chose dans "${a#$b}"
ou "${a%$b}"
ou "${a##*$b*}"
où $b
devrait être citée si elle ne doit pas être considérée comme un motif).
Cela signifie que [[ $a = $b ]]
peut retourner vrai dans les cas où $a
est différent de $b
(par exemple quand $a
est anything
et $b
est *
) ou peut retourner faux quand ils sont identiques (par exemple quand les deux $a
et $b
sont [a]
).
Cela peut-il créer une vulnérabilité de sécurité? Oui, comme n'importe quel bug. Ici, l'attaquant peut modifier le flux de code logique de votre script et / ou briser les hypothèses émises par votre script. Par exemple, avec un code comme:
if [[ $1 = $2 ]]; then
echo >&2 '$1 and $2 cannot be the same or damage will incur'
exit 1
fi
L'attaquant peut contourner le contrôle en passant '[a]' '[a]'
.
Maintenant, si ni cette correspondance de modèle ni l' opérateur split + glob ne s'appliquent, quel est le danger de laisser une variable sans guillemets?
Je dois admettre que j'écris:
a=$b
case $a in...
Là, citer ne nuit pas, mais n'est pas strictement nécessaire.
Toutefois, l’absence de guillemets dans ces cas (par exemple, dans les réponses aux questions et réponses) a pour effet secondaire d’envoyer un message erroné aux débutants: il peut être correct de ne pas citer de variables .
Par exemple, ils peuvent commencer à penser que si a=$b
OK est correct, alors le export a=$b
serait aussi (ce qui n'est pas dans beaucoup de shells , mais dans les arguments de la export
commande, donc dans le contexte d'une liste) ou env a=$b
.
Qu'en est- il zsh
?
zsh
n'a pas résolu la plupart de ces maladresses de conception. En zsh
(au moins lorsqu'ils ne sont pas en mode d'émulation sh / ksh), si vous voulez diviser , ou englobement ou correspondance de motif , vous devez demander explicitement: $=var
diviser, et $~var
à glob ou pour le contenu de la variable à traiter comme un motif.
Cependant, le fractionnement (mais pas la suppression) est toujours effectué implicitement lors de la substitution de commande non entre guillemets (comme dans echo $(cmd)
).
En outre, un effet secondaire parfois indésirable de ne pas citer de variable est la suppression des éléments vides . Le zsh
comportement est similaire à ce que vous pouvez obtenir dans d’autres coques en désactivant complètement l’effacement (avec set -f
) et le dédoublement (avec IFS=''
). Toujours dedans:
cmd $var
Il n'y aura pas de split + glob , mais si $var
est vide, au lieu de recevoir un argument vide, il cmd
ne recevra aucun argument.
Cela peut causer des bugs (comme l'évidence [ -n $var ]
). Cela peut éventuellement casser les attentes et les hypothèses d’un script et causer des vulnérabilités, mais je ne peux pas vous donner d’exemple pas trop farfelu pour le moment).
Qu'en est- il lorsque vous avez besoin de la scission + glob opérateur?
Oui, c’est généralement le cas lorsque vous souhaitez laisser votre variable non citée. Mais ensuite, vous devez vous assurer que vous réglez correctement vos opérateurs split et glob avant de l’utiliser. Si vous ne voulez que la partie divisée et non la partie globale (ce qui est le cas la plupart du temps), vous devez désactiver globbing ( set -o noglob
/ set -f
) et corriger $IFS
. Sinon, vous causerez également des vulnérabilités (comme l'exemple CGI de David Korn mentionné ci-dessus).
Conclusion
En bref, laisser une variable (ou une substitution de commande ou une expansion arithmétique) sans mention de shells dans les shells peut être très dangereux, surtout dans les mauvais contextes, et il est très difficile de savoir quels sont ces mauvais contextes.
C'est l'une des raisons pour lesquelles c'est considéré comme une mauvaise pratique .
Merci d'avoir lu jusqu'ici. Si cela vous dépasse, ne vous inquiétez pas. On ne peut pas s'attendre à ce que tout le monde comprenne toutes les implications de l'écriture de son code de la façon dont il l'écrit. C'est pourquoi nous avons des
recommandations de bonnes pratiques , afin qu'elles puissent être suivies sans nécessairement comprendre pourquoi.
(et si cela n’est pas encore évident, évitez d’écrire du code sensible à la sécurité dans des shells).
Et citez vos variables sur vos réponses sur ce site!
¹Dans ksh93
et pdksh
et dérivés, le développement d'accolades est également effectué, sauf si la suppression est désactivée (dans le cas de ksh93
versions jusqu'à ksh93u +, même lorsque l' braceexpand
option est désactivée).