I have the following code:
SqlDataAdapter adapter = new SqlDataAdapter("SELECT TOP 0 * FROM [tableName]", conn);
DataTable dt = new DataTable();
adapter.FillSchema(dt, SchemaType.Mapped);
I am using this to build a DataTable of the table schema.
Normally, when people are asking about SQL injection, they are talking about query params :), my question is about the table name:
SELECT TOP 0 *
FROM [tableName]
[tableName] is actually going to be dynamic / determined at runtime (this is for a framework btw)...
The tableName passed into the method is not trusted, so I want to make sure there isn't any funny business going on?
Do I have to manually scrub the table name (and I'm sure I'll miss something)? Or is there a built in method for that? Or somehow to prevent the SQL injection on the table name?
You could just go with the rules for valid table names
128 char - letters, numbers, and limited other special characters
In those rules I don't think you could do an injection
CreateTable see table_name
ValidIdentifier
You can use the following query to get a list of all tables in the database and use that as a white list:
SELECT TABLE_NAME FROM <DATABASE_NAME>.INFORMATION_SCHEMA.Tables
WHERE TABLE_TYPE='BASE TABLE'
Replace
<DATABASE_NAME>
with your database name.
If someone attempts to input a table name which is not in that list, then don't execute the query.
Update
As for the "multiple connection strings" case, you can also use a simple query to check the current database name:
SELECT db_name()
So you should be able to easily craft a method that simply gets a list of valid table names from the current database, no matter if the connection-string/db-name is dynamic or not. And in the grand scheme of things, I doubt that these two queries are going to affect the performance of your application, like you said, you could easily cache the table list, if necessary.
The tableName passed into the method is not trusted, so I want to make
sure there isn't any funny business going on?
One method is to first execute a parameterized query, passing the table name as a parameter (nvarchar max length 128) to the QUOTENAME function:
SELECT QUOTENAME(#TableName) AS ScrubbedTableName;
The returned value can then be used in your TableAdapter query without the risk of SQL injection. However, this will not prevent disclosure of meta data if a table other than one expected is specified.
You can keep sqlDataAdapter and use
Adapter.selectcommand.parameters.add(#"whatever", value)
Edit: Sorry! I missed the part about this being related to a table name, this code doesnt work. :( I apologize.
Related
I'm trying to set up so that the table name is passed to the command text as a parameter, but I'm not getting it to work. I've looked around a bit, and found questions like this: Parameterized Query for MySQL with C#, but I've not had any luck.
This is the relevant code (connection == the MySqlConnection containing the connection string):
public static DataSet getData(string table)
{
DataSet returnValue = new DataSet();
try
{
MySqlCommand cmd = connection.CreateCommand();
cmd.Parameters.AddWithValue("#param1", table);
cmd.CommandText = "SELECT * FROM #param1";
connection.Open();
MySqlDataAdapter adap = new MySqlDataAdapter(cmd);
adap.Fill(returnValue);
}
catch (Exception)
{
}
finally
{
if (connection.State == ConnectionState.Open)
connection.Close();
}
return returnValue;
}
If I change:
cmd.CommandText = "SELECT * FROM #param1";
to:
cmd.CommandText = "SELECT * FROM " + table;
As a way of testing, and that works (I'm writing the xml from the dataset to console to check). So I'm pretty sure the problem is just using the parameter functionality in the wrong way. Any pointers?
Also, correct me if I'm mistaken, but using the Parameter functionality should give complete protection against SQL injection, right?
You can not parameterize your table names, column names or any other databse objects. You can only parameterize your values.
You need to pass it as a string concatenation on your sql query but before you do that, I suggest use strong validation or white list (only fixed set of possible correct values).
Also, correct me if I'm mistaken, but using the Parameter
functionality should give complete protection against SQL injection,
right?
If you mean parameterized statements with "parameter functionality", yes, that's correct.
By the way, be aware, there is a concept called dynamic SQL supports SELECT * FROM #tablename but it is not recommended.
As we have seen, we can make this procedure work with help of dynamic
SQL, but it should also be clear that we gain none of the advantages
with generating that dynamic SQL in a stored procedure. You could just
as well send the dynamic SQL from the client. So, OK: 1) if the SQL
statement is very complex, you save some network traffic and you do
encapsulation. 2) As we have seen, starting with SQL 2005 there are
methods to deal with permissions. Nevertheless, this is a bad idea.
There seems to be several reasons why people want to parameterise the
table name. One camp appears to be people who are new to SQL
programming, but have experience from other languages such as C++, VB
etc where parameterisation is a good thing. Parameterising the table
name to achieve generic code and to increase maintainability seems
like good programmer virtue.
But it is just that when it comes to database objects, the old truth
does not hold. In a proper database design, each table is unique, as
it describes a unique entity. (Or at least it should!) Of course, it
is not uncommon to end up with a dozen or more look-up tables that all
have an id, a name column and some auditing columns. But they do
describe different entities, and their semblance should be regarded as
mere chance, and future requirements may make the tables more
dissimilar.
Using table's name as parameter is incorrect. Parameters in SQL just works for values not identifiers of columns or tables.
One option can be using SqlCommandBuilder Class, This will escape your table name and not vulnerable to SQL Injection:
SqlCommandBuilder cmdBuilder = new SqlCommandBuilder();
string tbName = cmdBuilder.QuoteIdentifier(tableName);
You can use the tbName in your statement because it's not vulnerable to SQL Injection now.
My Code:
SqlCommand command = new SqlCommand("SELECT min(Score) FROM MenAthletics WHERE [(#sportevent)] < (#result);", connect);
command.Parameters.AddWithValue("#sportevent", sportEvent);
command.Parameters.AddWithValue("#result", result);
the #result works fine (just a double variable)
the #sportevent doesnt't work (error: invalid columnname) (sportEvent is a string)
how can I choose a column by giving in a string?
You can parameterize values in SQL statements, but you cannot parameterize column or table names. You need to change the column name in the SQL string itself, for example, with string.Format:
SqlCommand command = new SqlCommand(
string.Format("SELECT min(Score) FROM MenAthletics WHERE [{0}] < (#result);", sportEvent)
, connect
);
command.Parameters.AddWithValue("#result", result);
Make sure that the column name does not come from user's input, otherwise you would open up your code to SQL injection attacks. In case the column name does come from user's input, you can validate the string against a list of available table columns, which could be made statically or by examining the structure of your table at runtime.
You could dynamically build the SQL query, instead of passing the column name as a parameter.
You can't use a column name as a parameter; you should instead consider constructing your query this way:
SqlCommand command =
new SqlCommand(
String.Format(#"SELECT min(Score)
FROM MenAthletics WHERE [{0}] < #result;",
sportEvent),
connect);
command.Parameters.AddWithValue("#result", result);
This kind of sql is called "dynamic sql" and can be an effective way of constructing queries on the fly.
However, there are pitfalls. As well as validating the user input, also make sure that the user you are connecting to the database with only has enough permissions to carry out the actions you want to do.
Another approach, which is less elegant, but can be placed directly into a stored procedure, is to use a CASE statement;
For example:
SELECT min(Score)
FROM MenAthletics
WHERE
CASE
WHEN #sportEvent = 'SomeColumnName' THEN SomeColumnName
WHEN #sportEvent = 'SomeColumnName2' THEN SomeColumnName2
END < #result;
This gets very tedious to both create and maintain on large tables. The advantage is that the query is not dynamic.
This is because value in the sportEvent string which you are passing as a parameter is not matching with actual column existing in your database table.
Make sure that both of them matches and then only this error will go.
Otherwise dont pass table's column name as a parameter, directly write it in the query and let its column value be a parameter.
Hope it helps.
I am working with C#. I need to write a select inline query.
The table name should be taken from config. I cannot write a stored procedure.
SqlCommand myCommand= new SqlCommand();
myCommand.CommandText = "Select * from " + tableName;
myCommand.CommandType = CommandType.Text;
myCommand.Connection = connString;
How to avoid sql injection ?
Just create a query with a real param and check for the existence of the tablename - somthing like:
SELECT COUNT(*) FROM SYS.TABLES WHERE NAME = #pYOURTABLENAME
IF that returns 1 then you know that the table exists and thus can use it in the SELECT you showed in the question...
However I strongly recommend to try anything to get rid of the need for any code prone to SQL injection!
I would ensure table name contains only these characters:
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz[]. -_0123456789
E.g.,
Regex regex = new Regex(#"^[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\[\]. -_0123456789]{1,128}$");
if (!regex.IsMatch(tableName)) throw new ApplicationException("Invalid table name");
To do a more comprehensive job including non-English languages see this reference on what a valid table names:
http://msdn.microsoft.com/en-us/library/ms175874.aspx
You need to verify that tableName is appropriate. After some sanity checking (making sure it has no spaces, or other disallowed characters for table names, etc), I would then first query the database for the names of all tables, and programmatically verify that it is one of those table names. Then proceed to run the query you show.
I'd look at moving the SQL to a stored proc and review this article by Erland Sommarskog as it has some great ideas for using dynamic SQL within stored procs. I'd certainly look into it. It discusses a lot of the issues around SQL injection and possible alternatives to dynamic SQL.
He also has another great article on ways to use arrays in stored procs. I know you didn't ask for that, but I often refer to these two articles as I think they are quite insightful and provide you with some useful ideas with regards to writing your procedures.
In addition to some of the suggestions linked above, I still have some basic parameter sanitisation mechanisms that I use if I am ever using dynamic SQL. An example of this is as follows;
IF LEN(#TableName) < 5 OR LEN(#TableDisplayName) < 5
BEGIN
RAISERROR('Please ensure table name and display name are at least 5 characters long', 16, 1)
END
IF NOT (#TableName not like '%[^A-Z]%')
BEGIN
RAISERROR('The TableName can only contain letters', 16, 1)
END
IF NOT (#TableDisplayName not like '%[^0-9A-Z ]%')
BEGIN
RAISERROR('The TableDisplayName can only contain letters, numbers or spaces', 16, 1)
END
This combined with using parameters within your dynamic sql and then executing using sp_executesql certainly help to minimise the possibility of a SQL injection attack.
Is there anyway to just have the GUID be part of the SQL Query itself without using a parameter?
Let me explain exactly what I am trying to do and why. I am working with an existing application that uses an ADO.Net to create and connect to a SQLite database. The current structure of the database and the method for querying it are horrendous. I am in the midst of a redesign of how it works fundamentally. However, that is not something that can be accomplished quickly. While that redesign is being completed, I have a situation that needs a band-aid solution. As a heads up, the reasoning behind the initial implementation of the code is obscure and appears to have been built by someone who had little database knowledge. Refactoring the entire database to not need this situation is the ultimate solution, but for now I'm just looking to work within the existing structure.
The design of the database relies on GUID's to uniquely identify rows. To perform filtered retrieval of the data the system dynamically builds a command that has an IN clause listing the GUIDs that should be retrieved. Right now the GUIDs are inserted into the query by using a parameter with type GUID, so the query will look like
SELECT * FROM data_table WHERE guid_col IN( ?, ?, ?, ?)
The problem comes in when I need to retrieve a relatively large amount of information. SQLite has a limitation of a 1000 parameters in a single query. If I need to pass in more than a 1000 GUIDs for the retrieval the query will just break. When building the above string it loops over a list of GUID's to insert the question marks and create the paramters. My band-aid fix to the problem was going to be to have the GUID value directly inserted into where the question marks are currently and forego having parameters in the query. After all, its kind of using parameters for a purpose they don't need to be used for.
The problem with this 'solution' is that I can't seem to get the GUID to match the data in the column i.e. the query is always returning null. I understand that GUIDs are not a native type to SQLite and underneath it is actually being represented as a BLOB (yes I am sure we are using the BLOB and not the string representation). Yet, I've been unable to get the query to execute correctly.
I've tried all the following so far:
I've tried calling ToString() on the GUID so the query looks like
SELECT * FROM data_table WHERE guid_col IN
( 'b5080d4e-37c3-4286-9c3a-413e8c367f36', 'aa0ff789-3ce9-4552-9840-5ed4d73c1e2c')
I've tried calling ToString("N") on the GUID so the query looks like
SELECT * FROM data_table WHERE guid_col IN
( 'b5080d4e37c342869c3a413e8c367f36', 'aa0ff7893ce9455298405ed4d73c1e2c')
I've tried calling ToString("B") on the GUID so the query looks like
SELECT * FROM data_table WHERE guid_col IN
( '{b5080d4e-37c3-4286-9c3a-413e8c367f36}',
'{aa0ff789-3ce9-4552-9840-5ed4d73c1e2c}')
I've tried calling ToByteArray() on the GUID and putting the result into the query by adding each byte to the string calling ToString("X") on each byte so the query looks like
SELECT * FROM data_table WHERE guid_col IN
( '4ED8B5C33786429C3A413E8C367F36', '89F7FAAE93C524598405ED4D73C1E2C')
In reading the SQLite documentation I read the following "BLOB literals are string literals containing hexadecimal data and preceded by a single "x" or "X" character. If I try to apply this to my query so it looks like
SELECT * FROM data_table WHERE guid_col IN
( x'4ED8B5C33786429C3A413E8C367F36', x'89F7FAAE93C524598405ED4D73C1E2C')
I get an error saying that "x" is not a recognized symbol.
Is it possible to get the GUID into the query string without the use of the parameter?
I suggest that you use a temporary table for something like this. For example...
PRAGMA temp_store = MEMORY;
CREATE TEMP TABLE tbl_guids (guid_col text);
INSERT INTO tbl_guids VALUES ('b5080d4e-37c3-4286-9c3a-413e8c367f36');
INSERT INTO tbl_guids VALUES ('aa0ff789-3ce9-4552-9840-5ed4d73c1e2c');
... more inserts ...
SELECT * FROM data_table WHERE guid_col IN ( SELECT guid_col FROM tbl_guids);
DROP TABLE tbl_guids;
Make sure to wrap a transaction around all of the INSERT INTO statements. This will help with performance by a ton. Also SQLite allows for temp tables to exist entirely in memory by setting PRAGMA temp_store = MEMORY
If you have a multiple users accessing the table at the same time sharing the same connection, you will need to create some uniqueness with the temp table such as adding a random number to the table name such as tbl_guids_9087.
You can pass GUID's in as strings if you set "BinaryGuid=False" in the connection string.
If this is done, "ToString()" should work fine.
When I built my .xsd, I had to choose the columns for each table, and it made a schema for the tables, right? So how can I get that Select string to use as a base Select command for new instances of dataadapters, and then just append a Where and OrderBy clause to it as needed?
That would keep me from having to keep each DataAdapter's field list (for the same table) in synch with the schema of that table in the .xsd file.
Isn't it common to have several DataAdapters that work on a certain table schema, but with different params in the Where and OrderBy clauses? Surely one does not have to maintain (or even redundently build) the field list part of the Select strings for half a dozen DataAdapters that all work off of the same table schema.
I'm envisioning something like this pseudo code:
BaseSelectString = MyTypedDataSet.JobsTable.GetSelectStringFromSchema() // Is there such a method or technique?
WhereClause = " Where SomeField = #Param1 and SomeOtherField = #Param2"
OrderByClause = " Order By Field1, Field2"
SelectString=BaseSelectString + WhereClause + OrderByClause
OleDbDataAdapter adapter = new OleDbDataAdapter(SelectString, MyConn)
Each table has a default query (The one on top with the check on it). When you dragged your tables in to the dataset to create the query, it wrote a SQL statement which it uses to schema your table. Keep that query simple, you might not actually use it in code, and you can always edit that query to update the table schema.
Every time you open the default query it connects to your datasource and allows you to select new columns that weren't in there before. If you want to update your existing columns, delete all the columns out of the table before you attempt to open the query. When you save the query, your updated columns get added back.
Make sure your connection string has permissions to view column information.
You can add multiple queries to a single TableAdapter. TableAdapters in the designer appear sectioned with a table schema at the top, and queries on the bottom. The default query will control which columns are available for output from the other queries. To add an additional query, right click on the TableAdapter and select "Add->Query" or if you are selecting the bottom part of the TableAdapter you can select "Add Query...". Any new SQL query you create will start off with the SQL from the default query. You give each new query a method name which you can use instead of the default query's "Fill" or "GetData" methods. The assumption is that each new query will have a result set that matches the default query even though they can have different "where" clause parameters.
In short
You may have a single TableAdapter for each table, just add multiple queries.
Each additional query can have different "Where" clause parameters as long as they all return the same columns.