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

joh*_*ose 113 c# sql sql-injection sqlcommand parameterized-query

SQL注入方面,我完全理解参数化string参数的必要性; 这是本书中最古老的技巧之一.但什么时候可以证明参数化SqlCommand?是否认为任何数据类型不安全参数化?

例如:我不认为自己在SQL专家附近,但我不能想到任何可能容易受到SQL注入接受a bool或an int并且只是将其连接到查询中的情况.

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

为了澄清,这个问题被标记为,这是一种强类型语言; 当我说"参数"时,想一想 public int Query(int id).

Rob*_*Rob 101

我觉得从技术上来说这是安全的,但这是一个很糟糕的习惯.你真的想写这样的查询吗?

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

sqlCommand.Parameters.AddWithValue("firstName", "Rob");
Run Code Online (Sandbox Code Playgroud)

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

因此,我们已将EmployeeNumber的类型更改intstring,但忘记更新我们的SQL查询.哎呀.

  • 我们可以停止使用`AddWithValue`吗?http://blogs.msmvps.com/jcoehoorn/blog/2014/05/12/can-we-stop-using-addwithvalue-already/ (24认同)
  • @RemarkLima关键是"我们可以停止使用`AddWithValue`吗?" 应该真的"如果你知道类型,那么你应该避免使用`AddWithValue`. (7认同)
  • @RemarkLima当你动态生成将值映射到参数的代码时,有什么解决方案?博客文章无法解决这种情况.是的,当它已知*时,设置SQL类型*是一行,但是当它不存在时,则会出现问题(或者您必须使用带有该信息的注释模型). (5认同)
  • 嘿,不要拍摄信使,我没有写它;-)但重点仍然存在,如果从一开始就内置到你的设计中,你没有理由不知道这种类型.最佳实践和所有爵士乐:-) (2认同)

Joe*_*orn 65

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

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

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

实际上,没有充分的理由不对这些值使用查询参数.这是解决这个问题的正确方法.当它们确实是常量时,继续将值硬编码到sql字符串中,但是,为什么不使用参数呢?这不像是很难.

最终,我不会把它称为一个bug本身,但我会称之为一种气味:这个东西本身就是一个错误,但是有一个强烈的迹象表明错误在附近,或者最终会出现.好的代码避免留下气味,任何好的静态分析工具都会标记这一点.

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

  • 这是一种"气味":本身没有虫子的东西,但表明虫子可能在附近.好的代码试图消除气味.任何好的静态分析工具肯定会标记它. (7认同)
  • 我认为"这是一个错误"这个问题是我的问题归结为. (2认同)
  • 这是错误的。例如,请参阅我的答案,了解如何仍然使用 `DateTime` 或 `int` 创建 SQL 注入 (2认同)

Mac*_*iek 53

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

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

  • 我认为这是回答这个问题的唯一一个帖子,主要是"如何用'int`s'进行注射?" (14认同)
  • 虽然如果你能够注入自定义代码,例如booby被困的`CultureInfo`,很难知道为什么你还需要SQL注入. (3认同)

Kas*_*ols 51

这是不是即使对于非字符串类型安全.始终使用参数.期.

请考虑以下代码示例:

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

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

日期时间注入

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

SELECT * FROM People WHERE created_on <= '26.09.2015' OR '1'<>' 21:21:43'
Run Code Online (Sandbox Code Playgroud)

对于int类型可以做同样的事情,因为用户可以定义自定义负号,可以很容易地将其更改为SQL注入.

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

  • 谁可以更改服务器设置?如果一个人可以在您的服务器中执行此操作,则他不需要SQL注入来销毁数据. (10认同)
  • 这是最好的答案,它显示了一种验证OP关注的方法,这是一个bug /安全漏洞.例如,在SQL中连接日期时间的性能和未来证明不仅仅是*气味*或*技术债务*.@RezaAghaei这个问题从未提及服务器端,它可能是带有SQLExpress的Windows应用程序 - 无论哪种方式都不是问题的标准.任何人都可以说,但谁有权访问服务器设置来反驳这个优秀的答案,就像任何人都可以说共享服务器托管或Y2K错误一样.我同意你的服务器被锁定 - 这不是一个先决条件. (5认同)
  • 你能提供一个关于`int`类型的例子吗? (2认同)

Rez*_*aei 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"
Run Code Online (Sandbox Code Playgroud)

如果您不想在此处使用参数化查询,则应使用类型化值:

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


你的问题


我的问题出现了,因为一位同事写了一堆连接整数值的查询,我想知道是否浪费我的时间来解决所有这些问题.

我认为改变这些代码不是浪费时间.确实推荐变化!

如果您的同事使用int变量,它没有安全风险,但我认为更改这些代码不是浪费时间,建议更改这些代码.它使代码更易读,更易于维护,并使执行速度更快.


You*_*nse 18

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

虽然我认识到OP对于他们的特殊情况很重要,对于来自谷歌的读者来说,重要的是要回答更一般的问题,这可以被称为"如果我确定的话,连接就像准备好的陈述一样安全我连接的每个文字都是安全的吗?" 所以,我想专注于后者.答案是

绝对没有.

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

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

SQL注入的问题不能简化为某个特定语法问题.它比一般的开发人员想象的要广泛.这也是一个方法论问题.它不仅是"我们必须应用哪种特定格式",而且" 它必须如何完成".

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

当你试图解决保护问题时,不是整体而是作为一组不同的语法问题,你面临着许多问题.

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

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

  • 它可以用一个易于遵循的简单规则的形式表达.
  • 它本质上是不可取的措施,意味着开发人员不能干涉,并且愿意或不情愿地破坏这个过程.
  • 注入保护实际上只是预处理语句的副作用,其真正目的是产生语法正确的语句.语法正确的语句是100%注入证明.然而,尽管有任何注入可能性,我们仍需要我们的语法正确.
  • 如果一直使用,它会保护应用程序,无论开发人员的经验如何.比如说,有一种叫二阶注射的东西.还有一个非常强烈的妄想,即"为了保护, 逃脱所有用户提供的输入 ".如果开发人员自由决定,需要保护什么,什么不需要,它们结合在一起就会导致注入.

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

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

  • 没有回答这个问题 (10认同)

mco*_*tle 15

我们不仅要考虑安全性或类型安全的考虑因素.

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

除非

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

现在

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

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


use*_*167 8

要向Maciek添加一些信息,请回答:

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

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;
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

这仅在BobbysApp的Main函数是公共的时才有效.如果Main不公开,可能还有其他公共功能可能会调用.

  • 谁可以更改服务器设置或谁可以在服务器中摩擦此类代码?如果某人可以在您的服务器上完成此操作,则无需SQL注入即可销毁数据。 (2认同)

小智 7

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