I have a utility that generates query strings, but the static code analyzers (and my coworkers) are complaining because of risk of "SQL Injection Attack".
Here is my C# code
public static string[] GenerateQueries(string TableName, string ColumnName)
{
return new string[] {
"SELECT * FROM " + TableName,
"SELECT * FROM " + TableName + " WHERE 1=2",
"SELECT * FROM " + TableName + " WHERE [" + TableName + "Id] = #id",
"SELECT * FROM " + TableName + " WHERE [" + TableName + "Id] = IDENT_CURRENT('" + TableName + "')",
"SELECT * FROM " + TableName + " WHERE [" + ColumnName + "] = #value"
};
}
In the code I always call it only with constant strings, such as
var queryList = GenerateQueries("Person", "Name");
Is there any way to rewrite this function so that it is "safe"? For example, if I were using C instead of C#, I could write a macro to generate the strings safely.
At the moment, the only choice I have is to copy/paste this block of SELECT statements for every single table, which is ugly and a maintenance burden.
Is copy/paste really my only option?
EDIT:
Thank you for the replies, esp William Leader. Now I see that my question is wrong-headed. It isn't just the fact that I am concatenating query strings, but also storing them in a variable. The only proper way to do this is to construct the SqlDataAdapter using a constant such as,
var adapter = new SqlDataAdapter("SELECT * FROM PERSON");
There is no other choice. So yes, there will be a lot of copy/paste. I'm starting to regret not using EF.
I was shocked at first, but on reflection this is no different than having an SQL statement already in your code that looks like this:
"SELECT * FROM Person"
We do that kind of thing all the time.
IF
There's an important caveat here. That only remains true if you can control how the function is called. So if this method is a private member of a data layer class somewhere, you might be okay. But I also wonder how useful this really is. It seems like you're not saving much over what you'd get from just writing the queries.
Additionally, it's not good to be in the habit of ignoring your static analysis tools. Sometimes they give you stuff you just know is wrong, but you change it anyway so that when they do find something important you're not conditioned to ignore it.
What your Code analyser is telling you is that you should most likely be calling a procedure with some parameters instead of sending SQL across the wire.
It does not mater a single bit whether or not you use a macro to generate your SQL statements, if you are sending raw SQL across the wire you are open to SQL Injection Attacks
Sending SQL commands to an endpoint making a non sanctioned call. If we fire up a network packet sniffer, we can see that you have a database configured to allow SQL commands to be sent, so we can inject illegal SQL into the system
You could still rely on a single procedure for calling your updates, but if you elect to move to procedures, why would you want to do that?
EDITED to provide an example
create PROC sp_CommonSelectFromTableProc #tableName varchar(32)
AS
-- code to check the tableName parameter does not contain SQL and/or is a valid tableName
-- your procedure code here will probable use
-- exec mydynamicSQLString
-- where mydynamicSQLString is constructed using #tableName
END;
or maybe a table specific procedure
create PROC sp_SelectFromSpecificTableProc
AS
SELECT * FROM SpecificTable
END;
What is important to remember is that SQL injection is independent of the technology used for the underlying application.
It is just overt when the application contains such constructs as
return new string[] {
"SELECT * FROM " + TableName,
"SELECT * FROM " + TableName + " WHERE 1=2",
"SELECT * FROM " + TableName + " WHERE [" + TableName + "Id] = #id",
"SELECT * FROM " + TableName + " WHERE [" + TableName + "Id] = IDENT_CURRENT('" + TableName + "')",
"SELECT * FROM " + TableName + " WHERE [" + ColumnName + "] = #value"
SQL Injection must be addressed at both ends of the data channel.
Here is a pretty good starting point for understanding how to mitigate for SQL Injection attacks
Related
I have a query to insert a row into a table, which has a field called ID, which is populated using an AUTO_INCREMENT on the column. I need to get this value for the next bit of functionality, but when I run the following, it always returns 0 even though the actual value is not 0:
MySqlCommand comm = connect.CreateCommand();
comm.CommandText = insertInvoice;
comm.CommandText += "\'" + invoiceDate.ToString("yyyy:MM:dd hh:mm:ss") + "\', " + bookFee + ", " + adminFee + ", " + totalFee + ", " + customerID + ")";
int id = Convert.ToInt32(comm.ExecuteScalar());
According to my understanding, this should return the ID column, but it just returns 0 every time. Any ideas?
EDIT:
When I run:
"INSERT INTO INVOICE (INVOICE_DATE, BOOK_FEE, ADMIN_FEE, TOTAL_FEE, CUSTOMER_ID) VALUES ('2009:01:01 10:21:12', 50, 7, 57, 2134);last_insert_id();"
I get:
{"You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'last_insert_id()' at line 1"}
MySqlCommand comm = connect.CreateCommand();
comm.CommandText = insertStatement; // Set the insert statement
comm.ExecuteNonQuery(); // Execute the command
long id = comm.LastInsertedId; // Get the ID of the inserted item
[Edit: added "select" before references to last_insert_id()]
What about running "select last_insert_id();" after your insert?
MySqlCommand comm = connect.CreateCommand();
comm.CommandText = insertInvoice;
comm.CommandText += "\'" + invoiceDate.ToString("yyyy:MM:dd hh:mm:ss") + "\', "
+ bookFee + ", " + adminFee + ", " + totalFee + ", " + customerID + ");";
+ "select last_insert_id();"
int id = Convert.ToInt32(comm.ExecuteScalar());
Edit: As duffymo mentioned, you really would be well served using parameterized queries like this.
Edit: Until you switch over to a parameterized version, you might find peace with string.Format:
comm.CommandText = string.Format("{0} '{1}', {2}, {3}, {4}, {5}); select last_insert_id();",
insertInvoice, invoiceDate.ToString(...), bookFee, adminFee, totalFee, customerID);
Use LastInsertedId.
View my suggestion with example here: http://livshitz.wordpress.com/2011/10/28/returning-last-inserted-id-in-c-using-mysql-db-provider/
It bothers me to see anybody taking a Date and storing it in a database as a String. Why not have the column type reflect reality?
I'm also surprised to see a SQL query being built up using string concatenation. I'm a Java developer, and I don't know C# at all, but I'd wonder if there wasn't a binding mechanism along the lines of java.sql.PreparedStatement somewhere in the library? It's recommended for guarding against SQL injection attacks. Another benefit is possible performance benefits, because the SQL can be parsed, verified, cached once, and reused.
Actually, the ExecuteScalar method returns the first column of the first row of the DataSet being returned. In your case, you're only doing an Insert, you're not actually querying any data. You need to query the scope_identity() after you're insert (that's the syntax for SQL Server) and then you'll have your answer. See here:
Linkage
EDIT: As Michael Haren pointed out, you mentioned in your tag you're using MySql, use last_insert_id(); instead of scope_identity();
OleDbConnection my_con = new OleDbConnection(#"Provider=Microsoft.ACE.OLEDB.12.0;
Data Source=C:\\Users\\SS\\Documents\\131Current1\\125\\Current one\\ClinicMainDatabase.accdb");
my_con.Open();
OleDbCommand o_cmd1 = my_con.CreateCommand();
o_cmd1.CommandText = "INSERT INTO Personal_Details(Date,Time,Patient_Name,Contact_Number,Gender,Allergic_To,KCO) VALUES ('" + DateTime.Now.ToString("dd-MM-yyyy") + "','" + DateTime.Now.ToString("h:mm:ss tt") + "','" + txtPatientName.Text + "','" + txtContactNo.Text + "','" + comboBoxGender.Text + "','" + txtAllergic.Text + "','" + txtKCO.Text + "')";
int j = o_cmd1.ExecuteNonQuery();
I am getting the Syntax error in Insert Statement I don't understand what is mistake if any one help me I am really thank full.Thanks in Advance.
Date and Time are typically reserved keywords in many database systems. You should at the very least wrap them with [ ]. More preferably, if you are designing the table, change the field name to something more descriptive. For example if the Date and Time represented a reminder then you could use ReminderDate and ReminderTime so as not to interfere with reserved keywords.
And follow the parameter advice that's already been given.
Use command parameters instead of concatenating strings. Your code is open for SQL Injection attacks or in your specific case the problem may be related with invalid user input. Try to thing about this situation:
What if the txtContactNo.Text returns this string "Peter's contact is +123456" ? How does the SQL query will look then? Pay close attention to ' character.
You should ALWAYS use parametrized SQL queries no matter how good you thing your input validation is. It also has more advantages like query plan caching etc.
So in your case the code must be written like this:
OleDbConnection my_con = new OleDbConnection(#"Provider=Microsoft.ACE.OLEDB.12.0;
Data Source=C:\\Users\\SS\\Documents\\131Current1\\125\\Current one\\ClinicMainDatabase.accdb");
using(my_con)
{
my_con.Open();
using(OleDbCommand o_cmd1 = my_con.CreateCommand())
{
o_cmd1.CommandText = #"
INSERT INTO Personal_Details ([Date], [Time], Patient_Name, Contact_Number, Gender, Allergic_To, KCO)
VALUES (#date, #time, #name, #contNo, #gender, #alergic, #kco)";
o_cmd1.Parameters.AddWithValue("#date", DateTime.Now.ToString("dd-MM-yyyy"));
o_cmd1.Parameters.AddWithValue("#time", DateTime.Now.ToString("h:mm:ss tt"));
o_cmd1.Parameters.AddWithValue("#name", txtPatientName.Text);
o_cmd1.Parameters.AddWithValue("#contNo", txtContactNo.Text);
o_cmd1.Parameters.AddWithValue("#gender", comboBoxGender.Text);
o_cmd1.Parameters.AddWithValue("#alergic", txtAllergic.Text);
o_cmd1.Parameters.AddWithValue("#kco", txtKCO.Text);
o_cmd1.ExecuteNonQuery();
}
}
Also make sure that you are properly disposing the connection and the command objects (by using :) the using keyword)
For more info read the docs in MSDN
https://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlparametercollection.addwithvalue(v=vs.110).aspx
I have following query that works.
string sqlCommandText = "SELECT * FROM Admin_T where AdminID =
'" + textBox.Text + "'";
It is a fix command and I cannot use it with user given Table names and Column names at run time.
What I am actually trying to make is command like
string sqlCommandText = "SELECT * FROM Admin_T where
'" + UserGivenColumnName + "' = '" + conditionTB.Text + "'";
"UserGivenColumnName" can be any column that is part of that specific table.
Trying to create flexibility so that same command can be used under different circumstances.
SqlCommand and none of related classes used by ADO.NET does not support such a functionality as far as I know.
Of course your should never build your sql queries with string concatenation. You should always use parameterized queries. This kind of string concatenations are open for SQL Injection attacks.
But prepared statements only for values, not column names or table names. If you really wanna put your input string to your column name, create a whitelist and use it as a validation before you put it in your query.
http://codeblog.jonskeet.uk/2014/08/08/the-bobbytables-culture/
I think an Object-Relational Mapper (ORM) is perhaps the droid you are looking for. Entity Framework might be a good place to start.
Please also do take the time to understand what SQL injection is, as the other users have also prompted you to.
It is not returning anything as it is just comparing two strings
With the 'UserGivenColumnName' it is a string comparison
And those two strings are not equal
You can do it (column) by just not including the '
But it is still a bad idea
SQLinjection is a very real and very bad thing
string sqlCommandText =
"SELECT * FROM Admin_T where " + UserGivenColumnName + " = '" + conditionTB.Text + "'";
or
string sqlCommandText =
"SELECT * FROM Admin_T where [" + UserGivenColumnName + "] = '" + conditionTB.Text + "'";
datetime=Datetime.Now;
string strquery = #"INSERT INT0 [Destination_CMS].[dbo].[Destination_CMS_User]
values('" + userid + "','" + email + "','"
+ userType + "','" + userStatus + "','" + processed + "','"
+ datetime.ToLongDateString() + "')";
cmd = new SqlCommand(strquery, con);
con.Open();
cmd.ExecuteNonQuery();
con.Close();
I am getting error:
Incorrect syntax near 'Destination_CMS'.
You've written INT0 rather than INTO.
Also, use parameterized queries.
You should try to change INT0 to INTO.
INSERT INT0 [Destination_CMS].[dbo]
I think its INSERT INTO rather than INT0 (zero)
Print the query to the screen, and verify where the syntax error is.
Next to that; use parametrized queries, like this:
string query = "INSERT INTO [tablename] ( column, column ) VALUES (#p_param1, #p_param2)";
var command = new SqlCommand (query);
command.Parameters.Add ("#p_param1", SqlDbType.DateTime).Value = DateTime.Now;
...
You are risking sql injection, if not using parametrized queries..
Your problem looks solved, so my next question would be, why not use an ORM like NHibernate/EF etc.., depending on your requirements offocourse, but ADO.NET plumbing in my books is where performance is an absolute issue.
You could write this as a stored procedure instead, which has the advantage of making typos like this a lot easier to spot and fix.
I've got a error which I can't understand. When I'm debugging and trying to run a insert statement, its throwing the following exception:
"There are fewer columns in the INSERT statement than values specified in the VALUES clause. The number of values in the VALUES clause must match the number of columns specified in the INSERT statement."
I have looked all over my code, and I can't find the mistake I've made.
This is the query and the surrounding code:
SqlConnection myCon = DBcon.getInstance().conn();
int id = gm.GetID("SELECT ListID from Indkøbsliste");
id++;
Console.WriteLine("LNr: " + listnr);
string streg = GetStregkode(navne);
Console.WriteLine("stregk :" + strege);
string navn = GetVareNavn(strege);
Console.WriteLine("navn :" + navne);
myCon.Open();
string query = "INSERT INTO Indkøbsliste (ListID, ListeNr, Stregkode, Navn, Antal, Pris) Values(" + id + "," + listnr + ", '" + strege + "','" + navn + "'," + il.Antal + ", "+il.Pris+")";
Console.WriteLine(il.Antal+" Antal");
Console.WriteLine(il.Pris+" Pris");
Console.WriteLine(id + " ID");
SqlCommand com = new SqlCommand(query, myCon);
com.ExecuteNonQuery();
com.Dispose();
myCon.Close();
First of all check the connection string and confirm the database location and number of columns a table has.
Suggestion : Do not use hardcoded SQL string. Use parameterized sql statements or stored-proc.
Try parameterized way,
string query = "INSERT INTO Indkøbsliste (ListID, ListeNr, Stregkode, Navn, Antal, Pris)
Values (#ListID, #ListeNr, #Stregkode, #Navn, #Antal, #Pris)"
SqlCommand com = new SqlCommand(query, myCon);
com.Parameters.Add("#ListID",System.Data.SqlDbType.Int).Value=id;
com.Parameters.Add("#ListeNr",System.Data.SqlDbType.Int).Value=listnr;
com.Parameters.Add("#Stregkode",System.Data.SqlDbType.VarChar).Value=strege ;
com.Parameters.Add("#Navn",System.Data.SqlDbType.VarChar).Value=navn ;
com.Parameters.Add("#Antal",System.Data.SqlDbType.Int).Value=il.Antal;
com.Parameters.Add("#Pris",System.Data.SqlDbType.Int).Value=il.Pris;
com.ExecuteNonQuery();
Please always use parametrized queries. This helps with errors like the one you have, and far more important protects against SQL injection (google the term, or check this blog entry - as an example).
For example, what are the actual values of strege and/or navn. Depending on that it may render your SQL statement syntactically invalid or do something worse.
It (looks like) a little more work in the beginning, but will pay off big time in the end.
Are you using danish culture settings?
In that case if il.Pris is a double or decimal it will be printed using comma, which means that your sql will have an extra comma.
Ie:
INSERT INTO Indkøbsliste (ListID, ListeNr, Stregkode, Navn, Antal, Pris) Values(33,5566, 'stegkode','somename',4, 99,44)
where 99,44 is the price.
The solution is to use parameters instead of using the values directly in you sql. See some of the other answers already explaining this.