Hey I was using parametrized queries for my application which worked just fine but now (I don't know why) they aren't replaced anymore with the values...
So instead of running something like "SELECT [TABLE_NAME] FROM [MyDefinetelyExistingDatabase]"; it tries to execute "SELECT [TABLE_NAME] FROM [#targetDatabase]"; which, of course, will fail.
var dataBaseToGetTablesFrom = "MyDefinetelyExistingDatabase";
var results = new List<string>();
const string query = #"SELECT
[TABLE_NAME] AS tableName
FROM
[#targetDatabase].[INFORMATION_SCHEMA].[TABLES] ;";
using (var context = new ConnectionHandler(true))
{
if (context.Connection.State != ConnectionState.Open)
throw new ConnectionFailedException(context.Connection.State);
using (var command = new SqlCommand(query, context.Connection))
{
command.Parameters.AddWithValue("#targetDatabase", dataBaseToGetTablesFrom);
using (var reader = command.ExecuteReader())
{
if (!reader.HasRows)
return results.ToArray();
while (reader.Read())
results.Add(reader.GetString(0));
}
}
}
return results.ToArray();
I now tried different formats and things to add the parameters but it results in the same...
I don't want to do this by inserting the values into the query directly via string.Format eg but I want to have those parameters (which work properly at different places in the code (???) but not where I want.
In fact, I need to use parameters in every statement and must be able to address different databases by calling them like [DB].[Table-Schema].[Table]
[EDIT]
Hey guys, figured the problem some days ago and thought I share it with you.
As far as I have noticed, my problem at the whole was to try to replace the databasename and / or in some other examples, the table name as well.
So this won't work which makes clearly sense to me as the server can't prepare to execute a statement if it doesn't even know on which table it should work and therefore doesn't know anything about the structure etc.
So I changed my statements to fit my new knowledge and it worked as expected like a charm.
I don't know what ConnectionHandler is, but if that is your own code you can implement it with SqlConnectionStringBuilder which will allow you to use a variable to assign the InitialCatalog instead of putting the database name in the query. This would be preferable to dynamic sql which requires careful sanitization.
You would need dynamic sql for this something like.....
DECLARE #Sql NVARCHAR(MAX);
SET #Sql = N' SELECT [TABLE_NAME] AS tableName '
+ N' FROM ' + QUOTENAME(#targetDatabase) + N'.[INFORMATION_SCHEMA].[TABLES]'
Exec sp_executesql #Sql
Related
I'm writing a C# class library in which one of the features is the ability to create an empty data table that matches the schema of any existing table.
For example, this:
private DataTable RetrieveEmptyDataTable(string tableName)
{
var table = new DataTable() { TableName = tableName };
using var command = new SqlCommand($"SELECT TOP 0 * FROM {tableName}", _connection);
using SqlDataAdapter dataAdapter = new SqlDataAdapter(command);
dataAdapter.Fill(table);
return table;
}
The above code works, but it has a glaring security vulnerability: SQL injection.
My first instinct is to parameterize the query like so:
using var command = new SqlCommand("SELECT TOP 0 * FROM #tableName", _connection);
command.Parameters.AddWithValue("#tableName", tableName);
But this leads to the following exception:
Must declare the table variable "#tableName"
After a quick search on Stack Overflow I found this question, which recommends using my first approach (the one with sqli vulnerability). That doesn't help at all, so I kept searching and found this question, which says that the only secure solution would be to hard-code the possible tables. Again, this doesn't work for my class library which needs to work for arbitrary table names.
My question is this: how can I parameterize the table name without vulnerability to SQL injection?
An arbitrary table name still has to exist, so you can check first that it does:
IF EXISTS (SELECT 1 FROM sys.objects WHERE name = #TableName)
BEGIN
... do your thing ...
END
And further, if the list of tables you want to allow the user to select from is known and finite, or matches a specific naming convention (like dbo.Sales%), or belongs to a specific schema (like Reporting), you could add additional predicates to check for those.
This requires you to pass the table name in as a proper parameter, not concatenate or token-replace. (And please don't use AddWithValue() for anything, ever.)
Once your check that the object is real and valid has passed, then you will still have to build your SQL query dynamically, because you still won't be able to parameterize the table name. You still should apply QUOTENAME(), though, as I explain in these posts:
Protecting Yourself from SQL Injection in SQL Server - Part 1
Protecting Yourself from SQL Injection in SQL Server - Part 2
So the final code would be something like:
CREATE PROCEDURE dbo.SelectFromAnywhere
#TableName sysname
AS
BEGIN
IF EXISTS (SELECT 1 FROM sys.objects
WHERE name = #TableName)
BEGIN
DECLARE #sql nvarchar(max) = N'SELECT *
FROM ' + QUOTENAME(#TableName) + N';';
EXEC sys.sp_executesql #sql;
END
ELSE
BEGIN
PRINT 'Nice try, robot.';
END
END
GO
If you also want it to be in some defined list you can add
AND #TableName IN (N't1', N't2', …)
Or LIKE <some pattern> or join to sys.schemas or what have you.
Provided nobody has the rights to then modify the procedure to change the checks, there is no value you can pass to #TableName that will allow you to do anything malicious, other than maybe selecting from another table you didn’t expect because someone with too much access was able to create before calling the code. Replacing characters like -- or ; does not make this any safer.
You could pass the table name to the SQL Server to apply quotename() on it to properly quote it and subsequently only use the quoted name.
Something along the lines of:
...
string quotedTableName = null;
using (SqlCommand command = new SqlCommand("SELECT quotename(#tablename);", connection))
{
SqlParameter parameter = command.Parameters.Add("#tablename", System.Data.SqlDbType.NVarChar, 128 /* nvarchar(128) is (currently) equivalent to sysname which doesn't seem to exist in SqlDbType */);
parameter.Value = tableName;
object buff = command.ExecuteScalar();
if (buff != DBNull.Value
&& buff != null /* theoretically not possible since a FROM-less SELECT always returns a row */)
{
quotedTableName = buff.ToString();
}
}
if (quotedTableName != null)
{
using (SqlCommand command = new SqlCommand($"SELECT TOP 0 FROM { quotedTableName };", connection))
{
...
}
}
...
(Or do the dynamic part on SQL Server directly, also using quotename(). But that seems overly and unnecessary tedious, especially if you will do more than one operation on the table in different places.)
Aaron Bertrand's answer solved the problem, but a stored procedure is not useful for a class library that might interact with any database. Here is the way to write RetrieveEmptyDataTable (the method from my question) using his
answer:
private DataTable RetrieveEmptyDataTable(string tableName)
{
const string tableNameParameter = "#TableName";
var query =
" IF EXISTS (SELECT 1 FROM sys.objects\n" +
$" WHERE name = {tableNameParameter})\n" +
" BEGIN\n" +
" DECLARE #sql nvarchar(max) = N'SELECT TOP 0 * \n" +
$" FROM ' + QUOTENAME({tableNameParameter}) + N';';\n" +
" EXEC sys.sp_executesql #sql;\n" +
"END";
using var command = new SqlCommand(query, _connection);
command.Parameters.Add(tableNameParameter, SqlDbType.NVarChar).Value = tableName;
using SqlDataAdapter dataAdapter = new SqlDataAdapter(command);
var table = new DataTable() { TableName = tableName };
Connect();
dataAdapter.Fill(table);
Disconnect();
return table;
}
Below is the line of code where I truncate table records. The table value is coming from the front end. In my Veracode scan, it is showing SQL injection. How can I avoid this? I cannot create a stored procedure as the connection string is dynamic where I need to truncate this table. Is there another approach?
SqlCommand cmd = connection.CreateCommand();
cmd.Transaction = transaction;
cmd.CommandText = "TRUNCATE TABLE " + tablename;
cmd.ExecuteNonQuery();
You need dynamic sql:
string sql = #"
DECLARE #SQL nvarchar(150);
SELECT #SQL = 'truncate table ' + quotename(table_name) + ';'
FROM information_schema.tables
WHERE table_name = #table;
EXEC(#SQL);";
using (var connection = new SqlConnection("connection string here"))
using (var cmd = new SqlCommand(sql, connection))
{
cmd.Transaction = transaction;
cmd.Parameters.Add("#table", SqlDbType.NVarChar, 128).Value = tablename;
connection.Open();
cmd.ExecuteNonQuery();
}
This is one of very few times dynamic SQL makes things more secure, rather than less. Even better, if you also maintain a special table in this database listing other tables users are allowed to truncate, and use that rather than information_schema to validate the name. The idea of letting users just truncate anything is kind of scary.
Parametrized or not, you can make it only a little more secured in this case. Never totally secured. For this you need
create table TruncMapping in DB where you store
id guid
statement varchar(300)
your data will look like
SOME-GUID-XXX-YYY, 'TRUNCATE TABLE TBL1'
In your front end use a listbox or combobox with text/value like "Customer Data"/"SOME-GUID-XXX-YYY"
In your code use ExecuteScalar to execute Select statement from TruncMapping where id = #1 , where id will be parameterized GUID from combo value
Execute your truncate command using ExecuteNonQuery as you do now but with a retrieved string from previous call.
Your scan tool will most likely choke. If it is still thinking code is unsafe, you can safely point this as false positive because what you execute is coming from your secured DB. Potential attacker has no way to sabotage your "non-tuncatable tables" because they are not listed in TruncMapping tables.
You've just created multi-layered defense against sql injection.
here is one way to hide it from scanning tools
private const string _sql = "VFJVTkNBVEUgVEFCTEU=";
. . . .
var temp = new { t = tablename };
cmd.CommandText =
Encoding.ASCII.GetString(Convert.FromBase64String(_sql)) + temp.t.PadLeft(temp.t.Length + 1);
security by obscurity
I followed this answer,
How can I supply a List<int> to a SQL parameter?
Please see these questions of mine for understanding scenario,
How can I update Crate IDs of List of Fruits in single SQL query in c#
how can i update SQL table logic
What I am trying and not working
private void relate_fruit_crate(List<string> selectedFruitIDs, int selectedCrateID)
{
string updateStatement = "UPDATE relate_fruit_crate set CrateID = #selectedCrateID where FruitID = #selectedFruitIDs";
using (SqlConnection connection = new SqlConnection(ConnectionString()))
using (SqlCommand cmd = new SqlCommand(updateStatement, connection))
{
connection.Open();
cmd.Parameters.Add(new SqlParameter("#selectedCrateID", selectedCrateID.ToString()));
cmd.Parameters.Add(new SqlParameter("#selectedFruitIDs", String.Join(",",selectedFruitIDs.ToArray())));
cmd.ExecuteNonQuery();
}
}
My code runs without any error,
You need to use the IN keyword in your scenario. The problem is that the SqlCommand.Parameters pattern does not build the query itself, but calls a stored procedure on the database:
exec sp_executesql N'UPDATE relate_fruit_crate set CrateID = #selectedCrateID where FruitID in(''#selectedFruitIDs'')', N'#selectedCrateID nvarchar(1),#selectedFruitIDs nvarchar(5)', #selectedCrateID = N'1', #selectedFruitIDs = N'1,2'
This will not work as the array is escaped.
The workaround would be to either use a normal StringBuilder to create the query. (Warning! SQL Injection) or to call the query for each ID separately.
Maybe there's a way to do this with the SqlCommand.Parameters, but I could not find one.
OLD POST::
string updateStatement = "UPDATE relate_fruit_crate set CrateID IN ('#selectedCrateID') where FruitID = '#selectedFruitIDs'";
[....]
cmd.Parameters.Add(new SqlParameter("#selectedFruitIDs", String.Join("','",selectedFruitIDs.ToArray())));
and equals (=) query will only match a single value.
Multi-value parameter queries are a bit of a pain in TSQL. There are options like table-valued parameters, or "split" UDFs - otherwise... it is a bit tricky. You end up having to add multiple parameters (depending on the data), and change the query to suit. If I may suggest... a library like "dapper" may help you here - it is designed to make scenarios like this easy:
using Dapper; // at the top of your code file, to enable dapper
...
private void relate_fruit_crate(List<string> selectedFruitIDs, int selectedCrateID)
{
// note the slightly unusual "in" here (no paranethesis) - that is because
// dapper is going to do some voodoo...
using (SqlConnection connection = new SqlConnection(ConnectionString()))
{
connection.Open();
connection.Execute(
"UPDATE relate_fruit_crate set CrateID = #selectedCrateID where FruitID in #selectedFruitIDs",
new { selectedFruitIDs, selectedCrateID });
}
}
here "dapper" does all the work of figuring out how to express that in using multiple parameters, adding the correct number of parameters. It is also just much easier (in particular, look at how little work we did with commands and parameters; it handles readers nicely too).
Dapper is freely available from NuGet
I use ADO.NET to delete some data from DB like this:
using (SqlConnection conn = new SqlConnection(_connectionString))
{
try
{
conn.Open();
using (SqlCommand cmd = new SqlCommand("Delete from Table where ID in (#idList);", conn))
{
cmd.Parameters.Add("#idList", System.Data.SqlDbType.VarChar, 100);
cmd.Parameters["#idList"].Value = stratIds;
cmd.CommandTimeout = 0;
cmd.ExecuteNonQuery();
}
}
catch (Exception e)
{
//_logger.LogMessage(eLogLevel.ERROR, DateTime.Now, e.ToString());
}
finally
{
conn.Close();
}
}
That code executes without Exception but data wasn't deleted from DB.
When I use the same algorithm to insert or update DB everything is OK.
Does anybody know what is the problem?
You can't do that in regular TSQL, as the server treats #idList as a single value that happens to contain commas. However, if you use a List<int>, you can use dapper-dot-net, with
connection.Execute("delete from Table where ID in #ids", new { ids=listOfIds });
dapper figures out what you mean, and generates an appropriate parameterisation.
Another option is to send in a string and write a UDF to perform a "split" operation, then use that UDF in your query:
delete from Table where ID in (select Item from dbo.Split(#ids))
According to Marc's Split-UDF, this is one working implementation:
CREATE FUNCTION [dbo].[Split]
(
#ItemList NVARCHAR(MAX),
#delimiter CHAR(1)
)
RETURNS #IDTable TABLE (Item VARCHAR(50))
AS
BEGIN
DECLARE #tempItemList NVARCHAR(MAX)
SET #tempItemList = #ItemList
DECLARE #i INT
DECLARE #Item NVARCHAR(4000)
SET #tempItemList = REPLACE (#tempItemList, ' ', '')
SET #i = CHARINDEX(#delimiter, #tempItemList)
WHILE (LEN(#tempItemList) > 0)
BEGIN
IF #i = 0
SET #Item = #tempItemList
ELSE
SET #Item = LEFT(#tempItemList, #i - 1)
INSERT INTO #IDTable(Item) VALUES(#Item)
IF #i = 0
SET #tempItemList = ''
ELSE
SET #tempItemList = RIGHT(#tempItemList, LEN(#tempItemList) - #i)
SET #i = CHARINDEX(#delimiter, #tempItemList)
END
RETURN
END
And this is how you could call it:
DELETE FROM Table WHERE (ID IN (SELECT Item FROM dbo.Split(#idList, ',')));
I want to give this discussion a little more context. This seems to fall under the topic of "how do I get multiple rows of data to sql". In #Kate's case she is trying to DELETE-WHERE-IN, but useful strategies for this user case are very similar to strategies for UPDATE-FROM-WHERE-IN or INSERT INTO-SELECT FROM. The way I see it there are a few basic strategies.
String Concatenation
This is the oldest and most basic way. You do a simple "SELECT * FROM MyTable WHERE ID IN (" + someCSVString + ");"
Super simple
Easiest way to open yourself to a SQL Injection attack.
Effort you put into cleansing the string would be better spent on one of the other solutions
Object Mapper
As #MarcGravell suggested you can use something like dapper-dot-net, just as Linq-to-sql or Entity Framework would work. Dapper lets you do connection.Execute("delete from MyTable where ID in #ids", new { ids=listOfIds }); Similarly Linq would let you do something like from t in MyTable where myIntArray.Contains( t.ID )
Object mappers are great.
However, if your project is straight ADO this is a pretty serious change to accomplish a simple task.
CSV Split
In this strategy you pass a CSV string to SQL, whether ad-hoc or as a stored procedure parameter. The string is processed by a table valued UDF that returns the values as a single column table.
This has been a winning strategy since SQL-2000
#TimSchmelter gave a great example of a csv split function.
If you google this there are hundreds of articles examining every aspect from the basics to performance analysis across various string lengths.
Table Valued Parameters
In SQL 2008 custom "table types" can be defined. Once the table type is defined it can be constructed in ADO and passed down as a parameter.
The benefit here is it works for more scenarios than just an integer list -- it can support multiple columns
strongly typed
pull string processing back up to a layer/language that is quite good at it.
This is a fairly large topic, but Table-Valued Parameters in SQL Server 2008 (ADO.NET) is a good starting point.
I have a c# application (2008) that gets data from sql server (2005).
I have a view in sql server that prepares data for display, something like this (simplified):
select Places.Name as [Location], Parts.Name as [Part Name]
from Places inner join Parts
on Places.Id=Parts.Location
I have to filter this with "where" statement that is built in code and is like:
where (Places.Id=1 or Places.Id=15) and
(Parts.Id=56 or Parts.Id=8 or Parts.Id=32)
I can of course keep the basic select statement in my code, but i likw to have things defined only in one place :) and the question is if there is any way to get the select statement behind the view in sql server? Or to get the contents of stored procedure?
Thanks a lot!
Take a look at Information Schema View, you may find your solution.
Using the information schema views as jani suggested is one option.
Another is using the sp_helptext system stored procedure. sp_helptext YourView or sp_helptext YourStoredProcedure gets you the entire object definition.
You can find more information about the at sp_helptext system stored procedure here.
If you want a stored procedure to execute your query (and combining your basic query string, with your where clause), you can accomplish this by using the following code:
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
string selectCommand = "EXEC sp_YourStoredProcedure #whereClause";
SqlCommand command = new SqlCommand(selectCommand, connection);
command.Parameters.Add("#whereClause", System.Data.SqlDbType.NVarChar);
command.Parameters["#whereClause"] = whereClause;
using (SqlDataReader reader = command.ExecuteReader())
{
while (reader.NextResult())
{
string location = reader.GetString(0);
string partName = reader.GetString(1);
// do something
}
}
connection.Close();
}
Edit: Example of dynamic stored procedure:
CREATE PROCEDURE sp_YourStoredProcedure
(
#whereClause NVARCHAR(MAX)
)
AS
BEGIN
DECLARE #sql AS NVARCHAR(MAX)
SET #sql = N'
select Places.Name as [Location], Parts.Name as [Part Name]
from Places inner join Parts
on Places.Id=Parts.Location '
+ #whereClause
EXEC sp_executesql #sql
END