Input Parsing
Un grave bug trovato in awstats nelle versioni uguali o inferiori a 6.2-1.1 mi da lo spunto per parlare di un problema di sicurezza molto sentito... un adeguato filtraggio dell'input proveniente da HTTP.
Awstats è un popolarissimo tools in Perl per la generazione di statistiche su visite a siti web. A differenza di altri non lavora recependo direttamente i dati delle visite, ma leggendo i file di log del webserver. E' in grado di generare suoi file di database in cui conserva i dati che poi elabora per la visualizzazione.
Si tratta di tre casi di mancato controllo dell'input inserito dall'utente.
Primo caso.
1 if ($ENV{'QUERY_STRING'}) {
2 $QueryString = $ENV{'QUERY_STRING'};
3 }
4 # ...
5 $QueryString = CleanFromCSSA($QueryString);
6 # ...
7 if ($QueryString =~ /configdir=([^&]+)/i) {
8 $DirConfig=&DecodeEncodedString("$1");
9 }
10 # ...
11 &Read_Config($DirConfig);
12 # ...
13 sub CleanFromCSSA {
14 my $stringtoclean=shift;
15 $stringtoclean =~ s/</</g;
16 $stringtoclean =~ s/>/>/g;
17 return $stringtoclean;
18 }
19 # ...
20 sub DecodeEncodedString {
21 my $stringtodecode=shift;
22 $stringtodecode =~ tr/\+/ /s;
23 $stringtodecode =~ s/%([A-F0-9][A-F0-9])/pack("C", hex($1))/ieg;
24 return $stringtodecode;
25 }
26 # ...
27 sub Read_Config {
28 my $configdir=shift;
29 # $configdir è il valore del parametro omonimo configdir
30 my @PossibleConfigDir=();
31 if ($configdir) {
32 @PossibleConfigDir=("$configdir");
33 # entra nell'array delle possibili directory di
34 # configurazione
35 } else {
36 @PossibleConfigDir= ("$DIR","/etc/awstats",
37 "/usr/local/etc/awstats","/etc","/etc/opt/awstats");
38 }
39 # Open config file
40 $FileConfig=$FileSuffix='';
41 foreach (@PossibleConfigDir) {
42 my $searchdir=$_;
43 # viene estratto dall'array e inserito in $searchdir
44 if ($searchdir && $searchdir !~ /[\\\/]$/) {
45 $searchdir .= "/";
46 }
47 if (open(CONFIG,"$searchdir$PROG.$SiteConfig.conf")) {
48 # qui si verifica il danno.
49 $FileConfig="$searchdir$PROG.$SiteConfig.conf";
50 $FileSuffix=".$SiteConfig"; last;
51 }
52 # ...
53 }
In awstats.pl (riga 2) viene ricevuto l'input, e il valore del parametro
configdir viene assegnato alla variabile
$DirConfig (riga 7 e 8) dopo avere subito il parsing da due
funzioni: CleanFromCSSA (riga 5) che si limita ad eliminare
i caratteri <> (righe 13-18), e &DecodeEncodedString che è
la classica funzione di decodifica dei parametri GET e POST (righe 20-25).
Successivamente la variabile $DirConfig viene passata alla
funzione &Read_Config($DirConfig) (riga 11), li valorizza
$configdir (riga 28), la quale contribuisce a formare il nome
di un file (righe 32, 41 e 42) che viene aperto tramite la funzione "open" (riga 47).
La funzione "open" di Perl non si limita ad aprire files il lettura o scrittura, ma può
aprire delle "pipe" tramite il carattere "|" con altri programmi, provocandone
l'esecuzione, così come avviene nel meccanismo tipico di invio della posta:
open (MAIL, "|/usr/sbin/sendmail");
E' quindi chiaro cha mancando un effettivo controllo sull'input passato dall'utente
diviene possibile eseguire codice arbitrario. Un possibile attacco potrebbe essere
condotto chiamando con un browser un indirizzo simile:
http://www.sito.com/cgi-bin/awstats/awstats.pl?configdir=|/bin/cat /etc/passwd|
che produrrebbe la visualizzazione del file delle password.
Secondo caso
Questo secondo problema si verifica solo se nel file awstats.conf la variabile
AllowToUpdateStatsFromBrowser è settata a 1.
Ciò consente l'aggiornamento del database da browser, oltre che da linea
di comando o da cron.
In tal caso è possibile un attacco simile al precedente. Infatti anche qui abbiamo
un input, non controllato, passato alla funzione "open".
1 if ($ENV{'QUERY_STRING'}) {
2 $QueryString = $ENV{'QUERY_STRING'};
3 }
4 $QueryString = CleanFromCSSA($QueryString);
5 # ...
6 if ($QueryString =~ /logfile=([^&]+)/i) {
7 $LogFile=&DecodeEncodedString("$1");
8 }
9 # ...
10 open(LOG,"$LogFile") ||
error("Couldn't open server log file \"$LogFile\" : $!");
Le righe da 1 a 9 hanno lo stesso significato del primo caso, e nella riga 10 avviene
il passaggio diretto dell'input alla funzione "open", senza alcuna forma di controllo
sui caratteri immessi.
Anche il possibile exploite è è simile al precedente:
http://www.sito.com/cgi-bin/awstats/awstats.pl?logfile=|/bin/cat /etc/passwd|
Terzo caso
Anche questo caso si rifà al problema del mancato controllo dell'input, ma qui
la funzione "colpevole" non è "open", ma "eval".
In Perl "eval" si usa in due forme come dice perlfunc:
eval ();
eval {};
Nella prima forma, il valore restituito da ESPR viene analizzato sintatticamente ed
eseguito come se fosse un piccolo programma Perl. Il valore dell'espressione (che
viene di per sè determinato all'interno del contesto scalare) viene prima
controllato dal punto di vista sintattico e, se non ci sono stati errori, eseguito
nel contesto lessicale del programma Perl corrente, in maniera che tutte le
definizioni di formato, le subroutine, e i valori dati alle variabili rimangano
intatti. Va notato che il valore viene verificato sintatticamente ogni qualvolta
l'eval effettua un'esecuzione. Se ESPR viene omessa, esso valuta
$_. Questa forma viene usata tipicamente per ritardare la
verifica sintattica e la successiva esecuzione del testo di ESPR fino al tempo di
esecuzione.
Nella seconda forma, il codice all'interno del BLOCCO viene verificato sintatticamente
solo una volta, allo stesso momento in cui il codice che circonda lo stesso eval
viene verificato sintatticamente, ed eseguito all'interno del contesto del programma
Perl corrente. Questa forma viene usata tipicamente per catturare le eccezioni in
maniera più efficiente che non la prima (vedete qui sotto), pur fornendo anche
il vantaggio di controllare il codice dentro BLOCCO a tempo di compilazione.
Il codice in questione è il seguente:
1 if ($ENV{'QUERY_STRING'}) {
2 $QueryString = $ENV{'QUERY_STRING'};
3 }
4 $QueryString = CleanFromCSSA($QueryString);
5 # ...
6 if ($QueryString =~ /pluginmode=([^&]+)/i) {
7 $PluginMode=&DecodeEncodedString("$1");
8 }
9 # ...
10 if ($PluginMode) {
11 my $function="BuildFullHTMLOutput_$PluginMode()";
12 eval("$function");
13 if ($? || $@) { error("$@"); }
14 &html_end(0);
15 exit 0;
16 }
Le righe da 1 a 8 sono equivalenti agli casi precedenti, vi viene valorizzata la
variabile $PluginMode direttamente tramite l'input senza
alcun controllo.
Nelle righe 10-12 si vede che $PluginMode viene utilizzata
per creare una stringa che viene passata a "eval()", che quindi la valuta alla stregua
di codice eseguibile Perl. A questo punto l'attacco è semplice:
http://www.sito.com/cgi-bin/awstats/awstats.pl?pluginmode=:system("/bin/cat /etc/passwd");
Anche in questo caso visualizziamo il file delle password.
Conclusioni
Tutti e tre questi casi dimostrano l'importanza di un adeguato parsing dell'input fornito tramite HTTP. Senza degli adeguati filtri si va incontro a grossi problemi di sicurezza. Ma come si può evitare ciò?
Il metodo migliore è filtrare l'input consentendo solo i caratteri necessari, ad esempio:
if ($input =~ /[^a-zA-Z0-9._/-]/ or $input =~ /\.\./) {
die "Errore: inseriti caratteri non ammessi\n";
}
In questo caso diciamo al programma di consentire solo i carattari alfa-numerici, il
singolo punto, l'underscore, il trattino e la slash. Evitando così tutti caratteri
pericolosi, come & < > " | ; eccetera.
| Indice del sito |
