Take this code as an example:
IAmazonSimpleDB client = new AmazonSimpleDBClient(Amazon.RegionEndpoint.USEast1);
SelectResponse response = client.Select(new SelectRequest() {
SelectExpression = "SELECT * FROM `foo` where FooID = '" + id + "'" });
I can rewrite it as such:
IAmazonSimpleDB client = new AmazonSimpleDBClient(Amazon.RegionEndpoint.USEast1);
SelectResponse response = client.Select(new SelectRequest() {
SelectExpression = "SELECT * FROM `foo` where FooID = '{0}'", id });
But from my understanding, that still leaves it vulnerable to injection right?
Is there anything else I can do here? We aren't using SQL so I can't do SQL Parameters.
I usually do a check to see if the id is an integer. That way you will get an exception or a Boolean value if it isn't an int. It will work fine unless you are using GUID values.
var isNumeric = int.TryParse("123", out int n); //Will give a bool
Int32.Parse(yourString); //This will give an exception if it is not an possible integer
If it's anything more than that then you could use a Regex expression to look for strange values and remove characters that shouldn't be there such as spaces. Most SQL injection attacks wont work if there's no spaces... I think. Removing all the spaces is pretty easy and I would assume your ID (even if it is complex) won't include spaces.
string s = " "
string t = s.Replace(" ", ""). //It will be hard to do a sql attack if the spaces are removed.
A little off topic but with C# 6.0 you can format string differentlyl; It's a new feature called "string interpolation" (thanks Etienne de Martel).
$"SELECT * FROM `foo` where FooID = '{id}'"
I was given a task to rewrite an old web API.
This API reads SQL queries from the database.
There's literally a view with "Queries" in the name which contains "SqlText" column.
SELECT SqlText FROM Queries WHERE QueryID = 123
The "SqlText" contains only simple SQL queries in the format SELECT [columns] FROM [table] by convention.
The query is altered depending on the URL parameters in the request. The result of this query is then shown as result.
string parsedColumns = ParseColumns(queryRow); //contains "Column1, Column2";
string parsedTable = ParseTable(queryRow); //contains "SomeTable"
string requestColumns = HttpContext.Request["columns"];
string sqlColumns = requestColumns ?? parsedColumns;
string col1Condition = HttpContext.Request["Column1"]
string col2Condition = HttpContext.Request["Column2"]
string sqlQuery = "SELECT " + sqlColumns
+ " FROM " + parsedTable
+ " WHERE Column1 = " + col1Condition
+ " AND Column2 = " + col2Condition;
This is obvious SQL injection issue so I started rewritting it.
Now there are three other problems.
I cannot change the structure of the database or the convention
The database is either Oracle or SQL Server
I don't know how to correctly work with the "columns" URL parameter to avoid SQL injection.
It's easy to convert the URL parameters in the WHERE clause to the SQL parameters for both SQL Server and Oracle.
SQL Server
var sqlCommand = new SqlCommand("SELECT * FROM SomeTable WHERE Condition1 = #con1 AND Condition2 = #con2");
Oracle
var oracleCommand = new OracleCommand("SELECT * FROM SomeTable WHERE Condition1 = :con1 AND Condition2 = :con2");
Column identifiers
The problem is with the HttpContext.Request["columns"]. I still need to somehow alter the SQL query string with URL parameters which I don't like at all.
To simplify the issue, let's consider a single column from URL request.
string column = HttpContext.Request["column"];
var cmd = new SqlCommand($"SELECT {column} FROM ...");
I know that in SQL Server the identifier can be surrounded by braces. So my line of thinking is that I'm safe if I strip all braces from the column.
string column = HttpContext.Request["column"];
column = column.Replace("[", "").Replace("]", "");
column = $"[{column}]";
var cmd = new SqlCommand($"SELECT {column} FROM ...");
Oracle uses quotation marks.
string column = HttpContext.Request["column"];
column = column.Replace("\"", "");
column = $"\"{column}\"";
var cmd = new OracleCommand($"SELECT {column} FROM ...");
The question
Is this sql-injection safe enough?
Or is this use case inherently sql-injection unsafe?
Since you are working with a basic program design that you cannot change what about just trying to add edits to the input to look for injection elements. For example if the input is a column name it will need to have a maximum length of 30 (before 12.x) characters and should not contain a semicolon or the strings " OR" or " AND" in them. While not a perfect solution this should be practical solution.
I have a View View_Booking in sql server 2014:
bkID bkSlot bkStatus
---- ------ --------
2 Lunch 1
4 Lunch 1
6 Dinner 0
7 Lunch 1
While in c# I have used a gridview and casted bkStatus into string like:
<asp:Label ID="lblStatus" Text='<%# (Eval("bkStatus")+"" == "1") ? "Booked" : "Pending" %>'
... ></asp:Label>
bkID bkSlot bkStatus
---- ------ --------
2 Lunch Booked
4 Lunch Booked
6 Dinner Pending
7 Lunch Booked
Now I'm searching into View using this query:
SELECT * FROM View_Booking
WHERE CAST(bkID AS NVARCHAR(MAX)) LIKE '%" + keyword + "%'
OR bkSlot LIKE '%"+keyword+"%'
OR bkStatus LIKE << ? >>
But don't know how to search for bkStatus which is passed as string from c# while it's a int in sql?
Some recommendations
The query you have provided need to be optimized:
First, using CAST(bkID AS NVARCHAR(MAX)) will affect the performance of the query, because it will not use any index, also casting to NVARCHAR(MAX) will decrease the performance.
bkStatus is a numeric column so you have to use = operator and compare with numeric values (0 or 1 or ...), also the text values provided are defined in the asp tag not in the database, so they are used in the application level not the data level.
if you are using CAST(bkID AS NVARCHAR(MAX)) to search for the bkid column that contains a specific digit (ex: search for 1 -> result 1,10,11,...), then try Casting to a specific size (ex: CAST(bkID as NVARCHAR(10))
It is recommended to use parameterized queries for a better performance and to prevent Sql injection attacks. look at #un-lucky answer
You can use a dictionary Object to store the ID values related to the keywords
Example
Note: The use of CAST and Like will not used any index, this example is based on your requirements (i tried to combine the recommendations i provided with others recommendations)
var dicStatus = new Dictionary<int, string> {
{ 0, "Pending" },
{ 1, "Booked" },
{ 2, "Cancelled" }
// ...
};
string querySql = " SELECT * FROM View_Booking" +
" WHERE CAST(bkID AS NVARCHAR(10)) LIKE #bkID" +
" OR bkSlot LIKE #bkSlot" +
" OR bkStatus = #status";
using (SqlConnection dbConn = new SqlConnection(connectionString))
{
dbConn.Open();
using (SqlCommand sqlCommand = new SqlCommand(querySql, dbConn))
{
sqlCommand.Parameters.Add("#bkID", SqlDbType.VarChar).value ="%" + keyword + "%";
sqlCommand.Parameters.Add("#bkSlot", SqlDbType.VarChar).value ="%" + keyword + "%";
sqlCommand.Parameters.Add("#status", SqlDbType.Int).value = dicStatus.FirstOrDefault(x => x.Value == keyword).Key;
sqlCommand.ExecuteNonQuery();
}
}
Also if BkID is an integer column it is better to use
sqlCommand.Parameters.Add("#bkID", SqlDbType.Int).value = (Int)keyword ;
References & Helpful Links
Like operator for integer
Performance hit using CAST in T-SQL
How much do CAST statements affect performance?
SQL Server: Index columns used in like?
C# Dictionary get item by index
Getting query to work with parameter and "like"
So you need a search box in which user can search by using bkID,bkSlot or bkStatus, If the search text is Booked or Pending we have to add the filter for bkStatus which will be an integer field in the database. right? Few more thing that I have to mention here is the usage of using as well as the parameterization for queries for a smarter and safer way of execution. So I would like to suggest to build and execute the query like the following:
int statusCode = -1;
if(keyword.ToLower() == "booked")
statusCode = 1;
else if(keyword.ToLower() == "pending")
statusCode = 0;
string querySql = " SELECT * FROM View_Booking" +
" WHERE CAST(bkID AS NVARCHAR(MAX)) LIKE #bkID" +
" OR bkSlot LIKE #bkSlot" +
" OR bkStatus = #status";
using (SqlConnection dbConn = new SqlConnection("connectionString here"))
{
dbConn.Open();
using (SqlCommand sqlCommand = new SqlCommand(querySql, dbConn))
{
sqlCommand.Parameters.Add("#bkID", SqlDbType.VarChar).value ="%" + keyword + "%";
sqlCommand.Parameters.Add("#bkSlot", SqlDbType.VarChar).value ="%" + keyword + "%";
sqlCommand.Parameters.Add("#status", SqlDbType.int).value = statusCode;
sqlCommand.ExecuteNonQuery();
}
}
Please note the following:
If you want to include the bkStatus filter for book, Pend etc.. then you have to change the condition accordingly by using .Contains() or .StartsWith() instead for .ToLower()
statusCode is initialized with -1 to avoid bkStatus based filter for all other values
You can use declare function to create a temporary table that has a list of bkStatus.
It will be easier for you to create a query by using bkstatus as a foreign key. After that, you don't have to use cast or like function anymore. It will be a little bit inefficient.
You can try this code below :
declare #bkstatus table (number int primary key , bkstatus varchar(10) )
insert into #bkstatus (number , bkstatus)
values ( 0 , 'Pending'), (1 , 'Booked')
and then using this query :
SELECT * FROM View_Booking v
INNER JOIN #bkstatus b on v.bkstatus = b.number
WHERE b.bkstatus = #keyword
Just another option using CHOOSE() to decode the bkStatus and TRY_CONVERT() to test bkID.
Example
Declare #KeyWord varchar(50) = 'Pending';
Select *
From View_Booking
Where bkID = try_convert(int,#KeyWord)
or bkSlot like '%'+#KeyWord+'%'
or choose(bkStatus+1,'Pending','Booked')=#KeyWord
Returns
bkID bkSlot bkStatus
6 Dinner 0
If keyword will be status name and not status id, I would create BookingStatus table, have bkStatus and bkStatusTitle columns there and join it to the View_Booking. You could easily do LIKE on bkStatusTitle then.
SELECT * FROM View_Booking
WHERE CAST(bkID AS NVARCHAR(16)) LIKE '%' + #keyword + '%'
OR bkSlot LIKE '%' + #keyword + '%'
OR bkStatusTitle LIKE '%' + #keyword + '%'
If keyword will be a string representation of bkStatus, I would just see if the values are same.
As a side note, it's a bad idea to build your SQL queries concatenating user input into it like '%' + keyword + '%'. This is open to SQL injection attacks. It's best to use SQL parameters to pass user input to SQL queries. Using '%' + #keyword + '%' in the SQL bit and in C# something like example below would be much safer.
sqlCommand.Parameters.Add("#keyword", SqlDbType.VarChar, 1000);
sqlCommand.Parameters["#keyword"].Value = searchText;
Parameterized queries also give you a benefit of same query text for multiple requests, which in turn allows SQL Server to cache SQL execution plans and reuse them, giving slightly better performance.
Your bkStatus is integer. In the view you translate the integer value into a user meaningful string. Up to that point it's all ok. Now, for the user to search all you need to do is reverse your translations from strings to integer and search for integers.
Keeping things simple
searchStatusKey = yourVariableHodingTheString == "Booked" ? 1 : 0;
However, to avoid tedious bugs and painless upgrades of code in multiple places (say because they decided to add another status there) I would recommend a translation table here. Something like HashMap (associative array).
var statusMap = new Dictionary<int, string> {
{ 0, "Pending" },
{ 1, "Booked" },
/* can always add more pairs in the future as needed */
};
Now, assuming your parameter is in a variable called searchStatus
searchStatusKey = statusMap.FirstOrDefault(x => x.Value == searchStatus).Key;
Now Provide as bkStatus parameter in the where part the searchStatusKey value and you're done.
select * from View_Booking where bkStatus = << ? >>
I will just focus on this part of your question (Is it the question itself?):
But don't know how to search for bkStatus which is passed as string from c# while it's a int in sql?
One way of dealing with that in SQL is with the help of the CASE clause. In your specific case you could (doesn`t mean should) do something like:
SELECT * FROM View_Booking
WHERE CAST(bkID AS NVARCHAR(MAX)) LIKE '%" + keyword + "%'
OR bkSlot LIKE '%"+keyword+"%'
OR bkStatus = CASE '%"+keyword+"%' WHEN 'Booked' THEN CAST(1 AS INT) WHEN 'Pending' THEN CAST(0 AS INT) WHEN ... THEN ... ELSE ... END'
But I suggest the use of parameters as indicated in #un-lucky's answer. There's a whole lot more we could discuss in terms of best practices here, so I suggest you to take a look at the following articles:
Lookup tables: You stated that bkStatus is of type INT soy I assume you could have more options than Booked or Pending, for example: Reserved or Cancelled. In that case your actual code may become increasingly untidy with every option you add.
Best practices for using ADO.NET: You did not specify how do you access the database from your front end. Even though this article has been around for years, most of its content is still current. I assume this may be helpful.
Building Better Entity Framework: In case you are using Entity Framework to access your database.
Hope it helps.
Make an enum for BookingStatus and make a function that accepts string and returns the enum value. See the below code.
public enum BookingStatus {
[Description("Pending")]
Pending = 0,
[Description("Booked")]
Booked = 1
}
Now the function is as below,
public static T GetValueFromDescription<T>(string p_description)
{
var type = typeof(T);
if (!type.IsEnum) throw new InvalidOperationException();
foreach (var field in type.GetFields())
{
var attribute = Attribute.GetCustomAttribute(field,
typeof(DescriptionAttribute)) as DescriptionAttribute;
if (attribute != null)
{
if (attribute.Description == p_description)
return (T)field.GetValue(null);
}
else
{
if (field.Name == p_description)
return (T)field.GetValue(null);
}
}
throw new ArgumentException("Not found.", "description");
// or return default(T);
}
Now in the parameter in sql query, call this function with parameter as "Booked" or "Pending" and it will return enum BookingStatus.Booked. You can easily extract int value from that.
(int)BookingStatus.Booked // will give 1
It looks as if you are trying to search freely amongst several columns. This is quite a common problem, and the real solution can be found at www.Sommarskog.se on dynamic search conditions.
Your solution looks as if it is vulnerable to SQL Injection. May I suggest that you implement something similar to the stored procedure search_orders_3?
My friend wants to transfer her data from the database to a textbox to her program in c# but it gets an error of this:
"Data is Null. This method or property cannot be called on Null
values."
Here is the code by the way:
sql_command = new MySqlCommand("select sum(lt_min_hours) as TotalLate from tbl_late where (late_date between '" + date_start + "' and '" + date_to + "' and empid = '" + empid + "')", sql_connect);
sql_reader = sql_command.ExecuteReader();
if (sql_reader.Read())
{
textBox_tlate.Text = sql_reader.GetString("TotalLate");
}
else
{
MessageBox.Show("No Data.");
}
From documentation;
SUM() returns NULL if there were no matching rows.
But first of all, You should always use parameterized queries. This kind of string concatenations are open for SQL Injection attacks.
After that, you can use ExecuteScalar instead of ExecuteReader which is returns only one column with one row. In your case, this is exactly what you want.
textBox_tlate.Text = sql_command.ExecuteScalar().ToString();
Also use using statement to dispose your connection and command automatically.
You need to test for DbNull.Value against your field before assigning it to the textbox
if (sql_reader.Read())
{
if(!sql_reader.IsDbNull(sql_reader.GetOrdinal("TotalLate")))
textBox_tlate.Text = sql_reader.GetString("TotalLate");
}
EDIT
According to your comment, nothing happens, so the WHERE condition fails to retrieve any record and the result is a NULL.
Looking at your query I suppose that your variables containing dates are converted to a string in an invalid format. This could be fixed using a ToString and a proper format string (IE: yyyy-MM-dd) but the correct way to handle this is through a parameterized query
sql_command = new MySqlCommand(#"select sum(lt_min_hours) as TotalLate
from tbl_late
where (late_date between #init and #end and empid = #id", sql_connect);
sql_command.Parameters.Add("#init", MySqlDbType.Date).Value = date_start;
sql_command.Parameters.Add("#end", MySqlDbType.Date).Value = date_end;
sql_command.Parameters.Add("#id", MySqlDbType.Int32).Value = empid;
sql_reader = sql_command.ExecuteReader();
This assumes that date_start and date_end are DateTime variables and empid is an integer one. In this way, the parsing of the parameters is done by the MySql engine that knows how to handle a DateTime variable. Instead your code uses the automatic conversion made by the Net Framework that, by default, uses your locale settings to convert a date to a string.
Two small issues, mostly concerning the #AT syntax when dealing with data in ASP.Net (C#). Most online tutorials show a lot of this following type of code but fail to mention (or I may have overlooked) the actual purpose of the ampersand although they do explain the general purpose of the code. In this example, just querying the database to get data pertaining to a certain month for a calender control.
protected DataSet GetCurrentMonthData(DateTime firstDate,
DateTime lastDate)
{
DataSet dsMonth = new DataSet();
ConnectionStringSettings cs;
cs = ConfigurationManager.ConnectionStrings["ConnectionString1"];
String connString = cs.ConnectionString;
SqlConnection dbConnection = new SqlConnection(connString);
String query;
query = "SELECT HolidayDate FROM Holidays " + _
" WHERE HolidayDate >= #firstDate AND HolidayDate < #lastDate";
SqlCommand dbCommand = new SqlCommand(query, dbConnection);
dbCommand.Parameters.Add(new SqlParameter("#firstDate",
firstDate));
dbCommand.Parameters.Add(new SqlParameter("#lastDate", lastDate));
SqlDataAdapter sqlDataAdapter = new SqlDataAdapter(dbCommand);
try
{
sqlDataAdapter.Fill(dsMonth);
}
catch {}
return dsMonth;
}
First issue: What do #firstDate and #lastDate mean or reference in the database string query? Is that referencing the parameters being passed in GetCurrentMonthData method or the actual column name in the database table ?
query = "SELECT HolidayDate FROM Holidays " + _
" WHERE HolidayDate >= #firstDate AND HolidayDate < #lastDate";
Second issue: What is the difference between #firstDate and firstDate? Am I correct in saying firstDate is the actual parameter variable itself?
dbCommand.Parameters.Add(new SqlParameter("#firstDate",
firstDate));
I agree with #har07. That is not a ampersand. An ampersand, as far as I know, looks like this -> &. To answer the question, the 'at' sign (#) is used to indicate variables in a parameterized query in c#.
In the code
dbCommand.Parameters.Add(new SqlParameter("#firstDate",firstDate));
you are assigning the value of the DateTime variable firstDate to the #firstDate variable in your query.
Here's an example that's a bit less confusing(I hope):
Let's say I have a string variable called myName and I want to pass that to my query select * from students where name = #name.
To pass the value of myName to #name in my query, I would do
dbCommand.Parameters.Add(new SqlParameter("#name",myName));
I hope that helps.
Your First Question :
According to the documentation, the name must start with an #:
The ParameterName is specified in the form #paramname.
More Information :
Is it necessary to add a # in front of an SqlParameter name?
Second Question :
First, that isn't ampersand but at sign or commercial at. It is used in this particular context to indicates an SQL parameter name.
And this part showing how you pass the actual value (contained in the firstDate C# variable) to the SQL parameter (#firstDate) :
dbCommand.Parameters.Add(new SqlParameter("#firstDate",
firstDate));
You can read your parameterized SQL query statement like string concatenation but with big advantages (the former save you from SQL injection, arbitrary data type to string conversion with correct formatting*, etc) :
query = "SELECT HolidayDate FROM Holidays " + _
" WHERE HolidayDate >= " + firstDate + " AND HolidayDate < " + lastDate;
*) See that in the string concatenation version above you need to convert firstDate and lastDate to string with correct format according to your RDBMS local settings to make it work.
The main use of #inputvalue in query statement is to avoid sql injection attacks.
If you use normal concatenation method in building query statement, Hackers can easily bypass the statements with sql injection.
Eg:
"Select * from user where username ='" + username.text + "' and password ='" + password.text + "'"
If you use the above statement to validate the user login, think what will happen if the user types a' = 'a') or 'sometext in username textbox and sometext in password box. It will returns more than one record on execution and enters into account area, if you checks with no of return records in user validation.
To avoid this, Microsoft introduced #parameter to build sql query statements. Whatever value you pass in the #parameter is considered as input parameter value and you can't inject sql statements in it.
Answer for your second question
dbCommand.Parameters.Add(new SqlParameter("#firstDate",
firstDate));
The above method is used to replace the #parameter(first argument in Add method) with parameter value(second argument in Add method). It uses # as the delimiter. it checks if there is a word with prefix '#' in query statement, it will be marked as a parameter and it is replaced by the actual value which is passed as the second argument in the Add method.