views:

597

answers:

6

Hi all,

The problem

We need to defend against a 'WAITFOR DELAY' sql injection attack in our java application.

Background

[This is long. Skip to 'Solution?' section below if you're in a rush ]

Our application mostly uses prepared statements and callable statements (stored procedures) in accessing the database.

In a few places we dynamically build-and-execute queries for selection. In this paradigm we use a criteria object to build the query depending on the user-input criteria. For example, if the user specified values for first_name and last_name, the result querying always looks something like this:

SELECT first_name,last_name FROM MEMBER WHERE first_name ='joe' AND last_name='frazier'

(In this example the user would have specified "joe" and "frazier" as his/her input values. If the user had more or less critieria we would have longer or shorter queries. We have found that this approach is easier than using prepared statements and quicker/more performant than stored procedures).

The attack

A vulnerability audit reported an sql injection failure. The attacker injected the value 'frazier WAITFOR DELAY '00:00:20' for the 'last_name' parameter, resulting in this sql:

   SELECT first_name,last_name FROM MEMBER WHERE first_name ='joe' AND last_name='frazier' WAITFOR DELAY '00:00:20'

The result: the query executes successfully, but takes 20 seconds to execute. An attacker could tie up all your database connections in the db pool and effectively shut down your site.

Some observations about this 'WAITFOR DELAY' attack

  • I had thought that because we used Statement executeQuery(String) we would be safe from sql injection. executeQuery(String) will not execute DML or DDL (deletes or drops). And executeQuery(String) chokes on semi-colons, thus the 'Bobby Tables' paradigm will fail (i.e. user enters 'frazier; DROP TABLE member' for a parameter. See. http://xkcd.com/327/)

  • The 'WAITFOR' attack differs in one important respect: WAITFOR modifies the existing 'SELECT' command, and is not a separate command.

  • The attack only works on the 'last parameter' in the resulting query. i.e. 'WAITFOR' must occur at the very end of the sql statement

Solution, Cheap Hack, or Both?

The most obvious solution entails simply tacking "AND 1=1" onto the where clause.

The resulting sql fails immediately and foils the attacker:

   SELECT first_name,last_name FROM MEMBER WHERE first_name ='joe' AND last_name='frazier' WAITFOR DELAY '00:00:20' AND 1=1

The Questions

  • Is this a viable solution for the WAITFOR attack?
  • Does it defend against other similar vulnerabilities?
  • I think the best option would entail using prepared statements. More work, but less vulnerable.
+10  A: 

SQL injection is SQL injection - there's nothing special about a WAITFOR DELAY.

There is absolutely no excuse for not using prepared statements for such a simple query in this day and age.

(Edit: Okay, not "absolutely" - but there's almost never an excuse)

Aaronaught
looking over the code, several of these 'finder' DAO methods generate 'IN clauses' (i.e. "WHERE STATUS in (1,2,3)"). No excuses, but these are more difficult with prepared statements.
Not really. Just parameterize the individual IN tokens (`WHERE status IN (?, ?, ?)`).
Aaronaught
one wrinkle: the list size can vary. dynamic list sizes present a problem, AFAIK the preparedStatement sql can have a long list of '?' (i.e. upper bound of the list size); you can set the first 3 and use setNull to set the last upperbound-3.i.e., : WHERE status IN (?,?,?,?,?,?,?,?,?,?)
This is a fully-solved problem: http://www.javaranch.com/journal/200510/Journal200510.jsp#a2 Any of those approaches are better than raw strings.
Aaronaught
No, I think it's 'absolutely'. There are no exceptions. Anything you think is an exception is actually something you need to solve another way.
Noon Silk
A: 

How about follow xkcd and sanitize the input. You could check for reserved words in general and for WAITFOR in particular.

David Soroko
Please, no. Don't try to sanitize with some flaky blacklisting system. If you have some mysterious but intense aversion to prepared statements then *escape* the strings.
Aaronaught
I didn't -1 you, but it might seem, that the first sentence of your answer could warrant a +1 (since it mentions xkcd), but then you get to the second sentence. And that's where it all falls apart.
shylent
Banning reserved keywords is a strategy only the TSA can afford to spend billions on and get away with.
Coincoin
Ha, I was blinded by the authority of xkcd :-) Using prepared statements instead is obviously the way to go.
David Soroko
+2  A: 

I think you suggested the solution yourself: Parameterized Queries.

How did you find that your dynamically built query is quicker than using a stored procedure? In general it is often the opposite.

Daniel Vassallo
Ah yes. In general true, but in these specific cases we've found otherwise.Here's the situation: you have a DAO method which has a specification/criteria object with 30 fields on it. You could have one stored procedure with 30 parameters on it with sql that looks like this:SELECT last_name, first_name FROM member WHERE (@pLastName IS NULL OR @pLastName=last_name) AND (@pFirstName IS NULL OR @pFirstName=first_name) AND (@pSerialNbr IS NULL OR @pSerialNbr=serial_nbr) AND (@pZodiacSign IS NULL OR @pZodiacSign=zodiac_sign) AND (@pPartyRank IS NULL OR @pPartyRank=party_rank)
from my experience, the SQL optimizers/engines perform very poorly on such procedures; they re-use a very generic access plan (usually full-table scan) rather than recognize on-the-fly to use a particular index .Additionally, some of the values are in fact lists (i.e. 'WHERE rank in (1,2,3)' ). I have in places passed in comma-separated lists to a stored procedure, parsed and populated a temporary table, and joined against the temporary table but this in practice performs *MUCH WORSE* than a simple sql with an in clause.
finally, to summarize, from my experience : simple sql performs best. Super-fancy sql makes for super-slow. Optimizers and access-plan-generators aren't that smart.(also we support a few different db's so have limits how many hints we can provide)
If one must therefor use dynamically generated SQL, sanitize the input. By which I mean escaping strings, and validating numeric types, etc. There is plenty of pre-existing code to do that for you.
Dems
Stored procedures won't protect you against SQL injection if you smash strings together *in the stored procedure itself* and then exec() the result. I have seen this done.The important thing to emphasise is **parametrised queries**.
jammycakes
Good point. Thanks. Fixed the emphasis.
Daniel Vassallo
+10  A: 

The correct way to handle SQL injection is to use parameterized queries. Everything else is just pissing in the wind. It might work one time, even twice, but eventually you'll get hit by that warm feeling that says "you screwed up, badly!"

Whatever you do, except parameterized queries, is going to be sub-optimal, and it will be up to you to ensure your solution doesn't have other holes that you need to patch.

Parameterized queries, on the other hand, works out of the box, and prevents all of these attacks.

Lasse V. Karlsen
Good point, I guess I should have been more specific - a prepared statement won't do you any good if you pass it one big concatenated string instead of one with parameter placeholders!
Aaronaught
Which is why I said "parameterized query", not "prepared query".
Lasse V. Karlsen
"The correct way to handle SQL injection is to use parameterized queries. Everything else is just pissing in the wind"Thank you for telling the truth! This is the answer to almost any security programming problem actually.
Longpoke
+1  A: 

To answer all your questions:

Is this a viable solution for the WAITFOR attack?

No. Just add -- to the attack string and it will ignore your fix.

Does it defend against other similar vulnerabilities?

No. See above.

I think the best option would entail using prepared statements. More work, but less vulnerable.

Yes. You don't fix SQL injection yourself. You use what's already existing and you use it right, that is, by parametrizing any dynamic part of your query.

Another lesser solution is to escape any string that is going to get inserted in your query, however, you will forget one one day and you only need one to get attacked.

Coincoin
A: 

Everyone else has nailed this (parameterize!) but just to touch on a couple of points here:

  • Is this a viable solution for the WAITFOR attack?
  • Does it defend against other similar vulnerabilities?

No, it doesn't. The WAITFOR trick is most likely just being used for 'sniffing' for the vulnerability; once they've found the vulnerable page, there's a lot more they can do without DDL or (the non-SELECT parts of) DML. For example, think about if they passed the following as last_name

' UNION ALL SELECT username, password FROM adminusers WHERE 'A'='A

Even after you add the AND 1 = 1, you're still hosed. In most databases, there's a lot of malicious things you can do with just SELECT access...

Cowan