risposta-alla-domanda-sullo-sviluppo-web-bd.com

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

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

Sto cercando di capire cosa sta succedendo, ma non sono del tutto sicuro di capirlo. Come può essere eseguito echo 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"

output 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 con patch parzialmente (versione precedente):

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

output con patch 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:

244
jippie

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 di ambiente foo ha 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. Shell esamina le variabili di ambiente, vede foo, che sembra soddisfare i vincoli che conosce sull'aspetto di 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 il tuo esplicito intento 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. Per esempio, HTTP_USER_AGENT potrebbe essere impostato sul contenuto del tuo programma utente. Ciò significa che se falsi il tuo programma utente in modo che sia qualcosa come '() {:; }; echo foo ', quando viene eseguito quello script Shell, echo foo verrà eseguito. Ancora, echo foo potrebbe essere qualsiasi cosa, dannoso o no.

209
Chris Down

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, Shell vede che la variabile d'ambiente (fittizia) contiene una definizione di funzione e valuta la definizione per definire quella funzione nel suo ambiente (si noti che non sta eseguendo la funzione: che stamperebbe 'ciao'.)

Sfortunatamente, non valuta solo 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 delle 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.

86
sdenham

Ho scritto questo come un rifacimento 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 parametro envp non è molto visto, il main nel tuo programma C ha tre parametri: main(int argc, char *argv[], char *envp[]) dove l'ultimo array di puntatori è un array di variabili Shell con le loro definizioni).

Esportiamo quindi t come segue:

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

Mentre sopra t non era definito nella subshell, ora appare dopo averlo esportato (usare export -n t Se 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 fn viene convertita in una variabile di ambiente proprio come la nostra esportazione della variabile Shell t era sopra. Ciò non accade quando fn era una variabile locale, ma dopo l'esportazione possiamo vederla come una variabile Shell. Tuttavia, è possibile anche avere una variabile Shell normale (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 env per mostrare tutte le variabili Shell contrassegnate per l'esportazione e sia il normale fn sia 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 fn come abbiamo fatto sopra, o direttamente come una normale assegnazione di variabili:

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

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

Annulliamo l'esportazione della funzione fn e lasciamo intatto il nuovo fn regolare (come mostrato sopra).

$ export -nf fn

Ora la funzione fn non viene più esportata, ma lo è la variabile normale fn e contiene () { echo "direct" ; }.

Ora quando una subshell vede una variabile regolare che inizia con () Interpreta il resto come una definizione di funzione. Ma questo è solo quando inizia una nuova Shell. Come abbiamo visto sopra, la semplice 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 () La interpreta come una funzione. Tuttavia, se dopo la parentesi graffa di chiusura viene indicato di più, la funzione esegue qualunque cosa è presente anche.

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 regolare ex è stata passata alla subshell che è stata interpretata come una funzione ex ma i comandi finali sono stati eseguiti (this is bad) Quando è stata generata la subshell.


Spiegazione del 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 il : In bash è solo una scorciatoia per true. true e : Valutano entrambi su (hai indovinato) vero, in bash:

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

In secondo luogo, il comando env (anch'esso incorporato in bash) stampa le variabili di ambiente (come abbiamo visto sopra) ma può anche essere usato per eseguire un singolo comando con una variabile (o variabili) esportata data a quel comando, e bash -c Esegue un singolo comando dalla sua 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 () In modo che la subshell interpreti come una funzione. Se Shellshock è presente, eseguirà immediatamente anche tutti i comandi finali nella subshell. Dal momento che la funzione che passiamo è irrilevante per noi (ma deve analizzarla!) Utilizziamo la funzione valida più breve che si possa immaginare:

$ f() { :;}
$ f
$ 

La funzione f qui esegue solo il comando :, Che restituisce true ed esce. Ora aggiungi quel comando "malvagio" ed esporta una variabile regolare in una subshell e vinci. Ecco di nuovo il one-liner:

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

Quindi x viene esportato come una variabile normale con una semplice funzione valida con echo vulnerable Applicato alla fine. Questo viene passato a bash e bash interpreta x come una funzione (di cui non ci interessa), quindi forse esegue echo vulnerable Se Shellshock è presente.

Potremmo accorciare un po 'la riga rimuovendo il messaggio this is a test:

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

Questo non dà fastidio con this is a test Ma esegue di nuovo il comando silenzioso :. (Se lasci il -c :, Ti siedi nella sotto-shell 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"
72
Fixee

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 d'ambiente, ma piuttosto nel meccanismo attraverso il quale un estraneo potrebbe alimentare variabili d'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 di ambiente che possono modificare il comportamento del programma. Detto un po 'più astratto, per una particolare invocazione, è possibile creare una lista bianca di nomi di variabili d'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 denominata GET_REQUEST_TO_BE_PROCESSED_BY_MY_SCRIPT, non ti aspetteresti che nessun altro programma oltre al tuo stesso script interpreti i contenuti di quella variabile d'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 program1 chiama program2 che a sua volta chiama program3, poi program1 potrebbe passare i dati a program3 tramite variabili di ambiente. Ogni programma ha un elenco specifico di variabili d'ambiente che imposta e un elenco specifico su cui agisce. Se hai scelto un nome non riconosciuto da program2, puoi trasmettere i dati da program1 per program3 senza preoccuparti che ciò abbia effetti negativi su program2.

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

Ma questo si è rotto se program2 era uno script bash, perché a causa di questo errore bash interpretava ogni variabile d'ambiente come codice.

20
kasperd

È 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 invocato Shell.

Il che significa che il bash viene 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 davvero speciale dello snippet di codice che hai pubblicato, a quanto ho capito, è che usando la definizione di una funzione prima del codice che vogliamo eseguire, è possibile aggirare alcuni meccanismi di sicurezza.

9
Bananguin