I\'ve been looking around for a way to enter a variable table name and it seems the best way is to use dynamic sql, although it can lead to SQL injection. Can anyone demonst
The simplest way is to simply use string concatenation to put the table name into your SQL query. However, you'd still be open to SQL injection as long as a malicious user can inject an arbitrary string into your query. For instance, let's say your API URL is http://example.org/all?table_name=customer&order=desc, and you are just extracting "table_name" from the URL for the user name.
You should prevent possible SQL injections by having a static server side whitelist of valid table names (e.g. String[] ValidTableNames = new String[] { "customer", "purchase", ... }
, and ensuring that the table_name param value is one of these values before adding it to your SQL query.
The Best way I can think of is two part.
You have a stored procedure that takes the name as a parameter. This this proc first looks up the table name in the catalog (Information_Schema.Table
) and get's the proper name to verify it's a valid.
Then using this look up the stored procedure constructs the required SQL and not from the parameter.
Using this look up you are essentially protecting against non valid table names being passed in.
CREATE PROCEDURE RunSQL
(@table NVARCHAR(50))
AS
BEGIN
DECLARE @validTableName NVARCHAR(50)
SELECT
@validTableName = TABLE_NAME
FROM
INFORMATION_SCHEMA.TABLES
WHERE
TABLE_NAME = @table
AND SCHEMA_NAME = 'dbo' -- here you can limit or customise the schema
IF @validTableName IS NULL
RAISE ... raise an exception
DECLARE @dynamicSql NVARCHAR(100) = REPLACE('SELECT * FROM dbo.[#TABLE#]', '#TABLE' @validTableName)
EXECUTE sp_executesql .... @dynamicSql ....
END
You can use extend this to validate, schema, columns etc...
You ultimately need to construct your own SQL and do the protection against injection. There is no getting around that.
It seems that you have to use dynamic SQL, which basically means you're going to have to concatenate the table name into a query string and run it through the 'sp_executesql' stored procedure in TSQL.
Using SqlCommand is not dynamic SQL. Although you are dynamically building an SQL string, you are still running a plain old SQL string in the end.
To do so safely and project against SQL injection, you must ensure the table name is valid, and you must do so yourself through any means necessary. Fortunately, you have 3 good options, one of which is very easy and virtually fool-proof.
As others have mentioned, you could:
I had a similar question about dropping a table by name, and that's where the QUOTENAME function was mentioned: https://stackoverflow.com/a/19528324/88409
To use dynamic SQL, you would actually have to do something like this:
string sql =
"declare @query nvarchar(max) = 'SELECT * FROM ' + QUOTENAME(@tablename) + ' WHERE ' + QUOTENAME(@columnname) + ' = @cv'; " +
"declare @params nvarchar(500) = N'@cv nvarchar(500)'; " +
"exec sp_executesql @query, @params, @cv=@columnvalue;";
SqlCommand command = new SqlCommand( sql, conn );
command.Parameters.AddWithValue( "@tablename", tableName );
command.Parameters.AddWithValue( "@columnname", columnName );
command.Parameters.AddWithValue( "@columnvalue", columnValue );
If by chance the SqlCommand class doesn't support such a complex query with 'declare' statements, then you'll need to just move the value of the 'sql' string into a stored procedure that takes those same three parameters then call it by name by setting the SqlCommand.CommandType to StoredProcedure like so:
SqlCommand command = new SqlCommand( "MyStoredProcedureName", conn );
command.CommandType = System.Data.CommandType.StoredProcedure;
command.Parameters.AddWithValue( "@tablename", tableName );
command.Parameters.AddWithValue( "@columnname", columnName );
command.Parameters.AddWithValue( "@columnvalue", columnValue );
I once created a web site that allowed certain users access to specific tables. Using the ASP Membership and the user ID I had a table that built a relationship between the USER ID whenb the user account was created. The table name they were allowed to access was assigned at that time. The table names were hardcoded and built dynamically based on parameters of the project. So a table of tblCustomersNew or tlbInventoryOld, etc. was linked to the UserID.
When a user logged in, I simply queried the UserID against the SQL table to get the table name, then added it to the query or passed it to the stored procedure.
Works great, all happens behind an authenticated user account as well.
Edit: I just wanted to add that this avoids SQL injection, because your table names are predefined in the project and stored in the database. You only need to query them with the authenticated user account to access the tables that the user has rights to.