web-dev-qa-db-fra.com

Qu'est-ce que env x = '() {:;}; commande 'bash do et pourquoi n'est-il pas sûr?

Il y a apparemment une vulnérabilité (CVE-2014-6271) dans bash: Bash spécialement conçu pour l'attaque par injection de code de variables d'environnement

J'essaie de comprendre ce qui se passe, mais je ne suis pas tout à fait sûr de le comprendre. Comment le echo peut-il être exécuté tel quel entre guillemets simples?

$ env x='() { :;}; echo vulnerable' bash -c "echo this is a test"
vulnerable
this is a test

EDIT 1 : Un système patché ressemble à ceci:

$ env x='() { :;}; echo vulnerable' bash -c "echo this is a test"
bash: warning: x: ignoring function definition attempt
bash: error importing function definition for `x'
this is a test

EDIT 2 : Il existe une vulnérabilité/un correctif connexe: CVE-2014-7169 qui utilise un test légèrement différent:

$ env 'x=() { :;}; echo vulnerable' 'BASH_FUNC_x()=() { :;}; echo vulnerable' bash -c "echo test"

sortie non corrigée:

vulnerable
bash: BASH_FUNC_x(): line 0: syntax error near unexpected token `)'
bash: BASH_FUNC_x(): line 0: `BASH_FUNC_x() () { :;}; echo vulnerable'
bash: error importing function definition for `BASH_FUNC_x'
test

sortie partiellement corrigée (première version):

bash: warning: x: ignoring function definition attempt
bash: error importing function definition for `x'
bash: error importing function definition for `BASH_FUNC_x()'
test

sortie corrigée jusqu'au CVE-2014-7169 inclus:

bash: warning: x: ignoring function definition attempt
bash: error importing function definition for `BASH_FUNC_x'
test

EDIT 3 : l'histoire continue avec:

244
jippie

bash stocke les définitions de fonctions exportées en tant que variables d'environnement. Les fonctions exportées ressemblent à ceci:

$ foo() { bar; }
$ export -f foo
$ env | grep -A1 foo
foo=() {  bar
}

C'est-à-dire que la variable d'environnement foo a le contenu littéral:

() {  bar
}

Lorsqu'une nouvelle instance de bash est lancée, elle recherche ces variables d'environnement spécialement conçues et les interprète comme des définitions de fonction. Vous pouvez même en écrire un vous-même et voir qu'il fonctionne toujours:

$ export foo='() { echo "Inside function"; }'
$ bash -c 'foo'
Inside function

Malheureusement, l'analyse des définitions de fonctions à partir de chaînes (les variables d'environnement) peut avoir des effets plus larges que prévu. Dans les versions non corrigées, il interprète également les commandes arbitraires qui se produisent après la fin de la définition de la fonction. Cela est dû à des contraintes insuffisantes dans la détermination de chaînes de type fonction acceptables dans l'environnement. Par exemple:

$ export foo='() { echo "Inside function" ; }; echo "Executed echo"'
$ bash -c 'foo'
Executed echo
Inside function

Notez que l'écho en dehors de la définition de fonction a été exécuté de manière inattendue lors du démarrage de bash. La définition de la fonction n'est qu'une étape pour obtenir l'évaluation et l'exploitation, la définition de la fonction elle-même et la variable d'environnement utilisée sont arbitraires. Le shell examine les variables d'environnement, voit foo, qui semble répondre aux contraintes qu'il connaît sur la définition d'une fonction, et il évalue la ligne, exécutant également involontairement l'écho (qui pourrait être n'importe quelle commande) , malveillants ou non).

Ceci est considéré comme non sûr car les variables ne sont généralement pas autorisées ou attendues, en elles-mêmes, à provoquer directement l'invocation de code arbitraire qu'elles contiennent. Peut-être que votre programme définit des variables d'environnement à partir d'une entrée utilisateur non fiable. Il serait très inattendu que ces variables d'environnement puissent être manipulées de telle manière que l'utilisateur puisse exécuter des commandes arbitraires sans votre intention explicite de le faire en utilisant cette variable d'environnement pour une telle raison déclarée dans le code.

Voici un exemple d'attaque viable. Vous exécutez un serveur Web qui exécute un shell vulnérable, quelque part, dans le cadre de sa durée de vie. Ce serveur Web transmet les variables d'environnement à un script bash, par exemple, si vous utilisez CGI, les informations sur la requête HTTP sont souvent incluses en tant que variables d'environnement à partir du serveur Web. Par exemple, HTTP_USER_AGENT peut être défini sur le contenu de votre agent utilisateur. Cela signifie que si vous usurpez votre agent utilisateur pour qu'il ressemble à '() {:; }; echo foo ', lorsque ce script Shell s'exécute, echo foo sera exécuté. Encore, echo foo pourrait être n'importe quoi, malveillant ou non.

209
Chris Down

Cela peut aider à démontrer davantage ce qui se passe:

$ export dummy='() { echo "hi"; }; echo "pwned"'
$ bash
pwned
$

Si vous exécutez un shell vulnérable, lorsque vous démarrez un nouveau sous-shell (ici, simplement en utilisant l'instruction bash), vous verrez que le code arbitraire (echo "pwned") est immédiatement exécuté dans le cadre de son lancement. Apparemment, le Shell voit que la variable d'environnement (factice) contient une définition de fonction, et évalue la définition afin de définir cette fonction dans son environnement (notez qu'il n'exécute pas la fonction: cela afficherait 'hi'.)

Malheureusement, il n'évalue pas seulement la définition de la fonction, il évalue le texte entier de la valeur de la variable d'environnement, y compris les instructions éventuellement malveillantes qui suivent la définition de la fonction. Notez que sans la définition de fonction initiale, la variable d'environnement ne serait pas évaluée, elle serait simplement ajoutée à l'environnement sous forme de chaîne de texte. Comme l'a souligné Chris Down, il s'agit d'un mécanisme spécifique pour implémenter l'importation des fonctions Shell exportées.

Nous pouvons voir la fonction qui a été définie dans le nouveau Shell (et qu'elle a été marquée comme exportée là-bas), et nous pouvons l'exécuter. De plus, le mannequin n'a pas été importé en tant que variable de texte:

$ declare -f
dummy ()
{
    echo "hi"
}
declare -fx dummy
$ dummy
hi
$echo $dummy
$

Ni la création de cette fonction, ni rien de ce qu'elle ne ferait si elle était exécutée, ne fait partie de l'exploit - c'est seulement le véhicule par lequel l'exploit est exécuté. Le fait est que si un attaquant peut fournir du code malveillant, précédé d'une définition de fonction minimale et sans importance, dans une chaîne de texte qui est placée dans une variable d'environnement exportée, il sera exécuté lors du démarrage d'un sous-shell, qui est un événement courant dans de nombreux scripts. De plus, il sera exécuté avec les privilèges du script.

86
sdenham

J'ai écrit ceci comme une refonte de style tutoriel de l'excellente réponse de Chris Down ci-dessus.


En bash, vous pouvez avoir des variables Shell comme celle-ci

$ 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 l'exportation, bash mettra un drapeau qui signifie qu'ils iront dans l'environnement des sous-processus (bien que le paramètre envp ne soit pas beaucoup vu, le main dans votre programme C a trois paramètres: main(int argc, char *argv[], char *envp[]) où ce dernier tableau de pointeurs est un tableau de variables Shell avec leurs définitions).

Exportons donc t comme suit:

$ echo $t
hi there
$ export t
$ bash
$ echo $t
hi there
$ exit

Alors que ci-dessus t n'était pas défini dans le sous-shell, il apparaît maintenant après que nous l'avons exporté (utilisez export -n t Si vous voulez arrêter 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 générez 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 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 ne se produit pas lorsque fn était une variable locale, mais après l'exportation, nous pouvons la voir comme une variable Shell. Cependant, vous pouvez aussi avoir une variable Shell régulière (c'est-à-dire non fonctionnelle) du même nom. bash distingue en fonction du contenu de la variable:

$ echo $fn

$ # See, nothing was there
$ export fn=regular
$ echo $fn
regular
$ 

Maintenant, nous pouvons utiliser env pour afficher toutes les variables Shell marquées pour l'exportation et les deux fn et la fonction fn s'affichent:

$ env
.
.
.
fn=regular
fn=() {  echo "test"
}
$

Un sous-shell va ingérer les deux définitions: l'une en tant que variable régulière 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 syntaxe fn() {...}. Mais puisque bash l'exporte à travers l'environnement, nous pouvons "raccourcir" directement la définition régulière ci-dessus. Notez que (contrairement à votre intuition, peut-être) cela pas résulte en une nouvelle fonction fn disponible dans le Shell actuel. Mais si vous générez un ** sub ** Shell, alors ce sera le cas.

Annulons l'exportation de la fonction fn et laissons le nouveau fn normal (comme indiqué ci-dessus) intact.

$ export -nf fn

Maintenant, la fonction fn n'est plus exportée, mais la variable régulière fn l'est, et elle contient () { echo "direct" ; }.

Désormais, lorsqu'un sous-shell voit une variable régulière qui commence par (), Il interprète le reste comme une définition de fonction. Mais c'est seulement quand un nouveau Shell commence. Comme nous l'avons vu ci-dessus, la simple définition d'une variable Shell standard commençant par () Ne la 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 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, il exécute quoi que ce soit est également là.

Ce sont les exigences, encore une fois:

  1. Un nouveau bash est généré
  2. Une variable d'environnement est ingérée
  3. Cette variable d'environnement commence par "()", puis contient un corps de fonction entre accolades, puis a des commandes par la suite

Dans ce cas, un bash vulnérable exécutera ces 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 ex a été transmise au sous-shell qui a été interprété comme une fonction ex mais les commandes de fin ont été exécutées (this is bad) Lorsque le sous-shell est apparu.


Explication du test lisse d'une ligne

Un one-liner populaire pour tester la vulnérabilité 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 : Dans bash n'est qu'un raccourci pour true. true et : sont tous deux évalués (vous l'avez deviné) vrai, en bash:

$ if true; then echo yes; fi
yes
$ if :; then echo yes; fi
yes
$

Deuxièmement, la commande env (également intégrée à bash) imprime 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 ou plusieurs variables exportées données à 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 tous ces trucs ensemble, nous pouvons exécuter bash en tant que commande, lui donner quelque chose de faux à faire (comme bash -c echo this is a test) Et exporter une variable qui commence par () Pour que le sous-interpréteur interprète en tant que fonction. Si Shellshock est présent, il exécutera également immédiatement toutes les commandes de fin dans le sous-shell. Étant donné que la fonction que nous transmettons ne nous concerne pas (mais doit être analysée!), Nous utilisons la fonction valide la plus courte imaginable:

$ f() { :;}
$ f
$ 

La fonction f exécute simplement ici la commande :, Qui renvoie true et quitte. Maintenant, ajoutez à cette commande "evil" et exportez une variable régulière vers un sous-shell et vous gagnez. Voici à nouveau le one-liner:

$ env x='() { :;}; echo vulnerable' bash -c "echo this is a test"

Donc x est exporté en tant que variable régulière avec une simple fonction valide avec echo vulnerable Collé à 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 message this is a test:

$ env x='() { :;}; echo vulnerable' bash -c :

Cela ne dérange pas avec this is a test Mais exécute à nouveau la commande silencieuse :. (Si vous laissez le -c : Alors vous vous asseyez dans le sous-shell et devez quitter manuellement.) Peut-être la version la plus conviviale serait celle-ci:

$ env x='() { :;}; echo vulnerable' bash -c "echo If you see the Word vulnerable above, you are vulnerable to Shellshock"
72
Fixee

Si vous pouvez envoyer des variables d'environnement arbitraires à un programme, vous pouvez le faire faire n'importe quoi en lui faisant charger les bibliothèques de votre choix. Dans la plupart des cas, cela n'est pas considéré comme une vulnérabilité dans le programme recevant ces variables d'environnement, mais plutôt dans le mécanisme par lequel un étranger pourrait alimenter des variables d'environnement arbitraires.

Cependant CVE-2014-6271 est différent.

Il n'y a rien de mal à avoir des données non fiables dans une variable d'environnement. Il suffit de s'assurer qu'il ne soit placé dans aucune de ces variables d'environnement qui peuvent modifier le comportement du programme. En résumé, pour une invocation particulière, vous pouvez créer une liste blanche de noms de variables d'environnement, qui peuvent être spécifiés directement par un étranger.

Un exemple qui a été proposé dans le contexte de CVE-2014-6271 est les scripts utilisés pour analyser les fichiers journaux. Ceux-ci peuvent avoir un besoin très légitime de transmettre des données non fiables dans des variables d'environnement. Bien sûr, le nom d'une telle variable d'environnement est choisi de manière à ce qu'il n'ait aucun effet négatif.

Mais voici ce qui est mauvais à propos de cette vulnérabilité bash particulière. Il peut être exploité via n'importe quel nom de variable. Si vous créez une variable d'environnement appelée GET_REQUEST_TO_BE_PROCESSED_BY_MY_SCRIPT, vous ne vous attendriez pas à ce qu'un autre programme que votre propre script interprète le contenu de cette variable d'environnement. Mais en exploitant ce bogue bash, chaque variable d'environnement devient un vecteur d'attaque.

Notez que cela ne signifie pas que les noms des variables d'environnement devraient être secrets. La connaissance des noms des variables d'environnement impliquées ne facilite pas une attaque.

Si program1 appels program2 qui à son tour appelle program3, puis program1 pourrait transmettre des données à program3 via des variables d'environnement. Chaque programme a une liste spécifique de variables d'environnement qu'il définit et une liste spécifique sur laquelle il agit. Si vous avez choisi un nom non reconnu par program2, vous pouvez transmettre des données de program1 à program3 sans se soucier que cela ait des effets négatifs sur program2.

Un attaquant connaissant le nom exact des variables exportées par program1 et noms des variables interprétées par program2 ne peut pas exploiter cette connaissance pour modifier le comportement de 'program2` s'il n'y a pas de chevauchement entre l'ensemble des noms.

Mais cela est tombé en panne si program2 était un script bash, car à cause de ce bogue bash interpréterait chaque variable d'environnement comme du code.

20
kasperd

C'est expliqué dans l'article que vous avez lié ...

vous pouvez créer des variables d'environnement avec des valeurs spécialement conçues avant d'appeler le shell bash. Ces variables peuvent contenir du code, qui est exécuté dès que le shell est appelé.

Ce qui signifie que le bash qui est appelé avec -c "echo this is a test" exécute le code entre guillemets simples lorsqu'il est invoqué.

Bash a des fonctions, bien que dans une implémentation quelque peu limitée, et il est possible de placer ces fonctions bash dans des variables d'environnement. Cette faille est déclenchée lorsque du code supplémentaire est ajouté à la fin de ces définitions de fonction (à l'intérieur de la variable d'enivronment).

Signifie que l'exemple de code que vous avez publié exploite le fait que le bash invoqué n'arrête pas d'évaluer cette chaîne après avoir effectué l'affectation. Une affectation de fonction dans ce cas.

La particularité de l'extrait de code que vous avez publié, si je comprends bien, est qu'en utilisant une définition de fonction avant le code que nous voulons exécuter, certains mécanismes de sécurité peuvent être contournés.

9
Bananguin