The essence of this type of attack is to inject a string of characters into the program’s input data that, instead of being handled by the software as a standard batch of data, changes the logic of the code executed by the interpreter. Input data can come through a web application, web service, messenger, or even unconventional routes: data introduced to the system by employees of the attacked institution or exchanged with other entities.
The most common targets of the attack are:
- SQL database queries
- System command calls
- LDAP
- NoSQL
- XML/Xpath parsers
The attack begins with an analysis of the vulnerability of all input parameters of the application, often using automated scripts that have a set of ready-made phrases testing popular interpreters. These types of tests are applied to the input fields of the attacked application. These fields can be indicated by the attacker, or in the case of automated bots scouring the internet, all “entries” found in the application can be tested.
The goal of such a bot is not yet theft or data destruction. The aim is to catch places where, after injecting code, the application terminated with a non-standard error (e.g., HTTP 500 response – internal server error). This provides the opportunity to assume that the application is vulnerable to attack in such a location. At this point, the hacker typically starts manual work to practically use such an entry.
Example of SQL injection
SQL Injection is definitely the most common attack of this type. It mainly affects applications of beginner programmers, who do not use ready-made mechanisms and libraries. Let’s create a simple example of logging into a website:
And a simple code verifying the entered data with the user saved in the database:
Let’s skip the issue of storing the password in the database in plain text here. When checking the password’s correctness, the programmer uses the code:
$query = "SELECT * FROM users WHERE login='".$login."' AND password='".$password."'";
The result of such code will be the query:
SELECT * FROM users WHERE login='john.smith' AND password='mysecretpassword'
However, if we enter bug ‘ OR ‘bug’=’bug in the password field of the login form, we will get the query:
SELECT * FROM users WHERE login='john.smith' AND password='bug' OR 'bug'='bug'
This query, through the always fulfilled condition at the end, will return the same response as when entering the correct password. As a result, we will most likely be logged into the application.
Suggested protections against SQL Injection
OWASP proposes three basic solutions:
- Using parameterized SQL queries (prepared statements).
- Utilizing SQL procedures.
- Filtering all input data for SQL Injection.
Additionally, we can implement:
- Limited permissions for the database user that the application connects with.
- Input data validation from users based on a whitelist.
Using parameterized SQL queries
This is definitely a recommended solution. Most programming languages have libraries implementing the prepared statements mechanism (including PDO, JDBC). The handling on the database side usually looks as follows:
- Preparation of a query template. The template is supplied to the database engine, with places marked for data.
- The query is parsed, compiled, and optimized.
- Execution. In the next step, the application provides a portion of data to the database to fill the query. The query is then executed.
PHP example:
$query = $pdo->prepare(
'SELECT * FROM users WHERE login=:login AND password=:password'
);
$query->bindParam('login', $login);
$query->bindParam('password', $password);
$query->execute();
Thanks to the use of this mechanism, no matter what comes from the form to the database, it will be treated as a single batch of data and will not modify the structure of the query itself. Using parameterized queries has another advantage. It significantly speeds up queries executed multiple times. With each subsequent execution, only data is sent and the query prepared earlier is executed.
Utilizing SQL procedures.
This is another solution that blocks the possibility of modifying the structure of the query itself. The query is embedded in the stored procedure of the database. The user through whom the application connects to the database can only run the procedure by passing the appropriate parameters to it. They do not have the rights to execute any SQL query.
CallableStatement query = connection.prepareCall("{call checkUserAuth(?,?)}");
query.setString(1, login);
query.setString(2, password);
ResultSet results = query.executeQuery();
Additional benefits include:
- Enhanced transparency in communication with the database.
- Improved performance (only data and the procedure name are transmitted).
Filtering all input data for SQL Injection.
Every database system has a set of markers that allow special characters to be inserted as text. The third method involves filtering and preparing data before it gets to the query. In most cases, this should protect the application from SQL code injection. It is recommended to use ready-made encoders, (e.g., ESAPI) that allow preparing content for MySQL and Oracle databases.
Example for MySQL:
NUL (0x00) --> \0 (cyfra "0")
BS (0x08) --> \b
TAB (0x09) --> \t
LF (0x0a) --> \n
CR (0x0d) --> \r
SUB (0x1a) --> \Z
" (0x22) --> \"
% (0x25) --> \%
' (0x27) --> \'
\ (0x5c) --> \\
_ (0x5f) --> \_
The weak point of this solution is the lack of certainty that it will work every time. Especially when the data, after being filtered, can still be processed. Another downside is the need to remember this throughout the entire application code.
Limited permissions
There is no justification for having the right to delete tables in the database for the user that the web application uses. As early as the database design stage, you can define the permissions required for individual tables by different parts of the application. This helps to limit the potential consequences of a break-in, as the attacker is restricted by the user permissions we assigned in the application to connect to the database. In the example shown earlier (user login), the database user only needs access to the login and password columns of the users table. Using a database user with such limited access at the system login stage significantly protects us against attacks from people who do not have access to the application (and other potential internal vulnerabilities). This restriction can be created by preparing a table view that contains only these two columns and having permissions only for this view.
Validation whitelist
You should specify exactly what can be found in all input fields. If only letters and numbers can be used in the login field, you should prepare a validator that will only allow such data to be entered. The same goes for fields like date, email, or numerical fields. Strong white-list validation is always recommended. It also helps protect against other types of attacks and reduces the likelihood of application errors. Here too, it is definitely better to rely on ready-made and tested solutions (e.g., the zend-validate package).
Operating System Code Injection
The risk occurs wherever an application (or its libraries) calls system commands with parameters. An example could be invoking some background processing process.
exec("calculate-data -d ".$date);
In the case where the date parameter is input by the user and is not adequately secured, it gives the attacker full access to the shell with the privileges of the user under which the application is running. As a result, the invoked command may look like this:
calculate-data -d 2016-01-01; rm -rf /
This allows, in particular, the execution of code placed on the server by a hacker. The code can even get there through Apache logs, or applications. The basic protection is strong validation (whitelist) on parameters calling system commands. If possible, you should also use safe libraries implementing various system commands.
XPATH
If your data is stored or transmitted further in XML format, an attacker may try to modify its structure. Also, storing for example a site map in an XML file, you must be particularly careful when searching for an XPATH path containing the site name from the GET parameter.
Summary
Code injection carries a very serious risk. It can be significantly reduced by applying basic principles at many levels of application development:
Architecture and implementation phase
- Use libraries and frameworks that support fighting code injection (e.g., Hibernate, EJB, PDO, zend-validate).
- Use mechanisms that separate data from code (parameterized queries), as they often already have protection against code injection.
- Run code with as few privileges as possible (including splitting code into pieces and assigning them only required permissions).
- Choose to call external applications through libraries instead of system commands.
- Verify data format using “whitelists” at the application input (in the case of client-server applications, also mandatory on the server side).
- Create an abstract layer of input data, with a map specifying their type, location, and name. Do not allow access to other input data from within the application.
- Assume in advance that every data input will be maliciously attacked and no data is secure.
- Use strictly defined whitelists during validation.
- Do not let anything pass that does not match 100%. When handling errors, try to display as little technical information to the user as possible.
System operation phase
- Use a firewall capable of detecting a code injection attempt.
- Use mechanisms that allow system commands to be called by the program only from a whitelist.
- Globally disable exception display at the application output. Leave only their logging.
In conclusion, I will also show an interesting case of a hacker who attempted to destroy the databases of telemarketing companies by registering a company with a specific name. The screenshot is from the Polish federal companies database, where the underlined section represents the company’s name.