October 2004 - Posts

SQL Injection - Are parameterized queries safe?

After listening to Joe Stagner on .NET Rocks, I was surprised at the response with regard to his statement that he is able to perform a SQL injection even with parameterized queries. Why was I surprised? Well when he made that statement immediately I could think of scenarios where this was possible. More statements made me feel that many people might not realize that this could be a very real problem so I have decided to share some of these scenarios, for this post I will keep it simple and start with the most obvious.

One of the more frowned on practices when writing a stored procedure is the use of dynamic SQL. Be that as it may, I still come across more than a fair share of dynamic SQL and here in lies a potential vulnerability. To demonstrate this I provide the following procedure.

create proc VulnerableDynamicSQL(@userName nvarchar(25))
as
  declare @sql nvarchar(255)
  set @sql = 'select * from users where UserName = '''
    + @userName + ''''
  exec sp_executesql @sql

go

Obviously this procedure is contrived to make the point and not to do anything useful and definitely would not warrant the use of dynamic SQL. The unsuspecting programmer that is calling this procedure from .NET code might think do something like the following.

oCmd.CommandText = "VulnerableDynamicSQL";
oCmd.CommandType = CommandType.StoredProcedure;
oCmd.Parameters.Add( "@userName", strUserName);
oCon.Open();
string result = (string)oCmd.ExecuteScalar();
oCon.Close();

Feeling perfectly safe with the above code, the .NET programmer has written a secure piece of code. But unfortunately this code is just as susceptible to a SQL injection, albeit not at the .NET code level. For example the following user input could be used to exploit the above vulnerability.

';drop table users --

This input from the user would result in the following SQL being executed:

select * from users where UserName = '';drop table users --'

Again I have chosen the above for simplicity and obviously this would require adequate permissions to be successful. This does not make this sample any less significant, had the procedure been a procedure used to authenticate users of a system; only slightly more complex injection would be required to bypass the authentication.

Can dynamic SQL be done more securely? The answer to that is a definite ‘YES’, firstly you should always consider if you require dynamic SQL normally there is an alternate solution that does not require dynamic SQL. However if you have exhausted all possibilities and the only recourse is dynamic SQL then there is a safer way to us it. Just like we are able to use parameters for our SQL statements from within .NET, so can you use parameters for your dynamic SQL. The following procedure is a reimplementation using parameterized dynamic SQL.

create proc SaferDynamicSQL(@userName nvarchar(25))
as
declare @sql nvarchar(255)
set @sql = 'select * from users where UserName = @p_userName'
exec sp_executesql @sql,
  N'@p_userName nvarchar(25)',
  @p_userName = @userName

go

As you can see in this case I have defined a parameter to be used in the dynamic SQL statement called @p_userName. This parameter is defined as part of the call to sp_executesql and its value set in the same call.

Don’t make the mistake of thinking that security alone will prevent users from performing a successful SQL injection. It might prevent the user from dropping your tables, but you will still be open to many other forms of attack. When ever you concatenate strings to build SQL statements think carefully of the potential implications. Security requires a mindset where you are always questioning the implications of your decisions and even questioning things you might not have been responsible for.

And most importantly: Always validate user input.

CWaitCursor for .NET

Being mostly a middle tier type I have not done much front end UI stuff for quite sometime. But lately I have been doing some Windows Forms development and I have been making extensive use of the following piece of code that I developed years ago.

class CursorManager : IDisposable
{
  private bool _disposed = false;
  private Control _owner;
  private Cursor _prevCursor;
  public CursorManager(Cursor cursor) : this(null, cursor)
  {
  }

  public CursorManager(Control owner, Cursor cursor)
  {
    _owner = owner;
    if ( _owner != null )
    {
      _prevCursor = _owner.Cursor;
      _owner.Cursor = cursor;
    }
    else
    {
      _prevCursor = Cursor.Current;
      Cursor.Current = cursor;
    }
  }

  ~CursorManager()
  {
    Dispose();
  }

  public void Dispose()
  {
    if (!_disposed)
    {
      if ( _owner != null )
        _owner.Cursor = _prevCursor;
      else
        Cursor.Current = _prevCursor;

      _disposed =
true;
    }

    GC.SuppressFinalize(this);
  }
}

as elegant as the C++ solution. The C# using statement brings some elegance to this technique, while the current VB.NET syntax is not quite so sleek the VB.NET 2.0 will provide the same elegance as the C# syntax.

C#

using (new CursorManager(this, Cursors.WaitCursor))
{
 
DoLongOperation();
}

VB.NET

Dim waitCursor As CursorManager
Try
 
waitCursor = New CursorManager(Me, Cursors.WaitCursor)
  DoLongOperation()
Finally
 
If Not waitCursor Is Nothing Then waitCursor.Dispose()
End Try

Performance - Loading XML Documents

With my focus on .NET technologies one of the things that I find myself doing very often is processing XML data in some form or the other. This is one of the areas that the .NET class library provides numerous options and while these options are well documented I still find many developers not fully aware of the potential impact that there choice of XML processing technology could have on the scalability of the software being developed. To that end I have chosen 5 alternatives to loading an XML document into memory for further processing.

  1) XmlDocument class
  2) Untyped DataSet
  3) Typed DataSet
  4) XmlSerializer
  5) XmlTextReader

For each of these I benchmarked the time required to create an in-memory representation of the XML document. For the XmlDocument I used the following code

XmlDocument dom = new XmlDocument();
dom.Load("c:\\orders.xml");

The untyped DataSet was equally simple

  DataSet ds = new DataSet();
  ds.ReadXml("c:\\orders.xml");

For the typed DataSet I created a XSD that represented the XML Document to be loaded from that I generated the typed DataSet and used the following code to load the DataSet

  DSOrders ds = new DSOrders();
  ds.ReadXml("c:\\orders.xml");

Then for the XmlSerializer and the XmlTextReader I needed to build a set of classes that could be populated with the data from the XML Document. For this purpose I created the following classes to represent the data.

[Serializable(), XmlRoot("data")]
public class Data
{
  [XmlElement()]
 
public Order[] orders;
}

[Serializable()]
public class Order
{
  [XmlAttribute()]
public string OrderID;
 
[XmlAttribute()]public string CustomerID;
  [XmlAttribute()]
public string EmployeeID;
  [XmlAttribute()]
public string OrderDate;
  [XmlAttribute()]
public string RequiredDate;
  [XmlAttribute()]
public string ShippedDate;
  [XmlAttribute()]
public string ShipVia
  [XmlAttribute()]
public string Freight;
  [XmlAttribute()]
public string ShipName;
  [XmlAttribute()]
public string ShipAddress;
  [XmlAttribute()]
public string ShipCity;
  [XmlAttribute()]
public string ShipPostalCode;
  [XmlAttribute()]
public string ShipCountry;
}

The above classes represent a collection of orders and as you might have guessed by now the data in the XML document is derived from the Northwinds Orders table. With the classes defined using the XmlSerializer was a simple matter as the following code snip shows:

  XmlSerializer xmlser = new XmlSerializer(typeof(Data));
  StreamReader rdr =
new StreamReader("c:\\orders_small.xml");
  Data d = (Data)xmlser.Deserialize(rdr);

And then there was the more verbose code using the XmlTextReader to load the XML data into instances of the Order class and add them to the Data class’ orders collection. For brevity I will not include that code here.

Once all the code was written I put the code through my benchmarking application. I ran each test twice first with a XML document containing 860 records and a second time with a smaller set of 10 records. Since the results where very similar I will only show the results for the larger set. The following graph shows the median time taken for each of the techniques.

Test

Time (Seconds)

XmlDocument

0.193951745263714

Untyped DataSet

0.435501439428754

Typed DataSet

0.129763140287383

XmlSerializer

0.087618677792848

XmlTextReader

0.0392390653001988

There is of course on catch with the XmlSerializer which is not depicted in the above results and that is the initial cost of instantiation. When an instance of XmlSerializer is created for a particular type a temporary assembly is created which is dedicated to performing the serialization and de-serialization of objects of that type. Since this is a once off cost I ignored this overhead and while less significant I also did the same for the other tests. Only the XmlTextReader solution had no initial setup costs.

Conclusion

For tasks where the XML schema is known upfront I feel that the XmlSerializer gives the best balance between lines of code written and performance. While the typed DataSet is a very good performer it is much less flexible than the XmlSerializer. The XmlDocument is infinitely more flexible but you pay for that in performance and memory consumption even though I ensured that all the tests loaded the entire XML into a memory structure the XmlDocument was significantly more expensive in terms of memory consumption, but I leave that for a later post.

With its short comings and all, I am a big fan of the XmlSerializer and through clever use of the XML Serialization attributes find that I can manipulate most XML document structures.

For the tests run I did not make any attempts to optimize the code that was executed, especially in the case of the XmlTextReader where I took a very straight forward approach which could easily have been improved especially with regards to memory consumption.