Is it safe to not parameterize an SQL query when the parameter is not a string?

SQL 注入而言,我完全理解参数化 string参数的必要性; 这是本书中最古老的技巧之一。但是什么时候才能证明 没有参数化 SqlCommand是正确的呢?是否有任何数据类型被认为不参数化是“安全的”?

例如: 我不认为自己在任何地方的 附近的 SQL 专家,但我不能想到任何情况下,它会是潜在的脆弱的 SQL 注入接受一个 boolint,只是连接到查询。

我的假设是正确的吗,或者这可能在我的程序中留下一个巨大的安全漏洞?

为了澄清,这个问题被标记为 ,它是一种强类型语言; 当我说“参数”时,想象一下类似于 public int Query(int id)的内容。

13385 次浏览
"SELECT * FROM Table1 WHERE Id=" + intVariable.ToString()

Security

It is OK.
攻击者无法在类型化 int 变量中注入任何内容。

表演

不好。

最好使用参数,这样查询将编译一次并缓存以备下次使用。下一次,即使使用不同的参数值,查询也会被缓存,不需要在数据库服务器中编译。

编码风格

练得不好。

  • 参数更具可读性
  • Maybe it makes you get used to queries without parameters, then maybe you made a mistake once and use a string value this way and then you probably should say goodbye to your data. Bad habit!

"SELECT * FROM Product WHERE Id=" + TextBox1.Text

Although it is not your question, but maybe useful for future readers:

保安

灾难!

即使 Id字段是整数,您的查询也可能受到 SQL 注入的影响。假设在应用程序 "SELECT * FROM Table1 WHERE Id=" + TextBox1.Text中有一个查询。攻击者可以插入到文本框 1; DELETE Table1中,查询将是:

SELECT * FROM Table1 WHERE Id=1; DELETE Table1

如果不想在这里使用参数化查询,应该使用类型化值:

string.Format("SELECT * FROM Table1 WHERE Id={0}", int.Parse(TextBox1.Text))

你的问题

我的问题出现是因为一个同事写了一堆查询 连接整数值,我想知道它是否是一个 浪费我的时间去修复它们。

我认为更改这些代码不是浪费时间。确实建议更改!

如果您的同事使用 int 变量,那么它没有安全风险,但是我认为更改这些代码不会浪费时间,并且确实建议更改这些代码。它使代码更易读、更易维护,并使执行更快。

实际上一个问题里有两个问题。标题提出的问题与任择议定书在随后的评论中表示的关切几乎没有什么关系。

尽管我意识到对于 OP 来说,他们的特殊情况才是最重要的,但是对于来自 Google 的读者来说,回答一个更普遍的问题是很重要的,这个问题可以被表述为“如果我确保我所连接的每个文字都是安全的,那么连接是否和准备好的语句一样安全?”.所以,我想把精力集中在后一个问题上。答案是

绝对没有。

这个解释并不像大多数读者希望的那样直接,但是我会尽力的。

我一直在思考这个问题有一段时间了,结果是在 文章(尽管是基于 PHP 环境)中我试图总结所有内容。我突然想到,SQL 注入的保护问题经常被一些相关但范围较窄的主题所回避,比如字符串转义、类型转换等。虽然一些措施可以被认为是安全的时候采取自己,没有制度,也没有一个简单的规则来遵循。这使得它非常光滑的地面,把太多的开发人员的注意力和经验。

SQL 注入的问题不能简化为某些特定语法问题。它比一般开发人员过去认为的要宽。这也是一个 方法问题。它不仅是“我们必须应用哪种特定的格式”,而且是“ How它必须完成”。

(从这个角度来看,另一个答案中引用的 Jon Skeet 的一篇文章做得不好而不是好,因为它又一次挑剔了一些边缘情况,专注于一个特定的语法问题,并且没有从整体上解决这个问题。)

When you're trying to address the question of protection not as whole but as a set of different syntax issues, you're facing multitude of problems.

  • 可能的格式选择非常多。意味着人们很容易忽略一些。或者混淆它们(例如,对 标识符使用 绳子转义)。
  • 连接意味着所有的保护措施都必须由程序员来完成,而不是程序。这个问题本身就会导致若干后果:
    • 这样的格式是手动的。手动意味着 extremely容易出错。一个人可以简单地忘记应用。
    • moreover, there is a temptation to move formatting procedures into some centralized function, messing things even more, and spoiling data that is not going to database.
  • 当涉及到多个开发人员时,问题会成倍增加。
  • when concatenation is used, one cannot tell a potentially dangerous query at glance: they all potentially dangerous!

与这种混乱不同,事先准备好的声明确实是“圣杯”:

  • 它可以用一个容易遵守的简单规则来表达。
  • it is essentially undetacheable measure, means the developer cannot interfere, and, willingly or unwillingly, spoil the process.
  • 注入保护实际上只是准备语句的 副作用,其真正目的是生成句法正确的语句。语法正确的陈述是100% 的注入证明。然而,我们需要我们的语法是正确的,尽管任何注入的可能性。
  • if used all the way around, it protects the application regardless of the developer's experience. Say, there is a thing called 二阶注入,二阶注入. And a very strong delusion that reads "in order to protect, 转义所有用户提供的输入". Combined together, they lead to injection, if a developer takes the liberty to decide, what needs to be protected and what not.

(进一步思考,我发现当前的占位符集合不足以满足现实生活的需要,必须进行扩展,不管是对于复杂的数据结构,如数组,甚至是 SQL 关键字或标识符,有时也必须动态地添加到查询中,但是对于这种情况,开发人员没有武器,被迫回到字符串串联,但这是另一个问题)。

Interestingly, this question's controversy is provoked by the very controversial nature of Stack Overflow. The site's idea is to make use of 来自直接提问的用户的特殊问题 to achieve the goal of having a database of 适用于搜索用户的通用答案. The idea is not bad 本质上, but it fails in a situation like this: when a user asks a 非常狭隘的问题, particularly to get an argument in a dispute with a colleague (or to decide if it worth to refactor the code). While most of experienced participants are trying to write an answer, keeping in mind 任务 of Stack Overflow at whole, making their answer good for as many readers as possible, not the OP only.

我认为这是安全的... 技术上来说,但这是一个可怕的习惯进入。您真的想要编写这样的查询吗?

var sqlCommand = new SqlCommand("SELECT * FROM People WHERE IsAlive = " + isAlive +
" AND FirstName = @firstName");


sqlCommand.Parameters.AddWithValue("firstName", "Rob");

当类型从一个整数变成一个字符串时(想想员工号,尽管它的名字可能包含字母) ,它也会让你处于脆弱的境地。

因此,我们将 EmployeeNumber 的类型从 int更改为 string,但是忘记更新 sql 查询。

在我看来,如果你能保证你的参数将永远不会包含一个字符串,它是安全的,但我不会这样做,在任何情况下。此外,由于正在执行连接,您将看到性能略有下降。我想问的问题是,为什么不想使用参数?

在您控制的计算机(如 Web 服务器)上使用强类型平台时,可以防止只使用 boolDateTimeint(和其他数值)值的查询的代码注入。令人担忧的是,由于强制 sql 服务器重新编译每个查询,并且阻止它获得关于以什么频率运行查询的良好统计数据(这会损害缓存管理) ,从而导致性能问题。

但是“在您控制的计算机上”这一部分很重要,因为否则用户可以改变系统用于从这些值生成字符串的行为,使其包含任意文本。

我也喜欢从长远考虑。如果今天那些老掉牙的强类型代码库通过自动翻译被移植到最新的动态语言中,你突然失去了类型检查,但是还没有对动态代码进行所有正确的单元测试,那么会发生什么呢?

实际上,没有理由不对这些值使用查询参数。这是 正确的方式的做法。如果 sql 字符串实际上是常量,那么继续将它们硬编码到 sql 字符串中,否则,为什么不直接使用参数呢?这又不难。

归根结底,我不会把它称为 臭虫本身,但我会把它称为 smell: 一些本身就不是 bug 的东西,但是它强烈地表明 bug 就在附近,或者最终会在附近。好的代码可以避免遗留问题,任何好的静态分析工具都会标记这一点。

不幸的是,我要补充一点,这不是那种你可以直接赢得的辩论。这听起来像是一种情况,“正确”已经不够了,而且单靠自己的力量来解决这个问题不太可能促进良好的团队动力; 它最终可能带来的伤害大于帮助。在这种情况下,更好的方法可能是促进静态分析工具的使用。这将使旨在回顾和修订现有准则的努力具有合法性和可信度。

让我们不要只考虑安全性或类型安全方面的考虑。

使用参数化查询的原因是为了提高数据库级别的性能。从数据库的角度来看,参数化查询是 SQL 缓冲区中的一个查询(使用 Oracle 的术语,尽管我想所有数据库内部都有类似的概念)。因此,数据库可以在内存中保存一定数量的查询,并准备好执行。这些查询不需要解析,而且速度更快。经常运行的查询通常位于缓冲区中,并且不需要每次使用它们时都进行解析。

UNLESS

有些人不使用参数化查询。在这种情况下,数据库引擎需要解析和运行一系列几乎完全相同的查询,这些查询会不断地冲刷缓冲区,性能会受到全面影响,因为即使是频繁运行的查询也会每天被重新解析很多次。我以调整数据库为生,这已经成为低挂果实的最大来源之一。

现在

To answer your question, IF your query has a small number of distinct numeric values, you will probably not be causing issues and may in fact improve performance infinitesimally. IF however there are potentially hundreds of values and the query gets called a lot, you are going to affect the performance of your system so don't do it.

是的,您可以增加 SQL 缓冲区,但最终总是以牺牲其他对内存更重要的用途(如缓存索引或数据)为代价。从道义上讲,相当虔诚地使用参数化查询,这样您就可以优化数据库,并为重要的事情使用更多的服务器内存..。

在某些情况下,可以使用字符串值以外的非参数化(连接)变量执行 SQL 注入攻击——请参阅 Jon 的本文: http://codeblog.jonskeet.uk/2014/08/08/the-bobbytables-culture/

问题是,当调用 ToString时,一些自定义区域性提供程序可以将一个非字符串参数转换为其字符串表示形式,从而在查询中注入一些 SQL。

这是可以的,但从来没有安全。.而且安全性总是取决于输入,例如,如果输入对象是 TextBox,攻击者可以做一些棘手的事情,因为文本框可以接受字符串,所以你必须放置某种验证/转换,以防止用户错误的输入。但问题是,这里不安全。就这么简单。

为了给 Maciek 的回答增加一些信息:

修改文化信息是很容易的。NET 第三方应用程序,通过反射调用程序集的 main-function:

using System;
using System.Globalization;
using System.Reflection;
using System.Threading;


namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
Assembly asm = Assembly.LoadFile(@"C:\BobbysApp.exe");
MethodInfo mi = asm.GetType("Test").GetMethod("Main");
mi.Invoke(null, null);
Console.ReadLine();
}


static Program()
{
InstallBobbyTablesCulture();
}


static void InstallBobbyTablesCulture()
{
CultureInfo bobby = (CultureInfo)CultureInfo.InvariantCulture.Clone();
bobby.DateTimeFormat.ShortDatePattern = @"yyyy-MM-dd'' OR ' '=''";
bobby.DateTimeFormat.LongTimePattern = "";
bobby.NumberFormat.NegativeSign = "1 OR 1=1 OR 1=";
Thread.CurrentThread.CurrentCulture = bobby;
}
}
}

这只有在 BobbyApp 的 Main 函数是公共的时候才有效。如果 Main 不是公共函数,则可以调用其他公共函数。

No you can get an SQL injection attack that way. I have written an old article in Turkish which shows how 给你. Article example in PHP and MySQL but concept works same in C# and SQL Server.

基本上,你攻击的方式。假设您有一个页面,它根据整数 id 值显示信息。您不需要参数化它的值,如下所示。

http://localhost/sqlEnjeksiyon//instructors.aspx?id=24

好的,我假设你正在使用 MySQL,我按照下面的方式进行攻击。

http://localhost/sqlEnjeksiyon//instructors.aspx?id=ASCII((SELECT%20DATABASE()))

注意,这里注入的值不是字符串。我们正在使用 ASCII 函数将 char 值改为 int。您可以使用“ CAST (YourVarcharCol ASINT)”在 SQLServer 中完成同样的事情。

然后,我使用 length 和 substring 函数查找数据库名称。

http://localhost/sqlEnjeksiyon//instructors.aspx?id=LEN((SELECT%20DATABASE()))


http://localhost/sqlEnjeksiyon//instructors.aspx?id=ASCII(SUBSTR(SELECT%20DATABASE(),1,1))

然后使用数据库名称,开始获取数据库中的表名称。

http://localhost/sqlEnjeksiyon//instructors.aspx?id=ASCII(SUBSTR((SELECT table_name FROM INFORMATION_SCHEMA.TABLES LIMIT 1),1,1))

当然,您必须使这个过程自动化,因为每个查询只能获得一个字符。但是你可以很容易地自动化它。我的文章展示了 中的一个例子。只使用一个页面并且不使用参数化 ID 值。我可以知道你数据库里每个表的名字。然后我就可以找重要的桌子了。这需要时间,但是是可行的。

即使对于非字符串类型,这也是 没有安全的。

Consider following code example:

var utcNow = DateTime.UtcNow;
var sqlCommand = new SqlCommand("SELECT * FROM People WHERE created_on <= '" + utcNow + "'");

乍一看,代码看起来很安全,但是如果你在 Windows 区域设置中做一些改变,并添加短日期格式的注入,一切都会改变:

Datetime Injection

现在生成的命令文本如下:

SELECT * FROM People WHERE created_on <= '26.09.2015' OR '1'<>' 21:21:43'

对于 int类型也可以这样做,因为用户可以定义自定义负号,这样就可以很容易地将其转换为 SQL 注入。

有人可能会争辩说,应该使用不变区域性而不是当前区域性,但我已经多次看到这样的字符串连接,当使用 +将字符串与对象连接时,很容易忽略它。