I know that PreparedStatements avoid/prevent SQL Injection. How does it do that? Will the final form query that is constructed using PreparedStatements will be a string or otherwise?
The problem with SQL injection is, that a user input is used as part of the SQL statement. By using prepared statements you can force the user input to be handled as the content of a parameter (and not as a part of the SQL command).
But if you don’t use the user input as a parameter for your prepared statement but instead build your SQL command by joining strings together, you are still vulnerable to SQL injections even when using prepared statements.
Consider two ways of doing the same thing:
PreparedStatement stmt = conn.createStatement("INSERT INTO students VALUES('" + user + "')"); stmt.execute();
PreparedStatement stmt = conn.prepareStatement("INSERT INTO student VALUES(?)"); stmt.setString(1, user); stmt.execute();
If “user” came from user input and the user input was
Robert'); DROP TABLE students; --
Then in the first instance, you’d be hosed. In the second, you’d be safe and Little Bobby Tables would be registered for your school.
To understand how PreparedStatement prevents SQL Injection, we need to understand phases of SQL Query execution.
1. Compilation Phase.
2. Execution Phase.
Whenever SQL server engine receives a query, it has to pass through below phases,
Parsing and Normalization Phase:
In this phase, Query is checked for syntax and semantics. It checks whether references table and
columns used in query exist or not.
It also has many other tasks to do, but let’s not go in detail.
In this phase, keywords used in query like select, from, where etc are converted into format
understandable by machine.
This is the phase where query is interpreted and corresponding action to be taken is decided.
It also has many other tasks to do, but let’s not go in detail.
Query Optimization Plan:
In this phase, Decision Tree is created for finding the ways in which query can be executed.
It finds out the number of ways in which query can be executed and the cost associated with each way
of executing Query.
It chooses the best plan for executing a query.
Best plan selected in Query optimization plan is stored in cache, so that whenever next
time same query comes in, it doesn’t have to pass through Phase 1, Phase 2 and Phase 3 again.
When next time query come in, it will be checked directly in Cache and picked up from there
In this phase, supplied query gets executed and data is returned to user as
Behaviour of PreparedStatement API on above steps
PreparedStatements are not complete SQL queries and contain placeholder(s),
which at run time are replaced by actual user-provided data.
Whenever any PreparedStatment containing placeholders is passed in to SQL Server engine,
It passes through below phases
- Parsing and Normalization Phase
- Compilation Phase
- Query Optimization Plan
- Cache (Compiled Query with placeholders are stored in Cache.)
UPDATE user set username=? and password=? WHERE id=?
Above query will get parsed, compiled with placeholders as special treatment, optimized and
Query at this stage is already compiled and converted in machine understandable format.
So we can say that Query stored in cache is Pre-Compiled and
only placeholders need to be replaced with user-provided data.
Now at run-time when user-provided data comes in, Pre-Compiled Query is picked up from Cache and placeholders are replaced with user-provided data.
(Remember, after place holders are replaced with user data, final query is not
compiled/interpreted again and SQL Server engine treats user data as pure data and not a
SQL that needs to be parsed or compiled again; that is the beauty of PreparedStatement.)
If the query doesn’t have to go through compilation phase again, then whatever data replaced on the
placeholders are treated as pure data and has no meaning to SQL Server engine and it directly
executes the query.
Note: It is the compilation phase after parsing phase, that understands/interprets the query
structure and gives meaningful behavior to it. In case of PreparedStatement, query is
compiled only once and cached compiled query is picked up all the time to replace
user data and execute.
Due to one time compilation feature of PreparedStatement, it is free of SQL Injection
You can get detailed explanation with example here:
The SQL used in a PreparedStatement is precompiled on the driver. From that point on, the parameters are sent to the driver as literal values and not executable portions of SQL; thus no SQL can be injected using a parameter. Another beneficial side effect of PreparedStatements (precompilation + sending only parameters) is improved performance when running the statement multiple times even with different values for the parameters (assuming that the driver supports PreparedStatements) as the driver does not have to perform SQL parsing and compilation each time the parameters change.
I guess it will be a string. But the input parameters will be sent to the database & appropriate cast/conversions will be applied prior to creating an actual SQL statement.
To give you an example, it might try and see if the CAST/Conversion works.
If it works, it could create a final statement out of it.
SELECT * From MyTable WHERE param = CAST('10; DROP TABLE Other' AS varchar(30))
Try an example with a SQL statement accepting a numeric parameter.
Now, try passing a string variable (with numeric content that is acceptable as numeric parameter). Does it raise any error?
Now, try passing a string variable (with content that is not acceptable as numeric parameter). See what happens?
Prepared statement is more secure. It will convert a parameter to the specified type.
stmt.setString(1, user); will convert the
user parameter to a String.
Suppose that the parameter contains a SQL string containing an executable command: using a prepared statement will not allow that.
It adds metacharacter (a.k.a. auto conversion) to that.
This makes it is more safe.
As explained in this post, the
PreparedStatement alone does not help you if you are still concatenating Strings.
For instance, one rogue attacker can still do the following:
- call a sleep function so that all your database connections will be busy, therefore making your application unavailable
- extracting sensitive data from the DB
- bypassing the user authentication
Not only SQL, but even JPQL or HQL can be compromised if you are not using bind parameters.
Bottom line, you should never use string concatenation when building SQL statements. Use a dedicated API for that purpose:
1) Precompilation and DB-side caching of the SQL statement leads to overall faster execution and the ability to reuse the same SQL statement in batches.
2) Automatic prevention of SQL injection attacks by builtin escaping of quotes and other special characters. Note that this requires that you use any of the PreparedStatement setXxx() methods to set the value.