This article explains how to test whether a website is safe from SQL injection using the SQLMAP penetration testing tool.
What is SQL Injection?
SQL Injection is a code injection technique where an attacker executes malicious SQL queries that control a web application’s database. With the right set of queries, a user can gain access to information stored in databases. SQLMAP tests whether a ‘GET’ parameter is vulnerable to SQL Injection.
For example, Consider the following php code segment:
$variable = $_POST['input'];
mysql_query("INSERT INTO `table` (`column`) VALUES ('$variable')");
If the user enters “value’); DROP TABLE table;–” as the input, the query becomes
INSERT INTO `table` (`column`) VALUES('value'); DROP TABLE table;--')
which is undesirable for us, as here the user input is directly compiled along with the pre written sql query. Hence the user will be able to enter an sql query required to manipulate the database.
Where can you use SQLMAP?
If you observe a web url that is of the form http://testphp.vulnweb.com/listproducts.php?cat=1, where the ‘GET’ parameter is in bold, then the website may be vulnerable to this mode of SQL injection, and an attacker may be able to gain access to information in the database. Furthermore, SQLMAP works when it is php based.
A simple test to check whether your website is vulnerable would to be to replace the value in the get request parameter with an asterisk (*). For example,
http://testphp.vulnweb.com/listproducts.php?cat=*
If this results in an error such as the error given above, then we can conclusively say that the website is vulnerable.
Installing sqlmap
SQLMAP comes pre – installed with kali linux, which is the preferred choice of most penetration testers. However, you can install sqlmap on other debian based linux systems using the command
sudo apt-get install sqlmap
Usage
In this article, we will make use of a website that is designed with vulnerabilities for demonstration purposes:
http://testphp.vulnweb.com/listproducts.php?cat=1
As you can see, there is a GET request parameter (cat = 1) that can be changed by the user by modifying the value of cat. So this website might be vulnerable to SQL injection of this kind.
To test for this, we use SQLMAP. To look at the set of parameters that can be passed, type in the terminal,
sqlmap -h
The parameters that we will use for the basic SQL Injection are shown in the above picture. Along with these, we will also use the –dbs and -u parameter, the usage of which has been explained in Step 1.
Using SQLMAP to test a website for SQL Injection vulnerability:
sqlmap -u http://testphp.vulnweb.com/listproducts.php?cat=1 --dbs
We get the following output showing us that there are two available databases. Sometimes, the application will tell you that it has identified the database and ask whether you want to test other database types. You can go ahead and type ‘Y’. Further, it may ask whether you want to test other parameters for vulnerabilities, type ‘Y’ over here as we want to thoroughly test the web application.
We observe that their are two databases, acuart and information_schema
3. sqlmap -u http://testphp.vulnweb.com/listproducts.php?cat=1
-D acuart --tables
Tables
In the above picture, we see that 8 tables have been retrieved. So now we definitely know that the website is vulnerable.
If we want to view the columns of a particular table, we can use the following command, in which we use -T to specify the table name, and –columns to query the column names. We will try to access the table ‘artists’.
sqlmap -u http://testphp.vulnweb.com/listproducts.php?cat=1
-D acuart -T artists --columns
Columns
6. sqlmap -u http://testphp.vulnweb.com/listproducts.php?cat=1
-D acuart -T artists -C aname --dump
From the above picture, we can see that we have accessed the data from the database. Similarly, in such vulnerable websites, we can literally explore through the databases to extract information
Prevent SQL Injection
SQL injection can be generally prevented by using Prepared Statements . When we use a prepared statement, we are basically using a template for the code and analyzing the code and user input separately. It does not mix the user entered query and the code. In the example given at the beginning of this article, the input entered by the user is directly inserted into the code and they are compiled together, and hence we are able to execute malicious code. For prepared statements, we basically send the sql query with a placeholder for the user input and then send the actual user input as a separate command.
Consider the following php code segment.
$db = new PDO('connection details');
$stmt = db->prepare("Select name from users where id = :id");
$stmt->execute(array(':id', $data));
In this code, the user input is not combined with the prepared statement. They are compiled separately. So even if malicious code is entered as user input, the program will simply treat the malicious part of the code as a string and not a command.
As explained in this article, an SQL Injection attack, or an SQLi, is a way of exploiting the underlying vulnerability of an SQL statement by inserting nefarious SQL statements into its entry field for execution. It first made its appearance in 1998, and ever since, it mostly targets retailers and bank accounts. When compounded with other forms of attacks such as DDOS attacks, cross-site scripting (XSS) or DNS hijacking, it can lead to large scale results.
Terminology:
To avoid SQL injection, all input that are to be concatenated in dynamic SQL must be correctly filtered and sanitized.
Anatomy of an SQL attack:
An SQL attack has the following two parts:
Example1:
Consider the following piece of code for an authentication form written in Java:
String query = "SELECT userName, balance FROM accounts" + "WHERE userID=" + request.getParameter("userID") + "and password='" + request.getParameter("Password") + "'";
try { Statement statement = connection.createStatement(); ResultSet rs = statement.executeQuery(query); while (rs.next()) { page.addTableRow(rs.getString("userName"), rs.getFloat("balance")); } } catch (SQLException e) {} |
Under normal conditions, a user enters his or her userID and password, and this generates the following statement for execution:
SELECT userName, balance
FROM accounts
WHERE userID=512 and password='thisisyoda'
A possible SQL injection attack would exploit the password field to generate a boolean expression which would make the expression evaluate to true for all cases. Imagine setting the userID and password fields as
userID = 1' or '1' = '1
password = 1' or '1' = '1
The SQL statement then becomes
SELECT userName, balance
FROM accounts
WHERE userID='1' OR '1'='1' and
password='1' OR '1'='1'
The query will return a value because the condition (OR 1=1) is always true. In this way the system has authenticated the user without knowing the username and password.
The vulnerability can be mitigated using a prepared statement to create a parameterized query as follows:
String query = "SELECT userName, balance "+ "FROM accounts WHERE userID = ? and password = ?";
try { PreparedStatement statement = connection.prepareStatement(query); statement.setInt(1, request.getParameter("userID")); ResultSet rs = statement.executeQuery(); while (rs.next()) { page.addTableRow(rs.getString("userName"), rs.getFloat("balance")); } } catch (SQLException e) { ... } |
If an attacker attempts to give a value to the userID field that is not a simple integer, then statement.setInt() will throw a SQLException error rather than permitting the query to complete.
Example2:
Consider another type of attack during authentication:
String query = "SELECT userID, userName, passwordHash"+ " FROM users WHERE userName = '" + request.getParameter("user") + "'"; int userID = -1; HashMap userGroups = new HashMap(); try { Satement statement = connection.createStatement(); ResultSet rs = statement.executeQuery(query); rs.first(); userID = rs.getInt("userID");
if (!hashOf(request.getParameter("password")).equals(rs.getString("passwordHash"))) { throw BadLoginException(); }
String userGroupQuery = "SELECT group FROM groupMembership"+ " WHERE userID = " + userID;
rs = statement.executeQuery(userGroupQuery);
while (rs.next()) { userGroup.put(rs.getString("group"), true); } } catch (SQLException e){} catch (BadLoginException e){} |
A normal query would be as follows.
SELECT userID, userName, passwordHash
FROM users
WHERE userName = 'Anannya'
The attacker may inject the following into the userName field.
Anannya';
INSERT INTO groupMmbership (userID, group)
VALUES (SELECT userID FROM users
WHERE userName='Anannya', 'Administrator'); --
Due to this, the actual query will change to:
SELECT userID, userName, passwordHash FROM
users WHERE userName = 'Anannya';
INSERT INTO groupMmbership (userID, group)
VALUES (SELECT userID FROM users
WHERE userName='Anannya', 'Administrator'); --'
This will cause another SQL statement to get appended to the actual statement, causing the user to get added to the Administrator database. The attack can be mitigated by using a prepared statement with a parameterized query as follows.
String query = "SELECT userID, userName, passwordHash"+ " FROM users WHERE userName = ?";
try { PreparedSatement statement = connection.prepareStatement(userLoginQuery); statement.setString(1, request.getParameter("user")); ResultSet rs = statement.executeQuery(); } |
Example3:
Consider another example of query vulnerability discussed below:
String query = "INSERT INTO users VALUES(" + request.getParameter("userName") + ");"; |
A general query will be:
INSERT INTO users VALUES("Anannya")
Consider if the attacker enters the following query into the userName field:
"Anannya); DROP TABLE users;"
The query will then change to:
INSERT INTO users VALUES("Anannya"); DROP TABLE users;
This query completely deletes the users table upon its execution. A workaround here, again, is a prepared statement.
How does using a Prepared Statement in Java help?
A prepared statement “sanitizes” the input. This means it makes sure that whatever the user enters is treated as a string literal in SQL and NOT as a part of the SQL query. It may also escape certain characters and detect/remove malicious code. In other languages such as PHP, filter_input or filter_input_array can be used to sanitize the string.