SQL Injection
Introduction
SQL injection refers to attacks against relational databases, while injections against non-relational databases are called NoSQL injections.
A Database Management System (DBMS) helps create, define, host, and manage databases.
A relational database is the most common type of database. It uses a schema, a template that dictates the data structure stored in the database. The relationship between tables within a database is also known as a schema.
A non-relational database (also called a NoSQL database) does not use tables, rows, and columns, or primary keys, relationships, or schemas. Instead, a NoSQL database stores data using various storage models (e.g., key-value), depending on the type of data stored.
Structured Query Language (SQL)
SQL syntax can differ from one RDBMS to another. However, they are all required to follow the ISO standard for Structured Query Language.
SQL statements aren't case-sensitive, which means USE users; and use users; refer to the same command. However, the database name is case-sensitive.
On Linux, you can use the terminal to connect to databases:
Logical Operators are used to apply multiple conditions at once.
AND (&&): The result is true if all conditions are true.
OR (||): The result is true if any of the conditions are true.
NOT(!): The result is the opposite of the condition.
SQL Injection
SQL injection occurs when user input is included in an SQL query string without proper sanitization or filtering. SQL Injections are categorized based on how and where we retrieve their output:
In-band: The output of both the intended and the new query is printed directly on the front end, allowing us to read it. It has two types:
Union-Based: We specify the exact location (e.g., column) where the output is directed to be printed.
Error-Based: We can get PHP or SQL errors on the front-end, allowing us to intentionally cause an SQL error that returns the output of our query.
Blind: The output isn't printed. Instead, we make assumptions based on how the application or website behaves during the attack in response to our queries. This method is generally more complicated than In-band and has two types:
Boolean Based: SQL conditional statements are used to control whether the page returns any output at all.
Time Based: SQL conditional statements delay the page response based on a conditional statement using the SLEEP() function.
Out-of-band: When we don't have direct access to the output, so we may have to direct the output to a remote location.
Checking for SQLi
These characters are usually used to check for SQLi; they are added after a payload, and the output/behavior of the server is checked for any anomalies:
If you manage to trigger an error, then the input is vulnerable to SQLi.
OR Injection
OR injection is used to bypass conditions. For example, in a login page where the query might look like this:
SELECT * FROM logins WHERE username='admin' AND password = 'something';
If we inject an OR operation with a true condition, we might be able to bypass the authentication process:.
SELECT * FROM logins WHERE username='admin' or '1'='1' AND password = 'something';
Using Comments
In SQL, both # and -- are used to declare a comment. If we take the previous example's query:
SELECT * FROM logins WHERE username='admin'-- ' AND password = 'something';
If we comment out everything after 'admin', the query becomes:
SELECT * FROM logins WHERE username='admin'
This grants us access as admin.
Union Injecton
The UNION clause is used to combine results from multiple SELECT statements. Through UNION injection, we can SELECT and dump data from across the DBMS, from multiple tables and databases. Note that the data types of the selected columns in all positions should be the same.
A UNION statement can only operate on SELECT statements with an equal number of columns. For example, if we attempt to UNION two queries with a different number of columns, we will get an error.
If the original query has more columns than our desired query, we can use any string as junk data, and the query will return the string as its output for that column. A better approach would be to select NULL, as 'NULL' fits all data types.
Exploitation
Detect number of columns:
To identify the number of columns in the original query, we can use either ORDER BY or UNION.
Using ORDER BY, we increase the column number one by one until we get an error:
Using UNION, we add UNION SELECT 1,2,3,... until we get an error, starting from 1 and adding columns one by one until we get the error, which indicates the number of columns.
Location of Injection:
To identify the columns where we can inject, we need to know which columns are displayed. We can use UNION to determine this. For example, if we have a query with 5 columns, we can use:
UNION SELECT 1,2,3,4,5--
The numbers that are displayed as output indicate our injection spots.
Exploitation
Database Enumeration
Before enumerating the database, we need to identify the type of DBMS we are dealing with since each DBMS has different queries.
As an initial guess, If the web server in the HTTP responses is Apache or Nginx, it's likely that the web server is running on Linux, so the DBMS is likely MySQL. Similarly, if the web server is IIS, it is likely to be MSSQL.
To detect the type of DBMS, here are some examples for MySQL:
SELECT @@version
When we have full query output
MySQL Version 'i.e. 10.3.22-MariaDB-1ubuntu1'
In MSSQL it returns MSSQL version. Error with other DBMS.
SELECT POW(1,1)
When we only have numeric output
1
Error with other DBMS
SELECT SLEEP(5)
Blind/No Output
Delays page response for 5 seconds and returns 0.
Will not delay response with other DBMS
The INFORMATION_SCHEMA database contains metadata about the databases and tables present on the server.
To reference a table present in another DB, we can use the dot ‘.’ operator. For example, to SELECT a table users present in a database named my_database, we can use:
SELECT * FROM my_database.users;
The following queries help identify the databases and tables:
Reading and Writing Files
Reading Files
SQL injection (SQLi) vulnerabilities can be leveraged to do more than just enumerate a database; they can sometimes allow an attacker to read and write files on the server and even gain remote code execution on the back-end server.
First, we need to determine which user we are within the database. This can be done using any of the following queries:
SELECT USER()
SELECT CURRENT_USER()
SELECT user from mysql.user
To check if we have super admin privileges (returns 'Y' for Yes if we do):
SELECT super_priv FROM mysql.user
SELECT super_priv FROM mysql.user WHERE user="<User>"
To see what other privileges we have (If we have FILE privilege, we can read files and potentially write to them as well):
SELECT grantee, privilege_type FROM information_schema.user_privileges WHERE grantee="<User>"
After verifying that we have the privilege to read files, we can use the following query to read files:
SELECT LOAD_FILE('
');
Writing Files
Modern DBMSes disable file-write by default and require certain privileges for database admins to write files. To write a file, we need three things:
User with FILE privilege enabled
MySQL global secure_file_priv variable not enabled
Write access to the location we want to write to on the back-end server
The
secure_file_priv
variable is used to determine where to read/write files from. An empty value allows reading files from the entire file system. Otherwise, if a certain directory is set, we can only read from the folder specified by the variable. NULL means we cannot read/write from any directory. To find out the value of secure_file_priv, we can use either of these queries:SHOW VARIABLES LIKE 'secure_file_priv';
SELECT variable_name, variable_value FROM information_schema.global_variables where variable_name="secure_file_priv"
After verifying that we have the privileges to write to files, we can write using this query:
To execute files that we write (e.g., a web shell), we must know the base web directory for the web server (i.e., the web root). This can be done by using LOAD_FILE to read the server configuration, fuzzing, or using server errors displayed to us to try to find the web directory.
After identifying the directory, we can write a web shell, for example:
<?php system($_REQUEST[0]); ?>
Mitigating SQL Injection
SQL injection, like other injection attacks, can be avoided by validating and sanitizing any user input, rendering injected queries useless. A way to do this is by using regex to match the input against a pattern and then using functions like
mysqli_real_escape_string()
to sanitize the input.Another important mitigation method is setting secure access controls.
The use of Web Application Firewalls (WAF) can also help detect and reject any malicious input.
Finally, using parameterized queries is crucial. Parameterized queries contain placeholders for the input data, which is then escaped and passed on by the drivers, preventing SQL injection.
SQLMap
SQLMap is a free and open-source penetration testing tool written in Python that automates the detection and exploitation of SQL injection (SQLi) vulnerabilities. It supports more DBMSes than any other SQL exploitation tool and is the only penetration testing tool that can detect and exploit all known SQLi types effectively.
One of the best and easiest ways to set up an SQLMap request against a specific target (such as a web request with parameters) is by utilizing the "Copy as cURL" feature within the Network panel of developer tools. However, for more complex requests, it’s better to use a request file.
When providing data for testing to SQLMap, there must either be a parameter value that could be assessed for SQLi vulnerability or specialized options/switches for automatic parameter finding, such as
--crawl
,--forms
, or-g
.SQLMap injection relies on two main components:
Vector: This is the central part of the payloads, for example, UNION ALL SELECT 1,2,VERSION().
Boundaries: These are the prefix and suffix formations that are added to the vector to complete the payload, for example, '-- -.
Code Cheatsheet:
While SQLMap, by default, targets only HTTP parameters, it is also possible to test headers for SQLi vulnerabilities. The easiest way to do this is by specifying a "custom" injection marker after the header's value, for example:
--cookie="id=1*"
While testing, SQLMap generates numerous messages that are helpful for understanding what’s happening during the process. Some of these messages and their meanings are highlighted below:
Last updated