views:

675

answers:

15

There seems to be some hysteria about SQL Injection attacks. Most recently, here

http://stackoverflow.com/questions/505838/vba-simple-database-query-from-word

If I'm creating a macro in Excel that connects to an Access database, do I really have to be concerned about SQL injection? It's not on the web, it's used in my office (you guys remember desktops right?). I'm not concerned that my co-workers are going to sabotage me. If they're smart enough to do a SQL injection, aren't they smart enough to crack my add-in password and just change the code?

+3  A: 

As a developer, you are responsible, if not wholly then at least in part, for the security of the data held by your application.

This is true whether your application is online or used only in your office. Do everything you can to ensure your data store is airtight.

At the end of the day, you don't want to be the one who has to explain to the boss where last year's sales figures have gone.

Galwegian
A: 

IMO if your system will be exposed to people who may wish to cause harm (e.g. on the Internet), then you really should protect against SQL injection.

On the other hand, if it is an internal system, where any malicious user who could access SQL injection could also harm it in other ways anyway, then it really is not that important.

I have written code myself which is vulnerable to SQL injection, but the only people with that sort of access are co-workers with SQL access anyway.

DanSingerman
A: 

Although we all would like applications that are invulnerable to any and all attacks, the time taken developing all of the bulletproofing has to be weighed alongside with the added benefit. If you can reasonably expect the security requirements to not be very high, this could be something you might want to pass on. If you think it is something potentially to worry about, maybe you should take the steps to prevent the possibility now and not have to worry anymore.

TheTXI
+9  A: 

If you're building SQL in your macro, it's vulnerable to SQL injection. Even if you trust the people who will be using the thing, you should at least watch for the basics, like people trying to put single-quote and semicolon characters into database fields. this isn't so much a security issue in your case as just data validation.

mjfgates
+1 Good point about the data validation.
C. Ross
+1  A: 

Honestly, if this is an existing app you're talking about, I wouldn't go rewrite it. However, if you are developing it as we speak, I can't see it being that hard to use parameterized queries instead of the alternative.

CodeMonkey1
+3  A: 

Physical security is always the first line of defense in data security. If your application is only going to be distributed inside an office and the data accessed is of insufficient value for someone to go to the trouble and expense of stealing and cracking, you can adhere to a lower security standard than you would use on an outward-facing web application.

But, real security is ultimately about what can happen, not what we expect to happen. If your application handles data entrusted to you by the public (SSNs, credit card numbers, etc) or if it is the only repository of data crucial to your company, you have to take into account what potentially malicious users could do with your code in the future. Today's happy employee is tomorrow's disgruntled sociopath.

A good rule of thumb is to ask yourself: If I, with full knowledge of this product, wanted to use it to hurt my company, how much damage could I do? Then, build in enough security to bring that number down to tolerable levels.

Jekke
A: 

No. (Yes.) Yes. :)

Oftentimes, I see developers wasting precious resources on fortifying the "front door", only to not notice the swinging screen door on the back. This is usually something like fortifying a frontend to an insecure backend, fortifying an app that is basically open to varied users, etc...

It's all nice and well to make blanket statement about security, but it must match requirements.

alphadogg
I guess if you don't toe the OCD line on security, you get voted down?!?
alphadogg
A: 

If I'm creating a macro in Excel that connects to an Access database, do I really have to be concerned about SQL injection?

Maybe. It depends, really. I personally wouldn't be concerned, but what kind of data are you trying to store and what is it's sensitivity?

If they're smart enough to do a SQL injection, aren't they smart enough to crack my add-in password and just change the code?

Maybe. Just because someone can do a sql injection does not mean they are smart enough to crack your add-in password. On the other hand, they could be.

Eppz
+12  A: 

SQL Injection is not just a security threat, it is a very real source of bugs too.

Are you sure that none of your records will ever have an apostophe (') in them?

INSERT INTO NAMES (FIRSTNAME, LASTNAME) VALUES('Jack', 'O'Neill')

In this case, you have a bug even though nobody wanted to crack your system.

DrJokepu
The question was about SQL injection, not about sanitising the string
DanSingerman
HermanD: My answer wasn't about string sanitization either - remember that most databases support parametrized queries too.
DrJokepu
It's the same problem. You solve the escaping problem so that ' can go in names, you solve the security problem at the same time - what's not to like? "Sanitising" - disallowing "bad" input - is the wrong approach. Don't tell Jack O'Neill he is "invalid". You wouldn't like Jack when he's angry.
bobince
+5  A: 
AlexCuse
A: 

Would someone please post proof-of-context Excel VBA code for SQL injection using a Jet database as the back end? Or demonstrate exactly what parameters could be passed to the code in http://stackoverflow.com/questions/505838/vba-simple-database-query-from-word that would be damaging (rather than just breaking the code)?

Given that Jet cannot execute multiple SQL statements separated by ";", I'm having a hard time conceiving of any SQL injection threat with a Jet back end. But perhaps that's because I'm just not as imaginative as hackers.

Oh, a free clue (on the subject of dangers other than traditional SQL injection):

  1. the Access expression service is unavailable via ODBC.

  2. it is available via DDE, but I don't know if you can pass SQL to Access via DDE (I haven't used DDE with Access in about 10 years).

  3. if you know nothing about the Access and Jet expression services, you're probably not qualified to be answering a question about Jet (and Access).

--
David W. Fenton
David Fenton Associates

David-W-Fenton
You are correct that you can't inject SQL DDL (DROP TABLE, etc) using ACE (formerly Jet <g>) SQL syntax but you can inject predicates into SQL DML (DELETE, UPDATE, etc), resulting in malicious data loss or corruption. See my exmaple posted in this thread.
onedaywhen
I can't think of a case in any app I've ever written where I accept arbitrary user input for SQL DML -- indeed, I can't even think of a case where I ever accept any form of user input in any of my apps for any SQL DML operations.
David-W-Fenton
A: 

Dick, It depends how you are handling parameters. Here's a VBA example of how not to do things:

Friend Function DeleteAnAccount() As Boolean

  Const SQL_DELETE_AN_ACCOUNT As String * 50 = _
      "DELETE FROM Accounts WHERE account_owner_ID = '?';"

  Dim sql As String
  sql = Replace$(SQL_DELETE_AN_ACCOUNT, "?", txtAccountOwnerID.Text)

  m_Connection.Execute sql

End Function

Consider that if some wag, instead of typing their account ID into the textbox (txtAccountOwnerID), actually typed this:

dummy' OR 'a' = 'a

then the resulting SQL string would be this:

DELETE FROM Accounts WHERE account_owner_ID = 'dummy' OR 'a' = 'a';

Not good becasue the 'a' = 'a' predicate would resolve to TRUE and all accounts would be deleted.

Better would be to use a prepared statement using Parameter objects e.g. ADODB.Command object.

Jamie.

--

onedaywhen
OK, I can see that. But there is no danger in the original example, which was a SELECT statement. I don't think I have ever once accepted a user parameter that I didn't validate in one way or the other (usually processing through the expression service, which prevent your example from working).
David-W-Fenton
There can be 'danger' in a SELECT statement too e.g. instead of returning just your account details, you get it to return details of all accounts.
onedaywhen
OK, true. But frankly, I've never programmed this kind of thing in Access except by validating input from the user and almost always passing it through Application.BuildCriteria to write the WHERE clause. Your exploit would fail with that approach, which is pretty standard in Access.
David-W-Fenton
I think we've now agreed this could indeed be a problem :)
onedaywhen
+2  A: 

I'd like to expand on the comment I made above in response to onedaywhen's post outlining how to exploit a SELECT statement in MS Access. Keep in mind that these are not generalized comments about how to protect from SQL injection, but apply specifically to programming in MS Access.

I've never seen any example code for Access that would allow the kind of exploit of a SELECT that onedaywhen outlined. The reason for this is that there's almost never a situation where you would use such simple methods for collecting criteria without some validation of the input somewhere along the way, not to avoid SQL injection, but to avoid bugs caused by invalid SQL.

Here's code implementing the simplest version of this:

Public Sub TestSQLExploit()
  Dim strSQL As String

  strSQL = "SELECT tblInventory.* FROM tblInventory WHERE InventoryID = "
  strSQL = strSQL & InputBox("Enter InventoryID")
  Debug.Print strSQL
End Sub

So, passing "10036 or 'a' = 'a'" produces this SQL:

SELECT tblInventory.*
FROM tblInventory
WHERE InventoryID=10036 Or 'a'='a'

And that's definitely not good!

Now, I would never write my code that way because I always want to allow for multiple values. Instead, if I were using the InputBox() function to collect the user input (which I honestly never do, since it's too hard to validate), I'd use Application.BuildCriteria to write the WHERE clause, since that would allow me to handle multiple criteria values. That would result in this code:

Public Sub TestSQLExploit1()
  Dim strSQL As String
  Dim strWhere As String

  strSQL = "SELECT tblInventory.* FROM tblInventory "
  strWhere = "WHERE " & Application.BuildCriteria("tblInventory.InventoryID", _
      dbLong, InputBox("Enter InventoryID"))
  strSQL = strSQL & strWhere
  Debug.Print strSQL
End Sub

I honestly thought that Application.BuildCriteria would throw an error on this, but it doesn't, and when passed "10036 or 'a' = 'a'" produces exactly the same SQL. And because of the way the Jet expression service works, it would be wide open, as you say.

Now, I never ever actually write on-the-fly SQL like this, because I just don't like the InputBox() function, precisely because you have to write a bunch of code to validate the input. And if you used it like the code above, you'd have to do a lot to make sure it was valid.

I have never seen any Access code examples for this kind of operation that does not recommend using parameterized SQL (which would, of course, avoid the problem) or a Query-By-Form interface. I generally don't use saved parameter queries in Access, because I like to write my saved queries to be usable everywhere. This means they mostly don't have WHERE clauses that have criteria that change at runtime. When I use these saved queries I provide the WHERE clause for the appropriate situation, whether as a recordsource in a form or a rowsource for a listbox or dropdown list.

Now, the point here is that I'm not asking the user for input in these cases, but drawing the criteria values from Access objects, such as a control on a form. Now, in most cases, this would be a control on a form that has only one purpose -- to collect criteria for some form of filtering. There would be no unvalidated free-text fields on that form -- date fields would have input masks (which would restrict input to valid dates), and fields that have a limited number of valid values would have control types that restrict the choices to valid data. Usually that would be something like a dropdown or an option group.

The reason for that kind of design is not necessarily to avoid SQL injection (though it will prevent that), but to make sure that the user is not frustrated by entering criteria that are invalid and will produce no results.

Now, the other consideration is that sometimes you do want to use some plain text fields so the user can put in certain kind of data that is not already restricted (such as looking up names). Just looking at some of my apps that have name lookup routines with unvalidated text fields, I find that I'm OK, because I don't use BuildCriteria in those cases, because it's designed to collect only one criterion at a time (though the user can input "*" to retrieve multiple records).

If I have a textbox where the user enters "fent* or 'a' = 'a'", and I use that in a WHERE clause:

WHERE tblDonor.LName Like "fent* or 'a' = 'a'"

The result is that nothing is found. If the user entered "fent* or a = a", it will still not work, because it's a text field and I'm using double quote around it. If the user entered:

fent* or "a" = "a"

that will break, too, because when my code puts double quotes around it, the WHERE clause will be invalid.

Now, with the case of just taking use input and putting double quotes around it, it's clear that inputting this:

" Or "fent*" or "a" = "a" Or "

would result in:

WHERE tblDonor.LName Like "" Or "fent*" or "a" = "a" Or ""

and that would be very bad, since it would return everything. But in my existing applications, I'm already cleaning double quotes out of the user input (since double quotes are theoretically valid within the LName field), so my apps construct this WHERE clause:

WHERE tblDonor.LName Like "? Or ?fent*? or ?a? = ?a? Or ?*"

That won't return any rows.

But the reason it doesn't is not because I was trying to avoid SQL injection, but because I want the user to be able to look up names that have double quotes embedded in them.

======

Some conclusions:

  1. never accept free-form input from users when filtering data -- instead, use controls that pre-validate input (e.g., textboxes with input masks, dropdown lists, options groups) and limit it to values that you know are valid.

  2. when accepting data from a textbox with no restrictions, avoid Application.BuildCriteria, which will process the input in such a way that the user could trick your app into returning all rows (though that's the extent of what the exploit could do).

What this means on a practical basis is that if you want to collect multiple criteria, you need to do it in a way that the user can only choose from preselected values. The simplest way to do that is with a multiselect listbox (or a pair of them with ADD>> and <<REMOVE command buttons in between).

Of course, whether or not you need to worry about this kind of SELECT exploit depends on the importance and privacy level of the data being retrieved, and exactly what is being returned to the user. It might be no problem to risk returning all rows of non-sensitive data when presenting the data in an uneditable form (e.g., a report), whereas it might be problematic if you presented it in an editable form and someone changed data that oughtn't be edited.

But with non-sensitive data, it will often simply not matter if the user gets too much data returned (except for performance issues, e.g., overloading a server -- but that's better handled in other ways).

So, my takeaway on all of this:

  1. never use InputBox() to collect criteria (this one I already avoid).

  2. always use the most limiting control types possible for collecting critiria (this is already something I do regularly).

  3. if using a textbox to collect string data, treat it as a single criterion no matter what's put in by the user.

This does mean that I have some apps out there where a user could input "Or 'a' = 'a'" along with a valid criterion and return all rows, but in those apps, this is simply not an issue, as the data is not sensitive.

But it's a good reminder to me not to be complacent. I had thought that Application.BuildCriteria would protect me, but now realize that the Jet expression service is way too forgiving in what it accepts in a WHERE clause.

2009/12/08 EDIT: Just found these links on SQL Injection in MS Access. All of these are targetted at web injection, so not directly applicable to a discussion of Non-Web SQL injection (many of them would be a waste of time in interactive Access, as you already have access to a lot of the information being brute-forced, e.g., information about file system, paths, executables, etc.), but many of the techniques would also work in an Access application. Also, executing from Access opens up a lot of functions that would not be runnable from ODBC/OLEDB. Food for thought.

David-W-Fenton
Glad you saw the light :) +1 for taking the time to explain things in Access terms.
onedaywhen
A: 

Three points:

  1. Using parametrized queries is generally less work than escaping possible ways to break your SQL (Mr. O'Neill, for instance) so that you can concatenate the data into the query string directly. If the more robust option is also less work to implement, then why would you not want to do it?

  2. I haven't used Jet for ages, so I don't know whether it supports pre-prepared statements these days or not, but, if you're going to run the statement more than once, using a parametrized query and re-running it with different parameters will be faster than building new queries each time.

  3. Even if all users are 100% trustworthy and will never become disgruntled enough to attempt to cause any damage, there's always the possibility of typos or other genuine mistakes. Guarding against user error is generally considered a Good Thing.

So you absolutely should use parametrized queries, as shown in Spolsky's reply to the other question, even if not for the sake of security. They're not just more secure, they're also more error-resistant, often quicker to write, and are higher-performance for repeated queries.

Dave Sherohman
A: 

>using a parametrized query and re-running it with different parameters will be faster than building new queries each time.

Actually, it will not improve performance in jet if you talking about query performance. In fact, from the JET white paper “Performance Overview and Optimization Techniques”, we get this gem:

page 18

Since stored queries have a precompiled query plan, parameterized queries that contain parameters on indexed columns may not execute efficiently. Since the query engine does not know the values to be passed in a parameter in advance, it can only guess as to the most efficient query plan. Based on customer performance scenarios that we have examined, we have discovered that in some instances substantial performance gains can be achieved by replacing a stored parameterized query with a temporary query. This means creating the SQL string in code and passing it to the DAO OpenRecordset or Execute methods of the Database object

Neat-o eh? And, I have experienced the above!

Keep in mind that the compile time for a query plan is in the 1000’s of a second anyway. I mean, really, the query plan time goes from .01 to .0001. Sure it 100 times faster, but that is only saving us a 100th of a second overall. We running a report that takes 2 seconds so that query plan time is not even a concern.

We have GOBS of processing today. It is disk drives, memory and the network i/o speeds that are the bottle necks. We also don’t have the issue of wasting up the server sql query cache for each new sql string submitted to JET. Those in-line sql queries plans are not cached anyway. And, MORE important JET is client based engine so when you have 10 users on your office lan, you have 10 copies of JET running local on each machine. Query plan cache is not an issue like it is for sql server.

As the above jet white paper shows (and my experience), the benefits of a better query plan by forcing a re-compiling of that sql without parameters outweighs the benefits of having a pre-complied query plan with paramaters.

However, to stay on track, have to agree with David. I don’t think that when you using odbc, or in this case the dao object model + jet, I can’t come up with ANY WAY to inject a real sql statement.

One can perhaps with the “lame” InputBox() example above enter conditions that might produce unexpected results. As pointed out applications built in access don’t work this way very often.

For things like deleting a record you be viewing a form and it will have a custom menu bar (or now ribbon), or simply a delete button placed on the form. The user thus can’t enter bad data for this type of deleting code.

MORE important when we do often accept input from users on forms, keep in mind our forms have built-in data masks. After all this is quite much what MS access was designed for. Hence if we are asking for a phone number, the user can’t enter letters or even any non numeric charters for that input mask. That mask will even put in () and – in the appropriate places in that phone number for display, but only numbers will in in the users actual input.

For most other types of prompts we use combo boxes, lisboxes and other UI elements that again limits the users ability to inject something other then what that form allows into that text box.

Due to such an affluent number of masking and input abilities that are far beyond most screen builders, injecting is rare topic for MS access based applications.

If anyone can show an JET example in which the user can execute a sql statement by injection, I am all ears as I don’t think it is possible with dao + jet.

For an MS-access applications, it might be possible, but once again, in actual practice, very difficult.

Albert D. Kallal
I agree with all you say, but would point out that the original context was executing a SQL statement from Word or Excel where you don't have rich UI-building tools.
David-W-Fenton