I have a list of keywords that i store in a list.
To fetch records from a table, am using the following query:
sqlBuilder.Append("SELECT name, memberid FROM members WHERE");
StringBuilder sqlBuilder = new StringBuilder();
foreach (string item in keywords)
{
sqlBuilder.AppendFormat(" LOWER(Name) LIKE '%{0}%' AND", item);
}
string sql = sqlBuilder.ToString();
As you might have noticed, my query is vulnerable to sql injection, thus i want to use parameters using SqlCommand(). I have tried the following but still doesn't work:
foreach (string item in keywords)
{
sqlBuilder.AppendFormat(" LOWER(Name) LIKE '%' + #searchitem + '%' AND", item);
SqlCommand cmd = new SqlCommand(sqlBuilder.ToString());
cmd.Parameters.AddWithValue("#searchitem",item);
}
Where could i be making the mistake, or rather, how should i got about it?
You are doing a few things wrong here:
You give all your parameters the same name #searchitem. That won't work. The parameters need unique names.
You create a new SqlCommand for each item. That won't work. Create the SqlCommand once at the beginning of the loop and then set CommandText once you are done creating the SQL.
Your SQL ends with AND, which is not valid syntax.
Improvement suggestions (not wrong per se, but not best practice either):
As Frederik suggested, the usual way is to put the % tokens in the parameter, rather than doing string concatenation inside the SQL.
Unless you explicitly use a case-sensitive collation for your database, comparisons should be case-insensitive. Thus, you might not need the LOWER.
Code example:
SqlCommand cmd = new SqlCommand();
StringBuilder sqlBuilder = new StringBuilder();
sqlBuilder.Append("SELECT name, memberid FROM members ");
var i = 1;
foreach (string item in keywords)
{
sqlBuilder.Append(i == 1 ? " WHERE " : " AND ");
var paramName = "#searchitem" + i.ToString();
sqlBuilder.AppendFormat(" Name LIKE {0} ", paramName);
cmd.Parameters.AddWithValue(paramName, "%" + item + "%");
i++;
}
cmd.CommandText = sqlBuilder.ToString();
Do not put the wildcard characters in your querystring, but add them to your parameter-value:
sql = "SELECT name FROM members WHERE Name LIKE #p_name";
...
cmd.Parameters.AddWithValue("#p_name", "%" + item + "%");
When you add the wildcard characters inside your query-string, the parameter will be escaped, but the wildcard chars will not; that will result in a query that is sent to the DB that looks like this:
SELECT name FROM members WHERE Name LIKE %'somename'%
which is obviously not correct.
Next to that, you're creating a SqlCommand in a loop which is not necessary. Also, you're creating parameters with a non-unique name, since you're adding them in a loop, and the parameter always has the same name.
You also need to remove the last AND keyword, when you exit the loop.
Related
I have a list of keywords that i store in a list.
To fetch records from a table, am using the following query:
sqlBuilder.Append("SELECT name, memberid FROM members WHERE");
StringBuilder sqlBuilder = new StringBuilder();
foreach (string item in keywords)
{
sqlBuilder.AppendFormat(" LOWER(Name) LIKE '%{0}%' AND", item);
}
string sql = sqlBuilder.ToString();
As you might have noticed, my query is vulnerable to sql injection, thus i want to use parameters using SqlCommand(). I have tried the following but still doesn't work:
foreach (string item in keywords)
{
sqlBuilder.AppendFormat(" LOWER(Name) LIKE '%' + #searchitem + '%' AND", item);
SqlCommand cmd = new SqlCommand(sqlBuilder.ToString());
cmd.Parameters.AddWithValue("#searchitem",item);
}
Where could i be making the mistake, or rather, how should i got about it?
You are doing a few things wrong here:
You give all your parameters the same name #searchitem. That won't work. The parameters need unique names.
You create a new SqlCommand for each item. That won't work. Create the SqlCommand once at the beginning of the loop and then set CommandText once you are done creating the SQL.
Your SQL ends with AND, which is not valid syntax.
Improvement suggestions (not wrong per se, but not best practice either):
As Frederik suggested, the usual way is to put the % tokens in the parameter, rather than doing string concatenation inside the SQL.
Unless you explicitly use a case-sensitive collation for your database, comparisons should be case-insensitive. Thus, you might not need the LOWER.
Code example:
SqlCommand cmd = new SqlCommand();
StringBuilder sqlBuilder = new StringBuilder();
sqlBuilder.Append("SELECT name, memberid FROM members ");
var i = 1;
foreach (string item in keywords)
{
sqlBuilder.Append(i == 1 ? " WHERE " : " AND ");
var paramName = "#searchitem" + i.ToString();
sqlBuilder.AppendFormat(" Name LIKE {0} ", paramName);
cmd.Parameters.AddWithValue(paramName, "%" + item + "%");
i++;
}
cmd.CommandText = sqlBuilder.ToString();
Do not put the wildcard characters in your querystring, but add them to your parameter-value:
sql = "SELECT name FROM members WHERE Name LIKE #p_name";
...
cmd.Parameters.AddWithValue("#p_name", "%" + item + "%");
When you add the wildcard characters inside your query-string, the parameter will be escaped, but the wildcard chars will not; that will result in a query that is sent to the DB that looks like this:
SELECT name FROM members WHERE Name LIKE %'somename'%
which is obviously not correct.
Next to that, you're creating a SqlCommand in a loop which is not necessary. Also, you're creating parameters with a non-unique name, since you're adding them in a loop, and the parameter always has the same name.
You also need to remove the last AND keyword, when you exit the loop.
I am receiving a Dictionary<string, string> and would like to forward its values to the DB inside SqlParameter. Is that even possible? This is the way I did it, and I am getting an error that column name doesn't match table definition.
SqlParameter param = new SqlParameter();
param.ParameterName = "#Values";
var sb = new StringBuilder();
foreach (var item in data)
{
sb.Append("'" + item.Value + "', ");
}
param.Value = sb.ToString().TrimEnd(',');
string insertString = $"insert into {tableName} values (#Values)";
SqlCommand command = new SqlCommand(insertString, connection);
command.Parameters.Add(param);
command.ExecuteNonQuery();
Sql server can't interpret the single variable you are passing as multiple values.
You can either generate your query with multiple variables, or use a table valued parameter.
For the first option, you must change the way you build your query:
var command = new SqlCommand();
var insertString = $"insert into {tableName} values (";
var sb = new StringBuilder(insertString);
int i = 0;
foreach (var item in data)
{
sb.Append("#P").Append(i).Append(",");
command.Parameters.Add("#P" + i, SqlDbType.VarChar).Value = item.Value;
i++;
}
command.Connection = connection;
command.CommandText = sb.ToString().TrimEnd(",") + ");";
command.ExecuteNonQuery();
Note: Code was not tested, there might be some errors.
For the second option, You must use a stored procedure. I've never tried to pass table valued parameters to an inline query and I don't think it's possible.
This post (also linked in Alex K's comment) explains how to do that.
Each value in the "Values" part of you t-SQL must be enclosed with parenthesis.
So, just change this line:
sb.Append("'" + item.Value + "', ");
to:
sb.Append("('" + item.Value + "'),"); // note: no space after the ,
Your tSQL would look something like this:
insert into myTable values ('A', 'B', 'C',)
It needs to look like this (assuming you've only got 1 column in the table):
insert into myTable values ('A'), ('B'), ('C')
And if your table contains multiple columns:
insert into myTable (myColumn) values ('A'), ('B'), ('C')
I think the best is create a split function in mssql (million of example in internet)and a stored. Pass a string comma(for example) separated to the stored Who call the function. Sorry for no example but i'm with my smartphone
I'm want to refactor the following statement to change the where businessID = something to a statement where I provide a list of strings which contain all possible Ids e.g. where businessID in List. As now the query is executed in a for loop for each Id, which I'm guessing is not really performant. I can't seem to find information on how to use a List of strings as a parameter in a prepared statement.
using (SqlConnection myConnection = new SqlConnection("Data Source=.\\SERVER;Initial Catalog=DB;Integrated Security=True;TrustServerCertificate=True;User Instance=False"))
using (SqlCommand myCommand = myConnection.CreateCommand())
{
myConnection.Open();
myCommand.CommandText = "SELECT BusinessName FROM Businessess WHERE BusinessID = #Param2";
myCommand.Parameters.AddWithValue("#Param2", myParam2);
using (SqlDataReader reader = myCommand.ExecuteReader())
{
if (reader.Read())
{
string businessName = reader.GetString(reader.GetOrdinal("BusinessName"));
MessageBox.Show(businessName);
}
else
{
MessageBox.Show(string.Format("Sorry, no business found with id = {0}", myParam2));
}
}
}
You can use IN instead of equality, by adding comma separated ids in #Param2
"SELECT BusinessName FROM Businessess WHERE BusinessID IN(#Param2)";
IN
Determines whether a specified value matches any value in a subquery or a list.
As #Prescott suggested you the way to make a comma separated string from list you can have your AddWithValue like
myCommand.Parameters.AddWithValue("#Param2", String.Join(",", list));
As a side not try using stored procedure instead of inline queries, see comparison Stored procedures vs. inline SQL.
first create convert your list to comma separated string
string commaSeparatedList = yourlist.Aggregate((a, x) => a + ", " + x);
change the (=) to (In) and put the #Param between parenthesis
myCommand.CommandText = "SELECT BusinessName FROM Businessess WHERE BusinessID IN ( #Param2 )";
add the string (commaSeparatedList ) as parameter to your command
myCommand.Parameters.AddWithValue("#Param2", commaSeparatedList );
I have an application I need to create which, given some user input in the form of CSV, needs to parse and generate this CSV into multiple formats. One of these formats is a series of SQL INSERT statements (as a string) for each line of CSV.
(At this point you can assume I've already parsed the CSV into a list of values or something, so that is not the point of the question)
Given that this input could contain vulnerabilities, I wish to generate the INSERT statements which have been validated and sanitised.
I am familiar with creating an SqlCommand object and adding values to its list of Parameters, but looking over a similar question it doesn't appear to work in way I had hoped.
So is there a way to generate sanitised SQL statements, as strings, in the way I need to?
EDIT: This is an example what I want to do.
CSV:
id,name,country
1,Alice,France
2,Bob,Austria
3,Carol,Germany
SQL:
...
INSERT INTO Users (id, name, country) VALUES (1, 'Alice', 'France');
INSERT INTO Users (id, name, country) VALUES (2, 'Bob', 'Austria');
INSERT INTO Users (id, name, country) VALUES (3, 'Carol', 'Germany');
...
As there are no data types given in the CSV, the application has to determine that as well.
Not so much an answer, as a cautionary note. If you end up needing to go the 'classic' escaping route to do this, and really need safety (i.e. the data is coming in from untrusted source), don't forget it's not just simple escaping you need to worry about.
Basic character escaping we hear about all the time:
' -> '' apostrophe's and stuff are quite obvious and documented ad-nauseum
; multiple-commands in one statement - not always allowed by the DB, but dangerous
If you're parsing for "nefarious behaviour" though, have you thought about:
SELECT/*not important*/1/*really...*/FROM/*im serious*/users
SELECT%09FIELDS%09FROM%0dTABLE_NAME
WHERE username=CONCAT(CHAR(97),CHAR(100),CHAR(109),CHAR(105),CHAR(110))
SELECT passwd FROM users WHERE username=0x61646d696e
In summary: Here Be Dragons.
http://www.ihteam.net/papers/blind-sqli-regexp-attack.pdf
http://ferruh.mavituna.com/sql-injection-cheatsheet-oku/#HexbasedSamples
Well chances are if you don't wan't to use SQL objects then you would have to sanatize the entries yourself. I'm not aware of any recommended format for SQL however for MySQL the following would work. I've changed it to work with SQL however I cant garantee it has covered all possible injection attacks.
public string sqlEscape(string VAL)
{
if (VAL == null)
{
return null;
}
return "\"" + Regex.Replace(VAL, #"[\r\n\x00\x1a\\'""]", #"\$0") + "\"";
}
to use you would then do (assuming your CSV line is stored in an array called csv):
string query = #"INSERT INTO Users (id, name, country) VALUES (" + sqlEscape(csv[0]) + ", " + sqlEscape(csv[1]) + ", " + sqlEscape(csv[2]) + ");";
if anyone can enhance this let me know!
Because i don't know how you've stored your variables, i'll show you a complete, possible implementation with your sample data using a List<Dictionary<String, Object>>():
Add your sample-data:
var tableName = "Users";
var records = new List<Dictionary<String, Object>>();
var recordFields = new Dictionary<String, Object>();
recordFields.Add("id", 1);
recordFields.Add("name", "Alice");
recordFields.Add("country", "France");
records.Add(recordFields);
recordFields = new Dictionary<String, Object>();
recordFields.Add("id", 2);
recordFields.Add("name", "Bob");
recordFields.Add("country", "Austria");
records.Add(recordFields);
recordFields = new Dictionary<String, Object>();
recordFields.Add("id", 3);
recordFields.Add("name", "Carol");
recordFields.Add("country", "Germany");
records.Add(recordFields);
Generate the parametrized insert statements:
using (var con = new SqlConnection(Settings.Default.ConnectionString))
{
con.Open();
foreach (var record in records)
{
String insertSql = String.Format("INSERT INTO {0} ({1}) VALUES ({2});"
, tableName
, String.Join(",", record.Select(r => r.Key))
, String.Join(",", record.Select(r => "#" + r.Key)));
using (var insertCommand = new SqlCommand(insertSql, con))
{
foreach (var field in record)
{
var param = new SqlParameter("#" + field.Key, field.Value);
insertCommand.Parameters.Add(param);
}
insertCommand.ExecuteNonQuery();
}
}
}
Note that this is not really tested(it compiles and looks good) but it should help you anyway.
Edit:
#Oded: I think the problem is that daniel doesn't want to run the
inserts, but show/output/save them. So using the SqlCommand parameters
is no good, because you don't see the generated SQL.
#Cylindric That's correct
That's not possible and a contradiciton, you cannot use a String with SqlParameters. So i'm afraid you're open to Sql-Injection if you would run these inserts later. I would suggest to use above code when you're actually running the statemenets.
Ok, I have a list that consists of a bunch of values from a sql query, that part works fine. What I want to do is use the items in that list to tell another query what to look for. So, what it is saying is that, it should return all columns from CMMReports where PartNumber is like %listItem1..2...3%, Any advice?
List<string> ImportedParts = GetImportedPartNumbers();
string query = "SELECT * FROM CMMReports WHERE (RacfId IS NULL OR RacfId = '') AND (FilePath NOT LIKE '%js91162%') AND PartNumber LIKE %" + ImportedParts + "% ORDER BY CreatedOn DESC;";
Not that I condone this as you should be using parameterized queries. However, this should work:
StringBuilder partNumbers = new StringBuilder();
foreach (string queryValue in ImportedParts)
{
string q = "PartNumber LIKE '%" + queryValue + "%'";
if (string.IsNullOrEmpty(partNumbers.ToString())
{
partNumbers.Append(q);
}
else
{
partNumbers.Append(" OR " + q);
}
}
string query = string.Format("SELECT * FROM CMMReports WHERE (RacfId IS NULL OR RacfId = '') " +
"AND (FilePath NOT LIKE '%js91162%') AND ({0}) " +
"ORDER BY CreatedOn DESC;", partNumbers.ToString());
You might look up the IN clouse for SQL that way you get the answer for the parts that SQL Server can find in the database. Using WHERE x = y for all the items means that if one item can't be found the whole query returns nothing.
I would consider doing this in a stored procedure and passing in your list as an Xml parameter.
See the following article for more info on using Xml parameters in a stored proc:
Passing lists to SQL Server 2005 with XML Parameters - By Jon Galloway
Form there you can easily use your list data inside your stored proc using the Xml syntax and treat it almost as another table of data.
Untested, but you should get the idea:
List<string> ImportedParts = GetImportedPartNumbers();
SqlCommand cmd = myConnection.CreateCommand();
cmd.CommandText = "SELECT * FROM CMMReports WHERE (RacfId IS NULL OR RacfId = '') AND (FilePath NOT LIKE '%js91162%') AND (";
int i = 0;
foreach (string part in ImportedParts) {
cmd.AddParameterWithValue("#param" + i.ToString(), "%" + part + "%");
if (i != 0) cmd.CommandText += " OR"
cmd.CommandText += " PartNumber LIKE #param" + i.ToString();
i++;
}
cmd.CommandText += ") ORDER BY CreatedOn DESC;";
This solution uses a parameterized query instead of just appending strings in the SQL, which is considered a potential security risk.