如何保护此功能免受SQL注入攻击?

10
public static bool TruncateTable(string dbAlias, string tableName)
{
    string sqlStatement = string.Format("TRUNCATE TABLE {0}", tableName);
    return ExecuteNonQuery(dbAlias, sqlStatement) > 0;
}

2
谁被允许调用 TruncateTable? - FrustratedWithFormsDesigner
1
不行,你需要使用参数化查询来确保安全。http://www.c-sharpcorner.com/UploadFile/puranindia/ParameterizedQuerySQLInjectionAttacks08102009011903AM/ParameterizedQuerySQLInjectionAttacks.aspx - Keith Adler
1
一般情况下,我不会允许用户界面调用 truncate table!如果您需要这样做,很可能存在严重的设计缺陷。 - HLGEM
11个回答

23

最常见的对抗SQL注入攻击的建议是使用SQL查询参数(本帖中有几位用户提到了这一点)。

但在这种情况下,这个建议是错误的。你不能在DDL语句中使用SQL查询参数来代替表名。

SQL查询参数只能用于SQL表达式中的文字值。这是SQL的每个实现中都是标准的。

当你需要处理表名时,我建议通过验证输入字符串与已知表名列表匹配来防止SQL注入攻击。

你可以从INFORMATION_SCHEMA获取有效表名列表:

SELECT table_name 
FROM INFORMATION_SCHEMA.Tables 
WHERE table_type = 'BASE TABLE'
  AND table_name = @tableName

现在你可以将输入变量作为SQL参数传递给此查询。如果查询返回零行,则知道输入不能用作表,如果返回一行,则匹配成功,因此您可以更有把握地安全使用它。

您还可以根据您定义的特定表列表验证表名是否可用于应用程序中要截断的表,就像@John Buchanan 建议的那样。

即使在验证tableName作为RDBMS中的表名存在之后,我还建议分隔表名,以防万一您使用带有空格或特殊字符的表名。在Microsoft SQL Server中,默认标识符分隔符是方括号:

string sqlStatement = string.Format("TRUNCATE TABLE [{0}]", tableName);

现在只有当tableName匹配一个真实的表,并且你实际上在表名中使用了方括号时,才会存在SQL注入的风险!

2
根据已知的可能输入进行验证。+1 - Greg
我认为很多人忘记了在这种类型的查询中不能使用参数化查询。观察得很好。 - Joe Phillips
1
OP还应考虑到两个或多个表具有相同名称但属于不同所有者/模式的可能性。 - MartW

6
据我所知,在Oracle或SQL Server中,您无法使用参数化查询来执行DDL语句/指定表名。如果我必须拥有一个疯狂的TruncateTable函数,并且必须免受SQL注入攻击,我会编写一个存储过程来检查输入是否是安全截断的表。

-- Sql Server specific!
CREATE TABLE TruncableTables (TableName varchar(50))
Insert into TruncableTables values ('MyTable')

go

CREATE PROCEDURE MyTrunc @tableName varchar(50)
AS
BEGIN

declare @IsValidTable int
declare @SqlString nvarchar(50)
select @IsValidTable = Count(*) from TruncableTables where TableName = @tableName

if @IsValidTable > 0
begin
 select @SqlString = 'truncate table ' + @tableName
 EXECUTE sp_executesql @SqlString
end
END

3
CREATE OR REPLACE PROCEDURE truncate(ptbl_name IN VARCHAR2) IS
  stmt VARCHAR2(100);
BEGIN
  stmt := 'TRUNCATE TABLE '||DBMS_ASSERT.SIMPLE_SQL_NAME(ptbl_name);
  dbms_output.put_line('<'||stmt||'>');
  EXECUTE IMMEDIATE stmt;
END;

3

如果您允许用户通过tablename变量输入内容到此函数中,我认为SQL注入并不是您唯一的问题。

更好的选择是通过自己的安全连接运行此命令,并完全不授予SELECT权限。TRUNCATE所需的只是ALTER TABLE权限。如果您使用的是SQL 2005或更高版本,还可以尝试使用带有EXECUTE AS的存储过程。


2

使用存储过程。任何像我使用的 MS Enterprise Library 这样的好的数据库库都会正确处理转义字符串参数。

另外,关于参数化查询:我更喜欢不必重新部署应用程序来修复数据库问题。在源代码中将查询存储为字面字符串会增加维护复杂性。


如果您不想重新部署,那么请在第一次正确地完成并测试它。 - Joe Phillips
4
为什么我没想到那个。顺便说一下,我将停止添加我这些年来编写的所有愚蠢错误。此外,天才,“deploy”可能意味着许多事情 - 比如从本地框架到开发服务器。通过重建/重新部署到开发环境打断其他开发人员可能会对工作造成很大干扰。 - 3Dave
1
部署存储过程的新版本也可能会造成破坏性。 - Bill Karwin
2
“使用存储过程”并不能解决SQL注入的风险。在存储过程中执行不安全的动态SQL查询和在应用程序代码中执行一样容易。 - Bill Karwin
无论如何,使用参数化查询和使用它一样有用。 - 3Dave

1

1

还有一些其他的帖子可以帮助解决SQL注入问题,所以我会点赞那些帖子,但另一个需要考虑的问题是如何处理权限。如果你授予用户db+owner或db_ddladmin角色,以便他们可以截断表,那么仅仅避免标准的SQL注入攻击是不够的。黑客可以发送其他可能有效但你不想截断的表名。

如果你给用户在特定表上的ALTER TABLE权限,并允许这些表被截断,那么情况会好一些,但在正常环境中,我仍然不太喜欢允许这样做。

通常情况下,TRUNCATE TABLE不会在日常应用程序使用中使用。它用于ETL场景或数据库维护期间。唯一可能会在面向前端应用程序中使用的情况是,如果你允许用户加载一个特定用户用于加载目的的表,但即使在这种情况下,我也可能会使用不同的解决方案。

当然,如果不知道你使用它的具体原因,我不能断言你应该重新设计,但如果作为DBA,我收到这样的请求,我会问开发人员很多问题。


0

使用参数化查询。


3
从表名来看?你有一些链接支持这个吗? - Adriaan Stander
1
一个奇怪但正确的答案。基于原始代码的小例子会更好。 - AnthonyWJones
是的,但人们真的应该了解在不使用参数化查询的情况下允许任何字符串传递到查询中的危险。我没有时间编写代码示例,但他应该知道它们。 - Crowe T. Robot

0

在这个具体的例子中,只有当表名来自外部来源时,您才需要保护免受SQL注入攻击。

为什么要允许这种情况发生呢? 如果您允许某些外部实体(最终用户、其他系统等)命名要删除的表,为什么不直接给他们管理员权限呢?

如果您正在创建和删除表以为最终用户提供某些功能,请不要让他们直接提供数据库对象的名称。 除了SQL注入之外,您还会遇到名称冲突等问题。 相反,自己生成真正的表名(例如DYNTABLE_00001、DYNTABLE_00002等),并保留一个连接它们与用户提供的名称的表。


关于生成动态SQL进行DDL操作的一些注意事项:

  • 在大多数RDBMS中,您必须使用动态SQL并将表名插入为文本。要特别小心。

  • 使用带引号的标识符([]在MS SQL Server中,“”在所有符合ANSI标准的RDBMS中)。这将使避免由无效名称引起的错误更容易。

  • 在存储过程中执行此操作,并检查所有引用的对象是否有效。

  • 不要做任何不可逆转的操作。例如,不要自动删除表。您可以将它们标记为要删除并向您的DBA发送电子邮件。她会在备份之后删除它们。

  • 如果可以的话,请尽量避免使用动态SQL。如果不能避免,则尽力将普通用户对其他(非动态)表的权限最小化。


-2

您可以使用SQLParameter来传递tableName的值。据我所知并测试,SQLParameter会处理所有参数检查,从而禁用注入的可能性。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接