当参数不是string时,是否安全地参数化SQL查询?

在SQL注入方面 ,我完全理解参数化string参数的必要性; 这是这本书中最古老的技巧之一。 但是,什么时候可以certificate参数化一个SqlCommand呢? 任何数据types被认为是“安全的”,不参数化?

例如:我不认为自己在SQL专家附近的任何地方,但我不能想到任何情况下,它可能容易受到SQL注入接受一个bool或一个int ,只是连接到查询。

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

为了澄清,这个问题被标记为C# ,这是一个强types的语言; 当我说“参数”,想像像 public int Query(int id)

我认为这在技术上是安全的,但这是一个可怕的习惯。 你真的想写这样的问题吗?

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

在一个types从一个整数变成一个string的情况下,这也会使你容易受到攻击(认为雇员号尽pipe名字可能包含字母)。

所以,我们将EmployeeNumber的types从int更改为string ,但忘记更新我们的sql查询。 哎呀。

在控制的计算机(如Web服务器)上使用强types平台时,可以防止仅使用boolDateTimeint (以及其他数字)值进行查询的代码注入。 值得关注的是由于强制sql server重新编译每个查询所导致的性能问题,并且阻止了以什么频率运行哪些查询(这会影响cachingpipe理)的统计信息。

真的,没有理由不对这些值使用查询参数。 这是正确的方式去做这件事。 当他们真的是常量时,请将值硬编码到sqlstring中,但除此之外,为什么不使用参数呢? 这不是很难。

我也喜欢长期思考。 当今天老式的强types代码库通过自动翻译移植到新兴的dynamic语言中时会发生什么情况,并且您突然失去了types检查,但是尚未针对dynamic代码进行所有正确的unit testing?

最终,我不会把这个错误称为本身,但是我会把它称为一种气味 :它本身就缺less一个错误,但却强烈地表明错误在附近,或者最终将会出现。 好的代码避免了留下的气味,任何好的静态分析工具都会标记这个。

我会补充说,不幸的是,这不是一种你可以直接赢得胜利的论点。 这听起来像是一个“正确的”已经不够的情况,踩着你的同事脚趾来自己解决这个问题不太可能促进良好的团队dynamic; 它可能最终伤害比它帮助。 在这种情况下更好的方法可能是促进使用静态分析工具。 这样做会使目标和回溯现有代码的努力具有合法性和可信度。

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

事情是,当ToString被调用时,一些自定义的文化提供者可以将一个非string参数转换成它的string表示,它将一些SQL注入到查询中。

即使对于非stringtypes也是不安全的。 始终使用参数。 期。

考虑下面的代码示例:

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

乍一看,代码看起来很安全,但如果在Windows区域设置中进行了一些更改并在短date格式中添加了注入,则所有内容都会更改:

日期时间注入

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

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

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

有人可能会争辩说,应该使用不变的文化来代替当前的文化,但是我已经看到了像这样的string连接这么多次,当使用+连接string与对象时很容易错过。

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


安全
没关系。
攻击者不能在你的types化的intvariables中注入任何东西。

性能
不好。

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

编码风格
糟糕的做法。

  • 参数更具可读性
  • 也许它会让你习惯于没有参数的查询,那么也许你犯了一个错误,并以这种方式使用string值,那么你可能应该告诉你的数据。 坏习惯!

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


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

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

 "SELECT * FROM Table1 WHERE Id=1; DELETE Table1" 

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

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

你的问题


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

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

如果你的同事使用intvariables,它没有安全风险,但是我认为改变这些代码并不是浪费时间,而且确实改变这些代码是被推荐的。 它使代码更具可读性,更易维护,并使执行速度更快。

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

虽然我认识到,对于OP来说,对于来自Google的读者来说,重要的是要回答更一般的问题,这可以被解释为“如果我确定的话,连接就像预先准备的语句一样安全我连接的每一个文字都是安全的?“ 所以,我想专注于后者。 答案是

肯定没有。

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

我一直在思考这个问题,导致文章 (虽然基于PHP环境),我试图总结一切。 我想到SQL注入保护的问题经常被忽略,只是一些相关而狭窄的话题,比如string转义,types转换等等。 虽然有些措施可以被认为是安全的,但是没有制度,也没有简单的规则可循。 这使得它很滑,太过于关注开发者的注意力和经验。

SQL注入的问题不能简化成某种特定的语法问题。 这是比一般的开发人员以前想象的更广泛。 这也是一个方法论问题。 这不仅是“我们必须采用哪种特定的格式”,还有“ 如何做”。

(从这个angular度来看,Jon Skeet在另一个答案中引用的一篇文章,做得比较不好,因为它在某些边缘情况下再次挑剔,专注于一个特定的语法问题,而没有解决整个问题。

当你试图解决保护的问题不是完整的,而是一系列不同的语法问题的时候,你面临着许多问题。

  • 可能的格式select列表真的很大。 意味着可以轻易忽略一些。 或混淆他们(通过使用string转义标识符为例)。
  • 连接意味着所有的保护措施都必须由程序员来完成,而不是程序。 仅这个问题就会导致几个后果:
    • 这种格式是手动的。 手动意味着非常容易出错。 人们可以简单地忘记申请。
    • 而且,将格式化过程转移到一些集中式的function,使事情变得更糟,并且破坏不会去数据库的数据是有诱惑力的。
  • 当涉及到多个开发人员时,问题会倍增十倍。
  • 当使用连接时,一眼就看不出有潜在危险的查询:它们都有潜在危险!

不像那个混乱,准备好的声明确实是圣杯:

  • 它可以用简单易懂的规则来expression。
  • 它本质上是不可琢磨的,意味着开发者不能干预,并且愿意或不愿意破坏这个过程。
  • 注射保护实际上只是准备好的陈述的副作用 ,真正的目的是产生句法上正确的陈述。 语法正确的语句是100%注入certificate。 然而我们需要我们的语法是正确的,尽pipe有任何注入的可能性。
  • 如果一直使用它,它保护应用程序,无论开发人员的经验。 说,有一个叫二次注射的东西。 还有一个非常强烈的错觉,即“为了保护, 退出所有用户提供的input ”。 如果一个开发者自由决定,什么需要保护,什么不需要,那么他们结合在一起就会导致注入。

(进一步思考,我发现当前的一组占位符不足以满足现实生活的需要,不得不扩展,无论是对于复杂的数据结构(如数组),还是SQL关键字或标识符,都必须添加到查询也是dynamic的,但是开发人员没有为这种情况做准备,并且被迫退回到string连接,但这是另外一个问题)。

有趣的是,这个问题的争议是由堆栈溢出的非常有争议的性质引起的。 该网站的想法是利用来自用户特定问题,用户直接要求实现具有适合于来自search的用户的通用答案的数据库的目标 。 这个想法本身并不坏,但在这样的情况下却失败了:当用户提出一个非常狭窄的问题时 ,特别是在与同事争执时(或者决定是否值得重构代码)。 虽然大多数有经验的参与者都在试图写出答案,但是要牢记堆栈溢出的使命 ,尽可能让读者尽可能多的回答,而不仅仅是OP。

我们不要只考虑安全或types安全的考虑因素。

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

除非

有人不使用参数化查询。 在这种情况下,缓冲区会通过几乎相同的查询stream持续刷新,每个查询都需要由数据库引擎进行分析和运行,性能会受到影响,因为即使是频繁运行的查询也会多次重新分析天。 为了生计,我调整了数据库,这已经是低悬的最大的来源之一。

现在

要回答你的问题,如果你的查询有less量不同的数字值,你可能不会造成问题,实际上可以无限地提高性能。 但是,如果可能有数百个值,并且查询被调用了很多,那么将会影响系统的性能,所以不要这样做。

是的,你可以增加SQL缓冲区,但最终总是以caching索引或数据等其他更重要的内存使用为代价。 道德,使用参数化的查询非常虔诚,所以你可以优化你的数据库,并使用更多的服务器内存的东西,重要的…

向Maciek添加一些信息回答:

通过reflection调用程序集的主要function很容易改变.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; } } } 

这只适用于BobbysApp的主要function是公共的。 如果Main不公开,那么可能会有其他可能调用的公共函数。

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

这是好的,但从来没有安全..安全总是依赖于input,例如,如果input对象是文本框,攻击者可以做一些棘手的事情,因为文本框可以接受string,所以你必须把某种validation/转换能够防止用户input错误。 但事情是,这是不安全的。 就这么简单。

不,您可以通过这种方式获得SQL注入攻击。 我写了一篇土耳其文的旧文章,说明如何在这里 。 在PHP和MySQL中的文章例子,但在C#和SQL Server中的概念工作原理是一样的。

基本上你以下面的方式攻击。 让我们考虑你有一个页面显示信息根据整数id值。 你不要参数化这个值,如下所示。

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

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

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

请注意,这里注入的值不是string。 我们正在使用ASCII函数将char值更改为int。 您可以使用“CAST(YourVarcharCol AS INT)”在SQL Server中完成同样的事情。

之后,我使用长度和子string函数来查找您的数据库名称。

 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值。 我可以学习数据库中的每个表名。 之后,我可以寻找重要的表格。 这将需要时间,但它是可行的。

那么…有一件事是肯定的:当你用你的SQL命令串连接一个string(由用户获取)时,安全性是不好的。 每当where子句引用一个Integer或任何types时,都不是问题; 可能会发生注射。

SQL注入中重要的是用于从用户获取值的variables的数据types。

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

  1. 用户variables是一个string。 那么好吧,注入(使用UNION)并不是很容易,但是很容易绕过使用'OR 1 = 1' – 类似的攻击。

  2. 如果用户variables是一个整数。 然后,我们可以通过为系统崩溃或者甚至是隐藏的缓冲区溢出(在最后一个string上)传递不寻常的大数字testing来“testing”系统的强度…;

也许参数查询或(甚至更好 – imo)存储过程是不是100%安全的威胁,但他们是最不需要的措施(或者如果你喜欢的基本一个),以尽量减less他们。

Interesting Posts