SQL Server 中的 SQL 注入如何击败转义单引号的卫生设施?

Posted

技术标签:

【中文标题】SQL Server 中的 SQL 注入如何击败转义单引号的卫生设施?【英文标题】:How can sanitation that escapes single quotes be defeated by SQL injection in SQL Server? 【发布时间】:2013-03-10 08:43:05 【问题描述】:

首先,我很清楚参数化查询是最好的选择,但我想问是什么让我在下面提出的策略容易受到攻击。人们坚持以下解决方案不起作用,所以我正在寻找一个为什么它不起作用的例子。

如果动态 SQL 在被发送到 SQL Server 之前使用以下转义在代码中构建,那么什么样的注入可以解决这个问题?

string userInput= "N'" + userInput.Replace("'", "''") + "'"

here 回答了一个类似的问题,但我认为这里没有任何答案适用。

在 SQL Server 中无法使用“\”转义单引号。

我相信 SQL Smuggling 与 Unicode(概述 here)会因为正在生成的字符串被单引号前面的 N 标记为 Unicode 而受阻。据我所知,SQL Server 不会自动将其他字符集转换为单引号。如果没有未转义的单引号,我不相信注入是可能的。

我也不相信 String Truncation 是一个可行的向量。 SQL Server 肯定不会进行截断,因为nvarchar 的最大大小是 2GB according to microsoft。 2 GB 的字符串在大多数情况下是不可行的,而在我的情况下是不可能的。

二阶注入是可能的,但如果:

    使用上述方法对进入数据库的所有数据进行清理 数据库中的值永远不会附加到动态 SQL 中(当您可以在任何动态 SQL 字符串的静态部分引用表值时,为什么还要这样做?)。

我并不是说这比使用参数化查询更好或替代,但我想知道我概述的内容是如何易受攻击的。有什么想法吗?

【问题讨论】:

没有。你仍然容易受到以下形式的攻击:"SELECT * FROM MyTable WHERE Field = " + userInputuserInput0; DROP TABLE OhNo; 这毫无意义。在上面的示例中,您的用户输入将被清理为 N'0;删除表哦不;'在被处决之前。 这仅用于清理字符串变量。如果在添加到查询之前将它们转换为 int,则不需要对诸如“int”之类的内容进行清理。无论如何,我现在只询问对字符串进行消毒的问题。此外,这里不需要粗鲁。如果您能想到一种不安全的方法,我很想知道。 单引号变成了两个单引号(这就是你如何转义单引号),使它们无害。然后您的用户输入变为:N'test'';删除表 ohno; print ''' 是无害的 在动态 SQL 不可避免的情况下,这不是一个好的选择吗? 【参考方案1】:

在少数情况下,此转义函数会失败。最明显的是不使用单引号时:

string table= "\"" + table.Replace("'", "''") + "\""
string var= "`" + var.Replace("'", "''") + "`"
string index= " " + index.Replace("'", "''") + " "
string query = "select * from `"+table+"` where name=\""+var+"\" or id="+index

在这种情况下,您可以使用双引号、反引号来“突破”。在最后一种情况下,没有什么可以“突破”的,所以你可以只写1 union select password from users-- 或攻击者想要的任何 sql 有效负载。

此转义函数将失败的下一个条件是,如果在转义字符串后获取子字符串(是的我在野外发现了这样的漏洞):

string userPassword= userPassword.Replace("'", "''")
string userName= userInput.Replace("'", "''")
userName = substr(userName,0,10)
string query = "select * from users where name='"+userName+"' and password='"+userPassword+"'";

在这种情况下,abcdefgji' 的用户名将通过转义函数转换为abcdefgji'',然后通过获取子字符串转换回abcdefgji'。这可以通过将密码值设置为任何 sql 语句来利用,在这种情况下,or 1=1-- 将被解释为 sql,而用户名将被解释为 abcdefgji'' and password=。查询结果如下:

select * from users where name='abcdefgji'' and password=' or 1=1-- 

T-SQL 和其他已经提到的高级 sql 注入技术。 Advanced SQL Injection In SQL Server Applications 是一篇很棒的论文,如果你还没有读过的话,你应该读一读。

最后一个问题是 unicode 攻击。此类漏洞的出现是因为转义函数不知道多字节编码,这可以是used by an attacker to "consume" the escape character。在字符串前面加上“N”将无济于事,因为这不会影响字符串后面的多字节字符的值。但是,这种类型的攻击非常少见,因为必须将数据库配置为接受 GBK unicode 字符串(我不确定 MS-SQL 是否可以做到这一点)。

二阶代码注入仍然是可能的,这种攻击模式是通过信任攻击者控制的数据源创建的。转义用于将控制字符表示为其字符文字。如果开发人员忘记转义从 select 获得的值,然后在另一个查询中使用该值,则 bam 攻击者将可以使用字符文字单引号。

测试一切,不信任任何东西。

【讨论】:

虽然我同意使用安全性而不是参数化查询是不好的,但如果您仔细阅读他的问题,他会指出他会采取几个步骤来“确保”安全性。 1)他确保一个数值实际上是一个数值,因此您的示例中的 id 注入将失败 2)他询问仅接受 ' 作为字符串分隔符的 SQL Server。使用勾号或双引号对攻击者没有帮助 @Nikola Radosavljević 是的,但是根据他如何植入此检查,回击和缺少引号仍然可能是一个问题。显然,这篇文章是从攻击者而不是防御者的角度来看的。辩护是众所周知的且无趣的。 问题中的代码不易受到攻击。作为转义字符串文字的一般指南,这是一个很好的答案,它既正确又有趣;但是列出的场景将无法破坏原始问题中的代码行。关于 2nd hand 攻击,无论文本最初是通过参数插入还是作为文字插入(如果它包含引号字符)都可能发生。【参考方案2】:

通过一些额外的规定,您的上述方法不易受到 SQL 注入的攻击。要考虑的主要攻击媒介是 SQL Smuggling。 SQL Smuggling 发生在类似的 unicode 字符以意想不到的方式被翻译时(例如 ` 更改为 ' )。有几个位置应用程序堆栈可能容易受到 SQL Smuggling 的攻击。

编程语言是否正确处理 unicode 字符串?如果该语言不支持 unicode,它可能会将 unicode 字符中的字节错误地识别为单引号并将其转义。

客户端数据库库(例如 ODBC 等)是否正确处理 unicode 字符串? .Net 框架中的 System.Data.SqlClient 可以,但是 windows 95 中的旧库呢?时代?第三方 ODBC 库确实存在。如果 ODBC 驱动程序不支持查询字符串中的 unicode 会怎样?

数据库是否正确处理输入?假设您使用的是 N'',现代版本的 SQL 是免疫的,但是 SQL 6.5 呢? SQL 7.0?我不知道有任何特定的漏洞,但是在 1990 年代,开发人员并没有注意到这一点。

缓冲区溢出?另一个问题是引用的字符串比原始字符串长。在哪个版本的 Sql Server 中引入了 2GB 的输入限制?在那之前有什么限制?在旧版本的 SQL 上,当查询超出限制时会发生什么?从网络库的角度来看,查询的长度是否存在任何限制?还是关于编程语言中字符串的长度?

是否有任何语言设置会影响 Replace() 函数中使用的比较? .Net 总是对 Replace() 函数进行二进制比较。会一直这样吗?如果 .NET 的未来版本支持在 app.config 级别覆盖该行为,会发生什么情况?如果我们使用正则表达式而不是 Replace() 来插入单引号会怎样?计算机的区域设置是否会影响此比较?如果确实发生了行为变化,它可能不容易受到 sql 注入的攻击,但是,它可能在到达数据库之前通过将看起来像单引号的 uni-code 字符更改为单引号来无意中编辑了字符串。

因此,假设您在当前版本的 .Net 上使用 C# 中的 System.String.Replace() 函数,并针对当前(2005-2012)版本的 SQL Server 使用内置 SqlClient 库,那么您的方法并不脆弱。当您开始改变事物时,就无法做出任何承诺。参数化查询方法是提高效率、性能和(在某些情况下)安全性的正确方法。

警告 上述 cmets 并非对该技术的认可。这种生成 SQL 的错误方法还有其他几个很好的原因。但是,详细说明它们超出了此问题的范围。

请勿将此技术用于新开发。

请勿将此技术用于新开发。

请勿将此技术用于新开发。

【讨论】:

如果不是这种技术,那么我应该在新开发中为CREATE LOGIN使用什么?【参考方案3】:

Using query parameters 比转义引号更好、更简单、更快。


关于您的评论,我看到您承认参数化,但值得强调。当您可以参数化时,为什么要使用转义?

在Advanced SQL Injection In SQL Server Applications 中,在文本中搜索“替换”一词,然后阅读一些示例,其中开发人员即使在转义用户输入后也无意中允许 SQL 注入攻击。


存在一种极端情况,即使用\ 转义引号会导致漏洞,因为\ 在某些字符集中成为有效多字节字符的一半。但这不适用于您的情况,因为 \ 不是转义字符。

正如其他人所指出的,您可能还为您的 SQL 添加了动态内容,而不是字符串文字或日期文字。表或列标识符在 SQL 中由 " 分隔,在 Microsoft/Sybase 中由 [ ] 分隔。 SQL 关键字当然没有任何分隔符。对于这些情况,我建议将要插入的值列入白名单

底线是逃避一种有效的防御,如果你能确保你始终如一地这样做。这就是风险:您的应用程序的开发人员团队之一可能会省略一个步骤并不安全地执行一些字符串插值。

当然,其他方法也是如此,比如参数化。它们只有在您始终如一地执行时才有效。但是我发现使用参数比找出正确的转义类型更容易和更快。开发人员更有可能使用方便且不会拖慢速度的方法。

【讨论】:

正如我在帖子的第一行中所说的,我很清楚这一点。我只是在问是否有某些特定原因导致我概述的内容不安全。 这是一个很好的资源,但其中建议的可能问题在这里并不是真正的问题。首先,使用 "char(0x63)" 方法构造引号是没有用的,因为该语句将被解释为字符串而不被执行。至于二阶 SQL 注入,我已经在原帖中提到了。 "当您可以参数化时,为什么要使用转义?"有时,遗留代码很难针对参数化查询进行改造。 @R.J.Dunnill,根据我的经验,修改代码并不是难事,即使对于遗留代码也是如此。在我的上一份工作中,我在大约 4 小时内更新了整个应用程序以使用参数化 SQL 查询。更大的困难是没有人知道如何访问、构建或部署代码。但在这种情况下,这不是问题。我们假设问题是关于代码已经在积极开发中的项目,而不是已经丢失的代码。【参考方案4】:

如果用户提供的输入被解释为命令,就会发生 SQL 注入。这里的命令表示任何不被解释为可识别的data type 文字。

现在,如果您仅在数据文字中使用用户输入,特别是仅在字符串文字中,则用户输入只有在能够离开字符串文字上下文时才会被解释为与字符串数据不同的内容。对于字符串或 Unicode 字符串字面量,用单引号括住字面量数据,而嵌入的单引号需要用两个单引号表示。

所以要留下字符串文字上下文,需要提供一个单引号 (sic),因为两个单引号被解释为字符串文字数据,而不是字符串文字结束分隔符。

因此,如果您将用户提供的数据中的任何单引号替换为两个单引号,则用户将不可能离开字符串文字上下文。

【讨论】:

【参考方案5】:

SQL 注入可以通过 unicode 进行。如果网络应用有这样的 URL:

http://mywebapp/widgets/?Code=ABC

生成类似的 SQL 从 Code = 'ABC' 的小部件中选择 *

但是黑客进入了这个:

http://mywebapp/widgets/?Code=ABC%CA%BC;drop 表格小部件--

SQL 看起来像 select * from widgets where Code = 'ABC';drop table widgets--'

SQL Server 将运行两条 SQL 语句。一个做选择,一个做删除。 您的代码可能会将 url 编码的 %CA%BC 转换为 unicode U02BC,这是一个“修饰符字母撇号”。 .Net 中的 Replace 函数不会将其视为单引号。但是,Microsoft SQL Server 将其视为单引号。这是一个可能允许 SQL 注入的示例:

string badValue = ((char)0x02BC).ToString();
badValue = badValue + ";delete from widgets--";
string sql = "SELECT * FROM WIDGETS WHERE ID=" + badValue.Replace("'","''");
TestTheSQL(sql);

【讨论】:

这将起作用:string sql = "SELECT * FROM WIDGETS WHERE ID='" + badValue.Replace("'","''") + "'";【参考方案6】:

如果您进行字符串连接,可能没有 100% 安全的方法。您可以做的是尝试检查每个参数的数据类型,如果所有参数都通过了此类验证,则继续执行。例如,如果你的参数应该是 int 类型,而你得到的东西不能转换为 int,那么就拒绝它。

如果您接受 nvarchar 参数,这将不起作用。

正如其他人已经指出的那样。最安全的方法是使用参数化查询。

【讨论】:

以上是关于SQL Server 中的 SQL 注入如何击败转义单引号的卫生设施?的主要内容,如果未能解决你的问题,请参考以下文章

具有 ASP 成员资格的 Sql Server 中的安全性和 SQL 注入

什么是 SQL Server 上的 SQL 注入攻击?我们如何预防它们? [复制]

如何在 SQL Server 中清理(防止 SQL 注入)动态 SQL?

(转)防SQL注入的方法

SQL Server 和 SQL 注入

php如何防止sql注入