A SQL injection attack forces an unaware application to execute database code not planned and not intended by the authors. Stated in this way, I bet that most developers would hardly believe it is ever possible. However, it is possible; and the problem has the same origin as XSS attacksunsanitized and/or unverified user input.
The execution of external SQL commands is possible when commands are built programmatically mixing user input with predefined stubs. Here's the typical scenario:
string cmd = "SELECT * FROM table WHERE id=";
cmd += Request["id"].ToString();
The preceding code works great as long as correct input is provided through the id query string or form field. If the parameter contains invalid data, quite likely the execution of the command fails and is communicated to the outside world as an HTTP 500 error (internal server error). It may mean little to you and end users; however, it is a sort of invitation for a potential attacker. Worse yet, the code above accepts just any data, including data that results in correct SQL but executes commands not originally intended by the developer.
Sanitizing user input is the primary way to fight and mitigate SQL injections. Here sanitizing means two correlated actions: check the type of data you're receiving and remove any unnecessary characters to allow only those that are absolutely needed.
Back to the previous code snippet, if the id
field refers to an integer column, you should guarantee that an integer value is used to compose the final database command. The most effective way to ensure this is using SQL parameters rather than composing the final command text as a plain string:
string cmd = "SELECT * FROM table WHERE id=@id";
SqlCommand c = new SqlCommand();
c.CommandText = cmd;
In this case, if the id query string parameter doesn't evaluate to an integer, an exception is thrown and the database command never executes. The ADO.NET layer makes the check for you if you use explicit parameters.
This approach works great for numbers, dates, and Booleans; but it may be insufficient for arrays or strings. For arrays, you need a type-safe way to pass arrays to a SQL command or stored procedure; arrays, in fact, are not a native type for SQL dialects. This article
shows how to pass parameters to stored procedures. For strings, the problem is: how can you make sure that the content of the string is legal? Filtering undesired characters in a whitelist approach is the only safe way out.
It goes without saying that SQL injection is a cross-database problem that affects more bad programming habits than database servers. Both SQL Server and Oracle databases, and any other database, may suffer from SQL injection attacks through a number of their modules.
As you may guess, the results of a SQL exploit may be much more disruptive than an XSS hack. A SQL hole, in fact, may unveil the database and all of its contents to the attacker.
The end user is in no way responsible for SQL exploits; the responsibility, in this case, belongs entirely to the programmer and database administrator. Good forms of mitigation include input sanitization as well as database configuration. Sanitizing input includes using bound parameters and data clean-up removing undesired characters and escaping quotes. Using stored procedures instead of plain SQL commands is another good move, provided that the stored procedure is written properly (see Sidebar 2
, "Are Stored Procedures Safe Against SQL Injection?"). Database configuration entails using a database connection with the least set of rights possible and isolating the host Web server in a demilitarized zone (DMZ), so that even when getting full control of an individual machine an intruder can't easily proceed further (see Sidebar 3
, "Demilitarized Zones").
A frequent objection to most security considerations around SQL injection is the following: "How can the attacker guess my table scheme and field naming?" Quite correctly, the average developer devoid of strong security skills would wonder how a "blind" attack against an unknown database might ever succeed. How could a hacker armed with a text box crack a whole system?
The text box lets the attacker send arbitrary data down to the server; admittedly, without much hope. But the server's reply might sound quite encouraging and informative. Arbitrary text in a SQL query will likely fail. What's the response for the end user then? A response that contains technical information such as the type of the database or table and column names will greatly simplify the attacker's life. A generic error page won't stop the attack but will contribute to raise the security bar higher. Pay attention to the error messages displayed to end users in case of internal errors. ASP.NET allows you to hook up the error page mechanismdo that and let technical details show in the view only for local calls from 127.0.0.1
. Note though that an attacker can still grab data by sending a sequence of SQL statements built around questions with a True/False answer.
ASP.NET Built-in Barriers
ASP.NET has taken security seriously from the beginning. Do you remember last-minute changes back in 2002 just a few weeks before the official launch of ASP.NET 1.0? Among other things, the ASP.NET worker process runs on the weakest possible account and impersonation is turned off by default.
From a more developer-oriented perspective, it should be noted that with version 1.1 ASP.NET introduced a first line of defense against XSS. Called Input Request Validation (IRV), the feature automatically enables a static filter that matches the contents of input fields against a number of fixed potentially dangerous patterns. You can control the filter programmatically through the ValidateRequest
attribute on the @Page
directive. The attribute accepts a Boolean value and is set to true by default. If the filter detects that cookies or form fields are carrying potentially unsafe data, it throws an exception and stops the page execution.
Does this mean that a typical ASP.NET application is completely secured from XSS attacks?
Unfortunately, the ValidateRequest
attributelike analogous tools and programming language featuresis far from being a silver bullet. It certainly helps but doesn't solve all security issues with its sole presence; more importantly, it can't be the sole replacement for an effective validation layer.
As a matter of fact, you have to disable IRV in order to allow formatted input in your pages. You do this as follows:
<%@ Page ValidateRequest="false" %>
When you use this technique, bear in mind that you have to replace that barrier with other, possibly high and resistant barriers.
Also related to the prevention of XSS attacks is the ViewStateUserKey
property. Introduced with ASP.NET 1.1, it is a string property defined on the Page class. The default value is an empty string. What's the purpose of this key?
A variation of XSS is known as the one-click attack. Basically, it consists of the submission of a spoofed form to a Web page. It is assumed that the attacker knows a lot about the page and application and can make reasonable guesses about how each posted field is used. This information can be part of the attacker's background (i.e., a former employee) or it might have been obtained from messages in error pages, observing ongoing HTTP packets, and so on. In addition, the attacker might need a (stolen) authentication cookie so that his malicious request can be recognized and processed by the ASP.NET HTTP pipeline.
At this point, all that he has to do is prepare a local HTML page, set the action URL to the target site, and populate the form's fields. Hey, wait a moment. ASP.NET pages have the viewstate, don't they? The fact is, a viewstate that is valid at time 0 is still valid at time 1. Sure, the contents may be different at different times, but still the ASP.NET runtime is capable of decoding it correctly. To make it simple, the same viewstate that is downloaded when you connect anonymously to the page you plan to attack is good enough to use for the attack. Unless, the viewstate is bound to a hard-to-repeat key. Take a look at the following code snippet:
void Page_Init(object sender, EventArgs e)
ViewStateUserKey = User.Identity.Name;
In this way, you tell the ASP.NET runtime to add the specified keyfor example, the user nameto the hash that protects the viewstate against tampering. What's the net effect?
If the spoofed request doesn't have a matching authentication cookie attached, the viewstate can't be decoded and an exception is thrown. In other words, the ViewStateUserKey
property binds the viewstate to a particular user. In this way, the attack is successful only if the attacker can impersonate the exact user that the encoded viewstate refers to. In a one-click attack scenario, this is all but unlikely. The one-click attack, in fact, does use some social engineering techniques to steal information to plan the attack. Once again, the moral of the story is that the ViewStateUserKey
property, like other security techniques, can only raise the bar higher. As mentioned, the ViewStateUserKey
property is set to an empty string, which means that the feature is turned off. You obtain a real benefit out of it only if you set the property to a user-specific value, such as the user name. Setting ViewStateUserKey
to a constant string will make the decoding of the viewstate more complex from a performance perspective without adding protection.
|Figure 1. Event Validation Exception: Here's an example of a validation exception.|
ASP.NET 2.0 introduces yet another built-in barrier to fight back XSS attacksevent validation. When turned on, event validation saves to an encoded hidden field all known good values it can receive from posted fields. If any incompatibilities are detected among posted values, the framework throws a security exception (see Figure 1
More precisely, event validation is a feature that ASP.NET server controls and the host page implement together. During the loading stage, for each input control that declares to support event validation, the page checks if the posted value is any of the values stored in the event validation hidden field. Suppose that a drop-down list at rendering time contains strings like "High" and "Medium". On the client, the end user is expected to make a selection from the list of provided values. A user that posts back through the displayed page from the browser can only select "High" or "Medium". But what if an attacker prepares a tailor-made form where it passes "Low" through the drop-down list? If "Low" is a value that the code behind the page understands, the attack is successful. With event validation turned on, each control is asked to render the list of allowed values to a hidden field. When back from the client, the page infrastructure matches the input value against the list of allowed values and throws an exception in case of unknown values.
Event validation is enabled by default and can be disabled on a page or application basis. For example, here's how to disable it on a particular ASP.NET page:
<%@ Page EnableEventValidation="false" %>
You use the <pages> section in the configuration file to disable validation for all pages in the application. Event validation works only for server controls that explicitly declare to support it. Control developers turn event validation on through the SupportEventValidation
attribute. Most built-in server controls support event validation. When you create a custom control, you have to explicitly enable the attribute, because the attribute is not inheritable:
public class MyDropDownList: DropDownList
Each control implements event validation differently. In fact, text box validation doesn't do much because the control is designed to accept any data the user types. A drop-down list, instead, ensures that only listed values are posted.
Event validation raises another level of protection around posted data by preventing unknown data to be posted. A side effect of event validation is that dynamically created controls may not be allowed to post back or options dynamically and legitimately added to a drop-down list may raise an exception. There's no workaround for this issue, other than disabling the feature altogether. ASP.NET AJAX controls, in fact, run with event validation disabled.
Finally, it is worth noting that event validation can't be programmatically disabled for individual controls. The feature applies to all controls in a page, all pages in an application, or even all classes of a certain type used in pages or applications.