I have a dynamic SQL for searching records in Oracle, and VS2017 code analysis reports warning about using parameterized SQL query for this line (1st line, this code works):
string SQL = "SELECT " + string.Join(",", my_columns.ToArray()) + " FROM MyTable ";
string where_condition = " WHERE ";
//the rest of code follows as this...
if (!string.IsNullOrEmpty(textbox1.Text))
{
SQL = string.Concat(SQL, where_condition, " Name like :name");
cmd.Parameters.Add(new OracleParameter("name", string.Concat("%", textbox1.Text, "%")));
where_condition = " AND ";
} //...
So, I tried to put column names as parameters because of warning, but then I get ORA-01036- illegal variable name/number error:
string SQL = "SELECT :columns FROM MyTable ";
cmd.Parameters.Add(new OracleParameter("columns", string.Join(",",
my_columns.ToArray())));
string where_condition = " WHERE ";
What is wrong, maybe column names cannot be passed as parameters ? Or is there any other way to avoid warning in VS code analysis ?
You're right - column names can't be passed as parameters. That part has to be done dynamically, unless you want to change your database structure very significantly. (You could have one column with a value which is the logical column name, and one column for the value. I'm not recommending this - it's very much not how databases are intended to be used.)
The warning you're getting is there to avoid SQL injection attacks. When building the query dynamically, you have to do that differently. You basically need to make sure you have a whitelist of column names, and only build up SQL including those names.
You may well still get a code analysis warning at that point, but you should disable that just for this piece of code, with a comment explaining that you understand the warning, and what you've done to remove the risk of SQL injection attacks.
Related
I am trying to create a database using this code:
var createDatabaseQuery = "exec ('CREATE DATABASE ' + #db)";
var sqlCommand = new SqlCommand(createDatabaseQuery, sqlConnection);
sqlCommand.Parameters.Add("#db", SqlDbType.Text);
sqlCommand.Parameters["#db"].Value = "DbName";
sqlCommand.ExecuteNonQuery();
The above works perfectly but I try to do concatenation as follows, it throws an exception:
var sqlCommand = new SqlCommand(createDatabaseQuery, sqlConnection);
sqlCommand.Parameters.Add("#db", SqlDbType.Text);
sqlCommand.Parameters["#db"].Value = "DbName" + CustomId; //Doing the concatenation here
Exception:
System.Data.SqlClient.SqlException (0x80131904): Incorrect syntax near '-'
I know, there could be better ways to do it. But is there any way that I can make the above work?
Time to learn the real intricate sides of SQL.
The way you wan t to write it - is incorrect in multiple ways. DEBUG THE SQL. Do not care about the C# code, look at the SQL...
In your case - naming conversions.
Tables, databases etc. can not contains a "-" - OR they must be marked.
CREATE DATABASE my-database -> Error
CREATE DATABASE [my-database] -> correct, the [] brackets names and thus... no processing of the "-" is tried.
This is quite clear in the SQL documentation, but a part many people overlook (mostly because in most cases it does not matter). They likely wonder why generators bracket every item name (Database, schema, table, COLUMN). Well, that is the reason. What do you think "-1" means? Minus one, processing, or part of a name - the server has no way to determine this. Help him.
You need to make sure you are quoting the name correctly using QUOTENAME, because it contains characters that need escaping.
var createDatabaseQuery = "exec ('CREATE DATABASE ' + QUOTENAME(#db))";
Also, the parameter type should be nvarchar(128)
sqlCommand.Parameters.Add("#db", SqlDbType.NVarChar, 128).Value = "DbName" + CustomId;
Fairly new to SQL. Say I have a table with field1 of type string (VARCHAR) and field2 of type integer (INTEGER). As I understand it, you're supposed to use ='newValue' for string fields, and =newValue for integer fields (and =#newValue# for date fields).
Is there a trick that allows generic construction of the SET clause without needing to know the type of the field being updated in advance?
void UpdateDatabase(string field, string oldValue, string newValue)
{
// Construct without needing work out whether '', ## or (nothing) is required?
string sqlUpdate = (
"UPDATE MyTable" +
" SET " + field + " = " + newValue +
" WHERE " + field + " = " + oldValue);
// Execute the statement on the database
}
This might be used as follows:
UpdateDatabase("field1", "Danger Mouse!", "Mickey Mouse");
UpdateDatabase("field2", "15", "7");
Your code will need to be a lot more complex if you want to predetermine the datatypes of the fields you're inserting into.
Most SQL flavours have some kind of catalog, so for example on MS SQL Server you would need do something like:
SELECT DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'MyTable' AND COLUMN_NAME = 'field'
For every column you're about to write to, then apply the single-quote or not as per the data type.
There's ways and means of doing this, and depending on how many different fields you want to write at the same time will depend how complex the logic has to be to get the column datatypes.
For a large number of columns ideally you would gather all the column names you're about to write to into a table first, then use that table to query the datatypes
However, in SQL Server you can normally get away with putting single-quotes around everything, even integers and floats. SQL Server will intelligently remove the single quotes as needed.
It looks like you're using MySQL since you're required to put hashes around dates, in which case I am sorry for your loss.
I want to insert multiple rows into my database, I tried to use parameters but I did something wrong.
In debug mode, the parameters do have values, but once the insert is done, I still have #compte, #mo, #userimp in my database instead of their values.
string INSERTDELAMORTPart2 = "'123'," + "'#compte'" + "," + "'#mo'" + "," + "'ubs'" + "," + "'2'" + "," + "'#userimp'" + "," + "'22.10.17'" + ",'";
OdbcCommand Query = new OdbcCommand(INSERTDELAMORTPart2, InfoConnexion);
Query.Parameters.AddWithValue("#compte", _cb_Compte.SelectedItem);
Query.Parameters.AddWithValue("#mo", MondantResultat);
Query.Parameters.AddWithValue("#userimp", UserImp);
Query.ExecuteNonQuery();
I have tried Parameters.Add, and also Parameters["#name"].Value, but the result stays the same.
What am I doing wrong ?
EDIT :
I have an Oracle database and I need the single quote to insert my values.
I have tried to pass my whole INSERT string into a parameter but it doesn't work.
I'm about to give up and just replace the ' by '' even if it's a bad idea.
A few things.
If you are using Oracle, I highly recommend you use ODP.net or managed ODP.net, not ODBC. ODBC should work, but the native drivers will work more efficiently and will avoid any oddities you might with with the abstraction layer ODBC provides. Managed ODP.net has the additional advantage of not requiring an Oracle client be installed on the target machine
SQL Server (and other databases) use the "#" as a parameter, but Oracle uses the colon ":" instead. Some databases (like PostgreSQL) will take either. While the parameter in your SQL requires the colon, when you use assign the parameter in .NET you need to omit it. Again, this is an Oracle thing you won't find in other databases.
Your SQL doesn't appear to have any command in it... only values. Where is your "insert into?"
Especially with Oracle, you want to be explicit about your datatypes... so use Add instead of AddWithValue (which is actually an overload of Add in ODP.net). Alternatively, you can set the datatype later.
The converted code would look something like this:
string insert = "insert into my_table values (:COMPTE, :MO, :USERIMP)";
using (OracleCommand cmd = new OracleCommand(insert, Connection))
{
// no ":" in the parameter declarations
cmd.Parameters.Add(new OracleParameter("COMPTE", OracleDbType.Int32));
cmd.Parameters.Add(new OracleParameter("MO", OracleDbType.Varchar2));
cmd.Parameters.Add(new OracleParameter("USERIMP", OracleDbType.Varchar2));
cmd.Parameters[0].Value = 123;
cmd.Parameters[1].Value = "ubs";
cmd.Parameters[2].Value = "22.10.17";
cmd.ExecuteNonQuery();
}
I guessed at your datatypes -- make sure they match your DDL when you actually implement this, and if these aren't the only three columns, update your insert command appropriately.
If for some reason I misunderstood, and this is not Oracle, I still recommend use of the native driver rather than ODBC. When you use ODBC, you assume a lot about the target machine -- assumptions that may not turn out to be true.
The command should look something like this:
insert into t(compte, mo, userimp) -- of course the table and columns should be the right names
values (#compte, #mo, #userimp);
No single quotes are needed.
I would recommend against using AddWithValue(). Instead, be explicit about the types.
You have this:
string INSERTDELAMORTPart2 = "'123'," + "'#compte'" ...
Note the single quotes around the parameter names. They should not be there. You want this instead:
string INSERTDELAMORTPart2 = "'123'," + "#compte"
I am writing a generic sqldump utility that takes a DSN and a table name and dumps the contents to a file. It's an internal app so SQL Injection is not a serious threat, but I don't want to have to worry about it. The thing is, the variable part of the query is actually the tablename, so the query is going to look like:
select * from [tablename];
...which I don't imagine will work well with the OdbcCommand's parameterized query support. I am also trying to support all types of DSN's as generically as I can, regardless of the driver on the other side of the DSN.
Is there some universal way to sanitize my tablename input to protect against all SQL Injection using the OdbcCommand object?
I'd check the user input against the list of tables you know are there, using code roughly like what's posted here to retrieve the table list (code from the link included for posterity):
class Program
{
static void Main(string[] args)
{
string connectionString = GetConnectionString();
using (SqlConnection connection = new SqlConnection(connectionString))
{
// Connect to the database then retrieve the schema information.
connection.Open();
DataTable table = connection.GetSchema("Tables");
// Display the contents of the table.
DisplayData(table);
Console.WriteLine("Press any key to continue.");
Console.ReadKey();
}
}
That said, I agree with #KeithS above. This is probably a Bad Idea.
The only special character in a [] quoted identifier for SQL Server is ], and it can be escaped by passing ]]. So for that, "select * from [" + tableName.Replace("]", "]]") + "];" should be safe. Other systems, however, may use other escape mechanisms, so this is not a full solution if you want to connect to a different type of database.
Alternatively, consider each character, and see if it is a valid character for table names you wish to support. If you say table names only contain letters, digits, and/or whitespace, then SQL injection is not possible, because you'll never be able to unquote the [quoted table name].
You could first query the information_schema to find out if the table exists:
select *
from information_schema.tables
where table_schema = #your_database_name and table_name = #table_name
This query can be parameterized and is NOT prone to SQL injections.
Following that, you can issue your select * from #table_name query.
If the table name is enclosed in [ ] then just do not allow table names to contain "]". ] could be used by malicious people to terminated the sql command and to introduce dangerous code.
If you are constructing the sql like this
string sql = "SELECT * FROM [" + tablename + "]";
and the tablename is defined like this
string tablename = "tablename]; DELETE FROM [tablename";
The resulting sql becomes
SELECT * FROM [tablename]; DELETE FROM [tablename];
However, this is only possible if the table name contains a ].
Note:
If you are replacing string values like this, then replacing a single quote by two single quotes makes it safe too.
string sql = "SELECT * FROM tbl WHERE Name = '" + input.Replace("'","''") + "'";
I have an SQL query of this form
string cmdText = "Select * from " + searchTable
+ "WHERE " + searchTable
+ "Name =' " + searchValue + "'";
Basically what I am trying to do is get a particular actor's info from the database's Actors table. The variable searchTable has the value 'Actor' which is the table name and searchValue has the actor's name (which is represented by the ActorName attribute in the Actor's table, here I am trying to form the name of the attribute by concatenating the words 'Actor' and 'Name' )
So, well, all this concatenation results in (or at least should result in) a query of the form:
Select * from Actor where ActorName ='some actor';
But when I try to run this it gives me the error "Incorrect syntax near '=' " in the browser. Could anyone please help?
You can put (and should!) parameters into your SQL queries for the values in e.g. your WHERE clause - but you cannot parametrize stuff like your table name.
So I'd rewrite that query to be:
SELECT (list of columns)
FROM dbo.Actor
WHERE ActorName = #ActorName
and then pass in just the value for #ActorName.
If you need to do the same thing for directors, you'd have to have a second query
SELECT (list of columns)
FROM dbo.Directors
WHERE DirectorName = #DirectorName
Using parameters like this
enhances security (prohibits SQL injection attacks!)
enhances performance: the query plan for that query can be cached and reused for second, third runs
PS: the original problem in your setup is this: you don't have any space between the first occurence of your table name and the WHERE clause - thus you would get:
SELECT * FROM ActorWHERE ActorName ='.....'
If you really insist on concatenating together your SQL statement (I would NOT recommend it!), then you need to put a space between your table name and your WHERE !
Update: some resources for learning about parametrized queries in ADO.NET:
The C# Station ADO.NET Tutorial / Lesson 06: Adding Parameters to Commands
Using Parameterized Queries with the SqlDataSource
You shouldn't concatenate string to SQL, as this will open you up to SQL Injection attacks.
This is a rather long read about dynamic SQL, but worth reading to understand the risks and options.
You should be using parameterized queries instead, though the only way to use a table name as a parameter is to use dynamic SQL.
I urge you to change your approach regarding table names - this will lead to problems in the future - it is not maintainable and as I mentioned above, could open you to SQL Injection.
The error you are seeing is a result of the concatenation you are doing with the "Where " clause - you are missing a space before it. You are also adding a space after the ' in the parameter ending with "Name".
Your resulting string, using your example would be:
Select * from ActorWHERE ActorName =' some actor';
There is a blank missing and one too much:
searchTable + "Name =' "
should read
searchTable + " Name ='"
Beside that, use SQL parameters to prevent SQL injection.
string cmdText = "Select * from " + searchTable + " WHERE Name = '" + searchValue + "'";