Preambolo
Innanzitutto, direi che non è il modo giusto di affrontare il problema. È un po 'come dire " non dovresti uccidere le persone perché altrimenti andrai in prigione ".
Allo stesso modo, non citi la tua variabile perché altrimenti stai introducendo vulnerabilità di sicurezza. Citi le tue variabili perché è sbagliato non (ma se la paura della prigione può aiutare, perché no).
Un piccolo riassunto per coloro che sono appena saliti sul treno.
Nella maggior parte delle shell, lasciare un'espansione variabile non quotata (anche se (e il resto di questa risposta) si applica anche alla sostituzione dei comandi ( `...`
o $(...)
) e all'espansione aritmetica ( $((...))
o $[...]
)) ha un significato molto speciale. Il modo migliore per descriverlo è che è come invocare una sorta di operatore implicito split + glob¹ .
cmd $var
in un'altra lingua verrebbe scritto qualcosa del tipo:
cmd(glob(split($var)))
$var
viene prima suddiviso in un elenco di parole in base a regole complesse che coinvolgono il $IFS
parametro speciale (la parte divisa ) e quindi ogni parola risultante da tale suddivisione viene considerata come un modello che viene espanso in un elenco di file corrispondenti ( parte glob ) .
Ad esempio, se $var
contiene *.txt,/var/*.xml
e $IFS
contiene ,
, cmd
verrebbe chiamato con un numero di argomenti, il primo è cmd
e quelli successivi sono i txt
file nella directory corrente e i xml
file in /var
.
Se volessi chiamare cmd
con solo i due argomenti letterali cmd
e *.txt,/var/*.xml
, dovresti scrivere:
cmd "$var"
che sarebbe nella tua altra lingua più familiare:
cmd($var)
Cosa intendiamo per vulnerabilità in una shell ?
Dopotutto, è noto fin dagli albori che gli script di shell non devono essere utilizzati in contesti sensibili alla sicurezza. Sicuramente, OK, lasciare una variabile non quotata è un bug ma non può fare così tanto male, vero?
Bene, nonostante il fatto che qualcuno ti direbbe che gli script di shell non dovrebbero mai essere usati per i CGI web, o che per fortuna la maggior parte dei sistemi non consente al giorno d'oggi script di shell setuid / setgid, una cosa che shellshock (il bug bash sfruttabile da remoto che ha reso il i titoli di settembre 2014) hanno rivelato che le shell sono ancora ampiamente utilizzate dove probabilmente non dovrebbero: nei CGI, negli script hook del client DHCP, nei comandi sudoers, invocati da (se non come ) comandi setuid ...
A volte inconsapevolmente. Ad esempio system('cmd $PATH_INFO')
in uno script php
/ perl
/ python
CGI invoca una shell per interpretare quella riga di comando (per non parlare del fatto che cmd
può essere uno script di shell e il suo autore non si sarebbe mai aspettato che fosse chiamato da un CGI).
Hai una vulnerabilità quando esiste un percorso per l'escalation dei privilegi, ovvero quando qualcuno (chiamiamolo l'attaccante ) è in grado di fare qualcosa che non è destinato a.
Invariabilmente ciò significa che l'attaccante fornisce i dati, che i dati vengono elaborati da un utente / processo privilegiato che inavvertitamente fa qualcosa che non dovrebbe fare, nella maggior parte dei casi a causa di un bug.
Fondamentalmente, hai un problema quando il tuo codice buggy elabora i dati sotto il controllo dell'attaccante .
Ora, non è sempre ovvio da dove provengano quei dati ed è spesso difficile stabilire se il tuo codice potrà mai elaborare dati non attendibili.
Per quanto riguarda le variabili, nel caso di uno script CGI, è abbastanza ovvio, i dati sono i parametri CGI GET / POST e cose come i parametri cookie, percorso, host ...
Per uno script setuid (eseguito come utente quando richiamato da un altro), sono gli argomenti o le variabili di ambiente.
Un altro vettore molto comune sono i nomi dei file. Se stai ricevendo un elenco di file da una directory, è possibile che i file siano stati piantati lì dall'aggressore .
A tale proposito, anche al prompt di una shell interattiva, potresti essere vulnerabile (durante l'elaborazione di file in /tmp
o ~/tmp
per esempio).
Anche a ~/.bashrc
può essere vulnerabile (ad esempio, bash
lo interpreterà quando invocato ssh
per eseguire un ForcedCommand
like nelle git
distribuzioni del server con alcune variabili sotto il controllo del client).
Ora, uno script non può essere chiamato direttamente per elaborare dati non attendibili, ma può essere chiamato da un altro comando che lo fa. Oppure il tuo codice errato può essere copiato e incollato in script che lo fanno (da te 3 anni o uno dei tuoi colleghi). Un punto in cui è particolarmente critico è nelle risposte nei siti di domande e risposte poiché non saprai mai dove potrebbero finire le copie del tuo codice.
Giù per affari; quanto è cattivo?
Lasciare una variabile (o sostituzione di comando) non quotata è di gran lunga la fonte numero uno delle vulnerabilità di sicurezza associate al codice della shell. In parte perché quei bug si traducono spesso in vulnerabilità, ma anche perché è così comune vedere variabili non quotate.
In realtà, quando si cercano vulnerabilità nel codice della shell, la prima cosa da fare è cercare variabili non quotate. È facile da individuare, spesso un buon candidato, generalmente facile da rintracciare ai dati controllati dagli aggressori.
Esiste un numero infinito di modi in cui una variabile non quotata può trasformarsi in una vulnerabilità. Darò solo alcune tendenze comuni qui.
Rivelazione di un 'informazione
La maggior parte delle persone si imbatterà in bug associati a variabili non quotate a causa della parte divisa (ad esempio, è comune per i file avere spazi nei loro nomi al giorno d'oggi e lo spazio è nel valore predefinito di IFS). Molte persone
trascureranno la parte globale . La parte glob è pericolosa almeno quanto la
parte divisa .
Globbing fatto su input esterni non autorizzati significa che l'attaccante può farti leggere il contenuto di qualsiasi directory.
Nel:
echo You entered: $unsanitised_external_input
se $unsanitised_external_input
contiene /*
, ciò significa che l'attaccante può vedere il contenuto di /
. Nessun grosso problema. Diventa più interessante, però, con il /home/*
quale ti dà un elenco di nomi utente sulla macchina /tmp/*
, /home/*/.forward
per suggerimenti su altre pratiche pericolose, /etc/rc*/*
per servizi abilitati ... Non c'è bisogno di nominarli singolarmente. Un valore di /*
/*/* /*/*/*...
elencherà solo l'intero file system.
Vulnerabilità di negazione del servizio.
Portando il caso precedente un po 'troppo lontano e abbiamo un DoS.
In realtà, qualsiasi variabile non quotata nel contesto di un elenco con input non inizializzato è almeno una vulnerabilità DoS.
Persino gli esperti scripter shell comunemente dimenticano di citare cose come:
#! /bin/sh -
: ${QUERYSTRING=$1}
:
è il comando no-op. Che cosa potrebbe andare storto?
Questo è destinato ad assegnare $1
a $QUERYSTRING
se $QUERYSTRING
non fosse impostato. Questo è un modo rapido per rendere richiamabile anche uno script CGI dalla riga di comando.
Questo $QUERYSTRING
è comunque espanso e poiché non è quotato, viene richiamato l'operatore split + glob .
Ora, ci sono alcuni globs che sono particolarmente costosi da espandere. L' /*/*/*/*
uno è già abbastanza grave in quanto significa che le directory di quotazione fino a 4 livelli verso il basso. Oltre all'attività del disco e della CPU, ciò significa memorizzare decine di migliaia di percorsi di file (40k qui su una VM server minima, 10k di cui directory).
Ora /*/*/*/*/../../../../*/*/*/*
significa 40k x 10k ed
/*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/*
è sufficiente per mettere in ginocchio anche la macchina più potente.
Provalo tu stesso (anche se sii pronto per il crash o il blocco della macchina):
a='/*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/*' sh -c ': ${a=foo}'
Naturalmente, se il codice è:
echo $QUERYSTRING > /some/file
Quindi puoi riempire il disco.
Basta fare una ricerca su google su shell cgi o bash cgi o ksh cgi e troverai alcune pagine che mostrano come scrivere CGI nelle shell. Nota come la metà di quelli che elaborano i parametri sono vulnerabili.
Anche quello di David Korn
è vulnerabile (guarda la gestione dei cookie).
fino a vulnerabilità di esecuzione di codice arbitrario
L'esecuzione di codice arbitrario è il peggior tipo di vulnerabilità, poiché se l'attaccante può eseguire qualsiasi comando, non c'è limite a ciò che può fare.
Questa è generalmente la parte divisa che porta a quelli. Tale suddivisione comporta la trasmissione di numerosi argomenti ai comandi quando è previsto solo uno. Mentre il primo verrà utilizzato nel contesto previsto, gli altri si troveranno in un contesto diverso, quindi potenzialmente interpretato diversamente. Meglio con un esempio:
awk -v foo=$external_input '$2 == foo'
Qui, l'intenzione era di assegnare il contenuto della
$external_input
variabile shell alla foo
awk
variabile.
Adesso:
$ external_input='x BEGIN{system("uname")}'
$ awk -v foo=$external_input '$2 == foo'
Linux
La seconda parola risultante dalla divisione di $external_input
non è assegnata foo
ma considerata come awk
codice (qui che esegue un comando arbitrario:) uname
.
Questo è un problema soprattutto per i comandi che possono eseguire altri comandi ( awk
, env
, sed
(GNU uno), perl
, find
...) in particolare con le varianti GNU (che accettano opzioni dopo argomenti). A volte, non sospetteresti che i comandi siano in grado di eseguire altri come ksh
, bash
o zsh
's [
o printf
...
for file in *; do
[ -f $file ] || continue
something-that-would-be-dangerous-if-$file-were-a-directory
done
Se creiamo una directory chiamata x -o yes
, il test diventa positivo, perché stiamo valutando un'espressione condizionale completamente diversa.
Peggio ancora, se creiamo un file chiamato x -a a[0$(uname>&2)] -gt 1
, con almeno tutte le implementazioni di ksh (che include la sh
maggior parte degli Unices commerciali e alcuni BSD), che viene eseguito uname
perché tali shell eseguono una valutazione aritmetica sugli operatori di confronto numerico del [
comando.
$ touch x 'x -a a[0$(uname>&2)] -gt 1'
$ ksh -c 'for f in *; do [ -f $f ]; done'
Linux
Lo stesso vale bash
per un nome file come x -a -v a[0$(uname>&2)]
.
Naturalmente, se non riescono a ottenere un'esecuzione arbitraria, l'attaccante può accontentarsi di un danno minore (che può aiutare a ottenere un'esecuzione arbitraria). Qualsiasi comando in grado di scrivere file o modificare autorizzazioni, proprietà o avere effetti principali o collaterali può essere sfruttato.
Tutti i tipi di cose possono essere fatte con nomi di file.
$ touch -- '-R ..'
$ for file in *; do [ -f "$file" ] && chmod +w $file; done
E ..
finisci per scrivere (ricorsivamente con GNU
chmod
).
Gli script che eseguono l'elaborazione automatica dei file in aree pubblicamente scrivibili come /tmp
devono essere scritti con molta attenzione.
Che dire [ $# -gt 1 ]
È qualcosa che trovo esasperante. Alcune persone si chiedono se una particolare espansione possa essere problematica per decidere se possono omettere le virgolette.
È come dire. Ehi, sembra che $#
non possa essere soggetto all'operatore split + glob, chiediamo alla shell di dividerlo + glob . Oppure Hey, scriviamo un codice errato solo perché è improbabile che il bug venga colpito .
Ora, quanto è improbabile? OK, $#
(o $!
, $?
o qualsiasi sostituzione aritmetica) può contenere solo cifre (o -
per alcuni), quindi la parte glob è fuori. Affinché la parte divisa faccia qualcosa, tutto ciò che serve è $IFS
contenere cifre (o -
).
Con alcune shell, $IFS
può essere ereditato dall'ambiente, ma se l'ambiente non è sicuro, è comunque un gioco finito.
Ora se scrivi una funzione come:
my_function() {
[ $# -eq 2 ] || return
...
}
Ciò significa che il comportamento della tua funzione dipende dal contesto in cui viene chiamato. O in altre parole, $IFS
diventa uno degli input ad esso. A rigor di termini, quando scrivi la documentazione API per la tua funzione, dovrebbe essere qualcosa del tipo:
# my_function
# inputs:
# $1: source directory
# $2: destination directory
# $IFS: used to split $#, expected not to contain digits...
E il codice che chiama la tua funzione deve assicurarsi $IFS
che non contenga cifre. Tutto questo perché non ti andava di scrivere quei 2 caratteri a virgoletta doppia.
Ora, affinché quel [ $# -eq 2 ]
bug diventi una vulnerabilità, avresti bisogno in qualche modo che il valore di $IFS
diventare sotto il controllo dell'attaccante . Concepibilmente, ciò non accadrebbe normalmente se l'attaccante non fosse riuscito a sfruttare un altro bug.
Questo non è inaudito però. Un caso comune è quando le persone dimenticano di disinfettare i dati prima di usarli nell'espressione aritmetica. Abbiamo già visto in precedenza che può consentire l'esecuzione di codice arbitrario in alcune shell, ma in tutte, consente
all'attaccante di assegnare a qualsiasi variabile un valore intero.
Per esempio:
n=$(($1 + 1))
if [ $# -gt 2 ]; then
echo >&2 "Too many arguments"
exit 1
fi
E con un $1
valore with (IFS=-1234567890)
, quella valutazione aritmetica ha l'effetto collaterale delle impostazioni IFS e il [
comando successivo fallisce, il che significa che il controllo per troppi argomenti viene ignorato.
Che dire quando l' operatore split + glob non viene richiamato?
C'è un altro caso in cui sono necessarie le virgolette intorno alle variabili e ad altre espansioni: quando viene utilizzato come modello.
[[ $a = $b ]] # a `ksh` construct also supported by `bash`
case $a in ($b) ...; esac
non verificare se $a
e $b
sono uguali (tranne con zsh
) ma se $a
corrisponde al modello in $b
. E devi citare $b
se vuoi confrontare come stringhe (la stessa cosa in "${a#$b}"
o "${a%$b}"
o "${a##*$b*}"
dove $b
dovrebbe essere quotata se non deve essere presa come un modello).
Ciò significa che [[ $a = $b ]]
può tornare vero nei casi in cui $a
è diverso da $b
(ad esempio quando $a
è anything
e $b
è *
) o può restituire falso quando sono identici (ad esempio quando entrambi $a
e lo $b
sono [a]
).
Ciò può costituire una vulnerabilità della sicurezza? Sì, come qualsiasi bug. Qui, l'attaccante può modificare il flusso di codice logico del tuo script e / o infrangere le ipotesi che il tuo script sta facendo. Ad esempio, con un codice come:
if [[ $1 = $2 ]]; then
echo >&2 '$1 and $2 cannot be the same or damage will incur'
exit 1
fi
L'attaccante può bypassare il controllo passando '[a]' '[a]'
.
Ora, se non si applicano né la corrispondenza dei pattern né l' operatore split + glob , qual è il pericolo di lasciare una variabile non quotata?
Devo ammettere che scrivo:
a=$b
case $a in...
Lì, la citazione non fa male ma non è strettamente necessaria.
Tuttavia, un effetto collaterale dell'omissione di virgolette in quei casi (ad esempio nelle risposte a domande e risposte) è che può inviare un messaggio sbagliato ai principianti: che può essere giusto non citare le variabili .
Ad esempio, potrebbero iniziare a pensare che, se a=$b
è OK, lo export a=$b
sarebbe anche (che non è presente in molte shell in quanto è presente negli argomenti del export
comando, quindi nel contesto dell'elenco) o env a=$b
.
Che dire zsh
?
zsh
risolto la maggior parte di quei disagi del design. In zsh
(almeno quando non si è in modalità di emulazione sh / ksh), se si desidera dividere , globbing o pattern matching , è necessario richiederlo esplicitamente: $=var
per dividere e $~var
glob o per far sì che il contenuto della variabile sia trattato come uno schema.
Tuttavia, la suddivisione (ma non il globbing) è ancora implicita in caso di sostituzione di comando non quotata (come in echo $(cmd)
).
Inoltre, un effetto collaterale a volte indesiderato di non quotare la variabile è la rimozione di vuoti . Il zsh
comportamento è simile a quello che puoi ottenere in altre shell disabilitando del tutto il globbing (con set -f
) e la divisione (con IFS=''
). Ancora dentro:
cmd $var
Non ci sarà split + glob , ma se $var
è vuoto, invece di ricevere un argomento vuoto, cmd
non riceverà alcun argomento.
Ciò può causare bug (come l'ovvio [ -n $var ]
). Questo può eventualmente infrangere le aspettative e le ipotesi di uno script e causare vulnerabilità, ma non posso trovare un esempio non troppo inverosimile proprio ora).
Che dire quando si fa bisogno della spaccatura + glob operatore?
Sì, in genere è quando si desidera lasciare la variabile non quotata. Ma poi devi assicurarti di sintonizzare correttamente gli operatori split e glob prima di usarlo. Se si desidera solo la parte divisa e non la parte glob (che è il caso per la maggior parte del tempo), è necessario disabilitare il globbing ( set -o noglob
/ set -f
) e correggere $IFS
. Altrimenti causerai anche vulnerabilità (come l'esempio CGI di David Korn menzionato sopra).
Conclusione
In breve, lasciare una variabile (o la sostituzione del comando o l'espansione aritmetica) non quotata nelle shell può essere molto pericolosa, specialmente se eseguita in contesti sbagliati, ed è molto difficile sapere quali siano quei contesti sbagliati.
Questo è uno dei motivi per cui è considerata una cattiva pratica .
Grazie per aver letto finora. Se ti passa per la testa, non preoccuparti. Non ci si può aspettare che tutti comprendano tutte le implicazioni della scrittura del proprio codice nel modo in cui lo scrivono. Ecco perché abbiamo
raccomandazioni sulle buone pratiche , quindi possono essere seguite senza necessariamente capire il perché.
(e nel caso non fosse ancora ovvio, si prega di evitare di scrivere codice sensibile alla sicurezza nelle shell).
E per favore cita le tue variabili nelle risposte su questo sito!
¹In ksh93
e pdksh
e derivati, viene eseguita anche l' espansione del controvento a meno che il globbing sia disabilitato (nel caso di ksh93
versioni fino a ksh93u +, anche quando l' braceexpand
opzione è disabilitata).