I'm trying to follow best practice (and also remove Visual Studio Code Analysis warnings) by using parameters when dropping a SQL Server index.
Not using parameters works fine:
string tableName = "dbo.TableName";
SqlCommand sqlCommand = new SqlCommand("DROP INDEX Blah ON " + tableName);
sqlCommand.Connection = sqlConnection;
sqlCommand.ExecuteNonQuery();
However, when I try to use a parameter I get an error
Incorrect syntax near '#TableName'.
Code:
string tableName = "dbo.TableName";
SqlCommand sqlCommand = new SqlCommand("DROP INDEX Blah ON #TableName");
sqlCommand.Parameters.Add(new SqlParameter("TableName", tableName));
sqlCommand.Connection = sqlConnection;
sqlCommand.ExecuteNonQuery();
What am I doing wrong?
You are doing nothing wrong. Parameters cannot be used to replace identifiers -- column names/aliases, table names/aliases, schema names, and database names. They also cannot be used to replace function names or operators or keywords.
That is a long list. They can be used to replace constants in the query.
I guess the way to remember this is that the parameterized query can be pre-compiled. In order to compile a query, all object references need to be resolved -- so the values cannot be provided by parameters.
You have already solved the problem by putting the table in the string. You can use quotename() to help protect against injection (see here).
DROP INDEX is a DDL statement, most DDL statements don't accept parameterized values. The best you can do is use dynamically constructed SQL and escape the table name using QUOTENAME
string tableName = "dbo.TableName";
string sql = #"
declare #sql nvarchar(500)
set #sql = N'DROP INDEX Blah ON ' + QUOTENAME(#TableName)
exec sp_executesql #sql
";
SqlCommand sqlCommand = new SqlCommand("");
sqlCommand.Parameters.Add("#TableName", SqlDbType.NVarChar, 50).Value = tableName;
sqlCommand.Connection = sqlConnection;
sqlCommand.ExecuteNonQuery();
I also updated your code to use the more "normal" way to add a parameter, explicitly setting the datatype of the parameter.
Related
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
In SQL:
INSERT INTO [dbo].[tblFiles]
([FullFilePath]
,[LastModified])
VALUES
('P:\test\test.csv',
null)
This will store the full path in the database :)
However, I need to do this in code.
SqlConnection connection = new SqlConnection(DatabaseHelper.ConnectionString);
connection.Open();
SqlCommand command = new SqlCommand( "stpInsertFile", connection);
command.CommandType = System.Data.CommandType.StoredProcedure;
command.Parameters.Add(new SqlParameter("#filepath", System.Data.SqlDbType.VarChar));
command.Parameters["#filepath"].Value = article.FullFilePath;
command.Parameters.Add(new SqlParameter( "#LastModified", System.Data.SqlDbType.DateTime));
command.Parameters["#LastModified"].Value = article.LastModified;
int newArticleID = Convert.ToInt32((decimal)command.ExecuteNonQuery());
command.Dispose();
connection.Close();
connection.Dispose();
return newArticleID;
With this all I get is 'P' in the full path column.
So I tried using LINQ and got the same result.
public int InsertArticleUsingLINQ(tblFile article) {
DataClassesDataContext context = new DataClassesDataContext();
tblFile newFileEntry = new tblFile();
newFileEntry.FullFilePath = article.FullFilePath;
newFileEntry.LastModified = article.LastModified;
context.tblFiles.InsertOnSubmit(newFileEntry);
context.SubmitChanges();
return newFileEntry.ID;
}
I'm not doing anything with the string before passing it to the database insert functions. I read that you need to escape the backslash but it seems to be escaping on the quote. Also read that you need an # symbol before the sql but how do you add this to a parameter?
warning: since you didn't share the stored procedure code this is just a wild guess.
did you set the size of the #filePath parameter in the definition of your stored procedure?
if you declare it as:
create procedure stpInsertFile
#filepath varchar,
#LastModified datetime
as
...
then you parameter is created as varchar(1) because of the default behaviour of varchar datatype and that would produce the result you get.please check reference documentation for char and varchar datatype on ms website.
if stpInsertFile is a Stored Procedure you will have to set in your code:
...
command.CommandType = CommandType.StoredProcedure;
else you have to set the query string in your command:
SqlCommand command = new SqlCommand( "INSERT INTO [dbo].[tblFiles] ([FullFilePath] [LastModified]) VALUES (#filepath,#LastModified)", connection);
...
Of the above options, the ADO code did not copy the full path, even with manually adding quotes. All I ever got was one character in the database.
On further inspection, the parameter was of type Varchar(1) !!! I changed this to Varchar(255) an it worked.
Note, the LINQ to SQL function did insert the full path as this was not using the Stored Procedure.
Apologies for the mistake.
How can I achieve something like following
SqlConnection connection = new SqlConnection("CNStr");
SqlCommand Command = connection.CreateCommand();
command.CommandText = "DECLARE #find varchar(30) = 'Test'; ";
command.ExecuteNonQuery();
command.CommandText = "select * from Some_View_Which_Has_Used_That_Variable_In_ItsWhere_Condition"
var result = command.ExecuteNonQuery();
command.Dispose();
connection.Dispose();
I want to declare some per connection variable, and then use them everywhere, in views, functions and so on.
How can I achieve this goal without using temp table and contextId ?
Thanks in advance
You can not use DECLARE in your sql statement.
It is not even valid Data Manipulation Language statement. It doesn't even effect your Some_View_Which_Has_Used_That_Variable_In_ItsWhere_Condition at all.
That's why it looks pointless to me. If you really want to declare #find, you can write in your view as manual.
And use using statement to dispose your SqlConnection and SqlCommand.
For example;
using(SqlConnection con = new SqlConnection(CNStr))
using(SqlCommand command = con.CreateCommand())
{
//
}
The only obvious per-connection object that persists for longer than a single batch (i.e. your ExecuteXXX calls) would be a temp table:
command.CommandText = "CREATE TABLE #Settings (find varchar(30) not null);
INSERT INTO #Settings (find) VALUES ('Test')";
command.ExecuteNonQuery();
command.CommandText = "select * from Some_View where
SomeColumn in (select find from #Settings)"
var result = command.ExecuteReader();
All variables (whether scalar or table-valued) have lifetimes that end at the end of their respective batches.
A few other things can be set at the connection/session level (e.g. CONTEXT_INFO) but they tend to have fixed, limited data types.
I iterate over an external source and get a list of strings. I then insert them into the DB using:
SqlCommand command = new SqlCommand(commandString, connection);
command.ExecuteNonQuery();
Where commandString is an insert into command. i.e.
insert into MyTable values (1, "Frog")
Sometimes the string contains ' or " or \ and the insert fails.
Is there an elegant way to solve this (i.e. #"" or similar)?
Parameters.
insert into MyTable values (#id, #name)
And
int id = 1;
string name = "Fred";
SqlCommand command = new SqlCommand(commandString, connection);
command.Parameters.AddWithValue("id", id);
command.Parameters.AddWithValue("name", name);
command.ExecuteNonQuery();
Now name can have any number of quotes and it'll work fine. More importantly it is now safe from sql injection.
Tools like "dapper" (freely available on NuGet) make this easier:
int id = 1;
string name = "Fred";
connection.Execute("insert into MyTable values (#id, #name)",
new { id, name });
You should look into using parameterized queries. This will allow you insert the data no matter the content and also help you avoid possible future SQL injection.
http://csharp-station.com/Tutorial/AdoDotNet/Lesson06
http://www.c-sharpcorner.com/uploadfile/puranindia/parameterized-query-and-sql-injection-attacks/
I'm writing a method to insert a Student into a local SQL database that contains a table with information about Students:
public void AddStudent(string name, string teachName, string pass)
{
string dbfile = new System.IO.FileInfo(System.Reflection.Assembly.GetExecutingAssembly().Location).DirectoryName + "\\Logo.sdf";
SqlCeConnection connection = new SqlCeConnection("Data Source=" + dbfile + "; Password = 'dbpass2011!'");
connection.Open();
SqlCeTransaction transaction = connection.BeginTransaction();
SqlCeCommand command = connection.CreateCommand();
command.Transaction = transaction;
command.CommandText = "INSERT INTO Students VALUES ('#name', '#id', '#pass', '#tname')";
command.Parameters.Add("#name", name);
command.Parameters.Add("#id", this.ID);
command.Parameters.Add("#pass", MD5Encrypt.MD5(pass));
command.Parameters.Add("#tname", teachName);
command.Prepare();
command.ExecuteNonQuery();
transaction.Commit();
connection.Dispose();
connection.Close();
}
Whenever I use this, it never inserts the data to the table when I look at the contents of the Students table in the database. Originally I had this return an int so I could see how many rows it affected, which it always returned 1, so I know it's working.
I've looked for answers to this, and the answer to similar questions was that the person asking was looking at the wrong .sdf file. I've made sure that I'm looking at the right file.
Any feedback would be much appreciated!
command.CommandText = "INSERT INTO Students VALUES ('#name', '#id', '#pass', '#tname')";
You should remove the extra single quotes - this should be:
command.CommandText = "INSERT INTO Students VALUES (#name, #id, #pass, #tname)";
Also I am not sure why you open a transaction for a single insert - that is also not needed.
You don't need to put single quote to parametrized query, in case of parametrized query the whole data will be parsed as required,
command.CommandText = "INSERT INTO Students VALUES (#name, #id, #pass, #tname)";
Also, its better to set parameter type, size and value explicitly as below:
SqlCeParameter param = new SqlCeParameter("#name", SqlDbType.NVarChar, 100);
param.Value = name; // name is a variable that contain the data of name field
//param.Value = 'Jhon Smith'; //Directly value also can be used
Hope this would be helpful, thanks for your time.
There is most likely an exception being raised in your code; you need to add a try/catch handler and/or debug the application to figure out exactly what is happening.
However, there are at least two issues with your code:
The prepare statement requires the data types of the parameters. From the MSDN documentation:
Before you call Prepare, specify the data type of each parameter in the statement to be prepared. For each parameter that has a variable-length data type, you must set the Size property to the maximum size needed. Prepare returns an error if these conditions are not met.
You need to close the connection before disposing it (this won't affect the insert, however).