Cosa significa env x = '() {:;}; comando 'bash do e perché non è sicuro?


237

Apparentemente c'è una vulnerabilità (CVE-2014-6271) in bash: attacco di iniezione di codice di variabili ambientali appositamente predisposte per Bash

Sto cercando di capire cosa sta succedendo, ma non sono del tutto sicuro di capirlo. Come può echoessere eseguito così com'è tra virgolette singole?

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

EDIT 1 : un sistema con patch è simile al seguente:

$ 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 : esiste una vulnerabilità / patch correlata: CVE-2014-7169 che utilizza un test leggermente diverso:

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

uscita senza patch :

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

output parzialmente (versione precedente) patchato :

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

uscita patchata fino a CVE-2014-7169 compreso:

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

EDIT 3 : la storia continua con:


Non è l'eco che viene eseguita. è la definizione della funzione di x. Se la funzione definita in x fa qualche subdolo lavoro subdolo, non è possibile che bash possa controllare il valore di ritorno se la funzione x fosse reale. Si noti che la funzione è vuota nel codice di test. Un valore di ritorno non controllato può portare all'iniezione di script. L'iniezione di script porta all'escalation dei privilegi e l'escalation dei privilegi porta all'accesso root. La patch disabilita la creazione di x come funzione
eyoung100

26
eyoung100, no l'eco viene eseguita. Puoi vedere che viene eseguito perché la parola vulnerableappare nell'output. Il problema principale è che bash sta analizzando ed eseguendo il codice anche dopo la definizione della funzione. Vedi la /bin/idparte di seclists.org/oss-sec/2014/q3/650 per un altro esempio.
Mikel,

4
Solo un breve commento laterale. Red Hat ha informato che la patch che è stata rilasciata è solo una patch parziale e lascia i sistemi ancora a rischio.
Peter,

2
@ eyoung100 la differenza è che il codice all'interno della funzione viene eseguito solo quando viene chiamata in modo esplicito la variabile di ambiente. Il codice dopo la definizione della funzione viene eseguito ogni volta che inizia un nuovo processo Bash.
David Farrell,

1
Vedi stackoverflow.com/questions/26022248/… per ulteriori dettagli
Barmar

Risposte:


204

bash memorizza le definizioni delle funzioni esportate come variabili di ambiente. Le funzioni esportate si presentano così:

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

Cioè, la variabile d'ambiente fooha il contenuto letterale:

() {  bar
}

Quando viene avviata una nuova istanza di bash, cerca queste variabili di ambiente appositamente predisposte e le interpreta come definizioni di funzione. Puoi anche scriverne uno tu stesso e vedere che funziona ancora:

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

Sfortunatamente, l'analisi delle definizioni delle funzioni dalle stringhe (le variabili di ambiente) può avere effetti più ampi del previsto. Nelle versioni senza patch, interpreta anche i comandi arbitrari che si verificano dopo la fine della definizione della funzione. Ciò è dovuto a vincoli insufficienti nella determinazione di stringhe simili a funzioni accettabili nell'ambiente. Per esempio:

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

Si noti che l'eco al di fuori della definizione della funzione è stata eseguita inaspettatamente durante l'avvio di bash. La definizione della funzione è solo un passo per ottenere la valutazione e l'exploit, la definizione della funzione stessa e la variabile di ambiente utilizzata sono arbitrarie. La shell esamina le variabili di ambiente, vede foo, che sembra soddisfare i vincoli che conosce su come appare una definizione di funzione e valuta la linea, eseguendo involontariamente anche l'eco (che potrebbe essere qualsiasi comando, dannoso o no).

Ciò è considerato insicuro perché in genere le variabili non sono autorizzate o previste, da sole, a causare direttamente l'invocazione del codice arbitrario in esse contenuto. Forse il tuo programma imposta le variabili di ambiente dall'input dell'utente non attendibile. Sarebbe del tutto inaspettato che tali variabili di ambiente possano essere manipolate in modo tale che l'utente possa eseguire comandi arbitrari senza l'intenzione esplicita di farlo utilizzando tale variabile di ambiente per tale motivo dichiarato nel codice.

Ecco un esempio di attacco praticabile. Esegui un server Web che esegue una shell vulnerabile, da qualche parte, come parte della sua vita. Questo server Web passa le variabili di ambiente a uno script bash, ad esempio, se si utilizza CGI, le informazioni sulla richiesta HTTP vengono spesso incluse come variabili di ambiente dal server Web. Ad esempio, HTTP_USER_AGENTpotrebbe essere impostato sul contenuto del proprio agente utente. Ciò significa che se falsi il tuo programma utente in modo che sia qualcosa come '() {:; }; echo foo ', quando viene eseguito lo script della shell, echo fooverrà eseguito. Ancora una volta, echo foopotrebbe essere qualsiasi cosa, dannoso o no.


3
Questo potrebbe influenzare qualsiasi altra shell simile a Bash allora, come Zsh?
Amelio Vazquez-Reina,

3
@ user815423426 No, zsh non ha questa funzione. Ksh ce l'ha, ma implementato in modo diverso, penso che le funzioni possano essere trasmesse solo in circostanze molto ristrette, solo se la shell si biforca, non attraverso l'ambiente.
Gilles,

20
@ user815423426 rc è l'altra shell che passa le funzioni nell'ambiente, ma è con variabile con nomi con il prefisso "fn_" e vengono interpretate solo quando invocate.
Stéphane Chazelas,

18
@ StéphaneChazelas - grazie per aver segnalato il bug.
Deer Hunter,

13
@gnclmorais Vuoi dire che corri export bar='() { echo "bar" ; }'; zsh -c bare viene visualizzato baranziché zsh:1: command not found: bar? Sei sicuro di non confondere la shell che stai invocando con la shell che stai utilizzando per impostare il test?
Gilles,

85

Questo può aiutare a dimostrare ulteriormente cosa sta succedendo:

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

Se stai eseguendo una shell vulnerabile, quando avvii una nuova subshell (qui, semplicemente usando l'istruzione bash), vedrai che il codice arbitrario ( echo "pwned") viene immediatamente eseguito come parte della sua iniziazione. Apparentemente, la shell vede che la variabile d'ambiente (fittizia) contiene una definizione di funzione e valuta la definizione per definire quella funzione nel suo ambiente (nota che non sta eseguendo la funzione: che stamperebbe 'ciao'.)

Sfortunatamente, non si limita a valutare la definizione della funzione, ma valuta l'intero testo del valore della variabile di ambiente, comprese le dichiarazioni potenzialmente dannose che seguono la definizione della funzione. Si noti che senza la definizione della funzione iniziale, la variabile di ambiente non verrebbe valutata, sarebbe semplicemente aggiunta all'ambiente come stringa di testo. Come ha sottolineato Chris Down, questo è un meccanismo specifico per implementare l'importazione di funzioni shell esportate.

Possiamo vedere la funzione che è stata definita nella nuova shell (e che è stata contrassegnata come esportata lì) e possiamo eseguirla. Inoltre, dummy non è stato importato come variabile di testo:

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

Né la creazione di questa funzione, né qualsiasi cosa farebbe se fosse eseguita, fa parte dell'exploit - è solo il veicolo con cui viene eseguita l'exploit. Il punto è che se un utente malintenzionato può fornire codice dannoso, preceduto da una definizione di funzione minima e non importante, in una stringa di testo che viene inserita in una variabile di ambiente esportata, verrà eseguito all'avvio di una subshell, che è un evento comune in molti script. Inoltre, verrà eseguito con i privilegi dello script.


17
Mentre la risposta accettata in realtà lo dice se la leggi attentamente, ho trovato questa risposta ancora più chiara e più utile nel capire che è il problema la valutazione della definizione (piuttosto che eseguire la funzione stessa).
Natevw,

1
perché questo esempio ha un exportcomando mentre gli altri avevano env? stavo pensando di envessere usato per definire le variabili ambientali che sarebbero chiamate quando verrà lanciata un'altra shell bash. allora come funzionaexport
Haris, il

Fino a questo momento non c'è stata una risposta accettata. Probabilmente aspetterò un altro paio di giorni prima di accettarne uno. Il rovescio della medaglia di questa risposta è che non scompone il comando originale, né discute come ottenere dal comando originale nella domanda ai comandi in questa risposta, dimostrando che sono identici. A parte questo, è una buona spiegazione.
jippie,

@ralph: entrambe enved exportesportano le definizioni di ambiente in modo che siano disponibili in una subshell. Il problema è in realtà il modo in cui queste definizioni esportate vengono importate nell'ambiente di una subshell e in particolare nel meccanismo che importa le definizioni delle funzioni.
sdenham,

1
@ralph: envesegue un comando con alcune opzioni e variabili d'ambiente impostate. Si noti che negli esempi della domanda originale, envimposta xuna stringa e chiama bash -ccon un comando per l'esecuzione. Se lo fai env x='foo' vim, Vim si avvierà e lì puoi chiamare la sua shell / ambiente contenente !echo $x, e stamperà foo, ma se poi esci e echo $x, non sarà definito, poiché esisteva solo mentre vim era in esecuzione tramite il envcomando. Il exportcomando imposta invece valori persistenti nell'ambiente corrente, in modo che una subshell eseguita successivamente li userà.
Gary Fixler,

72

L'ho scritto come una ripetizione in stile tutorial dell'eccellente risposta di Chris Down sopra.


In bash puoi avere variabili shell come questa

$ t="hi there"
$ echo $t
hi there
$

Per impostazione predefinita, queste variabili non sono ereditate dai processi figlio.

$ bash
$ echo $t

$ exit

Ma se li contrassegni per l'esportazione, bash imposterà un flag che significa che entreranno nell'ambiente dei sottoprocessi (anche se il envpparametro non è molto visto, mainnel tuo programma C ha tre parametri: main(int argc, char *argv[], char *envp[])dove l'ultimo array di puntatori è un array delle variabili shell con le loro definizioni).

Esportiamo quindi tcome segue:

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

Mentre sopra tnon era definito nella subshell, ora appare dopo che lo abbiamo esportato (utilizzare export -n tse si desidera interrompere l'esportazione).

Ma le funzioni in bash sono un animale diverso. Li dichiarate così:

$ fn() { echo "test"; }

E ora puoi semplicemente invocare la funzione chiamandola come se fosse un altro comando shell:

$ fn
test
$

Ancora una volta, se si genera una subshell, la nostra funzione non viene esportata:

$ bash
$ fn
fn: command not found
$ exit

Possiamo esportare una funzione con export -f:

$ export -f fn
$ bash
$ fn
test
$ exit

Ecco la parte difficile: una funzione esportata come fnviene convertita in una variabile di ambiente proprio come la nostra esportazione della variabile di shell tera sopra. Questo non accade quando fnera una variabile locale, ma dopo l'esportazione possiamo vederla come variabile di shell. Tuttavia, puoi anche avere una variabile di shell regolare (cioè non funzionale) con lo stesso nome. bash distingue in base al contenuto della variabile:

$ echo $fn

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

Ora possiamo usare envper mostrare tutte le variabili di shell contrassegnate per l'esportazione e vengono visualizzate sia la normale fnche la funzione fn:

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

Una sotto-shell inserirà entrambe le definizioni: una come variabile normale e una come funzione:

$ bash
$ echo $fn
regular
$ fn
test
$ exit

Puoi definire fncome abbiamo fatto sopra o direttamente come una normale assegnazione di variabili:

$ fn='() { echo "direct" ; }'

Nota che questa è una cosa insolita da fare! Normalmente definiremmo la funzione fncome abbiamo fatto sopra con la fn() {...}sintassi. Ma poiché bash lo esporta attraverso l'ambiente, possiamo "scorciatoia" direttamente alla definizione normale sopra. Nota che (contrariamente alla tua intuizione, forse) questo non si traduce in una nuova funzione fndisponibile nella shell corrente. Ma se si genera una shell ** sub **, allora lo farà.

Annulliamo l'esportazione della funzione fne lasciamo fnintatto il nuovo regolare (come mostrato sopra).

$ export -nf fn

Ora la funzione fnnon viene più esportata, ma la variabile regolare fnè e contiene () { echo "direct" ; }al suo interno.

Ora quando una subshell vede una variabile regolare che inizia con ()essa interpreta il resto come una definizione di funzione. Ma questo è solo quando inizia una nuova shell. Come abbiamo visto sopra, solo la definizione di una variabile shell normale che inizia con ()non comporta che si comporti come una funzione. Devi iniziare una subshell.

E ora il bug "shellshock":

Come abbiamo appena visto, quando una nuova shell ingerisce la definizione di una variabile regolare che inizia con ()essa, la interpreta come una funzione. Tuttavia, se dopo la parentesi graffa di chiusura che ne definisce la funzione viene fornito di più, esegue anche tutto ciò che è presente.

Questi sono i requisiti, ancora una volta:

  1. Viene generato un nuovo bash
  2. Viene ingerita una variabile di ambiente
  3. Questa variabile d'ambiente inizia con "()", quindi contiene un corpo di funzione all'interno di parentesi graffe, e successivamente ha i comandi

In questo caso, un bash vulnerabile eseguirà questi ultimi comandi.

Esempio:

$ export ex='() { echo "function ex" ; }; echo "this is bad"; '
$ bash
this is bad
$ ex
function ex
$

La variabile esportata normale è exstata passata alla subshell che è stata interpretata come una funzione exma i comandi finali sono stati eseguiti ( this is bad) quando la subshell è stata generata.


Spiegare il slick test di una riga

Un popolare one-liner per il test della vulnerabilità Shellshock è quello citato nella domanda di @ jippie:

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

Ecco un dettaglio: prima :in in bash è solo una scorciatoia per true. trueed :entrambi valutano (hai indovinato) vero, in bash:

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

In secondo luogo, il envcomando (anch'esso incorporato in bash) stampa le variabili di ambiente (come abbiamo visto sopra) ma può anche essere utilizzato per eseguire un singolo comando con una variabile esportata (o variabili) assegnata a quel comando ed bash -cesegue un singolo comando dal suo riga di comando:

$ bash -c 'echo hi'
hi
$ bash -c 'echo $t'

$ env t=exported bash -c 'echo $t'
exported
$

Quindi cucendo tutte queste cose insieme, possiamo eseguire bash come comando, dargli alcune cose fittizie da fare (come bash -c echo this is a test) ed esportare una variabile che inizia con ()così la subshell la interpreterà come una funzione. Se lo shellshock è presente, eseguirà immediatamente anche tutti i comandi finali nella subshell. Poiché la funzione che passiamo non è rilevante per noi (ma deve analizzarla!) Utilizziamo la funzione valida più breve che si possa immaginare:

$ f() { :;}
$ f
$ 

La funzione fqui esegue semplicemente il :comando, che restituisce true ed esce. Ora aggiungi quel comando "malvagio" ed esporta una variabile regolare in una sottostruttura e vinci. Ecco di nuovo il one-liner:

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

Quindi xviene esportato come una variabile normale con una semplice funzione valida con echo vulnerablevirata fino alla fine. Questo viene passato a bash, e bash interpreta xcome una funzione (di cui non ci interessa), quindi forse esegue echo vulnerablese lo shellshock è presente.

Potremmo accorciare un po 'la copertina eliminando il this is a testmessaggio:

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

Questo non dà fastidio, this is a testma esegue di nuovo il :comando silenzioso . (Se lo interrompi, -c :ti siedi nella subshell e devi uscire manualmente.) Forse la versione più user-friendly sarebbe questa:

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

12
Bella spiegazione. Questa domanda sta ricevendo molti punti di vista (probabilmente non tutti sono bravi come gli altri) e credo che nessuno abbia ancora speso un paio di parole su ciò che { :;};effettivamente dice. Secondo me sarebbe una bella aggiunta alla tua risposta. Puoi spiegare come passi dal tuo esempio al comando originale nella domanda?
jippie,

20

Se puoi alimentare variabili di ambiente arbitrarie a un programma, puoi fare in modo che faccia qualsiasi cosa facendo caricare librerie di tua scelta. Nella maggior parte dei casi questa non è considerata una vulnerabilità nel programma che riceve tali variabili di ambiente, ma piuttosto nel meccanismo attraverso il quale un estraneo potrebbe alimentare variabili di ambiente arbitrarie.

Tuttavia CVE-2014-6271 è diverso.

Non c'è nulla di sbagliato nell'avere dati non attendibili in una variabile di ambiente. Uno deve solo assicurarsi che non venga inserito in nessuna di quelle variabili d'ambiente che possono modificare il comportamento del programma. Detto un po 'più astratto, per una particolare invocazione, è possibile creare una whitelist di nomi di variabili di ambiente, che possono essere specificati direttamente da un estraneo.

Un esempio che è stato avanzato nel contesto di CVE-2014-6271 sono gli script utilizzati per l'analisi dei file di registro. Quelli possono avere un'esigenza molto legittima di trasferire dati non attendibili nelle variabili di ambiente. Naturalmente il nome per tale variabile d'ambiente è scelto in modo tale da non avere effetti negativi.

Ma ecco cosa c'è di male in questa particolare vulnerabilità bash. Può essere sfruttato con qualsiasi nome di variabile. Se si crea una variabile di ambiente chiamata GET_REQUEST_TO_BE_PROCESSED_BY_MY_SCRIPT, non ci si aspetterebbe che nessun altro programma oltre al proprio script interpreti il ​​contenuto di tale variabile di ambiente. Sfruttando questo bug bash, ogni singola variabile d'ambiente diventa un vettore di attacco.

Si noti che ciò non significa che i nomi delle variabili di ambiente siano segreti. Conoscere i nomi delle variabili d'ambiente coinvolte non semplifica l'attacco.

Se program1chiamate program2che a loro volta chiamano program3, quindi program1potrebbero passare i dati program3attraverso le variabili di ambiente. Ogni programma ha un elenco specifico di variabili d'ambiente che imposta e un elenco specifico su cui agisce. Se scegli un nome non riconosciuto da program2, puoi passare i dati da program1a program3senza preoccuparti che ciò abbia effetti negativi su program2.

Un utente malintenzionato che conosce i nomi esatti delle variabili esportate da program1e i nomi delle variabili interpretate da program2non può sfruttare questa conoscenza per modificare il comportamento di "program2" se non vi è sovrapposizione tra l'insieme di nomi.

Ma questo si è rotto se program2fosse uno bashscript, perché a causa di questo bug bashinterpreterebbe ogni variabile d'ambiente come codice.


1
"ogni singola variabile d'ambiente diventa un vettore di attacco" - questa è la parte che mi mancava. Grazie.
wrschneider,

9

È spiegato nell'articolo che hai collegato ...

è possibile creare variabili di ambiente con valori appositamente predisposti prima di chiamare la shell bash. Queste variabili possono contenere codice, che viene eseguito non appena viene invocata la shell.

Il che significa che bash chiamato con -c "echo this is a test"esegue il codice tra virgolette singole quando viene invocato.

Bash ha funzioni, sebbene in un'implementazione piuttosto limitata, ed è possibile inserire queste funzioni bash in variabili d'ambiente. Questo difetto viene attivato quando viene aggiunto del codice extra alla fine di queste definizioni di funzioni (all'interno della variabile di enivronment).

Indica l'esempio di codice che hai pubblicato sfrutta il fatto che la bash invocata non smette di valutare questa stringa dopo aver eseguito l'assegnazione. Un'assegnazione di funzione in questo caso.

La cosa veramente speciale dello snippet di codice che hai pubblicato, a quanto ho capito, è che usando una definizione di funzione prima del codice che vogliamo eseguire, alcuni meccanismi di sicurezza possono essere elusi.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.