Reputation: 21204
I need to build a SQL Server 2016 script using string concatenation from within C# (the script will be executed via ADO.NET). I cannot use query parameters, which I would normally do, as the script is more of a setup script and contains non-parameterizable statements.
What's the way to go to escape names such that:
"ALTER DATABASE " + Escape(databaseName) + " ADD ..."
is not vulnerable to SQL injection? How to implement Escape
? Currently I'm using square brackets around all names, however, that is not enough of course...
Upvotes: 2
Views: 1582
Reputation: 118
If you are on regular .NET, use SqlCommandBuilder.QuoteIdentifier:
Given an unquoted identifier in the correct catalog case, returns the correct quoted form of that identifier. This includes correctly escaping any embedded quotes in the identifier.
https://learn.microsoft.com/en-us/dotnet/api/system.data.sqlclient.sqlcommandbuilder.quoteidentifier
Upvotes: 2
Reputation: 45771
You can use the QUOTENAME SQL Server built-in function to achieve this.
As it looks very much like you're building up the script in C#, prior to executing it in SQL, you'll probably want to have a C# function that makes a trip down to the database to do this, perhaps also storing the resultant values in a Dictionary<string, string>
to eliminate round-trips for strings that have already been quoted.
For example:
private Dictionary<string, string> _quotedNames = new Dictionary<string, string>();
private string GetSqlQuotedName(string name)
{
if (!_quotedNames.ContainsKey(name))
{
_quotedNames[name] = GetSqlQuotedNameFromSqlServer(name);
}
return _quotedNames[name];
}
private string GetSqlQuotedNameFromSqlServer(string name)
{
/// Code here to use your Data access method of choice to basically execute
/// SELECT QUOTENAME(name) and return it
}
In fact, just to show this using the classes in the System.Data.SqlClient namespace, here's a class that performs this behaviour, when given a connection string to use to talk to SQL Server:
public class SqlNameEscaper
{
private Dictionary<string, string> _quotedNames = new Dictionary<string, string>();
private string _connectionString = string.Empty;
public SqlNameEscaper(string connectionString)
{
_connectionString = connectionString;
}
public string GetSqlQuotedName(string name)
{
if (!_quotedNames.ContainsKey(name))
{
_quotedNames[name] = GetSqlQuotedNameFromSqlServer(name);
}
return _quotedNames[name];
}
private string GetSqlQuotedNameFromSqlServer(string name)
{
using (var connection = new SqlConnection(_connectionString))
{
connection.Open();
using (var command = new SqlCommand("SELECT QUOTENAME(@name)", connection))
{
command.Parameters.AddWithValue("@name", name);
var result = command.ExecuteScalar();
return result.ToString();
}
}
}
}
This can then be called by doing this:
var sne = new SqlNameEscaper(@"CONNECTION_STRING_HERE");
var bracket = sne.GetSqlQuotedName("[");
Or, in the context of your example:
var sqlNameEscaper = new SqlNameEscaper(@"CONNECTION_STRING_HERE");
var text = "ALTER DATABASE " + sqlNameEscaper.GetSqlQuotedName(databaseName) + " ADD ...";
There is also a question on dba.stackexchange.com that's worth having a read of on this very subject: Should we still be using QUOTENAME to protect from injection attacks?
Upvotes: 2