GNU/Linux >> Linux Esercitazione >  >> Linux

Implicazioni sulla sicurezza di dimenticare di citare una variabile in shell Bash/posix?

Se segui unix.stackexchange.com da un po', si spera che
ormai dovresti sapere che lasciare una variabile
senza virgolette nel contesto dell'elenco (come in echo $var ) nelle shell Bourne/POSIX
(zsh è l'eccezione) ha un significato molto speciale e
non dovrebbe essere fatto a meno che tu non abbia una buona ragione per farlo.

È discusso a lungo in una serie di domande e risposte qui (Esempi:perché il mio script di shell si blocca su spazi bianchi o altri caratteri speciali?, Quando è necessaria la doppia virgoletta?, Espansione di una variabile di shell ed effetto di glob e divisa su di essa, tra virgolette vs espansione di stringhe senza virgolette )

Questo è stato il caso dal rilascio iniziale della shell Bourne
alla fine degli anni '70 e non è stato modificato dalla shell Korn
(uno dei più grandi rimpianti
di David Korn (domanda n. 7 )) o bash che principalmente
ha copiato la shell Korn, ed è così che è stato specificato da POSIX/Unix.

Ora, stiamo ancora vedendo una serie di risposte qui e persino
codice shell occasionalmente rilasciato pubblicamente in cui
le variabili non sono citate. Avresti pensato che le persone avrebbero
imparato ormai.

Nella mia esperienza, ci sono principalmente 3 tipi di persone che omettono di
citare le proprie variabili:

  • principianti. Questi possono essere scusati in quanto è certamente una sintassi
    completamente non intuitiva. Ed è il nostro ruolo su questo sito
    educarli.

  • persone smemorate.

  • persone che non sono convinte anche dopo ripetuti martellamenti,
    che pensano che sicuramente l'autore della shell Bourne non
    intendesse citare tutte le nostre variabili
    .

Forse possiamo convincerli se esponiamo il rischio associato a
questo tipo di comportamenti.

Qual è la cosa peggiore che potrebbe succedere se
ti dimentichi di citare le tue variabili. È davvero quello male?

Di che tipo di vulnerabilità stiamo parlando qui?

In quali contesti può essere un problema?

Risposta accettata:

Preambolo

Innanzitutto, direi che non è il modo giusto per affrontare il problema.
È un po' come dire "non dovresti uccidere le persone perché
altrimenti andrai in galera
“.

Allo stesso modo, non citi la tua variabile perché altrimenti
stai introducendo vulnerabilità di sicurezza. Citi le tue
variabili perché è sbagliato non farlo (ma se la paura della galera può aiutare, perché no).

Un piccolo riassunto per chi è appena salito sul treno.

Nella maggior parte delle shell, lasciare un'espansione variabile senza virgolette (sebbene
che (e il resto di questa risposta) si applichi anche alla sostituzione del comando
(`...` o $(...) ) ed espansione aritmetica ($((...)) o $[...] )) ha un significato
molto speciale. Il modo migliore per descriverlo è che è come
invocare una sorta di split+glob implicito operatore¹.

cmd $var

in un'altra lingua verrebbe scritto qualcosa come:

cmd(glob(split($var)))

$var viene prima suddiviso in un elenco di parole in base a complesse regole
che coinvolgono il $IFS parametro speciale (il split part)
e quindi ogni parola risultante da quella scissione è considerata come
un modello che viene espanso in un elenco di file che corrispondono a esso
(il glob parte).

Ad esempio, se $var contiene *.txt,/var/*.xml e $IFS contiene , , cmd verrebbe chiamato con un numero di argomenti,
il primo è cmd e il prossimo è il txt file nella directory corrente e xml file in /var .

Se vuoi 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, è risaputo fin dall'alba dei tempi che gli script di shell
non dovrebbero essere usati in contesti sensibili alla sicurezza.
Sicuramente, ok, lasciare una variabile senza virgolette è un bug ma non può
fanno così tanto male, vero?

Bene, nonostante qualcuno ti direbbe che gli script di shell
non dovrebbero mai essere usati per i CGI web, o che fortunatamente
la maggior parte dei sistemi non consente al giorno d'oggi gli script di shell setuid/setgid,
uno cosa che ha rivelato shellshock (il bash bug sfruttabile in remoto
che ha fatto notizia a settembre 2014) è che
le shell sono ancora ampiamente utilizzate dove probabilmente non dovrebbero:
nei CGI, nel client DHCP script hook, nei comandi sudoers,
invocati da (se non come ) comandi setuid...

A volte inconsapevolmente. Ad esempio system('cmd $PATH_INFO') in un php /perl /python Lo script CGI invoca una shell per interpretare quella riga di comando (non per
menzionare il fatto che cmd esso stesso potrebbe essere uno script di shell e il suo
autore potrebbe non essersi mai aspettato che fosse chiamato da un CGI).

Hai una vulnerabilità quando c'è un percorso per l'escalation dei privilegi
, cioè quando qualcuno (chiamiamolo l'attaccante )
è in grado di fare qualcosa per cui non è destinato.

Invariabilmente ciò significa l'attaccante fornendo dati, quei 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 buggato elabora
dati sotto il controllo dell'attaccante .

Ora, non è sempre ovvio dove si trovano quei dati potrebbe provenire da
ed è spesso difficile dire se il tuo codice riuscirà mai a
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 cookie, percorso, host… parametri.

Per uno script setuid (in esecuzione come un utente quando viene invocato 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ì da l'attaccante .

A tale proposito, anche al prompt di una shell interattiva, tu
potresti essere vulnerabile (durante l'elaborazione di file in /tmp o ~/tmp per esempio).

Anche un ~/.bashrc può essere vulnerabile (ad esempio, bash lo interpreterà
quando viene invocato su ssh per eseguire un ForcedCommand come in git distribuzioni del server con alcune variabili sotto il controllo
del client).

Ora, uno script potrebbe non essere chiamato direttamente per elaborare dati
non attendibili, ma potrebbe essere chiamato da un altro comando che lo fa. Oppure il tuo
codice errato potrebbe essere copiato e incollato in script che lo fanno (da te 3
anni dopo o da uno dei tuoi colleghi). Un luogo in cui è
particolarmente critico è nelle risposte nei siti di domande e risposte poiché
non saprai mai dove potrebbero finire le copie del tuo codice.

Al lavoro; quanto è grave?

Lasciare una variabile (o una sostituzione di comando) senza virgolette è di gran lunga
la fonte numero uno di vulnerabilità di sicurezza associate
al codice della shell. In parte perché questi bug spesso si traducono in
vulnerabilità, ma anche perché è così comune vedere variabili
non quotate.

Correlati:perché usare install anziché cp e mkdir?

In realtà, quando si cercano le vulnerabilità nel codice della shell, la
prima cosa da fare è cercare le variabili non quotate. È facile
individuare, spesso un buon candidato, generalmente è facile risalire a
dati controllati dagli aggressori.

Esiste un numero infinito di modi in cui una variabile senza virgolette può trasformarsi
in una vulnerabilità. Qui fornirò solo alcune tendenze comuni.

Divulgazione delle informazioni

La maggior parte delle persone si imbatterà in bug associati a variabili
non quotate a causa della divisione part (ad esempio, al giorno d'oggi è
comune per i file avere spazi nei loro nomi e spazio
è nel valore predefinito di IFS). Molte persone trascureranno il glob parte. Il globo la parte è pericolosa almeno quanto la divisione parte.

Il globbing eseguito su input esterno non disinfettato significa l'
aggressore
può farti leggere il contenuto di qualsiasi directory.

In:

echo You entered: $unsanitised_external_input

se $unsanitised_external_input contiene /* , ciò significa l'
attaccante
può vedere il contenuto di / . Nessun grosso problema. Diventa
più interessante però con /home/* che fornisce un elenco di
nomi utente sulla macchina, /tmp/* , /home/*/.forward per
accenni ad altre pratiche pericolose, /etc/rc*/* per i servizi
abilitati... Non c'è bisogno di nominarli individualmente. Un valore di /* /*/* /*/*/*... elencherà solo l'intero file system.

Vulnerabilità Denial of Service.

Se prendiamo un po' troppo in là il caso precedente, abbiamo un DoS.

In realtà, qualsiasi variabile senza virgolette nel contesto dell'elenco con input
non sterilizzato è almeno una vulnerabilità DoS.

Anche gli esperti di script di shell dimenticano comunemente di citare cose
come:

#! /bin/sh -
: ${QUERYSTRING=$1}

: è il comando no-op. Cosa potrebbe andare storto?

Questo significa assegnare $1 a $QUERYSTRING se $QUERYSTRING non era impostato. Questo è un modo rapido per rendere richiamabile uno script CGI anche da
dalla riga di comando.

Quel $QUERYSTRING è ancora ampliato e poiché
non è citato, il split+glob viene richiamato l'operatore.

Ora, ci sono alcuni glob che sono particolarmente costosi da
espandere. Il /*/*/*/* uno è già abbastanza grave in quanto significa elencare
directory fino a 4 livelli in basso. Oltre all'attività del disco e della CPU
, ciò significa archiviare decine di migliaia di percorsi di file
(40.000 qui su una VM minima del server, 10.000 delle quali directory).

Ora /*/*/*/*/../../../../*/*/*/* significa 40k x 10k e /*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/* è sufficiente per
mettere in ginocchio anche la macchina più potente.

Provalo tu stesso (anche se preparati al fatto che la tua macchina
si arresti in modo anomalo o si blocchi):

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 ti mostrano come scrivere CGI nelle shell. Nota
come la metà di coloro che elaborano i parametri sono vulnerabili.

Anche quello
proprio
di David Korn è vulnerabile (guarda la gestione dei cookie).

fino a vulnerabilità di esecuzione di codice arbitrario

L'esecuzione arbitraria di codice è il peggior tipo di vulnerabilità,
poiché se l'attaccante può eseguire qualsiasi comando, non c'è limite a
cosa può fare.

Questa è generalmente la divisione parte che conduce a quelli. Quella
scissione comporta il passaggio di diversi argomenti ai comandi
quando ne è previsto uno solo. Mentre il primo di questi verrà utilizzato
nel contesto previsto, gli altri saranno in un contesto diverso
quindi potenzialmente interpretati in modo diverso. Meglio con un esempio:

awk -v foo=$external_input '$2 == foo'

Qui, l'intenzione era di assegnare il contenuto del $external_input variabile della shell al foo awk variabile.

Ora:

$ external_input='x BEGIN{system("uname")}'
$ awk -v foo=$external_input '$2 == foo'
Linux

La seconda parola risultante dalla divisione di $external_input non è assegnato a foo ma considerato come awk code (qui che
esegue un comando arbitrario:uname ).

Questo è particolarmente un problema per i comandi che possono eseguire altri comandi
(awk , env , sed (GNU uno), perl , find ...) specialmente
con le varianti GNU (che accettano opzioni dopo gli argomenti).
A volte, non sospetti che 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 , quindi il test
diventa positivo, perché è un'espressione condizionale
completamente diversa che stiamo valutando.

Peggio ancora, se creiamo un file chiamato x -a a[0$(uname>&2)] -gt 1 ,
almeno con tutte le implementazioni ksh (che include sh della maggior parte degli Unice commerciali e di alcuni BSD), che esegue uname perché quelle shell eseguono valutazioni aritmetiche 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 con bash per un nome file come x -a -v a[0$(uname>&2)] .

Naturalmente, se non possono ottenere l'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 potrebbe essere sfruttato.

Con i nomi dei file è possibile fare qualsiasi cosa.

$ touch -- '-R ..'
$ for file in *; do [ -f "$file" ] && chmod +w $file; done

E finisci per creare .. scrivibile (ricorsivamente con GNU chmod ).

Script che eseguono l'elaborazione automatica di file in aree scrivibili pubblicamente come /tmp devono essere scritti con molta attenzione.

Che dire di [ $# -gt 1 ]

È qualcosa che trovo esasperante. Alcune persone si preoccupano
di chiedersi se una particolare espansione possa essere
problematica per decidere se possono omettere le virgolette.

È come dire. Ehi, sembra $# non può essere soggetto a
l'operatore split+glob, chiediamo alla shell di dividerlo+glob
.
Oppure Ehi, scriviamo codice errato solo perché è improbabile che il bug
venga rilevato
.

Ora quanto è improbabile? OK, $# (o $! , $? o qualsiasi
sostituzione aritmetica) può contenere solo cifre (o - per
alcuni²) quindi il glob parte è fuori. Per la divisione parte da fare
qualcosa però, tutto ciò di cui abbiamo bisogno è per $IFS per contenere cifre (o - ).

Con alcune shell, $IFS può essere ereditato dall'ambiente,
ma se l'ambiente non è sicuro, il gioco finisce comunque.

Correlati:il reindirizzamento a un nome di file globale non riesce?

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 chiamata. 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 che $IFS non
contiene cifre. Tutto questo perché non hai voglia di digitare
quei 2 caratteri tra virgolette.

Ora, per quel [ $# -eq 2 ] bug per diventare una vulnerabilità,
avresti bisogno in qualche modo per il valore di $IFS diventare sotto
controllo dell'attaccante . È plausibile che ciò non accada normalmente
a meno che l'attaccante riuscito a sfruttare un altro bug.

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 l'attaccante per assegnare a qualsiasi variabile un valore intero.

Ad esempio:

n=$(($1 + 1))
if [ $# -gt 2 ]; then
  echo >&2 "Too many arguments"
  exit 1
fi

E con un $1 con valore (IFS=-1234567890) , quella valutazione aritmetica
ha l'effetto collaterale delle impostazioni IFS e del successivo [ comando fallisce, il che significa che il controllo per troppi argomenti è
ignorato.

E quando il split+glob l'operatore non è stato richiamato?

C'è un altro caso in cui sono necessarie virgolette intorno a variabili e 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 gli stessi (tranne con zsh ) ma se $a corrisponde al modello in $b . E devi citare $b se vuoi confrontare come stringhe (stessa cosa in "${a#$b}" o "${a%$b}" o "${a##*$b*}" dove $b dovrebbe essere citato se non deve essere preso come modello).

Ciò significa che [[ $a = $b ]] può restituire true nei casi in cui $a è diverso da $b (ad esempio quando $a è anything e $b è * ) o possono restituire false quando sono identici (ad esempio quando entrambi $a e $b sono [a] ).

Questo può creare una vulnerabilità di sicurezza? Sì, come ogni bug. Ecco, l'attaccante può alterare il flusso di codice logico del tuo script e/o infrangere i presupposti 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ò aggirare il controllo passando '[a]' '[a]' .

Ora, se né quel modello corrisponde né il split+glob applica, qual è il pericolo di lasciare una variabile senza virgolette?

Devo ammettere che scrivo:

a=$b
case $a in...

Lì, citare non nuoce ma non è strettamente necessario.

Tuttavia, un effetto collaterale dell'omissione delle virgolette in questi casi (ad esempio nelle risposte a domande e risposte) è che può inviare un messaggio sbagliato ai principianti:che potrebbe essere giusto non citare le variabili .

Ad esempio, potrebbero iniziare a pensare che se a=$b è OK, quindi export a=$b sarebbe anche così (che non è in molte shell come è negli argomenti per export command so nel contesto della lista) o env a=$b .

Che ne dici di zsh ?

zsh ha risolto la maggior parte di quei problemi di progettazione. In zsh (almeno quando non sei in modalità di emulazione sh/ksh), se vuoi dividere o glob o corrispondenza del modello , devi richiederlo esplicitamente:$=var dividere e $~var per glob o per il contenuto della variabile da trattare come un pattern.

Tuttavia, la divisione (ma non il globbing) viene comunque eseguita implicitamente dopo la sostituzione di comandi senza virgolette (come in echo $(cmd) ).

Inoltre, un effetto collaterale a volte indesiderato di non citare la variabile è la rimozione svuota . Il zsh il comportamento è simile a quello che puoi ottenere in altre shell disabilitando del tutto il globbing (con set -f ) e dividere (con IFS='' ). Ancora, in:

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 ] ). Ciò può eventualmente infrangere le aspettative e le ipotesi di uno script e causare vulnerabilità.

Poiché la variabile vuota può causare la rimozione di un argomento , ciò significa che l'argomento successivo potrebbe essere interpretato nel contesto sbagliato.

Ad esempio,

printf '[%d] <%s>n' 1 $attacker_supplied1 2 $attacker_supplied2

Se $attacker_supplied1 è vuoto, quindi $attacker_supplied2 verrà interpretato come un'espressione aritmetica (per %d ) invece di una stringa (per %s ) e qualsiasi dato non disinfettato utilizzato in un'espressione aritmetica è una vulnerabilità di iniezione di comandi in shell simili a Korn come zsh.

$ attacker_supplied1='x y' attacker_supplied2='*'
$ printf '[%d] <%s>n' 1 $attacker_supplied1 2 $attacker_supplied2
[1] <x y>
[2] <*>

bene, ma:

$ attacker_supplied1='' attacker_supplied2='psvar[$(uname>&2)0]'
$ printf '[%d] <%s>n' 1 $attacker_supplied1 2 $attacker_supplied2
Linux
[1] <2>
[0] <>

Il uname comando arbitrario è stato eseguito.

E quando fai bisogno di split+glob operatore?

Sì, in genere è quando vuoi lasciare la tua variabile senza virgolette. Ma poi devi assicurarti di ottimizzare il tuo split e glob operatori correttamente prima di utilizzarlo. Se vuoi solo la divisione parte e non il glob parte (che è il caso la maggior parte delle volte), quindi è 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 una sostituzione di comando o
espansione aritmetica) senza virgolette nelle shell può essere molto pericoloso
anzi specialmente se eseguita nei contesti sbagliati, ed è molto
difficile sapere quali siano quei contesti sbagliati.

Questo è uno dei motivi per cui è considerata cattiva pratica .

Grazie per aver letto finora. Se ti passa sopra la testa, non
preoccuparti. Non ci si può aspettare che tutti comprendano tutte le implicazioni di
scrivere il proprio codice nel modo in cui lo scrivono. Ecco perché abbiamo consigli di buone pratiche , in modo che possano essere seguiti senza
necessariamente capirne il motivo.

Correlati:come calcolare la media dei valori in una colonna considerando le informazioni da un'altra colonna?

(e nel caso non sia ancora ovvio, evita di scrivere
codice sensibile alla sicurezza nelle shell).

E per favore cita le tue variabili sulle tue risposte su questo sito!


Linux
  1. Personalizzazione della shell Bash

  2. Script Bash(I)

  3. Bash Dynamic (variabile) Nomi di variabili?

  4. Funzioni nelle variabili Shell?

  5. [ :Operatore imprevisto nella programmazione della shell

.bashrc vs .bash_profile

Come impostare la variabile d'ambiente in Bash

Bash Beginner Series #2:Comprensione delle variabili nello scripting di Bash Shell

Che cos'è Subshell in Linux?

Differenza tra virgolette singole e doppie in Bash Shell

8 tipi di shell Linux