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 envp
parametro non è molto visto, main
nel 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 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 che lo abbiamo esportato (utilizzare 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 di shell t
era sopra. Questo non accade quando fn
era 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 env
per mostrare tutte le variabili di shell contrassegnate per l'esportazione e vengono visualizzate sia la normale fn
che 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 insolita da fare! Normalmente definiremmo la funzione fn
come 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 fn
disponibile nella shell corrente. Ma se si genera una shell ** sub **, allora lo farà.
Annulliamo l'esportazione della funzione fn
e lasciamo fn
intatto il nuovo regolare (come mostrato sopra).
$ export -nf fn
Ora la funzione fn
non 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:
- Viene generato un nuovo bash
- Viene ingerita una variabile di ambiente
- 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 è ex
stata passata alla subshell che è stata interpretata come una funzione ex
ma 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
. true
ed :
entrambi valutano (hai indovinato) vero, in bash:
$ if true; then echo yes; fi
yes
$ if :; then echo yes; fi
yes
$
In secondo luogo, il env
comando (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 -c
esegue 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 f
qui 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 x
viene esportato come una variabile normale con una semplice funzione valida con echo vulnerable
virata fino 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 lo shellshock è presente.
Potremmo accorciare un po 'la copertina eliminando il this is a test
messaggio:
$ env x='() { :;}; echo vulnerable' bash -c :
Questo non dà fastidio, this is a test
ma 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"