C# and SQL Injection Attacks

Recently I had a task to create MS SQL tables with dynamic names and my first thought was ok, I pass this as a parameter. But I was wrong, MS SQL (as of today) does not allow parameters as table names.

Note: One can argue that dynamic table names are bad programming practices, but let’s assume that we cannot go without it. The same principle applies for other dynamic queries. The purpose of this post is to demonstrate how to counter against SQL Injection if you have to use dynamic table names.

So, my option was string concatenation on the SQL statement. For example:

  1. sql = $"SELECT [id], [category] FROM [Client_{clientName}] ORDER BY [id]";

The problem with the code above is SQL Injection attacks. If the variable clientName comes directly from the user, or anything that someone can control (for example configuration file), one can easily run unintended SQL command. These unintended statements can be simply to request hidden information or execute malicious code on the server.

Let’s take an example. Assume we have a table called Client_A with the following columns and data:

Id    category
 1    white                                             
 2    yellow                                            
 3    green                                             
 4    blue                                              
 5    red                                               
 6    black                                             
 7    magenta 

Putting the above statement in a C# application will look like:

  1. clientName = "A";
  2.  
  3. cmd.CommandText = $"SELECT [id], [category] FROM [Client_{clientName}]";
  4. cmd.CommandType = System.Data.CommandType.Text;
  5.  
  6. cnx.Open();
  7. using (var rdr = cmd.ExecuteReader())
  8. {
  9.   while (rdr.Read())
  10.   {                            
  11.      Console.WriteLine($"id = {rdr.GetInt32(rdr.GetOrdinal("id"))},\tcategory = {rdr.GetString(rdr.GetOrdinal("category"))}");
  12.   }
  13. }

Once the code above is executed, it returns the following result:

Result set

Now, let’s assume that the value for variable clientName is changed to "A]; insert into [client_a] (id, category) values (7, 'magenta');". Now, once the code above is executed, it returns the same data but it also adds a new entry in the table. Thus:

SELECT * FROM [dbo].[Client_A];

returns:

Id    category
 1    white                                             
 2    yellow                                            
 3    green                                             
 4    blue                                              
 5    red                                               
 6    black                                             
 7    magenta                                           

So, how can we prevent such attacks? The idea is to first check for the table if it exists and then perform our query. To check if the table exists we can do something like the code below.

SELECT 1 FROM [sys].[TABLES] WHERE [name] = @tableName

Where @tableName is an SQL parameter. If the code above returns 1, then we are certain that the table exists and there are no additional code at the end of our variable. If we get a value of 0, then we may want to log the event and trigger some warnings. The snippet below shows how this is done in C#.

  1. private static bool IsTableNameValid(string tableName)
  2. {
  3.     var result = false;
  4.     using (var cnx = new SqlConnection(cnxString))
  5.     {
  6.         using (var cmd = cnx.CreateCommand())
  7.         {
  8.             cmd.CommandText = $"SELECT 1 FROM [sys].[tables] WHERE [name] = @tableName";
  9.             cmd.CommandType = System.Data.CommandType.Text;
  10.             cmd.Parameters.Add(new SqlParameter()
  11.             {
  12.                 ParameterName = "@tableName",
  13.                 SqlDbType = System.Data.SqlDbType.VarChar,
  14.                 Size = 100,
  15.                 SqlValue = $"Client_{tableName}",
  16.                 Direction = System.Data.ParameterDirection.Input
  17.             }) ;
  18.  
  19.             cnx.Open();
  20.             int rc = 0;
  21.             var obj = cmd.ExecuteScalar();
  22.             if (obj != DBNull.Value && obj != null)
  23.             {
  24.                 rc = (int)obj;
  25.             }
  26.  
  27.             result = rc == 1;
  28.         }
  29.     }
  30.  
  31.     return result;
  32. }

You can browse and download the code used above from github.