J'ai écrit ceci comme une refonte de l'excellente réponse de Chris Down ci-dessus, dans le style d'un tutoriel.
En bash, vous pouvez avoir des variables shell comme ceci
$ t="hi there"
$ echo $t
hi there
$
Par défaut, ces variables ne sont pas héritées par les processus enfants.
$ bash
$ echo $t
$ exit
Mais si vous les marquez pour exportation, bash définira un indicateur qui signifiera qu’ils iront dans l’environnement des sous-processus (bien que le envp
paramètre ne soit pas très visible, le programme main
de votre programme C comporte trois paramètres: main(int argc, char *argv[], char *envp[])
où ce dernier tableau de pointeurs est un tableau des variables de shell avec leurs définitions).
Exportons donc t
comme suit:
$ echo $t
hi there
$ export t
$ bash
$ echo $t
hi there
$ exit
Considérant que ce qui précède t
était indéfini dans le sous-shell, il apparaît maintenant après son exportation (utilisez-le export -n t
si vous souhaitez cesser de l'exporter).
Mais les fonctions de bash sont un animal différent. Vous les déclarez comme ceci:
$ fn() { echo "test"; }
Et maintenant, vous pouvez simplement appeler la fonction en l'appelant comme s'il s'agissait d'une autre commande shell:
$ fn
test
$
Encore une fois, si vous créez un sous-shell, notre fonction n'est pas exportée:
$ bash
$ fn
fn: command not found
$ exit
Nous pouvons exporter une fonction avec export -f
:
$ export -f fn
$ bash
$ fn
test
$ exit
Voici la partie la plus délicate: une fonction exportée comme fn
est convertie en une variable d’environnement, tout comme notre exportation de la variable shell t
était ci-dessus. Cela n'arrive pas quand fn
était une variable locale, mais après l'exportation, nous pouvons le voir comme une variable shell. Cependant, vous pouvez également avoir une variable shell régulière (c'est-à-dire non fonction) portant le même nom. bash distingue sur la base du contenu de la variable:
$ echo $fn
$ # See, nothing was there
$ export fn=regular
$ echo $fn
regular
$
Nous pouvons maintenant utiliser env
pour afficher toutes les variables de shell marquées pour l'exportation, ainsi que la fonction normale fn
et la fonction fn
:
$ env
.
.
.
fn=regular
fn=() { echo "test"
}
$
Un sous-shell va ingérer les deux définitions: une en tant que variable normale et l'autre en tant que fonction:
$ bash
$ echo $fn
regular
$ fn
test
$ exit
Vous pouvez définir fn
comme nous l’avons fait ci-dessus, ou directement comme une affectation de variable régulière:
$ fn='() { echo "direct" ; }'
Notez que c'est une chose très inhabituelle à faire! Normalement, nous définirions la fonction fn
comme nous l’avons fait ci-dessus avec la fn() {...}
syntaxe. Mais comme bash l'exporte via l'environnement, nous pouvons «raccourcir» directement vers la définition régulière ci-dessus. Notez que (contrairement à votre intuition, peut-être) cela n'entraîne pas une nouvelle fonction fn
disponible dans le shell actuel. Mais si vous créez un shell ** sous **, alors ce sera le cas.
Annulons l'exportation de la fonction fn
et laissons la nouvelle régulière fn
(comme indiqué ci-dessus) intacte.
$ export -nf fn
Maintenant, la fonction fn
n'est plus exportée, mais la variable régulière l' fn
est et contient () { echo "direct" ; }
-la.
Maintenant, quand un sous-shell voit une variable régulière qui commence par ()
elle interprète le reste comme une définition de fonction. Mais c'est seulement quand un nouveau shell commence. Comme nous l’avons vu plus haut, le simple fait de définir une variable de shell régulière en commençant par ()
ne le fait pas se comporter comme une fonction. Vous devez démarrer un sous-shell.
Et maintenant le bug "shellshock":
Comme nous venons de le voir, lorsqu'un nouveau shell ingère la définition d'une variable régulière en commençant par ()
il l'interprète comme une fonction. Cependant, s'il y a plus de données après l'accolade fermante qui définit la fonction, elle exécute aussi ce qui est là.
Ce sont les exigences, une fois de plus:
- La nouvelle bash est engendrée
- Une variable d'environnement est ingérée
- Cette variable d'environnement commence par "()" puis contient un corps de fonction à l'intérieur d'accolades, puis contient des commandes
Dans ce cas, un bash vulnérable exécutera les dernières commandes.
Exemple:
$ export ex='() { echo "function ex" ; }; echo "this is bad"; '
$ bash
this is bad
$ ex
function ex
$
La variable exportée régulière a ex
été transmise au sous-shell, ce qui a été interprété comme une fonction, ex
mais les commandes de fin ont été exécutées ( this is bad
) lors de l'apparition du sous-shell.
Expliquer le test d'une ligne lisse
Un one-liner populaire pour tester la vulnérabilité de Shellshock est celui cité dans la question de @ jippie:
env x='() { :;}; echo vulnerable' bash -c "echo this is a test"
Voici une ventilation: d'abord, le :
in bash n'est qu'un raccourci pour true
. true
et les :
deux évaluent à (vous l'avez deviné) vrai, en bash:
$ if true; then echo yes; fi
yes
$ if :; then echo yes; fi
yes
$
Deuxièmement, la env
commande (également intégrée à bash) affiche les variables d’environnement (comme nous l’avons vu ci-dessus) mais peut également être utilisée pour exécuter une seule commande avec une variable exportée (ou des variables) affectée à cette commande, et bash -c
exécute une seule commande à partir de sa ligne de commande:
$ bash -c 'echo hi'
hi
$ bash -c 'echo $t'
$ env t=exported bash -c 'echo $t'
exported
$
Donc, en cousant tout cela ensemble, nous pouvons exécuter bash en tant que commande, lui donner une tâche factice (comme bash -c echo this is a test
) et exporter une variable qui commence par ()
pour que le sous-shell l’interprète comme une fonction. Si shellshock est présent, il exécutera immédiatement toutes les commandes de fin du sous-shell. Comme la fonction que nous passons ne nous concerne pas (mais nous devons analyser!), Nous utilisons la fonction valide la plus courte imaginable:
$ f() { :;}
$ f
$
La fonction f
ici exécute simplement la :
commande, qui renvoie true et quitte. Ajoutez maintenant à cela une commande "diabolique" et exportez une variable normale dans un sous-shell et vous gagnerez. Voici à nouveau le one-liner:
$ env x='() { :;}; echo vulnerable' bash -c "echo this is a test"
Donc, x
est exporté sous forme de variable régulière avec une simple fonction valide avec echo vulnerable
clouée à la fin. Ceci est passé à bash, et bash interprète x
comme une fonction (ce qui nous importe peu) puis exécute peut-être le echo vulnerable
si shellshock est présent.
Nous pourrions raccourcir un peu le one-liner en supprimant le this is a test
message:
$ env x='() { :;}; echo vulnerable' bash -c :
Cela ne le dérange pas, this is a test
mais lance à nouveau la :
commande silencieuse . (Si vous quittez le, -c :
vous êtes assis dans le sous-shell et devez quitter manuellement.) La version la plus conviviale serait peut-être celle-ci:
$ env x='() { :;}; echo vulnerable' bash -c "echo If you see the word vulnerable above, you are vulnerable to shellshock"