What is it?
SQL Injection is a popular method of attack against web applications. In terms of what you can do with one, the options are almost limitless. It is almost like if you were trying to get into a nightclub and if the owner asks you for your ID, you instead hand him a crumpled up piece of paper saying ‘I am the owner, please give me all the money from the cash drawers’. It shouldn’t work…except in this case, the bouncer is the most gullible idiot imaginable and hands you the cash with with a big dumb grin on his face.
This would be incredibly stupid in the real world, so why does it work on websites? Well, this goes back to how SQL databases work. Every interaction with a database is essentially a line of text, much like how you interact with the commandline. So if you were on a login page, the interaction might look something a little like this:
SELECT username, password FROM users WHERE username='<username field>' AND password='<password field>' LIMIT 1;
The web application then swaps out <username field> and <password field> with whatever you entered as username and password. So far, so good. But the problem is, traditional SQL queries cannot tell the difference between what is code, and what is user-inputted data, so if you enter “admin” as your username and the following
' or 1=1;-- -
as a password, that SQL command can look like the following:
SELECT username, password FROM users WHERE username='admin' AND password='' or 1=1;-- -' LIMIT 1;
In other words, instead of checking both the username and password as it should, it is checking for the username and checking the password, but because 1 will always equal 1, it will disregard the lack of a correct password and let it through anyway. ‘– -‘ is a comment delimiter, meaning that anything past that point is disregarded by the SQL server as ‘human garbage’
What Can We Do With It?
There’s a lot of things you can potentially do with a SQL Injection. The obvious ones include pulling data or dropping tables, but with the right configuration, shells can be obtained on these boxes as well. This would effectively allow someone to execute any commands they like against the box.
The UNION SELECT Shuffle
If the web application uses the injectable SQL data to populate rows on a table, it can be possible to use this to obtain large amounts of data in one sitting. In these cases, first of all we need to ascertain how many columns it is filling up. To do this, we start our payload with something like this (for MySQL backends):
' UNION SELECT 1;-- -
Now, there’s a good chance that the application will either error out or just show a blank screen. This is because the command above only returns one column, but the web application is probably expecting more and doesn’t know what to do. So we continue the shuffle, as so:
' UNION SELECT 1, 2;-- -
And we keep adding numbers in this way until we get something back that isn’t an error or a blank screen. It may, however, look like a Baby’s First Counting Toy, because the numbers are being printed verbatim. Once we have the required amount of columns (I’m going to give an example of 6 columns here), we can run something more interesting:
' UNION SELECT username, password, 3, 4, 5, 6 FROM users;-- -
I’ve skipped ahead past the recon stage of this attack just to demonstrate an injection with some real teeth. In a real pentest/CTF, you’ll want to find out what databases and tables actually exist so you aren’t just hoping there’s a table called ‘users’ with the columns ‘username’ and ‘password’. More on that later.
Injecting what you can’t see
Just because an application fails seemingly gracefully without giving you any output, doesn’t mean that the field cannot be injected into. Indeed, it can still be very much possible to inject code from the application and even extract data from it, by way of inferential injections.
Inferential SQL injections, also known as Blind SQL injections, work a little bit differently from traditional injections. Instead of calling the data out directly, you are instead asking the server to ‘blink’ if the data is matching what you are expecting. This blink can take one of two forms: a Boolean value or a time-based delay. Using this, we can write a bit of code to develop something that works much like Partner-Assisted Scanning in care settings, as seen in the movie The Diving Bell and The Butterfly, in order to tease out strings of data.
Testing for an Inferential SQL Injection
First, we start off with a SQL query that returns a valid response. Once we have that, we tack on an ‘
AND 1=1‘ command to see if it returns the same information. If it doesn’t, try experimenting with ending the SQL statement with a semi-colon and comment denominator. Once you have the desired result, try changing the tacked on command to ‘
AND 1=2‘. If all goes well, you should get an empty result or an error. Note down any strings you may come across that only happen in cases of such failure. To do this with a time-based delay, simply swap out ‘
AND 1=1‘ with ‘
AND sleep(10)‘, noting the response time of the page (it should be over 10 seconds in this case. Note that ‘
sleep(10)‘ works only with MySQL, and for other SQL server backends, you will need to use a different command to get the same results.
Getting data from an Inferential SQL Injection
For a Boolean inferential, the command to, for example, obtain the list of databases available (again, MySQL only) looks something like this:
<valid data>' AND (ascii(substr((SELECT schema_name FROM information_schema.schemata LIMIT 0,1),1,1))) = <ascii decimal value>;-- -
Sounds like a mess? Let’s break it down:
ascii(<character>)‘ turns a given single character into its numerical ASCII value
substr(<string data>, <startpoint>, <length>)'returns a part of a string, from the start point until <length> is reached. In our case, we only ever want a <length> of 1. The beginning of the string is always denoted by a <startpoint> of 1
SELECT schema_name FROM information_schema.schemata‘ is simply the command we use to obtain the list of SQL databases. You can swap this out for any SQL injection payload.
LIMIT <start>,<number of rows>‘ limits us to the the number of rows, starting from the row of our choosing. Unlike with substr, the first row is always 0, not 1
= <ascii decimal value>‘ compares the whole statement to a given ascii value. If they match, the SQL code proceeds as normal, but if not, the page returns no data.
It should be noted that the above code doesn’t give you the data right away. What it does is tell you if a specific character in a specific row matches a specific ASCII value or not. In order to make this effective, we will need to write a script in order to send the many hundreds of requests, determine whether the page has returned valid data or not (our ‘tell’ in this scenario), and collate the data into a string. I highly recommend using Python and the ‘requests’ library to get this done.
For delay-based inferentials, it looks more like this:
' AND IF(((ascii(substr((SELECT schema_name FROM information_schema.schemata LIMIT 0,1),1,1)))) = <ascii decimal value>,sleep(10),NULL);-- -
Where the ‘if’ syntax goes as such:
IF(<condition>,<operation if true>,<operation if false>)‘
Cheating a little bit
To truly get the most out of SQL injections, I recommend bookmarking (or even better, creating your own) cheatsheets to make a note of likely commands you will need to use. I recommend the cheatsheets by ‘pentestmonkey’, however, the ones featured in ‘PayloadsAllTheThings’ are worth a look too