OWASP03 - Injection
Injection is a class of vulnerabilities which have one specific value in common. Every injection type can be broken down to missing data validation.
All types of injection are caused by unfiltered or insufficiently validated data that triggers unwanted behaviour of the application. Malicious (payload) data gets partly interpreted as commands by the system and can, in the worst case, lead to a fully attacker-controlled system.
Popular injection types are SQL Injections, Cross-Site-Scripting (XSS), File Inclusion Attacks (LFI/RFI) and many more.
Real Word Examples
The problem with finding real world examples of Injection is, that companies tend to not report them if they are not required by law to do so. Even if something gets reported most of the time there isn’t a lot of information about how the attack happened or what exactly was stolen. Never the less I have compiled a small list of successful Injection Attacks that showcase what a huge impact such vulnerabilities can have.
Attack vectors
The main problem for this attack is untrusted data not being validated or filtered. It gets passed to the vulnerable service and leads to the execution of unwanted instructions.
In this blogpost we will mainly focus on SQL injection, but there are several other forms of injection attacks.
Local File Inclusion
In some cases, the web application uses parameters given in the URL to present media resources.
If the filename is not being filtered for illegal characters, it might be possible to access hidden files of the webserver’s filesystem.
In this example, the login page has a tutorial document, which can be viewed in the webbrowser. The URL of this tutorial page contains the filename of the document:
http://127.0.0.1:1337/view?file=howto.pdf
If the filename does not get validated, it is possible to not only use relative, but also absolute filenames in the URL. In this way, an attacker could access any file the user account of the server application has access to.
Exfiltrating the database file (including highly sensitive user data) can be easily done by visiting the modified URL in this scenario:
http://127.0.0.1:1337/view?file=../database.db
In combination with insufficient rights management on the server, it can be possible to get access to the system files.
SQL Injection
In this case, the frontend directly passes untrusted userdata to the backend system. All data is passed as a parameter to the SQL-statement. The user data might contain control characters to escape from payload context to command context.
As a test scenario, a web application serves a login page using the Flask framework in python.
As a user tries to login, the username and the password value are getting transmitted to the framework over HTTP. The web application directly inserts the user data into the SQL statement:
cur.execute("SELECT username FROM users WHERE username = \"" + username + "\" AND password = \"" + password + "\"")
Let’s presume that the user enters invalid login data. This leads to the following SQL statement being executed:
SELECT username FROM users WHERE username = "Bob" AND password = "123"
If there is no user with the name “Bob” and password “123”, the statement will not return any results and the application will deny access.
Maybe Bob doesn’t remember his password, so he tries to login without any. If the application does not check the data entered by the user, it could contain control characters and lead to a command execution:
User input:
Username: Bob
Password: " OR 1=1 OR "abc"="
The framework builds the SQL statement and sends the query to the database, which will execute the following command:
SELECT username FROM users WHERE username = "Bob" AND password = "" OR 1=1 OR "abc"=""
The database will return the entry of the user Bob, even if the password is not correct. Due to the OR-statement 1=1, the premise is logically correct in every case.
Today, many existing frameworks and libraries natively don’t allow using unescaped data to build a query for a system. Nevertheless, SQL injections are still present and one of the most dangerous vulnerabilities.
What can you do to prevent Injection ?
According to the OWASP foundation, it is pretty easy to prevent SQL Injection from happening. SQL Injection vulnerabilities happen in your code when you create dynamic queries that include user-supplied input.
To prevent them, you need to either stop writing dynamic queries alltogether or you need to sanitize the user-supplied input.
In the following paragraphs, we are going to look at several methods of how you can prevent SQL Injection but before we do that we need to look at an unsafe Example.
String query = "SELECT account_balance
FROM user_data
WHERE user_name = "
+ request.getParameter("customerName");
try {
Statement statement = connection.createStatement(..);
ResultSet results = statement.executeQuery( query);
}
The request.getParamter(“customerName”) is just appended to the SQL query. Thus allowing a malicious perpetrator to inject any SQL Code they want.
There are four primary Defences against SQL Injection according to OWASP:
- Use of Prepared Statements (with Parameterized Queries)
- Use of Stored Procedures
- Allow-list Input Validation
- Escaping All User Supplied Input
We are going to cover all four of them.
Prepared Statements (with Parameterized Queries)
What is the difference between dynamic queries and prepared Statements with Parameterized Queries? As the name suggests with prepared Statements you assemble the query first and insert the user input as a parameter later. This has the advantage that the malicious user input gets treated as one String.
Take as an example the input for userID of tom' or ‘1’=‘1. A dynamic query would just add this to the SQL query resulting in a successful SQL Injection whereas the prepared Statement would look through the table for all entries with the userID = “tom’ or ‘1’=‘1”.
This coding style allows the database to distinguish between code and data, regardless of what user input is supplied and an implementation of Prepared Statements using the Java EE and PreparedStatement() is shown below:
// This should REALLY be validated too
String custname = request.getParameter("customerName");
// Perform input validation to detect attacks
String query = "SELECT account_balance
FROM user_data
WHERE user_name = ? ";
PreparedStatement pstmt = connection.prepareStatement( query );
pstmt.setString( 1, custname);
ResultSet results = pstmt.executeQuery( );
Practically any languages support parameterized query interfaces.
Stored Procedures
If you are using standard stored procedure programming constructs, they have the same effect as the use of parameterized queries.
The main difference between the two is that the SQL code for a stored procedure is defined and stored in the database itself.
It is technically possible to use unsafe dynamic SQL generation in stored Procedures but this is highly inadvisable and should be avoided. If it can’t be avoided you should use input validation and proper escaping to defend yourself against SQL Injection inside a stored procedure.
It is also noteworthy that there are some cases where stored procedures can increase the risk.
For example, on MS SQL servers there are some scenarios where all web apps run under db_owner rights for stored procedures to work. This means that if the server is breached, the attacker has full rights to the database instead of only read-access.
Java implements a stored procedure interface with CallableStatement. Note that in the example below the stored procedure sp_getAccountBalance needs to be predefined in the database.
// This should REALLY be validated
String custname = request.getParameter("customerName");
try {
CallableStatement cs;
cs = connection.
prepareCall("{call sp_getAccountBalance(?)}");
cs.setString(1, custname);
ResultSet results = cs.executeQuery();
// … result set handling
} catch (SQLException se) {
// … logging and error handling
}
Allow-list Input Validation
The aforementioned methods don’t work on all parts of an SQL query.
For Example the names of tables or columns, and the sort order indicator aren’t valid locations for the use of bind variables (i.e. place holders in SQL statements used in prepared statements).
Ideally, those values come from the code and not from the user input.
If user parameters are used, they should be mapped to expected values to make sure unvalidated user input doesn’t end up in the query.
String tableName;
switch(PARAM):
case "Value1": tableName = "fooTable";
break;
case "Value2": tableName = "barTable";
break;
...
default : throw new InputValidationException
("unexpected value provided"
+ " for table name");
TableName can be directly used in the SQL query.
Any time user input can be converted into a non-String like a date, boolean, numeric, enumerated type, etc. before it is used in a query, it ensures that it is safe to do so.
As an example for something simple like a sort order, it would be best if the user input is converted to a boolean. That boolean can then be used to select a safe value to append to the query.
public String someMethod(boolean sortOrder) {
String SQLquery = "some SQL ... order by Salary "
+ (sortOrder ? "ASC" : "DESC");
...
Allow list validation is appropriate for all input fields provided by the user. It is used to detect unauthorized input before it is passed to the SQL query.
Escaping All User-Supplied Input
This should only be used as a last resort when none of the methods above are feasible.
It involves escaping every user input before putting it in a query.
If you escape all user-supplied input using the proper escaping scheme for the database you are using, the DBMS will not confuse that input with SQL code written by the developer, thus avoiding any possible SQL injection vulnerabilities. Note that it is very database-specific in its implementation.
Additional: Least Privilege
To minimize the potential damage of a successful SQL injection attack, you should minimize the privileges assigned to every database account in your environment.
You can use SQL views to further increase the granularity of access by limiting the read access to specific fields of a table or joins of tables.
Conclusion
In conclusion, SQL Injection remains one of the biggest threats to web app security. Leaving software open to such vulnerabilities is dangerous and every developer should follow the steps above to minimize the risk of a breach.
Sources
https://owasp.org/Top10/A03_2021-Injection/
https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html
https://www.techrepublic.com/article/sql-injection-attacks-a-cheat-sheet-for-business-pros/
https://www.offensive-security.com/metasploit-unleashed/file-inclusion-vulnerabilities/