当参数不是字符串时,不参数化SQL查询是否安全?

时间:2015-09-18 02:02:08

标签: c# sql sql-injection sqlcommand parameterized-query

SQL injection而言,我完全理解参数化string参数的必要性;这是本书中最古老的技巧之一。但什么时候参数化SqlCommand是否合理呢?是否认为任何数据类型不安全参数化?

例如:我不认为自己附近的SQL专家,但我不能想到任何可能容易受SQL注入接受{{1或者bool,只需将其连接到查询中即可。

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

为了澄清,这个问题被标记为,这是一种强类型语言;当我说“参数”时,请考虑 int

12 个答案:

答案 0 :(得分:101)

我认为这很安全...... 从技术上讲 ,但这是一个可怕的习惯。你真的想写这样的查询吗?

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

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

在类型从整数变为字符串的情况下,它也会让您容易受到攻击(Think员工编号,尽管名称可能包含字母)。

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

答案 1 :(得分:65)

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

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

我也想长远思考。当今天的老式强类型代码库通过自动转换移植到新的热门动态语言时会发生什么,你突然失去了类型检查,但是还没有针对动态代码进行所有正确的单元测试?

实际上,没有充分的理由不对这些值使用查询参数。这是正确的方式。当它们确实是常量时,继续将值硬编码到sql字符串中,否则,为什么不使用参数呢?这不是很难。

最终,我不会将此称为错误本身,但我会称之为嗅觉:这个错误本身就是一个错误,但是强烈表明虫子在附近,或者最终会出现。好的代码可以避免留下气味,任何好的静态分析工具都会标记这一点。

我会补充说,不幸的是,这不是你可以直接赢得的那种争论。这听起来像是一种“正确”不再足够的情况,踩着你的同事脚趾来解决这个问题并不能促进良好的团队动力;它最终可能会伤害到更多的伤害。在这种情况下,更好的方法可能是促进使用静态分析工具。这将使目标和回归以及修复现有代码的努力具有合法性和可信度。

答案 2 :(得分:53)

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

事情是,当调用ToString时,某些自定义文化提供程序可以将非字符串参数转换为其字符串表示形式,从而将一些SQL注入到查询中。

答案 3 :(得分:51)

即使对于非字符串类型,这也是安全。 始终使用参数。周期。

请考虑以下代码示例:

(let ((foo "one")
      (bar "two"))
  (frobnicate foo bar))

乍一看代码看起来很安全,但如果您在Windows区域设置中进行了一些更改并以短日期格式添加注入,则一切都会更改:

Datetime Injection

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

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

SELECT * FROM People WHERE created_on <= '26.09.2015' OR '1'<>' 21:21:43' 类型也可以这样做,因为用户可以定义自定义负号,可以轻松更改为SQL注入。

有人可能会争辩说应该使用不变文化而不是当前的文化,但我已经看过很多次这样的字符串连接,并且在使用int将字符串与对象连接时很容易错过。

答案 4 :(得分:23)

“SELECT * FROM Table1 WHERE Id =”+ intVariable.ToString()

安全
没关系。
攻击者无法在你输入的int变量中注入任何东西。

<强>性能
不行。

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

编码风格
不好的做法。

  • 参数更具可读性
  • 也许它会让你习惯于没有参数的查询,然后你可能犯了一次错误并以这种方式使用字符串值然后你可能应该对你的数据说再见。坏习惯!


“SELECT * FROM Product WHERE Id =”+ TextBox1.Text

虽然这不是你的问题,但对未来的读者可能有用:

安全
灾难!
即使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变量,它没有安全风险,但我认为更改这些代码并不浪费时间,建议更改这些代码。它使代码更易读,更易于维护,并使执行速度更快。

答案 5 :(得分:18)

实际上有两个问题。标题中的问题与OP在之后的评论中表达的担忧几乎没有关系。

虽然我意识到OP对于他们的特殊情况很重要,对于来自Google的读者来说,回答更一般的问题很重要,可以表达为&#34;连接是安全的如果我确定我连接的每个字面都是安全的,那么准备好的陈述?&#34;。所以,我想专注于后者。答案是

绝对没有。

解释并不像大多数读者那样直接,但我会尽我所能。

我一直在思考这个问题,导致article(虽然基于PHP环境)我试图总结一切。在我看来,保护SQL注入的问题往往是针对一些相关但较窄的主题,如字符串转义,类型转换等。虽然有些措施可以被认为是安全的,但是没有系统,也没有简单的规则可循。这使得它非常滑,对开发人员的注意力和经验太过分了。

SQL注入的问题无法简化为某些特定语法问题。它比一般的开发人员想象的要广泛。它也是一个方法论问题。它不仅仅是&#34;我们必须应用哪种特定格式&#34;,但是&#34; 如何必须完成&#34;同样。

(从这个角度来看,Jon Skeet在另一个答案中引用的文章表现得相当糟糕,因为它在某些边缘情况下再次挑剔,专注于特定的语法问题而未能解决问题整体。)

当您尝试解决保护问题时,不是整体而是作为一组不同的语法问题,您将面临众多问题。

  • 可能的格式选择列表非常庞大。意味着一个人很容易忽视一些。或者混淆它们(例如,使用 string 转义为标识符)。
  • 连接意味着所有保护措施都必须由程序员完成,而不是程序。仅此问题就会产生一些后果:
    • 这样的格式是手动的。手动意味着极其容易出错。人们可能会忘记申请。
    • 此外,有一种诱惑是将格式化程序转移到某些集中式功能中,使事情变得更糟,并破坏不会进入数据库的数据。
  • 当涉及多个开发人员时,问题会增加十倍。
  • 当使用连接时,人们无法一眼就看出有潜在危险的查询:他们所有都有潜在的危险!

与那些混乱不同,准备好的陈述确实是圣杯:

  • 它可以用一个易于理解的简单规则的形式表达。
  • 它本质上是不可取的措施,意味着开发人员不能干涉,并且愿意或不情愿地破坏这个过程。
  • 注入保护实际上只是预准备语句的副作用,其真正目的是产生语法正确的语句。语法正确的语句是100%注入证明。然而,尽管有任何注入可能性,我们仍需要我们的语法正确。
  • 如果一直使用,它会保护应用程序,无论开发人员的经验如何。比如,有一个名为second order injection的东西。还有一个非常强烈的妄想,即为了保护,Escape All User Supplied Input&#34;。如果开发人员自由决定,需要保护什么以及不需要保护什么,它们结合在一起就会导致注入。

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

有趣的是,这个问题的争议是由Stack Overflow非常有争议的本质引起的。该网站的想法是利用直接要求
的用户的特定问题来实现拥有适用于来自搜索的用户的通用答案数据库的目标。这个想法本身并不坏本身,但它在这样的情况下失败:当用户提出非常狭窄的问题时,特别是在与同事(或决定是否值得重构代码)。虽然大多数有经验的参与者都在尝试撰写答案,但请牢记Stack Overflow的任务,让他们的答案尽可能多的读者,而不仅仅是OP。

答案 6 :(得分:15)

让我们不要只考虑安全性或类型安全性考虑因素。

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

除非

有人不使用参数化查询。在这种情况下,缓冲区通过几乎相同的查询流不断刷新,每个查询需要由数据库引擎解析和运行,并且性能全面受损,因为即使频繁运行的查询最终也会被重新解析多次天。我已经调整了数据库以维持生计,这一直是最低端果实的最大来源之一。

现在

要回答您的问题,如果您的查询具有少量不同的数字值,您可能不会导致问题,实际上可能无限制地提高性能。但是,如果有可能有数百个值,并且查询被大量调用,那么您将影响系统的性能,所以不要这样做。

是的,你可以增加SQL缓冲区,但它总是以牺牲其他更重要的内存用途为代价,比如缓存索引或数据。道德,非常虔诚地使用参数化查询,这样你就可以优化你的数据库并使用更多的服务器内存来处理那些重要的东西......

答案 7 :(得分:8)

向Maciek回复添加一些信息:

通过反射调用程序集的主函数,可以很容易地改变.NET第三方应用程序的文化信息:

RewriteCond %{REQUEST_URI} !^/admin$ [NC]
RewriteCond %{REQUEST_URI} !^/users$ [NC]
RewriteRule ^(.*)$ /admin [R=301,L,NC]

仅当BobbysApp的Main函数是公共的时才有效。如果Main不公开,可能还有其他公共功能。

答案 8 :(得分:7)

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

答案 9 :(得分:3)

没关系但永远不安全..并且安全总是依赖于输入,例如,如果输入对象是TextBox,攻击者可以做一些棘手的事情,因为文本框可以接受字符串,所以你必须放一些验证/转换能够防止用户输入错误。但事实是,它并不安全。就像那样。

答案 10 :(得分:-2)

不,你可以通过这种方式获得SQL注入攻击。我用土耳其语撰写了一篇旧文章,其中展示了here。 PHP和MySQL中的文章示例,但概念在C#和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 AS INT)”在SQL Server中完成相同的操作。

之后我使用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))

当然,您必须自动执行此过程,因为每个查询只能获得一个字符。但您可以轻松实现自动化。我的文章在watir中展示了一个例子。仅使用一个页面而不是参数化ID值。我可以学习数据库中的每个表名。之后我可以寻找重要的表格。这需要时间,但它是可行的。

答案 11 :(得分:-3)

嗯......有一件事是肯定的: 当您将字符串(由用户使用)与SQL命令字符串连接在一起时,安全性是不可行的。 无论何时where子句引用整数或任何类型都无关紧要;可能会发生注射。

SQL注入中最重要的是用于从用户获取值的变量的数据类型。

假设我们在where子句中有一个整数,并且:

  1. user-variable是一个字符串。那好吧,这不是很容易 注入(使用UNION)但很容易绕过使用&#39; OR 1 = 1&#39; - 喜欢攻击...

  2. 如果用户变量是整数。然后,我们可以再次测试&#39;该 通过传递不寻常的大数字测试系统的强度 系统崩溃甚至是隐藏的缓冲区溢出(在最后 string)...;)

  3. 查询或(甚至更好 - imo)对存储过程的参数可能不是100%威胁安全,但它们是最不需要的度量(或者如果您愿意,则是最基本的度量)来最小化它们。