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

237

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

J'essaie de comprendre ce qui se passe, mais je ne suis pas tout à fait sûr de comprendre. Comment peut- echoon l'exécuter 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é / correctif associé: 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

version partiellement corrigée (ancienne 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'à 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:

jippie
la source
Ce n'est pas l'écho qui est exécuté. sa la définition de la fonction de x. Si la fonction définie dans x effectue un travail sournois sournois, bash ne peut pas vérifier la valeur de retour si la fonction x est réelle. Notez que la fonction est vide dans le code de test. Une valeur de retour non contrôlée peut conduire à une injection de script. L'injection de script entraîne une élévation de privilèges et l'élévation de privilèges mène à un accès root. Le patch désactive la création de x en tant que fonction
eyoung100
26
eyoung100, pas d'écho est exécuté. Vous pouvez voir qu'il est en train de s'exécuter car le mot vulnerableapparaît dans la sortie. Le problème principal est que bash analyse et exécute également le code après la définition de la fonction. Voir la /bin/idpartie de seclists.org/oss-sec/2014/q3/650 pour un autre exemple.
Mikel
4
Juste un petit commentaire. Red Hat a indiqué que le correctif publié n'était qu'un correctif partiel et laissait les systèmes toujours en danger.
Peter
2
@ eyoung100 la différence est que le code dans la fonction ne s'exécute que lorsque la variable d'environnement est explicitement appelée. Le code qui suit la définition de la fonction s’exécute à chaque démarrage d’un nouveau processus Bash.
David Farrell
1
Voir stackoverflow.com/questions/26022248/… pour plus de détails
Barmar

Réponses:

204

bash stocke les définitions de fonctions exportées sous forme de 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 fooa 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 fonctions. Vous pouvez même en écrire un vous-même et constater que cela fonctionne toujours:

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

Malheureusement, l'analyse des définitions de fonction à 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 surviennent après la fin de la définition de la fonction. Ceci 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 la fonction a été exécuté de manière inattendue lors du démarrage de bash. La définition de la fonction est juste 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 connues sur la définition d'une fonction, et évalue la ligne en exécutant involontairement aussi l'écho (qui peut être n'importe quelle commande, malveillante ou non).

Cela est considéré comme non sécurisé car les variables ne sont généralement pas autorisées ni attendues, à elles seules, à provoquer directement l’invocation du 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 hautement inattendu que ces variables d'environnement puissent être manipulées de manière à ce 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 au cours de sa vie. Ce serveur Web transmet les variables d'environnement à un script bash. Par exemple, si vous utilisez CGI, les informations relatives à la demande HTTP sont souvent incluses en tant que variables d'environnement provenant du serveur Web. Par exemple, HTTP_USER_AGENTpeut ê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 ' echo foosera exécuté lors de l'exécution de ce script shell . Encore une fois, echo foopourrait être n'importe quoi, malveillant ou non.

Chris Down
la source
3
Est-ce que cela pourrait affecter tout autre shell similaire à Bash, comme Zsh?
Amelio Vazquez-Reina
3
@ user815423426 Non, zsh ne possède pas cette fonctionnalité. Ksh l'a, mais implémenté différemment, je pense que les fonctions ne peuvent être transmises que dans des circonstances très étroites, seulement si le shell forque, pas à travers l'environnement.
Gilles
20
@ user815423426 rc est l'autre shell qui transmet des fonctions à l'environnement, mais avec des variables dont le nom est précédé du préfixe "fn_" et qui ne sont interprétées que lorsqu'elles sont appelées.
Stéphane Chazelas
18
@ StéphaneChazelas - merci d'avoir signalé le bogue.
Deer Hunter
13
@gnclmorais Vous voulez dire que vous courez export bar='() { echo "bar" ; }'; zsh -c baret qu'il affiche barplutôt que zsh:1: command not found: bar? Êtes-vous sûr de ne pas confondre le shell que vous appelez avec celui que vous utilisez pour configurer le test?
Gilles
85

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, en utilisant simplement 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 (dummy) 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 ne se contente pas d’évaluer la définition de la fonction, il évalue l’ensemble du texte de la valeur de la variable d’environnement, y compris la ou les déclarations é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 en tant que chaîne de texte. Comme Chris Down l'a souligné, il s'agit d'un mécanisme spécifique pour implémenter l'importation de fonctions de 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 ferait si elle était exécutée, ne fait partie de l'exploit - ce n'est que 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 insérée dans une variable d'environnement exportée, elle sera exécutée au démarrage d'un sous-shell, événement courant. dans de nombreux scripts. De plus, il sera exécuté avec les privilèges du script.

Sdenham
la source
17
Même si la réponse acceptée dit effectivement ceci si vous la lisez attentivement, j’ai trouvé cette réponse encore plus claire et plus utile pour comprendre que c’est l’évaluation de la définition (plutôt que l’exécution de la fonction elle-même) qui pose problème.
Natevw
1
pourquoi cet exemple a-t-il le exportcommandement alors que les autres en avaient env? Je pensais envêtre utilisé pour définir les variables environnementales qui seraient appelées lors du lancement d'un autre shell bash. alors comment est-ce que cela fonctionneexport
Haris
Jusqu'à présent, aucune réponse n'a été acceptée. J'attendrai probablement encore quelques jours avant d'en accepter un. L'inconvénient de cette réponse est qu'elle ne décompose pas la commande d'origine, ni ne explique comment obtenir à partir de la commande d'origine de la question les commandes de cette réponse, en montrant qu'elles sont identiques. En dehors de cela, c'est une bonne explication.
Jippie
@ralph - envet exportles définitions d'environnement d'exportation afin qu'elles soient disponibles dans un sous-shell. Le problème réside en réalité dans la manière dont ces définitions exportées sont importées dans l'environnement d'un sous-shell, et plus précisément dans le mécanisme d'importation des définitions de fonctions.
Sdenham
1
@ralph - envexécute une commande avec quelques options et variables d'environnement définies. Notez que dans les exemples de questions d'origine, envdéfinit xune chaîne et appelle bash -cavec une commande à exécuter. Si vous le faites env x='foo' vim, Vim se lancera et vous pourrez y appeler son shell / environnement contenant !echo $x, et il imprimera foo, mais si vous quittez et echo $x, il ne sera pas défini, car il existait uniquement pendant l'exécution de vim via la envcommande. Au exportlieu de cela, la commande définit des valeurs persistantes dans l'environnement actuel de sorte qu'un sous-shell exécuté ultérieurement les utilise.
Gary Fixler
72

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 envpparamètre ne soit pas très visible, le programme mainde 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 tcomme 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 tsi 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 fnest 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 envpour afficher toutes les variables de shell marquées pour l'exportation, ainsi que la fonction normale fnet 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 fncomme 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 fncomme 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 fndisponible dans le shell actuel. Mais si vous créez un shell ** sous **, alors ce sera le cas.

Annulons l'exportation de la fonction fnet laissons la nouvelle régulière fn(comme indiqué ci-dessus) intacte.

$ export -nf fn

Maintenant, la fonction fnn'est plus exportée, mais la variable régulière l' fnest 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:

  1. La nouvelle bash est engendrée
  2. Une variable d'environnement est ingérée
  3. 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, exmais 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. trueet 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 envcommande (é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 -cexé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 fici 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, xest exporté sous forme de variable régulière avec une simple fonction valide avec echo vulnerableclouée à la fin. Ceci est passé à bash, et bash interprète xcomme une fonction (ce qui nous importe peu) puis exécute peut-être le echo vulnerablesi shellshock est présent.

Nous pourrions raccourcir un peu le one-liner en supprimant le this is a testmessage:

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

Cela ne le dérange pas, this is a testmais 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"
Fixee
la source
12
Belle explication. Cette question suscite beaucoup d'opinions (probablement tous ne sont pas aussi compétents en bash que les autres) et je pense que personne ne s'est encore exprimé sur ce { :;};qui est dit. Ce serait un bon ajout à votre réponse à mon avis. Peut expliquer comment vous obtenez de votre exemple à la commande d'origine dans la question?
Jippie
20

Si vous pouvez alimenter un programme en variables d’environnement arbitraires, vous pouvez le faire faire à peu près n'importe quoi en le chargeant des bibliothèques de votre choix. Dans la plupart des cas, ceci 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 tiers externe 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 que les variables d’environnement qui modifient le comportement du programme ne sont pas insérées. Pour résumer un peu, pour une invocation particulière, vous pouvez créer une liste blanche de noms de variables d’environnement, qu’il est permis de spécifier directement par un tiers.

Un exemple qui a été mis en avant dans le contexte de CVE-2014-6271 est constitué par 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 entendu, le nom d'une telle variable d'environnement est choisi de manière à ne pas avoir d'effet négatif.

Mais voici ce qui est mauvais à propos de cette vulnérabilité bash particulière. Il peut être exploité par 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 programme autre que votre propre script interprète le contenu de cette variable d'environnement. Mais en exploitant ce bogue, chaque variable d’environnement devient un vecteur d’attaque.

Notez que cela ne signifie pas que les noms des variables d'environnement doivent être secrets. Connaître les noms des variables d'environnement impliquées ne facilite pas l'attaque.

Si des program1appels appellent program2à leur tour program3, ils program1peuvent alors transmettre des données program3via 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à program3sans vous soucier des conséquences négatives program2.

Un attaquant connaissant les noms exacts des variables exportées par program1et les noms des variables interprétés par program2ne peut pas exploiter cette connaissance afin de modifier le comportement de 'program2` s'il n'y a pas de chevauchement entre l'ensemble des noms.

Mais ceci s’est rompu s’il program2s’agissait d’un bashscript, car en raison de ce bogue bash, chaque variable d’environnement serait interprétée comme un code.

Kasperd
la source
1
"chaque variable d'environnement devient un vecteur d'attaque" - c'est la partie qui me manquait. Merci.
wrschneider
9

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 appelé avec -c "echo this is a test"exécute le code entre guillemets simples quand il est appelé .

Bash a des fonctions, bien que dans une implémentation quelque peu limitée, et il est possible de mettre 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 fonctions (à l'intérieur de la variable d'enivronment).

Cela signifie que l'exemple de code que vous avez posté 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.

Si j'ai bien compris, l'extrait de code que vous avez posté est en fait spécial: en mettant une définition de fonction avant le code que nous voulons exécuter, certains mécanismes de sécurité peuvent être contournés.

Bananguin
la source