SQL injection prevention with 'AS QUERY' - c#

I have the following code which seems pretty standard on face value, however in query is another SQL statement hence why the 'AS QUERY' is at the end of the SQL string. I wanted to know if there was a sophisticated approach to parameterising the following SQL command instead of concatenating the entire query together.
The only solution I could think of would be to instead of having a query as a string, have it as an SQLCommand type object and initiate 2 commands. 1 to could and the other to display the preview of the data.
public static CommandStatus<int> GetQueryRecordCount(SqlConnection connection, String query)
{
String sql = "SELECT COUNT(1) FROM (" + query + ") AS QUERY";
SqlCommand cmd = new SqlCommand();
cmd.CommandType = CommandType.Text;
cmd.CommandText = sql;
cmd.Connection = connection;
cmd.CommandTimeout = GetTimeout();
try
{
SqlDataReader dataReader = cmd.ExecuteReader();
dataReader.Read();
String count = dataReader[0].ToString();
dataReader.Close();
return new CommandStatus<int>(Int32.Parse(count));
}
catch (Exception e)
{
return new CommandStatus<int>("Failed to GetQueryRecordCount[" + sql + "]:" + e.Message, e);
}
}
String SQL will end up being something like this
"SELECT COUNT(1) FROM (SELECT TOP 20 [RecordID],[Name],[SonsName],[DadsName],[MothersName],[DaughtersName] FROM [dbo].[sample] ) AS QUERY"

This function is literally SQL injection by design.
Whitelisting the SQL queries this function will accept is the only way to make it safe.
That is, the caller won't be able to inject any SQL query, they'll only be able to pick from a fixed list of pre-vetted queries. The list could even be defined as an array of static strings in the function you show.
But then they don't need to pass the whole query as a string, they only need to pass an ordinal integer to identify which query in the whitelist to run.

Related

Use Variable In SQL String

How can I add a variable to my SQL string and run it against the server successfully? I want to run this statement through my C#
protected void RunSQLQuery(string salesman, string connectionString)
{
SqlConnection cnn;
SqlCommand cmd;
StringBuilder sql = new StringBuilder();
SqlDataReader reader;
cnn = new SqlConnection(connectionString);
sql = new StringBuilder();
sql.Append("update database ");
sql.Append("set shippdate = GetDate() ");
sql.Append("where salesman = "' + salesman + "'");
sql.Append("and managerapproval is not null ");
cnn.Open();
cmd = new SqlCommand(sql.ToString(), cnn);
reader = cmd.ExecuteReader();
reader.Close();
cmd.Dispose();
cnn.Close
}
This presents multiple compile errors underlining my +salesman+ code. The errors are:
Only assignment, call, increment, decrement, and new object
expressions can be used as a statement
; expected
) expected
Too many characters in character literal Newline in constant
You are not adding the string object that salesman refers, you are adding salesman as a string literal.
Just add it as a parameter like;
var cmd = new SqlCommand("update database set shippdate = GetDate() where salesman = #salesman");
cmd.Parameters.Add("#salesman", salesman);
...
And use ExecuteNonQuery to execute your command, not SqlDataReader. This SqlDataReader is for return some data.
But more important, you should always use parameterized queries. This kind of string concatenations are open for SQL Injection attacks.
Also use using statement to dispose your connection and command automatically instead of calling Close or Dispose methods manually.
As a full example;
protected void RunSQLQuery(string salesman, string connectionString)
{
using(var cnn = new SqlConnection(connectionString))
using(var cmd = cnn.CreateCommand())
{
cmd.CommandText = #"update database set shippdate = GetDate()
where salesman = #salesman";
// I assume your column is nvarchar
cmd.Parameters.Add("#salesman", SqlDbType.NVarChar).Value = salesman;
cnn.Open();
cmd.ExecuteNonQuery();
}
}
For myself, I always prefer to use SqlParameterCollection.Add(string, SqlDbType, Int32) overload to specify my parameter type and it's size but since you never mentioned your salesman column type, I couldn't post this in my example.
As you can also see from the syntax highlighting, the compile errors are caused because you did not escape the quotes properly in sql.Append("where salesman = "' + salesman + "'");.
As a side note, you should never insert strings into sql queries without first validating them, or you are open to sql injection, e.g. if i pass "''; drop table database;" as salesman parameter. It is better to use SqlParameter.
I would suggest using the AddWithValue method from your sql command combined with the UPPER function to make it case insensitive:
SqlCommand cmd = cnn.CreateCommand();
cmd.CommandText = "UPDATE database SET shippdate = GetDate() WHERE UPPER(salesman) = UPPER(#salesMan)";
cmd.Parameters.AddWithValue("#salesMan", salesman);
if (cnn.State.Equals(ConnectionState.Closed))
{
cnn.Open();
}
cmd.ExecuteNonQuery();
cnn.Close();
As mentioned in above answers, yes, writing queries in this way is not a good way to do it. But still if you want to do it that way only, you will have to change:
sql.Append("where salesman = "' + salesman + "'");
to
sql.Append("where salesman = '" + salesman + "'");

What does "WHERE x = ?" mean in SQL

This code is written in C# and it is calling database to get the data from it. But I don't understand what does "WHERE b.CompRec = ?" mean
public string GetFileNameAndTitle(int compRec)
{
string fileNameAndTitle = "";
string sql = "SELECT a.FileName, a.Title FROM (Files a INNER JOIN Components b ON a.RecNo=b.FileRec) WHERE b.CompRec = ?";
using (OleDbCommand cmd = new OleDbCommand(sql, cn))
{
cmd.Parameters.AddWithValue("#CompRec", compRec);
OpenConnection(); }
It is a parameterized statement.
cmd.Parameters.AddWithValue("#CompRec", compRec);
That line sets the actual value when the query is executed at the server. This prevents SQL Injection and is the 100% right approach!
It's basically a placeholder where you will put data later. This lets you split up your SQL statement from the data used in the query. This is the syntax of parameterized statements.

ExecuteScalar always returns 0

I'm not sure why this is happening. I've seen the same issue online with little help out there to correct it.
When i run my query inside Access i get different values ranging from 0 - 10 but for some reason, it won't return that same value inside my code.
static int OrdersPerHour(string User)
{
int? OrdersPerHour = 0;
OleDbConnection conn = new OleDbConnection(strAccessConn);
DateTime curTime = DateTime.Now;
try
{
string query = "SELECT COUNT(ControlNumber) FROM Log WHERE DateChanged > #" + curTime.AddHours(-1) + "# AND User = '" + User + "' AND Log.EndStatus in ('Needs Review', 'Check Search', 'Vision Delivery', 'CA Review', '1TSI To Be Delivered');";
OleDbCommand dbcommand = new OleDbCommand(query, conn);
dbcommand.Connection.Open();
dbcommand.CommandType = CommandType.Text;
dbcommand.CommandText = query;
OrdersPerHour = (int?)dbcommand.ExecuteScalar();
}
catch (OleDbException ex)
{
}
finally
{
conn.Close();
}
return OrdersPerHour.Value;
}
Do not use string concatenation and the Access syntax to build your sql commands.
Use a simple parameterized query like this
string query = "SELECT COUNT(ControlNumber) FROM Log " +
"WHERE DateChanged > ? AND [User] = ? AND " +
"Log.EndStatus in ('Needs Review', 'Check Search', 'Vision Delivery'," +
"'CA Review', '1TSI To Be Delivered');";
OleDbCommand dbcommand = new OleDbCommand(query, conn);
dbcommand.Parameters.AddWithValue("#p1", curTime.AddHours(-1));
dbcommand.Parameters.AddWithValue("#p2", User);
dbcommand.Connection.Open();
dbcommand.CommandType = CommandType.Text;
OrdersPerHour = (int)dbcommand.ExecuteScalar();
In this way the burden to correctly interpret your value is passed to the Framework code that could format dates, decimals and strings according to your database requirements. By the way this will also prevent Sql Injection
Also, the word USER is a reserved keyword in Access SQL and thus you need to encapsulate it with square brackets
First and most important: Use Parametrized Queries!
Regarding your problem, I suggest you to debug the code:
Get the Commandtext of your "OleDbCommand dbcommand" and manually query to see if you get the same result.
Also, you should put your code within the try catch block, else it does not make sense at all.

SQL Select statement from a webpage control and check relevant check boxes according to result

I have a sql select statement in my VS2005 C# server-side coding for a web application and I am meeting some errors.
Below is a screenshot of the controls in the webpage:
Data Source SqlDataSource1 : Query:SELECT [Name] FROM [Users].
Dropdownlist UserNameList : Lists all userName retrieved from SqlDataSource1.
Checkboxes AdminCb and UserCb : Automatically checks if the userType of the userName is as.
Button loadUser : Gets the user type and checks the check boxes accordingly.
Below is my code for my loadUser button
SqlConnection conn = new SqlConnection("Data Source=DATASOURCE");
string sql = string.Format("SELECT [User Type] FROM [Users] where Name like " + UserNameList.Text);
SqlCommand cmd = new SqlCommand(sql, conn);
conn.Open();
cmd.ExecuteNonQuery();
conn.Close();
if(sql== "Administrator"){
AdminCb.Checked=true;
}
if(sql== "User"){
UserCb.Checked=true;
}
Currently I am stuck with the error (Wong is the 2nd word of the user's name):
Questions:
1) How can change my Sql query so that it can take in more than 1word?
2) And will I be able to check boxes once I am able to run my sql query?
Thank You.
You must have to use Parameter and call the ExecuteScalar() method instead of ExecuteNonQuery().
string sql = "SELECT [User Type] FROM [Users] where [Name]=#Name";
SqlCommand cmd = new SqlCommand(sql, conn);
cmd.Parameters.Add("#Name",SqlDbType.VarChar,50).Value=UserNameList.Text;
conn.Open();
Object result=cmd.ExecuteScalar();
conn.Close();
if(result!=null)
{
string usertype=result.ToString();
if(usertype=="Administrator")
{}
else
{}
}
In case, if result returned from the database contains more then one rows then use ExecuteReader() method.
string sql = "SELECT [User Type] FROM [Users] where [Name] like #Name";
SqlCommand cmd = new SqlCommand(sql, conn);
cmd.Parameters.Add("#Name",SqlDbType.VarChar,50).Value="%" + UserNameList.Text + "%";
conn.Open();
SqlDataReader result=cmd.ExecuteReader();
while(result.Read())
{
///
}
result.Close();
conn.Close();
Since you are concatenating the SQL string, if the input itself has a single quote in it, it thinks this is the end of the input, and the continuing input is SQL statements, which is why you may be getting that error.
Switch to using a parameter, or make sure any single quotes are escaped as a pair of single quotes, like:
string sql = string.Format("SELECT [User Type] FROM [Users] where Name like " + UserNameList.Text.Replace("'", "''"));
Since the error is indicating there is something wrong with the Name, I would take a closer look at this line:
string sql = string.Format("SELECT [User Type] FROM [Users] where Name like " + UserNameList.Text);
If you are using string.Format, you might as well use it
string sql = string.Format("SELECT [User Type] FROM [USERS] where Name like {0}", UserNameList.Text);

how to update a table using oledb parameters?

I am having a table which has three fields, namely LM_code,M_Name,Desc. LC_code is a autogenerated string Id, keeping this i am updating M_Name and Desc. I used normal update command, the value is passing in runtime but the fields are not getting updated. I hope using oledb parameters the fields can be updated.
Here is my code.
public void Modify()
{
String query = "Update Master_Accounts set (M_Name='" + M_Name + "',Desc='" + Desc + "') where LM_code='" + LM_code + "'";
DataManager.RunExecuteNonQuery(ConnectionString.Constr, query);
}
In DataManager Class i am executing the query string.
public static void RunExecuteNonQuery(string Constr, string query)
{
OleDbConnection myConnection = new OleDbConnection(Constr);
try
{
myConnection.Open();
OleDbCommand myCommand = new OleDbCommand(query, myConnection);
myCommand.ExecuteNonQuery();
}
catch (Exception ex)
{
string Message = ex.Message;
throw ex;
}
finally
{
if (myConnection.State == ConnectionState.Open)
myConnection.Close();
}
}
private void toolstModify_Click_1(object sender, EventArgs e)
{
txtamcode.Enabled = true;
jewellery.LM_code = txtamcode.Text;
jewellery.M_Name = txtaccname.Text;
jewellery.Desc = txtdesc.Text;
jewellery.Modify();
MessageBox.Show("Data Updated Succesfully");
}
This annoyed me, screwy little OleDB, so I'll post my solution here for posterity. It's an old post but seems like a good place.
OleDB doesn't recognize named parameters, but it apparently does recognize that you're trying to convey a named parameter, so you can use that to your advantage and make your SQL semantic and easier to understand. So long as they're passed in the same order, it'll accept a variable as a named parameter.
I used this to update a simple Access database in a network folder.
using (OleDbConnection conn = new OleDbConnection(connString))
{
conn.Open();
OleDbCommand cmd = conn.CreateCommand();
for (int i = 0; i < Customers.Count; i++)
{
cmd.Parameters.Add(new OleDbParameter("#var1", Customer[i].Name))
cmd.Parameters.Add(new OleDbParameter("#var2", Customer[i].PhoneNum))
cmd.Parameters.Add(new OleDbParameter("#var3", Customer[i].ID))
cmd.Parameters.Add(new OleDbParameter("#var4", Customer[i].Name))
cmd.Parameters.Add(new OleDbParameter("#var5", Customer[i].PhoneNum))
cmd.CommandText = "UPDATE Customers SET Name=#var1, Phone=#var2" +
"WHERE ID=#var3 AND (Name<>#var4 OR Phone<>#var5)";
cmd.ExecuteNonQuery();
cmd.Parameters.Clear();
}
}
It may look like an excess of code, and yes you're technically repeating yourself, but this makes it worlds easier when you're playing connect-the-dots later on.....
You are close with the rest of your connection and such, but as you note, doing it with parameterized queries is safer from SQL-Injection...
// Some engines used named parameters, others may not... The "?"
// are "place-holders" for the ordinal position of parameters being added...
String MyQuery = "Update MyTable set SomeField = ?, AnotherField = ? "
+ " where YourKeyField = ?";
OleDbCommand MyUpdate = new OleDbCommand( MyQuery, YourConnection );
// Now, add the parameters in the same order as the "place-holders" are in above command
OleDbParameter NewParm = new OleDbParameter( "ParmForSomeField", NewValueForSomeField );
NewParm.DbType = DbType.Int32;
// (or other data type, such as DbType.String, DbType.DateTime, etc)
MyUpdate.Parameters.Add( NewParm );
// Now, on to the next set of parameters...
NewParm = new OleDbParameter( "ParmForAnotherField", NewValueForAnotherField );
NewParm.DbType = DbType.String;
MyUpdate.Parameters.Add( NewParm );
// finally the last one...
NewParm = new OleDbParameter( "ParmForYourKeyField", CurrentKeyValue );
NewParm.DbType = DbType.Int32;
MyUpdate.Parameters.Add( NewParm );
// Now, you can do you
MyUpdate.ExecuteNonQuery();
Just to add to RJB's answer, it's a little-known fact that OleDb actually DOES accept named parameters. You've just got to declare the parameters in SQL as well.
See: low-bandwidth.blogspot.com.au/2013/12/positional-msaccess-oledb-parameters.html
If you DON'T declare the parameters in SQL, OleDb uses purely positional parameter insertion, and it doesn't matter if the names of the parameters match the SQL, or if parameters are used twice in the SQL - it will just go through and blindly replace any found parameters in the SQL in order from start to end, with those passed.
However if you DO declare the parameters correctly, you get the benefit of named parameters and parameters allowed to be repeated multiple times within the SQL statement.

Categories

Resources