在不使用 QUOTENAME 的情况下正确转义 SQL Server 中的分隔标识符

发布于 2024-08-26 18:35:29 字数 2178 浏览 10 评论 0原文

除了将标识符(表、视图、列)用双引号括起来以及标识符名称中出现的“双引号”之外,代码还必须执行其他操作吗?将不胜感激。

我继承了一个具有自定义对象关系映射 (ORM) 系统的代码库。 SQL 无法在应用程序中编写,但 ORM 最终仍必须生成 SQL 并将其发送到 SQL Server。所有标识符都用双引号引起来。

string QuoteName(string identifier) 
{ 
    return "\"" + identifier.Replace("\"", "\"\"") + "\"";
}

如果我在 SQL 中构建这个动态 SQL,我会使用内置的 SQL Server QUOTENAME 函数:

declare @identifier nvarchar(128);
set @identifier = N'Client"; DROP TABLE [dbo].Client; --';

declare @delimitedIdentifier nvarchar(258);
set @delimitedIdentifier = QUOTENAME(@identifier, '"');

print @delimitedIdentifier;
-- "Client""; DROP TABLE [dbo].Client; --"

我还没有找到任何关于如何转义带引号的标识符的明确文档SQL 服务器。我找到了分隔标识符(数据库引擎)并且我还看到了这个关于清理的 stackoverflow 问题

如果必须调用 QUOTENAME 函数只是为了引用标识符,那么 SQL Server 的流量就很大,这是不必要的。

ORM 似乎在 SQL 注入方面经过了深思熟虑。它是用 C# 编写的,早于 nHibernate 端口和实体框架等。所有用户输入都是使用 ADO.NET SqlParameter 对象发送的,它只是我在这个问题中关心的标识符名称。这需要在 SQL Server 2005 和 2008 上运行。


更新于 2010-03-31

虽然应用程序不应该允许用户在查询中输入标识符名称,但 ORM 通过它具有的 ORM 样式的查询语法来实现读取和自定义查询。我试图最终阻止所有可能的 SQL 注入攻击的是 ORM,因为与所有应用程序代码相比,它非常小且易于验证。

查询接口的一个简单示例:

session.Query(new TableReference("Client")
    .Restrict(new FieldReference("city") == "Springfield")
    .DropAllBut(new FieldReference("first_name"));

ADO.NET 发送此查询:

exec sp_executesql N'SELECT "T1"."first_name" 
FROM "dbo"."Client" AS "T1" 
WHERE "T1"."city" = @p1;', 
N'@p1 nvarchar(30)', 
N'Springfield';

也许这有助于思考类似的内容在 nHibernate 查询语言 (HQL) 中的外观:

using (ISession session = NHibernateHelper.OpenSession())
{
    Client client = session
        .CreateCriteria(typeof(Client))  \\ <-- TableReference in example above
        .Add(Restrictions.Eq("city", "Springfield"))  \\ <-- FieldReference above
        .UniqueResult<Client>();
    return client;
}

也许我应该看看 nHibernate 如何保护输入。

Is there anything else that the code must do to sanitize identifiers (table, view, column) other than to wrap them in double quotation marks and "double up" double quotation marks present in the identifier name? References would be appreciated.

I have inherited a code base that has a custom object-relational mapping (ORM) system. SQL cannot be written in the application but the ORM must still eventually generate the SQL to send to the SQL Server. All identifiers are quoted with double quotation marks.

string QuoteName(string identifier) 
{ 
    return "\"" + identifier.Replace("\"", "\"\"") + "\"";
}

If I were building this dynamic SQL in SQL, I would use the built-in SQL Server QUOTENAME function:

declare @identifier nvarchar(128);
set @identifier = N'Client"; DROP TABLE [dbo].Client; --';

declare @delimitedIdentifier nvarchar(258);
set @delimitedIdentifier = QUOTENAME(@identifier, '"');

print @delimitedIdentifier;
-- "Client""; DROP TABLE [dbo].Client; --"

I have not found any definitive documentation about how to escape quoted identifiers in SQL Server. I have found Delimited Identifiers (Database Engine) and I also saw this stackoverflow question about sanitizing.

If it were to have to call the QUOTENAME function just to quote the identifiers that is a lot of traffic to SQL Server that should not be needed.

The ORM seems to be pretty well thought out with regards to SQL Injection. It is in C# and predates the nHibernate port and Entity Framework etc. All user input is sent using ADO.NET SqlParameter objects, it is just the identifier names that I am concerned about in this question. This needs to work on SQL Server 2005 and 2008.


Update on 2010-03-31

While the application is not supposed to allow user-input for identifier names in queries, the ORM does via the query syntax that it has for both ORM-style reads and custom queries. It is the ORM that I am trying to ultimately prevent all possible SQL Injection attacks as that is very small and easy to verify as opposed to all the application code.

A simple example of the query interface:

session.Query(new TableReference("Client")
    .Restrict(new FieldReference("city") == "Springfield")
    .DropAllBut(new FieldReference("first_name"));

ADO.NET sends over this query:

exec sp_executesql N'SELECT "T1"."first_name" 
FROM "dbo"."Client" AS "T1" 
WHERE "T1"."city" = @p1;', 
N'@p1 nvarchar(30)', 
N'Springfield';

Perhaps it would help to think about how something similar this might look in nHibernate Query Language (HQL):

using (ISession session = NHibernateHelper.OpenSession())
{
    Client client = session
        .CreateCriteria(typeof(Client))  \\ <-- TableReference in example above
        .Add(Restrictions.Eq("city", "Springfield"))  \\ <-- FieldReference above
        .UniqueResult<Client>();
    return client;
}

Maybe I should look and see how nHibernate protects the input.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

盗梦空间 2024-09-02 18:35:29

您的 QuoteName 函数需要检查长度,因为 T-SQL QUOTENAME 函数指定了它返回的最大长度。使用您的示例:

String.Format(@"declare @delimitedIdentifier nvarchar(258);
set @delimitedIdentifier = {0};", QuoteName(identifier));

如果 QuoteName(identifier) 长度超过 258 个字符,则在分配给 @delimitedIdentifier 时,它将被静默截断。发生这种情况时,@delimitedIdentifier 可能会被不正确地转义。

一篇 MSDN 文章 由“Microsoft 安全软件开发人员”Bala Neerumalla 撰写,更深入地解释了该主题。该文章还包含我发现的最接近“有关如何在 SQL Server 中转义带引号的标识符的权威文档”的内容:

转义机制只是将右方括号的出现次数加倍。您不需要对其他字符执行任何操作,包括左方括号。

这是我当前使用的 C# 代码:

/// <summary>
/// Returns a string with the delimiters added to make the input string
/// a valid SQL Server delimited identifier. Brackets are used as the
/// delimiter. Unlike the T-SQL version, an ArgumentException is thrown
/// instead of returning a null for invalid arguments.
/// </summary>
/// <param name="name">sysname, limited to 128 characters.</param>
/// <returns>An escaped identifier, no longer than 258 characters.</returns>
public static string QuoteName(string name) { return QuoteName(name, '['); }

/// <summary>
/// Returns a string with the delimiters added to make the input string
/// a valid SQL Server delimited identifier. Unlike the T-SQL version,
/// an ArgumentException is thrown instead of returning a null for
/// invalid arguments.
/// </summary>
/// <param name="name">sysname, limited to 128 characters.</param>
/// <param name="quoteCharacter">Can be a single quotation mark ( ' ), a
/// left or right bracket ( [] ), or a double quotation mark ( " ).</param>
/// <returns>An escaped identifier, no longer than 258 characters.</returns>
public static string QuoteName(string name, char quoteCharacter) {
    name = name ?? String.Empty;
    const int sysnameLength = 128;
    if (name.Length > sysnameLength) {
        throw new ArgumentException(String.Format(
            "name is longer than {0} characters", sysnameLength));
    }
    switch (quoteCharacter) {
        case '\'':
            return String.Format("'{0}'", name.Replace("'", "''"));
        case '"':
            return String.Format("\"{0}\"", name.Replace("\"", "\"\""));
        case '[':
        case ']':
            return String.Format("[{0}]", name.Replace("]", "]]"));
        default:
            throw new ArgumentException(
                "quoteCharacter must be one of: ', \", [, or ]");
    }
}

Your QuoteName function needs to check the length, because the T-SQL QUOTENAME function specifies the maximum length it returns. Using your example:

String.Format(@"declare @delimitedIdentifier nvarchar(258);
set @delimitedIdentifier = {0};", QuoteName(identifier));

If QuoteName(identifier) is longer than 258 characters, it will be silently truncated when assigned to @delimitedIdentifier. When that happens, you open up the possibility for @delimitedIdentifier to be escaped improperly.

There is an MSDN article by Bala Neerumalla, a "security software developer at Microsoft", that explains the topic in more depth. The article also contains the closest thing I have found to "definitive documentation about how to escape quoted identifiers in SQL Server":

The escaping mechanism is simply doubling up the occurrences of right square brackets. You don't need to do anything with other characters, including left square brackets.

This is the C# code I am currently using:

/// <summary>
/// Returns a string with the delimiters added to make the input string
/// a valid SQL Server delimited identifier. Brackets are used as the
/// delimiter. Unlike the T-SQL version, an ArgumentException is thrown
/// instead of returning a null for invalid arguments.
/// </summary>
/// <param name="name">sysname, limited to 128 characters.</param>
/// <returns>An escaped identifier, no longer than 258 characters.</returns>
public static string QuoteName(string name) { return QuoteName(name, '['); }

/// <summary>
/// Returns a string with the delimiters added to make the input string
/// a valid SQL Server delimited identifier. Unlike the T-SQL version,
/// an ArgumentException is thrown instead of returning a null for
/// invalid arguments.
/// </summary>
/// <param name="name">sysname, limited to 128 characters.</param>
/// <param name="quoteCharacter">Can be a single quotation mark ( ' ), a
/// left or right bracket ( [] ), or a double quotation mark ( " ).</param>
/// <returns>An escaped identifier, no longer than 258 characters.</returns>
public static string QuoteName(string name, char quoteCharacter) {
    name = name ?? String.Empty;
    const int sysnameLength = 128;
    if (name.Length > sysnameLength) {
        throw new ArgumentException(String.Format(
            "name is longer than {0} characters", sysnameLength));
    }
    switch (quoteCharacter) {
        case '\'':
            return String.Format("'{0}'", name.Replace("'", "''"));
        case '"':
            return String.Format("\"{0}\"", name.Replace("\"", "\"\""));
        case '[':
        case ']':
            return String.Format("[{0}]", name.Replace("]", "]]"));
        default:
            throw new ArgumentException(
                "quoteCharacter must be one of: ', \", [, or ]");
    }
}
咿呀咿呀哟 2024-09-02 18:35:29

不能只使用 [ 和 ] 分隔符而不是引号(单引号或双引号)吗?

标识符永远不应该真正包含任何引号(除非你比现在更不幸),所以你删除了名称等中引号的正常使用因素

编辑:

但是如果对 ORM 的调用已经参数化,你不需要担心它,不是吗?使用 [ 和 ] 消除了 C# 字符串中复杂转义的需要

Can you not just use [ and ] delimiters instead of quotes (single or double)?

Identifiers should never really contain any quotes (unless you're more unlucky than now) so you remove the normal use factor of quotes in names etc

Edit:

But if the calls to the ORM are already parameterised, you don't need to worry about it, no? Using [ and ] removes the need for complex escaping in c# strings

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文