I'm working at a company where the person responsible for the database module is strictly against using prepared statements. I'm worrying that his implementation is not secure.
Here is the code we are currently using to make a SQL query (Java 8 Application with JDBC/MySQL 5.5):
String value = "Raw user input over HTTP-Form";
String sql = "SELECT * FROM db1.articles WHERE title like '" +
replaceSingleQuotes(value) + "'";
executeSQL(sql);
public static String replaceSingleQuotes(String value) {
value = value.replaceAll("\\\\", "\\\\\\\\");
return value.replaceAll("'", "\\\\'");
}
I was not able to come up with any injections but his solution seems very fishy to me. Can anyone point out any way how this escaping could be circumvented? He will not replace his code if I can't come up with anything and we have very sensitive information of thousands of customers in our application (banking).
Edit:
Unfortunately i can't show executeSQL() because there is a mess with a huge class hierarchy and everything is scattered. But it comes down to something like this:
String query = ... // query escaped with the above function
java.sql.Connection connection = ...
Statement stmt = connection.createStatement();
stmt.executeUpdate(query);
One method of attack would be by "loading" the attack.
First, you inject as user name, bank transfer message, whatever into it
transfer 0.01
to: 02020.020202.200202
name: johnny tables';drop table foobar --
will be escaped to
johnny tables\';drop table foobar --
So far so good. protection in effect. our attach failed. We try the loading attack.
Now we are going to make a scheduled payment order.
This is assuming a common error is made, that once inserted in the database, the value is "safe" because it has been checked once.
transfer 0.01
to: 02020.020202.200202
name: johnny tables';drop table foobar--
schedule: 1 day from now
Storing the order in the db
'johnny tables\';drop table foobar--'
will be stored as
johnny tables';drop table foobar--
Now at midnight the scheduler kicks in and starts iterating the scheduled payments
select name from scheduled where time > x and < y
so the bank code starts to chrunch
String name = result['name'];
String acct = result['acct'];
String amt = result['amt'];
string query = "insert into payment_process (name,acct,amt) values('"+name+"','"+acct+"','"+amt+"');
and boom, your table is dropped. *
When you go the manual route, you have to ensure all, each and every instance of the variable is escaped, that all unicode characters are accounted for, that all idiocrancies of the database engine are accounted for.
Also, using prepared statements can give a significant speed boost, because you don't have to rebuild queries. You can just build them once, store them in a cache and just swap out the parameters.
Especially when iterating large lists they are a godsent.
The root problem is that he probably doesn't understand prepared statements, doesn't get them how they work. Insecurity triggered can make aggressive and protective of a certain way, even fanatical, just to prevent to admit you just don't know how they work.
Try to talk to him about it, if he doesn't wish to listen to reason go to his manager and explain the issue, and that if the site/app gets hacked, it will be on the head of your co-worker and your manager, and tell him the risks are HUGE. Point to the recent hacks where a lot of money was stolen like the swift hack.
* May not actually work, dependent on actual query, joins, unions etc.. it's a very simplified example
Related
I have created the following query which is now in one of my java classes being used by Hibernate.
private static final String COUNT_INTERQUARTILE_SQL
= " SET #number_of_rows \\:= (SELECT COUNT(*) FROM carecube.visit)" +
" SET #quartile \\:= (ROUND(#number_of_rows*0.25))" +
" SET #medianquartile \\:= (ROUND(#number_of_rows*0.50))" +
" SET #sql_q1 \\:= (CONCAT('(SELECT 'Q1' AS quartile, visit.id FROM carecube.visit order by visit.id LIMIT 1 OFFSET ', #quartile, ')'))" +
" SET #sql \\:= (CONCAT_WS(' UNION ', #sql_q1, #sql_med))" +
" PREPARE stmt1 from #sql;" +
" EXECUTE stmt1;";`
The stack trace complains of a syntax errors for each line where I've set a mysql variable. Obviously it works in MySQL just fine.
I read that I can use double backslashes with assignments in Hibernate. This is the first time I've tried to use MySQL variables with Hibernate so am unsure if I'm missing anything out and whether 'PREPARE' and 'EXECUTE' are necessary?
Can someone with more knowledge point me where I am going wrong?
Also, where I am selecting Q1, I've placed that in single quotes, in MySQL workbench it is double quotes.
EDIT: I've added double quotes so hibernate doesn't throw a sissy fit with the assignments. I still can't for the life of me, figure out why I cannot just use '#sql' after i've prepared it.
EDIT: I receive the following error:
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'PREPARE stmt1 from #sql_max; EXECUTE stmt1' at line 1
Thanks
I'm not sure if this is possible, but in my opinion this doesn't make much sense.
Some people have asked similar questions (they have some code samples in the answers if you want to check):
How to use Mysql variables with Hibernate?
How can I use MySQL assign operator(:=) in hibernate native query?
Hibernate is an ORM (Object Relational Mapping), so it's supposed to convert data between incompatible type systems (data from database) in objects. As far as I could understand your query, you're returning a COUNT, so it would be just one single result (row) and one single column, is that right?
Due the complexity of your query, I would say there are some options I could think of:
Use Criteria or HQL to run each query using Hibernate and then in Java work with the logic to have your desired result (may become much slower than the query in MySQL)
Create a VIEW with this SELECT (if possible), map it into an object as an #Entity and query directly to it
Create a FUNCTION/PROCEDURE (this is possible) and call it using CallableStatement
I like to think that the two elements here (Hibernate x Database) should have a well define responsibility in the project. First of all, I would try to use only Criteria/HQL for the queries (to use object properties), but if needed to use SQL I would keep all queries ANSI to allow interoperability. If it's not possible, I would create an object in the database to return what I want (view/procedure/function).
Mixing specific database provider code in the query, like your example, doesn't look a good practice.
If possible, I would definitely go for option 2. If it can't be done, surely for number 3.
Hope it's somehow helpful.
I know that PreparedStatements avoid/prevent SQL Injection. How does it do that? Will the final form query that is constructed using PreparedStatements be a string or otherwise?
Consider two ways of doing the same thing:
PreparedStatement stmt = conn.createStatement("INSERT INTO students VALUES('" + user + "')");
stmt.execute();
Or
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.
Compilation Phase:
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.
Cache:
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
to execute.
Execution Phase:
In this phase, supplied query gets executed and data is returned to user as ResultSet object.
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
get Cached.
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
attack.
You can get detailed explanation with example here:
https://javabypatel.blogspot.com/2015/09/how-prepared-statement-in-java-prevents-sql-injection.html
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.
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.
For example 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.
SQL injection: when user has the chance to input something that could be part of the sql statement
For example:
String query = “INSERT INTO students VALUES(‘” + user + “‘)”
when user input “Robert’); DROP TABLE students; –” as the input, it causes SQL injection
How prepared statement prevents this?
String query = “INSERT INTO students VALUES(‘” + “:name” + “‘)”
parameters.addValue(“name”, user);
=> when user input again “Robert’); DROP TABLE students; –“, the input string is precompiled on the driver as literal values and I guess it may be casted like:
CAST(‘Robert’); DROP TABLE students; –‘ AS varchar(30))
So at the end, the string will be literally inserted as the name to the table.
http://blog.linguiming.com/index.php/2018/01/10/why-prepared-statement-avoids-sql-injection/
PreparedStatement:
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.
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, like JPA Criteria API.
In Prepared Statements the user is forced to enter data as parameters . If user enters some vulnerable statements like DROP TABLE or SELECT * FROM USERS then data won't be affected as these would be considered as parameters of the SQL statement
I am trying to validate data before inserting them into the database(POSTGRESQL). The data corresponding to email, zip code etc are easily validated with the use of Apache Commons Validator. But in case of names I used this:
^[a-zA-Z][ a-zA-Z]{1-30}$
This prevents any special characters from being added as name, but it fails to prevent users from adding DROP or GRANT as a name. As I am using PreparedStatement, I didn't think it was going to be a problem but it is now required that SQL keywords shouldn't go in the db as it may lead to a Second Order SQL Injection.
I thought of using blacklisting all SQL keywords (surely, this will prevent Huge Grant from logging into our site. :P) but it seems that there are >64 keywords. Is this (Blacklist filtering data for SQL Keywords) a proper approach for preventing Second Order SQL Injection? What are my options?
I am using this code:
String sql="INSERT INTO users (username, password, name) VALUES (?,?,?);";
try{
conn=SomeStaticClass.createConnection();
ps=conn.prepareStatement(sql);
ps.setString(1, dataBean.getUsername());
ps.setString(2, dataBean.getPassword());
ps.setString(3, dataBean.getName());
ps.execute();
}catch(SQLException e){
e.printStackTrace()
}catch(Exception e){
e.printStackTrace();
}finally{
try{
if(ps!=null){
ps.close();
}
conn.close();
}catch(SQLException e){
e.printStackTrace();
}
}
Is this a proper approach for this kind of a situation?
No.
SQL injection happens when you assemble an SQL queries by concatenating Strings.
The "best practice" approach to preventing SQL injection is to use a PreparedStatement with constant SQL queries that have placeholders for the parameters. Then you use the prepared statement set methods to set values for each of the placeholder parameters. This approach will guarantee that any "nasty" string parameters containing SQL keywords will be interpreted as literal strings.
UPDATE - Using PreparedStatements consistently should protect against second order attacks too ... assuming that you are referring to something like this:
http://download.oracle.com/oll/tutorials/SQLInjection/html/lesson1/les01_tm_attacks2.htm
You just need to make sure that you don't build the SQL query string from anything that could possibly be tainted. Provided you handle any potentially tainted data using placeholders, it doesn't matter where it came from.
(Black listing SQL keywords will help to keep garbage out of your database. But as you mentioned, it can potentially cause damage to legitimate data and impact on your system's usability. I wouldn't do it. It would be better to rely on good programmer discipline ... and thorough code reviews.)
Second order injection only occurs if you store the keywords in the database and then later use them in an unsafe manner. If you use prepared statements and they are properly parameterized it won't occur. Cisco have a good summary of understanding SQL injection:
http://www.cisco.com/web/about/security/intelligence/sql_injection.html
Apart from your example of "Grant" there are also many such as IF, BY, IS, IN, TO that will occur very commonly in English language / names.
It is extreamly difficult, if not impossible, to ensure that all data in your database can be used with any script language (like SQL or HTML) without proper escaping in the future. It is impossible to distinguish between "safe" and "unsafe" characters before you now how the characters are going to be used anyway.
Trying to escape and clean all data before they are inserted into the database may lead you to belive that user-generated data in the database is "safe", which is a very dangerous belief. You can only know if the data is safe when you know how they are going to be used, and you will only know that when you actually use the data (since data in a database can live for a very long time).
The best strategy for avoiding this kind of problems is to always escape all data when you actually use them, either by using PreparedStatement like you do, properly escaping them when you use them in html, escaping them when you insert them into an email etc. etc.
I gave some examples in this answer:
How to allow specific characters with OWASP HTML Sanitizer?
Along with using PreparedStatement, you must check your input provided by user, on your webpages.
So now you have 2 different checks.
1. On your web pages, which will reduce processing time.
2. If something passes your initial check then preparedstatement will make sure your query is parsed properly.
E.g User is searching some item..
User input is
' OR ITEM in (Select ITEM from SOME_TABLE) OR ITEM = ''
And you are building your SQL, by concatenating the strings, then it will make SQL command as
Select * from TABLE_X WHERE ITEM = '' OR ITEM in (Select ITEM from SOME_TABLE) OR ITEM = ''
So your DATABASE is hacked, but in other case PreparedStatement it will parse your query and would not let make user to modify the SQL...
I need to get an equivalent to this SQL that can be run using Hibernate. It doesn't work as is due to special characters like #.
SELECT place from (select #curRow := #curRow + 1 AS place, time, id FROM `testing`.`competitor` JOIN (SELECT #curRow := 0) r order by time) competitorList where competitorList.id=4;
My application is managing results of running competitions. The above query is selecting for a specific competitor, it's place based on his/her overall time.
For simplicity I'll only list the COMPETITOR table structure (only the relevant fields). My actual query involves a few joins, but they are not relevant for the question:
CREATE TABLE competitor {
id INT,
name VARCHAR,
time INT
}
Note that competitors are not already ordered by time, thus, the ID cannot be used as rank. As well, it is possible to have two competitors with the same overall time.
Any idea how I could make this work with Hibernate?
Hard to tell without a schema, but you may be able to use something like
SELECT COUNT(*) FROM testing ts
WHERE ts.score < $obj.score
where I am using the $ to stand for whatever Hibernate notation you need to refer to the live object.
I couldn't find any way to do this, so I had to change the way I'm calculating the position. I'm now taking the top results and am creating the ladder in Java, rather than in the SQL query.
We have the following code :
String templateQuery = "select * from my_table where col1=$1 or col2 like '%$2.$1'";
String tmp = templateQuery;
for(int i=1;i<=maxCols;i++) {
tmp = tmp.replaceAll("\\$"+i, data[i-1]);
}
This code works fine as maxCols never exceeds 10. But my colleague disagree with me stating that this code consumes too much memory. Can you help us ?
EDIT:
I have change the initial templateQuery with a much realistic one. Secondly, templateQuery can potentially be a big string.
EDIT 2:
Thanks for those who have pointed out the SQLInjection problem.
Don't do this.
Not for performance reasons (which will be miniscule compared with the cost of the database query), but to avoid SQL injection attacks. What happens if data[0] is actually the string
' OR 'x' = 'x
?
Then you'll end up with a SQL statement of:
SELECT * FROM my_table WHERE col1='' OR 'x' = 'x'
which I think we can agree isn't what you wanted.
Use a parameterized SQL statement instead (PreparedStatement) and get the database driver to send the parameter values separately.
EDIT: In other comments, the OP has specified that the template string can be quite long, and some parameters may actually involve multiple initial values combined together. I still say that the cost of replacement is likely to be insignificant in the grand scheme of things, and I still say that PreparedStatement is the way to go. You should perform whatever combining operations you need to on the input before setting them as the values for the PreparedStatement - so the template may need the SQL with SQL placeholders, and then "subtemplates" to work out how to get from your input to the parameters for the PreparedStatement. Whatever you do, putting the values directly into the SQL is the wrong approach.
Why aren't you just using a PreparedStatement with replacement parameters?
String templateQuery = "SELECT * FROM my_table WHERE col1 = ?";
PreparedStatement ps = con.prepareStatement(templateQuery);
for (int i = 0; i < data.length; i++) {
ps.setString(i + 1, data[i]);
}
ResultSet rs = ps.executeQuery();
You're otherwise vulnerable to SQL injection if you use string replacement like you have.
He is correct, because you create maxCols tmp Strings.
I realized that it is for Sql commands, if is it, why you do not use PreparedStatement (http://download.oracle.com/javase/1.4.2/docs/api/java/sql/PreparedStatement.html) for this task?
Also, for formatting strings, rather than use substitute, use Formatter, it is much more elegant: http://download.oracle.com/javase/1.5.0/docs/api/java/util/Formatter.html
Whether this consumes too much memory is open to debate (what's "too much"?)
Nonetheless, for this kind of stuff you should use PreparedStatement. It allows you to do pretty much exactly what you're trying to achieve, but in a much cleaner fashion.
Your colleague is right in that every string replacement creates a new copy of the string. (However, the cost of these is probably negligible with less than 10 parameters.) Moreover, for every execution of this query the SQL engine needs to parse it afresh, which consumes far more additional resources each time.
The potential bigger problem though is that the code is suscept to SQL injection. If the input data is coming from an external source, a hacker can pass in a parameter such as "col1; drop table my_table;", effectively deleting your whole table.
All of these can be solved by using a PreparedStatement instead.