【问题标题】:How do I protect this function from SQL injection?如何保护此函数免受 SQL 注入?
【发布时间】:2010-12-24 03:19:51
【问题描述】:
public static bool TruncateTable(string dbAlias, string tableName)
{
    string sqlStatement = string.Format("TRUNCATE TABLE {0}", tableName);
    return ExecuteNonQuery(dbAlias, sqlStatement) > 0;
}

【问题讨论】:

  • 谁可以调用TruncateTable?
  • 一点也不。您需要使用参数化查询来确定。 c-sharpcorner.com/UploadFile/puranindia/…
  • 我一般不会允许用户界面调用截断表!如果您需要这样做,您很可能存在严重的设计缺陷。

标签: c# sql sql-injection


【解决方案1】:

对抗 SQL 注入最常见的建议是使用 SQL 查询参数(这个线程上的几个人都建议过)。

在这种情况下这是错误的答案。不能将 SQL 查询参数用于 DDL 语句中的表名。

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 suggests

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

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

现在,如果tableName 匹配一个真实的表,并且您实际上在表名中使用了方括号,您就会面临 SQL 注入的风险!

【讨论】:

  • 根据已知可能的输入进行验证。 +1
  • 我想很多人都忘记了你不能在这种类型的查询中使用参数化查询。良好的观察力。
  • OP 还应该考虑两个或多个表具有相同名称但属于不同所有者/模式的可能性。
【解决方案2】:

据我所知,您不能使用参数化查询来执行 DDL 语句/指定表名,至少在 Oracle 或 Sql Server 中不能。如果我必须有一个疯狂的 TruncateTable 函数,我会做的是创建一个存储过程来检查输入是否是一个可以安全截断的表。


-- 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】:

    如果您允许用户定义的输入通过 tablename 变量潜入该函数,我认为 SQL 注入不是您唯一的问题。

    更好的选择是通过它自己的安全连接运行这个命令,并且根本不给它任何 SELECT 权限。所有 TRUNCATE 需要运行的是 ALTER TABLE 权限。如果您使用的是 SQL 2005 以上版本,您还可以尝试使用内部带有 EXECUTE AS 的存储过程。

    【讨论】:

      【解决方案4】:
      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;
      

      【讨论】:

        【解决方案5】:

        使用存储过程。任何像样的数据库库(我使用的是 MS 企业库)都可以正确处理转义字符串参数。

        另外,re:parameterized queries:我宁愿不必重新部署我的应用程序来修复数据库问题。在源中将查询存储为文字字符串会增加维护复杂性。

        【讨论】:

        • 如果你不想重新部署,那么首先正确地做并测试它。
        • 我怎么没想到。当我这样做的时候,我会停止添加这些年来我一直在编码的所有愚蠢的错误。另外,天才,deploy 可能意味着很多事情——比如从本地机器到开发服务器。通过重新构建/重新部署到开发来干扰其他开发人员可能会造成很大的破坏。
        • 部署存储过程的新版本也可能会造成破坏。
        • “使用存储过程”不能解决 SQL 注入风险。在存储过程中执行不安全的动态 SQL 查询就像在应用程序代码中一样容易。
        • 随便。它与使用参数化查询一样多地解决了这个问题。
        【解决方案6】:

        看看这个链接

        Does this code prevent SQL injection?

        从 tableName 字符串中删除不需要的。

        我认为您不能使用参数查询来获取表名。

        【讨论】:

          【解决方案7】:

          还有其他一些有助于 SQL 注入的帖子,所以我会投赞成票,但要考虑的另一件事是您将如何处理此权限。如果您授予用户 db+owner 或 db_ddladmin 角色以便他们可以截断表,那么仅仅避免标准 SQL 注入攻击是不够的。黑客可以发送其他可能有效但您不希望截断的表名。

          如果您向允许截断的特定表上的用户授予 ALTER TABLE 权限,那么您的状态会好一些,但它仍然比我喜欢在正常环境中允许的要多。

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

          当然,在不知道您使用它的具体原因的情况下,我不能断然说您应该重新设计,但如果我作为 DBA 收到此请求,我会向开发人员询问很多问题。

          【讨论】:

            【解决方案8】:

            使用参数化查询。

            【讨论】:

            • 来自表名?你有一些链接来支持这个吗?
            • 一个奇怪的正确但糟糕的答案。一个基于 OP 代码的小例子会更好。
            • 是的,但人们确实应该了解在不使用参数化查询的情况下将任何字符串传递到查询中的危险。我没有时间写代码示例,但他应该知道。
            【解决方案9】:

            在这个具体示例中,只有当表名来自外部源时,您才需要防止 SQL 注入。

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

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


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

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

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

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

            • 不要做任何不可逆转的事情。例如。不要自动删除表。 您可以将它们标记为被删除并通过电子邮件发送给您的 DBA。 她会在备份后放下它们。

            • 尽可能避免。如果你不能,尽你所能减少他人的权利 普通用户将拥有的(非动态)表。

            【讨论】:

              【解决方案10】:

              您可以使用SQLParameter 来传递tableName 值。据我所知和测试,SQLParameter 负责所有参数检查,因此禁止注入。

              【讨论】:

                【解决方案11】:

                如果你不能使用参数化查询(你应该)...用 '' 简单地替换所有 ' 实例应该可以工作。

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

                【讨论】:

                • 你是对的。实际上有很多东西需要替换(例如 ' ; --, /* */ xp_ 等...),因此我建议使用参数化查询(或任何其他有效选项,例如存储过程, ORM等...)。尽管如此,除了限制字符串“tableName”的长度......这个解决方案可能会在 100% 的时间内有效。
                猜你喜欢
                • 2011-02-03
                • 2020-05-06
                • 1970-01-01
                • 1970-01-01
                • 2021-03-19
                • 2016-05-31
                • 2013-03-12
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多