views:

69

answers:

4

Hi,

I have had several contexts where table names or schemas were not hard-coded, but rather configured by the administrator, or, worse, generated from user input.

Since cases were easy (schemas and table names in plain English, without numbers nor symbols), it was easy to avoid SQL Injection by just forbid any character outside A-Z and a-z ranges. But this approach sucks when an application must handle any Unicode characters when can be a part of a name of a schema or a table.

Now, if an SQL query uses '[' and ']' to include names to schema, table and column, is it enough to forbid only ']' character in names to avoid SQL Injection?

Example:

A'B is a valid name for a table. So:

string tableNameFromUserInput = "A'B"; // Let's imagine it is a user input.
using (SqlCommand getEverything = new SqlCommand(
    string.Format("select * from [dbo].[{0}]", tableNameFromUserInput),
    sqlConnection)
{
    // Do stuff.
}

is perfectly valid, and there is no reason to block the single quote character.

So is there a risk of SQL Injection in the following code?

string tableName = UserInput.GetUnsafeInputFromUser();

// Search for ']' character only.
if (tableName.IndexOf(']') != -1)
{
    throw new HackerDetectedException();
}
else
{
    using (SqlCommand getEverything = new SqlCommand(
        string.Format("select * from [dbo].[{0}]", tableNameFromUserInput),
        sqlConnection)
    {
        // Do stuff.
    }
}

Edit:

After some search, what I found is an article on Delimited identifiers in Microsoft SQL. According to it:

The body of the identifier can contain any combination of characters in the current code page, except the delimiting characters themselves.

By the way, the delimited identifiers are limited to 128 characters.

So to make it work:

  • The ']' character must be forbidden (or, better, replaced by ']]'; see Mark Byers answer below).
  • The length must be limited to 128 characters (not done in the sample code above),
  • The code page must probably be checked to get the correct table.
A: 

I think the following would crush your database perhaps the following from OWASP:
'and 1 in (select min(name ) from master.dbo.sysdatabases where name >'.' ) --'

For anyone who wants the OWASP resources:
OWASP

Woot4Moo
@Woot4Moo: Since `'; drop table yourTable;'` is a perfectly valid name for a table, `select * from [dbo].['; drop table yourTable;']` works well. Just tried. My database is still intact.
MainMa
it was just a random queryString. Try the ones from OWASP.
Woot4Moo
@Woot4Moo: tested with your second example. Still works as expected (selects data).
MainMa
@MainMa the day you think that you can't be SQL injected is the day you weep heavily. Anyways as Rook tends to say don't trust security advice that you find on SO
Woot4Moo
+2  A: 

if it is only select sql i.e. you are not intended to save any changes,you can run your sql command in a transaction in a try block and rollback in finally block.

this way , you can ensure at your code level that any other ddl , dml would not be run from your code. only select satements will be executing.

This particualr startegey works when you fetching data but not updating.

   try
        {
            //Run command in transaction.
        }
        catch (Exception ex)
        {
            // rollback transaction
        }

        finally
        {
            // rollback transaction
        }

and also this has been tested for Sybase database

saurabh
@saurabh: I apologize, I've forgot to precise in my question that the context requires not only to select, but also to insert, delete and update data.
MainMa
@MainMa : Than i can say that , you should first run the select statement in transaction and if you got correct result than only proceed further with insert/update statements.
saurabh
Uh, I'd be careful there. First not all DBs support DDL rollback, but since it is tagged TSQL let's assume it is SQL Server. Server-wide commands such as `DROP DATABASE xxx` will not be undone! There are other gotchas, such as http://blogs.msdn.com/b/joesack/archive/2010/04/08/gotcha-when-server-scoped-ddl-triggers-don-t-honor-rollback.aspx - so while this is certainly not a bad idea as additional security measure it doesn't fit the bill of securing your DB from injection attacks.
Lucero
@saurabh: I see. Another, quite similar, approach would be to load the list of table names as an `IEnumerable` and to look if the input matches one of the elements in a set. But both approaches require an additional query.
MainMa
+3  A: 

I think that would be safe. Characters such as backslash are treated as literal characters so they should be safe to include in table names. The only character I can immediately think of as being a problem is ], which you already disallow. By the way, to include ] in a table name it needs to be written as ]], so instead of denying it completely you could instead do tableName.Replace("]", "]]").

However to be on the safe side you should use a whitelist as you suggested. To do this while also allowing for unicode characters in table names you might want to consider using the regular expression @"^\w+$" to validate the table name as this accepts unicode characters too.

Mark Byers
+1 for the whitelist. But even then I wouldn't really allow user-based names, I'd rather generate them (GUIDs maybe) and match them via an additional whitelist. Or use `HASHBYTES()` to generate a binary hash from the user string which you can convert to hex, so that the name can be safely derived from the input without any risks.
Lucero
+2  A: 

Not sure entirely how complicated your inline SQL is, but why not just pass the schema/table strings from the user through QUOTENAME() to escape invalid characters?

Agent_9191
+1. Didn't know about that thing.
MainMa
Remember when using this function that the input must be handled correctly or else you could get an SQL injection vulnerability... :)
Mark Byers
@Mark Byers true, but it will at least handle the `[` and `]` escaping. One less thing to reinvent a solution for.
Agent_9191