I am trying to use a parametrized query which takes 2 column names and a table name and retrieves the data from a sql server DB.
The problem is it is not possible to parametrize the table name so i found a solution using a sqlcommandbuilder.quoteIdentifer(tablename) and this bit works...but apparently they don't play nice together.
I get exception containing a single word which is the column name
If i put the column name by hand it works.
What is wrong here?
public List<ItemsWithDescription> GetItemsFromDB(string name, string desc, string tableName)
{
List<ItemsWithDescription> items = new List<ItemsWithDescription>();
try
{
Status = 1;
SqlCommandBuilder builder = new SqlCommandBuilder();
cmd = new SqlCommand("Select #Name, #Desc from "+ builder.QuoteIdentifier(tableName), conn);
cmd.Parameters.AddWithValue("#Name", name);
cmd.Parameters.AddWithValue("#Desc", desc);
using (SqlDataReader dr = cmd.ExecuteReader())
{
while (dr.Read())
{
items.Add(new ItemsWithDescription(dr[name].ToString(), dr[name].ToString() + " | " + dr[desc].ToString()));
}
}
items.Sort((x, y) => string.Compare(x.Item, y.Item));
}
catch
{
Status = -1;
}
return items;
}
Edit:
This works but I would prefer to know why both can't be used together:
cmd = new SqlCommand("Select" +
builder.QuoteIdentifier(name) + "," +
builder.QuoteIdentifier(desc) + "from " +
builder.QuoteIdentifier(tableName), conn);
You can't parameterize column names. You can't do that in regular SQL actually.
What you need is Dynamic SQL.
If you follow the various newsgroups on Microsoft SQL Server, you
often see people asking why they can't do:
SELECT * FROM #tablename
SELECT #colname FROM tbl
SELECT * FROM tbl WHERE x IN (#list)
For all three examples you can expect someone to answer Use dynamic
SQL and give a quick example on how to do it. Unfortunately, for all
three examples above, dynamic SQL is a poor solution. On the other
hand, there are situations where dynamic SQL is the best or only way
to go.
Also take a look Table-Valued Parameters if you use SQL Server 2008 and above.
Related
I'm writing a C# class library in which one of the features is the ability to create an empty data table that matches the schema of any existing table.
For example, this:
private DataTable RetrieveEmptyDataTable(string tableName)
{
var table = new DataTable() { TableName = tableName };
using var command = new SqlCommand($"SELECT TOP 0 * FROM {tableName}", _connection);
using SqlDataAdapter dataAdapter = new SqlDataAdapter(command);
dataAdapter.Fill(table);
return table;
}
The above code works, but it has a glaring security vulnerability: SQL injection.
My first instinct is to parameterize the query like so:
using var command = new SqlCommand("SELECT TOP 0 * FROM #tableName", _connection);
command.Parameters.AddWithValue("#tableName", tableName);
But this leads to the following exception:
Must declare the table variable "#tableName"
After a quick search on Stack Overflow I found this question, which recommends using my first approach (the one with sqli vulnerability). That doesn't help at all, so I kept searching and found this question, which says that the only secure solution would be to hard-code the possible tables. Again, this doesn't work for my class library which needs to work for arbitrary table names.
My question is this: how can I parameterize the table name without vulnerability to SQL injection?
An arbitrary table name still has to exist, so you can check first that it does:
IF EXISTS (SELECT 1 FROM sys.objects WHERE name = #TableName)
BEGIN
... do your thing ...
END
And further, if the list of tables you want to allow the user to select from is known and finite, or matches a specific naming convention (like dbo.Sales%), or belongs to a specific schema (like Reporting), you could add additional predicates to check for those.
This requires you to pass the table name in as a proper parameter, not concatenate or token-replace. (And please don't use AddWithValue() for anything, ever.)
Once your check that the object is real and valid has passed, then you will still have to build your SQL query dynamically, because you still won't be able to parameterize the table name. You still should apply QUOTENAME(), though, as I explain in these posts:
Protecting Yourself from SQL Injection in SQL Server - Part 1
Protecting Yourself from SQL Injection in SQL Server - Part 2
So the final code would be something like:
CREATE PROCEDURE dbo.SelectFromAnywhere
#TableName sysname
AS
BEGIN
IF EXISTS (SELECT 1 FROM sys.objects
WHERE name = #TableName)
BEGIN
DECLARE #sql nvarchar(max) = N'SELECT *
FROM ' + QUOTENAME(#TableName) + N';';
EXEC sys.sp_executesql #sql;
END
ELSE
BEGIN
PRINT 'Nice try, robot.';
END
END
GO
If you also want it to be in some defined list you can add
AND #TableName IN (N't1', N't2', …)
Or LIKE <some pattern> or join to sys.schemas or what have you.
Provided nobody has the rights to then modify the procedure to change the checks, there is no value you can pass to #TableName that will allow you to do anything malicious, other than maybe selecting from another table you didn’t expect because someone with too much access was able to create before calling the code. Replacing characters like -- or ; does not make this any safer.
You could pass the table name to the SQL Server to apply quotename() on it to properly quote it and subsequently only use the quoted name.
Something along the lines of:
...
string quotedTableName = null;
using (SqlCommand command = new SqlCommand("SELECT quotename(#tablename);", connection))
{
SqlParameter parameter = command.Parameters.Add("#tablename", System.Data.SqlDbType.NVarChar, 128 /* nvarchar(128) is (currently) equivalent to sysname which doesn't seem to exist in SqlDbType */);
parameter.Value = tableName;
object buff = command.ExecuteScalar();
if (buff != DBNull.Value
&& buff != null /* theoretically not possible since a FROM-less SELECT always returns a row */)
{
quotedTableName = buff.ToString();
}
}
if (quotedTableName != null)
{
using (SqlCommand command = new SqlCommand($"SELECT TOP 0 FROM { quotedTableName };", connection))
{
...
}
}
...
(Or do the dynamic part on SQL Server directly, also using quotename(). But that seems overly and unnecessary tedious, especially if you will do more than one operation on the table in different places.)
Aaron Bertrand's answer solved the problem, but a stored procedure is not useful for a class library that might interact with any database. Here is the way to write RetrieveEmptyDataTable (the method from my question) using his
answer:
private DataTable RetrieveEmptyDataTable(string tableName)
{
const string tableNameParameter = "#TableName";
var query =
" IF EXISTS (SELECT 1 FROM sys.objects\n" +
$" WHERE name = {tableNameParameter})\n" +
" BEGIN\n" +
" DECLARE #sql nvarchar(max) = N'SELECT TOP 0 * \n" +
$" FROM ' + QUOTENAME({tableNameParameter}) + N';';\n" +
" EXEC sys.sp_executesql #sql;\n" +
"END";
using var command = new SqlCommand(query, _connection);
command.Parameters.Add(tableNameParameter, SqlDbType.NVarChar).Value = tableName;
using SqlDataAdapter dataAdapter = new SqlDataAdapter(command);
var table = new DataTable() { TableName = tableName };
Connect();
dataAdapter.Fill(table);
Disconnect();
return table;
}
I am trying to pull data from my table based on the button a user clicks, so if they click the 1940's button it will pull all products from that decade but I cant get the query to work. It has to do with the #decade parameter because that is where I am getting the user input from but it doesnt like it when I am trying to choose a column using that parameter
ImageButton decadeBtn = (ImageButton)sender;
var decade = decadeBtn.CommandArgument;
yearHead.InnerText = decade.ToString();
string cmd="";
DataSet ds;
if (typeOfArchive == "On Hand")
{
cmd = #"Select * From ARCHIVE_DECADE_TBL WHERE DECADE_#decade=#decade AND PRODUCT_LINE=#Line AND LOCATION is not null;";
}
else if(typeOfArchive == "All Other"){
cmd = #"Select * From ARCHIVE_DECADE_TBL WHERE DECADE_#decade=#decade AND PRODUCT_LINE=#Line AND LOCATION is null";
}
using (OleDbConnection dbConn = new OleDbConnection(connectionString))
using (OleDbDataAdapter dbCmdDecade = new OleDbDataAdapter(cmd, dbConn))
{
dbConn.Open();
dbCmdDecade.SelectCommand.Parameters.Add("#decade", OleDbType.Integer).Value = decade;
dbCmdDecade.SelectCommand.Parameters.Add("#line", OleDbType.VarChar).Value = productLine;
ds = new DataSet();
dbCmdDecade.Fill(ds, "products");
}
No you can't use a parameter in that way. As a rule, you cannot use a parameter to define a column name or a table name (or concatenating it to form a column name). A parameter could only be used to define a value used in the query. (or with a stored procedure to create an SQL Text inside the sp to be executed but that is another more complex story),
However, assuming that you are not allowing your users to type directly the decade value (Sql Injection vulnerability), then it is pretty simple to create a string with the column name desidered and use it in your query.
Add a method that just concatenate together you decade string with your prefix for the DECADE column
private string GetDecadeColumn(string decade)
{
return "DECADE_" + decade;
}
and in you query
if (typeOfArchive == "On Hand")
{
cmd = #"Select * From ARCHIVE_DECADE_TBL WHERE " +
GetDecadeColumn(decade) +
" AND PRODUCT_LINE=#Line AND LOCATION is not null;";
}
else if(typeOfArchive == "All Other"){
cmd = #"Select * From ARCHIVE_DECADE_TBL WHERE " +
GetDecadeColumn(decade) +
" AND PRODUCT_LINE=#Line AND LOCATION is null";
}
So ARCHIVE_DECADE_TBL has columns that are named something like DECADE_1990 with a value of 1990, DECADE_2000 with a value of 2000, etc?
It really should be designed to just be called "DECADE" with the value being 1990/2000/etc, but if that's not possible, you'll have to build your query dynamically. I don't believe those parameters will work to set the column name. They can set a value to check for, but not the column names.
You'll have to build the query out manually in c#, so something like:
cmd = #"Select * From ARCHIVE_DECADE_TBL WHERE DECADE_" + decade + #" = #decade AND PRODUCT_LINE=#Line AND LOCATION is not null;";
Now, if I misunderstood and your column is actually named DECADE_#decade, then I think you'll just need to change your variable so it's not #decade, so something like #mydecade. The conflict there will confuse it.
Sooooo like...
cmd = #"Select * From ARCHIVE_DECADE_TBL WHERE DECADE_#decade=#mydecade AND PRODUCT_LINE=#Line AND LOCATION is not null;";
And then down below:
dbCmdDecade.SelectCommand.Parameters.Add("#mydecade", OleDbType.Integer).Value = decade;
That probably shouldn't have an # in the column name though. :)
I am new to .net/C#. Coming from PHP and some Java, I am finding the new languages interesting and challenging.
I have an issue with a sql string
string query = #"select * from Users where role='member' and
SUBSTRinG(lname, 1, 1) = '"+querystring + "' ORDER BY lname ASC";
Which to me, looks fine. however when run my solution and output the query as it is not working, I get this as my output:
select * from Users where role='member' and SUBSTRinG(lname, 1, 1)
= ' O ' ORDER BY lname ASC
This is output into my Firebug console (the page that uses this query is accessed via AJAX).
Is their a reason my 's are being turned into their code version, ie '''
Thanks
In C# you should be using SqlCommand to excute the query, and to prevent sql injection using the parameter collection.
Your query seems fine - The issue might be the way you are running it or the parameters being supplied. Update your question with more details on what you are expecting vs what is happening, include any error messages generated.
Below is a general guideline of how to get data from a sql table to a c# Data Table object.
SqlConnection conn = new SqlConnection("YourConnectionString");
SqlCommand cmd = new SqlCommand(#"select * from Users where role='member' and
SUBSTRinG(lname, 1, 1) = #query ORDER BY lname ASC");
cmd.Parameters.AddWithValue("#query", querystring);
DataTable resultTable = new DataTable();
try
{
SqlDataAdapter da = new SqlDataAdapter(cmd);
da.Fill(resultTable);
} finally {
if (conn.State != ConnectionState.Closed) conn.Close();
}
Console.WriteLine(String.Format("Matched {0} Rows.", resultTable.Rows.Count));
For SQL injection protection:
You can provide escape sequence for single quotes by replacing them with two single quotes '' so that it will be treated as a single quote inside SQL strings. Otherwise it is considered as a start or end of the string value in SQL.
Replacing single quotes using ' in .net is also preferred but its better going with two single quotes.
In the following sample, I build a query to do a bulk insert into a MySQL database:
const string QUERY = "INSERT INTO contacts (first_name,last_name) VALUES{0};";
public string BuildQuery(IEnumerable<contact> contacts)
{
List<string> values = new List<string>();
foreach (var contact in contacts)
{
values.Add(string.Format("('{0}','{1}')", contact.first_name, contact.last_name));
}
return string.Format(QUERY, string.Join(",", values));
}
The result might look something like this:
INSERT INTO contacts (first_name,last_name) VALUES("J","Kappers"),("A","Temple")
What can I do to write a safer query that isn't prone to SQL Injection?
const string QUERY = "INSERT INTO contacts (first_name,last_name) VALUES" +
BuildQuery(c, contacts);
public string BuildQuery(MySQLCommand c, IEnumerable<contact> contacts)
{
List<string> values = new List<string>();
string query = null;
int i = 0;
foreach (var contact in contacts)
{
i++;
query += "(#firstName" + i + ", #lastName" + i + ")";
c.Parameters.AddWithValue("#firstName" + i, contact.first_name);
c.Parameters.AddWithValue("#lastName" + i, contact.last_name);
if(i < contacts.Count)
query += ",";
}
return query
}
You can see a relevant thread here!. I must have missed somethin trivial, but thats trivial for u to fix. Of course you know what happens when contacts has no elements. I dont see more edge cases. Btw, mind u there is a limit to how many such parameters you can add depending on mysql's max allowed packet size. You can change it, or take care of not exceeding that limit. Cheers! :)
You can escape your MySQL command arguments almost the same way as in normal SQL command. Here is example from official MySQL manual
private void PrepareExample()
{
MySqlCommand cmd = new MySqlCommand("INSERT INTO mytable VALUES (?val)", myConnection);
cmd.Parameters.Add( "?val", 10 );
cmd.Prepare();
cmd.ExecuteNonQuery();
cmd.Parameters[0].Value = 20;
cmd.ExecuteNonQuery();
}
P.S. Whatever you choose - never user string manipulation to add/insert parameters to SQL command. This is main source for SQL-Injections attacks
I'm wanting to get a list of the column names returned from a SQL SELECT statement. Can someone suggest an easy way to do this?
I have a tool that lets users define a query using any SQL SELECT statement. The results of the query are then presented in a custom manner. To set up the presentation, I need to know the column names so that the user can store formatting settings about each column.
Btw, the formatting settings are all being created via ASP.NET web pages, so the query results will end up in .NET if that helps with any ideas people have.
Any ideas?
You should be able to do this using the GetName method. Something like this probably:
SqlDataReader mySDR = cmd.ExecuteReader();
for(int i = 0;i < mySDR.FieldCount; i++)
{
Console.WriteLine(mySDR.GetName(i));
}
This is something you could do entirely from a asp.net page. No special/extra SQL required.
Assuming SQL Server: You could use SET FMTONLY to just return metadata (and not the actual data), e.g.:
USE AdventureWorks2008R2;
GO
SET FMTONLY ON;
GO
SELECT *
FROM HumanResources.Employee;
GO
SET FMTONLY OFF;
GO
You can get by something as following
Note : You need to fill the DataTable of the Dataset.........
DataSet1 DataSet1 = new DataSet1();
DataTable dt = DataSet1.Tables(0);
DataColumn dc = null;
foreach (DataColumn dc_loopVariable in dt.Columns) {
dc = dc_loopVariable;
Response.write(dc.ColumnName.ToString() + " " + dc.DataType.ToString() + "<br>");
}
Another method to just return meta data is
select top 0 * from table
If you know the table name you could try using:
desc <table_name>
I'm assuming you are using SQL Server.
Or as an alternative:
SELECT COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'TableNameGoesHere'
ORDER BY ORDINAL_POSITION
You might want to use the second option if you are going to be using ASP.NET
This will get you more than the column name if you need more information about each column like size, ordinal,etc. A few of the most important properties are listed, but there are more.
Note, DataObjects.Column is a POCO for storing column information. You can roll your own in your code. Also, note I derive the .Net type as well, useful for converting SQL data types to .Net (C#) ones. ConnectionString and TableName would be supplied from a caller.
using (SqlConnection conn = new SqlConnection(ConnectionString))
{
conn.Open();
SqlCommand comm = new SqlCommand("Select top(1) * from " + TableName + " Where 1=0");
comm.CommandType = CommandType.Text;
comm.Connection = conn;
using (SqlDataReader reader = comm.ExecuteReader(CommandBehavior.KeyInfo))
{
DataTable dt = reader.GetSchemaTable();
foreach (DataRow row in dt.Rows)
{
//Create a column
DataObjects.Column column = new DataObjects.Column();
column.ColumnName = (string)row["ColumnName"];
column.ColumnOrdinal = (int)row["ColumnOrdinal"];
column.ColumnSize = (int)row["ColumnSize"];
column.IsIdentity = (bool)row["IsIdentity"];
column.IsUnique = (bool)row["IsUnique"];
//Get the C# type of data
object obj = row["DataType"];
Type runtimeType = obj.GetType();
System.Reflection.PropertyInfo propInfo = runtimeType.GetProperty("UnderlyingSystemType");
column.type = (Type)propInfo.GetValue(obj, null);
//Set a string so we can serialize properly later on
column.DataTypeFullName = column.type.FullName;
//I believe this is SQL Server Data Type
column.SQLServerDataTypeName = (string)row["DataTypeName"];
//Do something with the column
}
}
}