Nig*_*gel 2 c# sql sql-server sql-injection prepared-statement
我正在编写一个 C# 类库,其中的功能之一是能够创建与任何现有表的架构相匹配的空数据表。
例如,这个:
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;
}
Run Code Online (Sandbox Code Playgroud)
上面的代码可以工作,但是它有一个明显的安全漏洞:SQL注入。
我的第一直觉是像这样参数化查询:
using var command = new SqlCommand("SELECT TOP 0 * FROM @tableName", _connection);
command.Parameters.AddWithValue("@tableName", tableName);
Run Code Online (Sandbox Code Playgroud)
但这会导致以下异常:
必须声明表变量“@tableName”
在 Stack Overflow 上快速搜索后,我发现了这个问题,它建议使用我的第一种方法(带有 sqli 漏洞的方法)。这根本没有帮助,所以我继续搜索并发现了这个问题,它说唯一安全的解决方案是对可能的表进行硬编码。同样,这不适用于我的类库,它需要适用于任意表名。
我的问题是:如何参数化表名而不受到 SQL 注入漏洞?
任意表名仍然必须存在,因此您可以首先检查它是否存在:
\nIF EXISTS (SELECT 1 FROM sys.objects WHERE name = @TableName)\nBEGIN\n ... do your thing ...\nEND\nRun Code Online (Sandbox Code Playgroud)\n此外,如果您希望允许用户从中选择的表列表是已知的且有限的,或者匹配特定的命名约定(例如dbo.Sales%),或者属于特定的模式(例如Reporting),您可以添加其他谓词来检查那些。
这要求您将表名作为适当的参数传递,而不是连接或标记替换。(并且请永远不要用于AddWithValue()任何用途。)
一旦您检查了对象的真实性和有效性,您仍然需要动态构建 SQL 查询,因为您仍然无法参数化表名。不过,您仍然应该申请QUOTENAME(),正如我在这些帖子中解释的那样:
所以最终的代码会是这样的:
\nCREATE PROCEDURE dbo.SelectFromAnywhere\n @TableName sysname \nAS\nBEGIN\n IF EXISTS (SELECT 1 FROM sys.objects\n WHERE name = @TableName)\n BEGIN\n DECLARE @sql nvarchar(max) = N\'SELECT * \n FROM \' + QUOTENAME(@TableName) + N\';\';\n EXEC sys.sp_executesql @sql;\n END\n ELSE\n BEGIN\n PRINT \'Nice try, robot.\';\n END\nEND\nGO\nRun Code Online (Sandbox Code Playgroud)\n如果您还希望它出现在某个定义的列表中,您可以添加
\nAND @TableName IN (N\'t1\', N\'t2\', \xe2\x80\xa6)\nRun Code Online (Sandbox Code Playgroud)\n或者LIKE <some pattern>或者加入sys.schemas或者你有什么。
如果没有人有权修改过程来更改检查,那么您就无法传递任何值来@TableName允许您执行任何恶意操作,除了可能从您不希望的另一个表中进行选择\xe2\x80\x99,因为有人在调用代码之前能够创建过多的访问权限。--替换诸如或 之类的字符;并不会让这变得更安全。