18

我刚刚遇到了一件奇怪的事情......我们网站上有一些代码正在使用一个巨大的 SQL 语句,通过基于一些用户值进行一些搜索和替换来修改它,然后将其传递给 SQL Server一个问题。

我认为这将作为对存储过程的参数化查询更清晰,以用户值作为参数,但是当我仔细观察时,我明白他们为什么会这样做......他们从中选择的表是可变地取决于这些用户值。

例如,在一种情况下,如果值是 ("FOO", "BAR"),则查询最终会变成类似于 "SELECT * FROM FOO_BAR"

有没有简单明了的方法来做到这一点?我正在尝试的一切似乎都不优雅。

编辑:当然,我可以在存储的过程中动态生成 sql,并执行它(bleh),但那时我想知道我是否有任何收获。

EDIT2:以某种智能方式重构表名,例如将它们全部放在一个具有不同名称的表中作为新列将是解决所有这些问题的好方法,有几个人直接指出或暗示了这一点。可悲的是,在这种情况下,这不是一个选择。

4

10 回答 10

48

首先,你不应该这样在客户端应用程序上执行 SQL 命令组合,就是 SQL 注入。(对于没有自己的权限的管理工具可以,但对于共享使用的应用程序则不行)。

其次,是的,对存储过程的参数化调用既干净又安全。

但是,由于您需要使用动态 SQL 来执行此操作,因此您仍然不希望将传递的字符串包含在已执行查询的文本中。相反,您希望使用传递的字符串来查找应允许用户查询的实际表的名称。

这是一个简单的天真示例:

CREATE PROC spCountAnyTableRows( @PassedTableName as NVarchar(255) ) AS
-- Counts the number of rows from any non-system Table, *SAFELY*
BEGIN
    DECLARE @ActualTableName AS NVarchar(255)

    SELECT @ActualTableName = QUOTENAME( TABLE_NAME )
    FROM INFORMATION_SCHEMA.TABLES
    WHERE TABLE_NAME = @PassedTableName

    DECLARE @sql AS NVARCHAR(MAX)
    SELECT @sql = 'SELECT COUNT(*) FROM ' + @ActualTableName + ';'

    EXEC(@SQL)
END

有些人公平地问为什么这更安全。希望小鲍比表可以更清楚地说明这一点:0 替代文字


更多问题的答案:

  1. 单独的 QUOTENAME 不能保证是安全的。MS 鼓励我们使用它,但他们没有保证它不会被黑客攻击。仅供参考,真正的安全就是保证。使用 QUOTENAME 查找表是另一回事,它是牢不可破的。

  2. 对于此示例,QUOTENAME 不是绝对必要的,仅 INFORMATION_SCHEMA 上的查找翻译通常就足够了。QUOTENAME 在这里是因为包含一个完整且正确的解决方案是一种很好的安全形式。这里的 QUOTENAME 实际上是在防止一个不同但类似的潜在问题,称为潜在注入


我应该注意,您可以对动态列名和INFORMATION_SCHEMA.COLUMNS表执行相同的操作。

您还可以通过使用参数化 SQL 查询来绕过对存储过程的需求(请参阅此处:https ://docs.microsoft.com/en-us/dotnet/api/system.data.sqlclient.sqlcommand.parameters?view=网络框架 4.8)。但我认为存储过程为此类情况提供了更易于管理且不易出错的安全设施。

于 2009-08-07T20:27:41.103 回答
5

(Un)幸运的是,没有办法这样做 - 除了动态 sql 生成之外,您不能使用作为参数传递给存储代码的表名。在决定在哪里生成 sql 代码时,我更喜欢应用程序代码而不是存储代码。应用程序代码通常更快更容易维护。

如果您不喜欢正在使用的解决方案,我建议您进行更深入的重新设计(即更改架构/应用程序逻辑,以便您不再需要将表名作为参数传递到任何地方)。

于 2009-08-07T20:20:18.437 回答
2

听起来您最好使用 ORM 解决方案。

当我在存储过程中看到动态 sql 时,我感到畏缩。

于 2009-08-07T20:21:50.400 回答
2

我反对在存储过程中动态生成 SQL;这会给你带来麻烦,并可能导致注入漏洞。

相反,我会分析所有可能受查询影响的表,并创建某种枚举来确定要用于查询的表。

于 2009-08-07T20:14:43.750 回答
1

您可以考虑的一件事是创建一个包含您想要的相同 SQL 命令的 case 语句,对每个有效表执行一次,然后将表名作为字符串传递到此过程中,并让 case 选择要运行的命令。

顺便说一句,作为安全人员,上面的建议告诉您从系统表中进行选择以确保您有一个有效的表,这对我来说似乎是一种浪费的操作。如果有人可以通过 QUOTENAME() 注入,那么注入将在系统表和基础表上工作。这有助于确保它是一个有效的表名,我认为上面的建议是一种更好的方法,因为你根本没有使用 QUOTENAME() 。

于 2011-03-01T15:01:21.213 回答
0

我不知道您将数据分布在多个表中的原因,但听起来您正在破坏其中一个基本面。数据应该在表格中,而不是作为表格名称。

如果表格具有或多或少相同的布局,请考虑是否最好将数据放在单个表格中。这将解决您的动态查询问题,并使数据库布局更加灵活。

于 2009-08-07T20:37:52.037 回答
0

您可以选择过程,而不是根据用户输入值查询表。也就是说
1. 创建一个过程 FOO_BAR_prc 并在其中放入查询 'select * from foo_bar' ,这样查询将由数据库预编译。
2. 然后根据用户输入从您的应用程序代码中执行正确的程序。

由于您有大约 50 张桌子,这可能不是一个可行的解决方案,因为它需要您做很多工作。

于 2009-08-07T20:38:44.063 回答
0

根据这些表中的列集是相同还是不同,从长远来看,我会以两种方式处理它:

1)如果它们相同,为什么不创建一个新列用作选择器,其值来自用户提供的参数?(是性能优化吗?)

2)如果它们不同,则它们的处理方式也可能不同。因此,似乎将选择/句柄代码拆分为单独的块,然后单独调用它们对我来说是一种最模块化的方法。您将重复“select * from”部分,但在这种情况下,表集希望是有限的。

允许调用代码提供表名的两个任意部分来进行选择感觉非常危险。

于 2009-08-07T20:28:23.270 回答
0

实际上,我想知道如何通过表名在存储过程中创建表。通过阅读一些答案并在最后尝试进行一些修改,我终于能够创建一个名称作为参数传递的表。这是其他人检查其中任何错误的存储过程。

使用 [数据库名称] GO /****** 对象:StoredProcedure [dbo].[sp_CreateDynamicTable] 脚本日期:06/20/2015 16:56:25 ******/ SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON开始创建过程 [dbo].[sp_CreateDynamicTable] @tName varchar(255) AS BEGIN SET NOCOUNT ON; 声明 @SQL nvarchar(max)

SET @SQL = N'CREATE TABLE [DBO].['+ @tName + '] (DocID nvarchar(10) null);'

    EXECUTE sp_executesql @SQL

结尾

于 2015-06-20T11:33:30.947 回答
0

@RBarry Young 您不需要在查询字符串中将括号添加到@ActualTableName,因为它已经包含在INFORMATION_SCHEMA.TABLES 中的查询结果中。否则,执行时会出现错误。

CREATE PROC spCountAnyTableRows( @PassedTableName as NVarchar(255) ) AS -- 计算任何非系统表中的行数,安全 开始声明 @ActualTableName AS NVarchar(255)

SELECT @ActualTableName = QUOTENAME( TABLE_NAME )
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME = @PassedTableName

DECLARE @sql AS NVARCHAR(MAX)
--SELECT @sql = 'SELECT COUNT(*) FROM [' + @ActualTableName + '];'

-- changed to this
SELECT @sql = 'SELECT COUNT(*) FROM ' + @ActualTableName + ';'

EXEC(@SQL)

结尾

于 2016-08-12T09:14:22.693 回答