19

Background

I've been contracted to analyze an existing Data Provider and I know the following code is faulty; but in order to point out how bad it is, I need to prove that it's susceptible to SQL injection.

Question

What "Key" parameter could break the PrepareString function and allow me to execute a DROP statement?

Code Snippet

Public Shared Function GetRecord(ByVal Key As String) As Record
    Dim Sql As New StringBuilder()

    With Sql
        .Append("SELECT * FROM TableName")
        If String.IsNullOrEmpty(Agency) Then
            .Append(" ORDER BY DateAdded")
        Else
            .Append(" WHERE Key = '")
            .Append(PrepareString(Key))
            .Append("'")
        End If
    End With

    Return ExecuteQuery(Sql.ToString())
End Function

Public Shared Function PrepareString(ByVal Value As String) As String
    Return Value.Replace("''", "'") _
                .Replace("'", "''") _
                .Replace("`", "''") _
                .Replace("´", "''") _
                .Replace("--", "")
End Function
Community
  • 1
  • 1
Josh Stodola
  • 81,538
  • 47
  • 180
  • 227
  • 17
    If you're using VB why would you go through the effort of trying to think of every possible attack vector instead of just using parametrized queries? – Donnie Nov 25 '09 at 21:20
  • Duh! I agree fully! I would never ever ever write code like this. Bottom line: I need to know what string breaks this function in order for me to prove a point. – Josh Stodola Nov 25 '09 at 21:29
  • 3
    @Josh: perhaps they don't want to encourage you to avoid using best practice... if you have to add PrepareString function to a "billion lines of code", why not parameterise? – gbn Nov 25 '09 at 21:29
  • No I am not adding a PrepareString function. It's already there in use. I was not contracted to write any code, I am doing analysis, and I need to find a string that exploits this vulnerability to prove to a bonehead that this code is brutally flawed. – Josh Stodola Nov 25 '09 at 21:31
  • 3
    This code is pathetic. I want to break it. How can I break it? I am not asking a question on how to properly query a database from VB.NET. I know how to do that, and I would used parameterized queries and/or stored procedures. People see this code and freak out and wave their index finger at me, instead of reading and answering the actual question. – Josh Stodola Nov 25 '09 at 21:34
  • 6
    Not to put too fine a point on it, but you asked the question incorrectly. You addressed the community harshly and you garnered downvotes in return. I've edited your question to address the intent of the question so maybe it will be upvoted since it could be a good question. – George Stocker Nov 25 '09 at 21:40
  • Why the close votes?? It's legitimate – erikkallen Nov 25 '09 at 22:43
  • 3
    @Shawn and Erik I originally posted a comment here that was somewhat rude (something like "Could you just answer the damn question, please?") because people were bashing me for not using parameterized queries. At one point, this question had a score of -7 and I think it was purely because of that rude comment. Rephrasing the question seemed to take care of all that, and now I don't think there are still people wanting to close the question. – Josh Stodola Nov 25 '09 at 22:47
  • Please see below for my proof that actually creates an injectable string to break your PrepareString method. – BenAlabaster Nov 25 '09 at 22:50
  • A bit late now (!), but SQL Server will generate a separate execution plan for each length of `Key`. If you use a parameterised query *and* specify the size of the parameter (the size of the column), it can generate a single execution plan. So you could go for the efficiency angle. – Andrew Morton Aug 28 '14 at 08:23

7 Answers7

25

In answer to your direct question: Does this code prevent SQL injection: No

Here's the proof - push this string through the PrepareString method:

Dim input = "'" & Chr(8) & "; Drop Table TableName; - " & Chr(8) & "-"
Dim output = PrepareString(input)

Console.WriteLine(input)
Console.WriteLine(output)

I modified the GetRecord method you posted to return the fully prepared SQL string rather than get the record from the database:

Console.WriteLine(GetRecord(output))

And this is the output

Input  = ; Drop Table TableName; --
Output = '; Drop Table TableName; --
Query  = SELECT * FROM TableName WHERE Key = ''; Drop Table TableName; --'

Add 1 extra line of code:

My.Computer.Clipboard.SetText(input)

And you've got the string you need copied right to your clipboard to paste into your input field on the website to complete your SQL injection:

'; Drop Table TableName; - -

[Noting that the control characters have been omitted from the post output by StackOverflow, so you'll have to follow the code example to create your output]

After the PrepareString method is run, it will have the exact same output - the Chr(8) ASCII code is the backspace which will remove the extra "'" that you're appending to mine which will close your string and then I'm free to add whatever I want on the end. Your PrepareString doesn't see my -- because I'm actually using - - with a backspace character to remove the space.

The resulting SQL code that you're building will then execute my Drop Table statement unhindered and promptly ignore the rest of your query.

The fun thing about this is that you can use non-printable characters to basically bypass any character check you can invent. So it's safest to use parameterized queries (which isn't what you asked, but is the best path to avoid this).

BenAlabaster
  • 39,070
  • 21
  • 110
  • 151
  • Sorry, but it has to be something that can be typed into a textbox on an HTML web page. It might be possible to do this via programmatic POST, but that also brings up an issue with ASP.NETs event validation model, which the site in question does utilize. Thank you very much for your efforts though, this is good stuff here! – Josh Stodola Nov 25 '09 at 23:16
  • Additionally, I should add that simply adding another `.Replace(Chr(8), "")` will take care of that. What I mean is: it's not exactly cold-hard evidence to convince a (stubborn) manager that converting 200,000 lines of production code to use parameterized queries is worthwhile. – Josh Stodola Nov 25 '09 at 23:21
  • 7
    How many exploits will convince him? I could do this all night - I could generate a POST from the original page data that you send out and doctor the correct form value to get past the event validation. I can then use a plugin in my web browser so that your server sees it as the same session. Sure replacing Chr(8) would fix *this* exploit, but what about the next one? – BenAlabaster Nov 25 '09 at 23:26
  • 7
    And to be honest - all you asked for was some code that proved that the method was flawed and I've produced that... and hence answered your question. – BenAlabaster Nov 26 '09 at 00:14
  • You could dump the text output to a file and use a hex editor to cut and paste it into the textbox on the site - that would effectively remove the need for the browser plugin... but would require a hex editor... – BobTheBuilder Nov 26 '09 at 00:22
  • 1
    Or just add a single extra line of code to your generator which copies it to the clipboard... it would almost be amusing to create a WinForms application to allow you to insert the control characters on the fly. – BenAlabaster Nov 26 '09 at 00:35
  • 1
    @Josh: I updated my code snippet to take care of your concern that the output couldn't be put into a textbox on your webpage - with one line of code the string is written to the clipboard ready to be pasted directly into the form. Now it's just a case of coming up with other string exploits to get through your PrepareString method... anyway, I think I've proven my point. – BenAlabaster Nov 26 '09 at 00:38
  • 2
    If your manager is that stubborn, no amount of examples will convince him. He'll just take the "well, just replace chr(8) as well" track that you used above. You're fighting a losing battle. – Donnie Nov 26 '09 at 00:53
  • 17
    -1. This "proof" only *appears* to exploit the code because the chr(8)'s are processed as actual backspaces by the Console. If you put those characters into a string and copy them to the clipboard as you suggest, they remain as characters in the string and DO NOT eat any characters. – Jon Seigel Mar 08 '10 at 19:49
  • 1
    +1 to @JonSeigel this answer is misleading, AFAIK escaping the quotes (by doubling them) is safe for string parameters in SQL server, I havn't seen a single example breaking that. Other note: there is some legit usages of non parametrized SQL like generating scripts, please don't use the "religious" approach when dealing with coding practices. – Guillaume86 Jul 26 '16 at 13:20
  • As already stated this answer is simply wrong. Try it on SQL Server to check it, only appear to be right on the console due how to character are displayed – Skary Feb 14 '23 at 11:50
  • I can't reproduce it using C# and MS SQL Server 15.0.2101.7. It doesn't process backspace character `\x8` and keeps them as is. – Vasiliy Zverev Aug 08 '23 at 00:01
17

To answer your questionable question, no it wouldn't work.

.Replace("``", "''") would prevent legitimate queries with '`'

.Replace("´", "''") would prevent legitimate queries with '´'

.Replace("--", "") would prevent legitimate queries with '--' in them

.Replace("''", "'") would incorrectly modify legitimate queries with '''' in them

and so on.

Furthermore, the full set of escape characters can vary from one RDBMS to another. Parameterized queries FTW.

Jacob Krall
  • 28,341
  • 6
  • 66
  • 76
Yann Schwartz
  • 5,954
  • 24
  • 27
  • 4
    agreed, there is no 100% surefire way to escape a querystring. They will just build a better mousetrap. – Neil N Nov 25 '09 at 21:48
  • What value passed in as the key would cause me to execute any SQL I want? I'm not worried about rare queries that will not work, but malicious queries that can do serious harm. – Josh Stodola Nov 25 '09 at 21:53
  • Josh, the point is, people find new ways to exploit canonization issues, and preventing them this way is bound to fail. It's like trying to be clever in escaping HTML tags, or file paths. Sometimes a null character inserted in the right place can fool the sanitization code, and so on. You have to rely on the underlying paramaterization mechanism and cross your fingers it works (or that it will be patched in due time). – Yann Schwartz Nov 25 '09 at 22:07
  • 1
    Furthermore, as I've said, the sanitization function do yield false positives and break legitimate queries, which is a problem too (just try to take a plane nowadays to feel how painful it can be). – Yann Schwartz Nov 25 '09 at 22:08
  • +1 don't mangle unrelated characters. Escape only the characters that need to be escaped. For SQL Server and standard ANSI SQL this is **solely** the string delimiter `'`. For MySQL and occasional others, the backslash can also be a problem. You would also want to ensure there aren't any control characters in the string (MySQL doesn't like a 0 byte), but hopefully you have already filtered those out of your input anyway. – bobince Nov 25 '09 at 22:09
  • @Yann I agree with you 100%. That said, I want ONE example of a string that lets me execute any SQL I want. You seem very very confident that the given code allows it, so please just provide ONE sample "Key" parameter that exploits the seriousness. That's all I ask. I know this is bad, so help me prove just how bad it is. Is that too much to ask? – Josh Stodola Nov 25 '09 at 22:24
2

I think it's safe (at least in SQL server), and I also think the only thing you actually need to do is s = s.Replace("'", "''"). Of course you should use parameterized queries, but you already know that.

erikkallen
  • 33,800
  • 13
  • 85
  • 120
2

I think it is unhackable if you just replace ' with ''. I have heard that it is possible to change the escape quote character, which could potentially break this, however I am not sure. I think you are safe though.

Shawn
  • 19,465
  • 20
  • 98
  • 152
1

This MSDN article covers most of the stuff you need to look out for (I'm afraid to say all when it comes to SQL injection).

But I will echo everyone else's sentiment of parameters parameters parameters.

As for your example some gotchas [Edit: Updated these]:

  • wouldn't the string "1 OR 1=1" allow the user to get back everything

  • or worse "1; drop table sometablename"

According to the article you want to check for:

; - Query delimiter.

' - Character data string delimiter.

-- - Comment delimiter.

/* ... / - Comment delimiters. Text between / and */ is not evaluated by the server.

xp_ - Used at the start of the name of catalog-extended stored procedures, such as xp_cmdshell.

brendan
  • 29,308
  • 20
  • 68
  • 109
  • If I was writing the code, you can believe that's how it would be. – Josh Stodola Nov 25 '09 at 21:28
  • Updated with a possible gotcha in your code, will try to think of more. – brendan Nov 25 '09 at 21:44
  • 1
    no, the clause WHERE Key = '1 OR 1=1' doesn't allow the user to get back everything because there are quote marks around the '1 OR 1=1'. – Jacob Krall Nov 25 '09 at 21:51
  • 3
    What horrible woolly-thinking advice in that article. Removing semicolons won't help you if an attacker can break out of a string literal because there are many other bad things you can put in an SQL statement. And if they *can't* break out of a string literal because the quotes are properly escaped (or, hopefully, because parameterisation is in effect), it will only be mauling perfectly legitimate input. – bobince Nov 25 '09 at 22:04
  • bobince, Look at the 2nd character delimiter in the article: ' - Character data string delimiter. – Stephen Curial Nov 25 '09 at 22:48
0

You are trying to black list characters to implement your own version of SQL Escaping. I would suggest reviewing this URL - SQL escaping is not necessarily the worst choice (i.e. quickly fixing existing apps) but it needs to be done right to avoid vulnerabilities.

That URL links to another page for escaping in SQL Server where the author gives suggestions that help you avoid vulnerabilities without limiting functionality.

If it helps, the articles suggest escaping braces too (I call them square brackets - but []).

Mayo
  • 10,544
  • 6
  • 45
  • 90
-4

If you try to use your code some one could pass a key (;select * from table; and get a list of what ever table they want.

In your code your not checking for the semi-colon which lets you end a t-sql statement and start another one.

I would go with a parameterized query.

Chuckie
  • 125
  • 2
  • 1
    I have a testing environment setup to try and break this. That string does not do anything but throw an exception because of invalid syntax. – Josh Stodola Nov 25 '09 at 21:41