避免不带参数的SQL注入

在这里我们正在讨论在我们的代码中使用参数化的sql查询。 我们有两方面的讨论:我和其他一些人说,我们应该总是使用参数来防止SQL注入和其他人认为这是不必要的。 相反,他们想要用所有string中的两个撇号replace单撇号来避免sql注入。 我们的数据库都运行Sql Server 2005或2008,我们的代码库运行在.NET框架2.0上。

让我给你一个简单的例子在C#中:

我希望我们使用这个:

string sql = "SELECT * FROM Users WHERE Name=@name"; SqlCommand getUser = new SqlCommand(sql, connection); getUser.Parameters.AddWithValue("@name", userName); //... blabla - do something here, this is safe 

而其他人想要这样做:

 string sql = "SELECT * FROM Users WHERE Name=" + SafeDBString(name); SqlCommand getUser = new SqlCommand(sql, connection); //... blabla - are we safe now? 

SafeDBString函数定义如下:

 string SafeDBString(string inputValue) { return "'" + inputValue.Replace("'", "''") + "'"; } 

现在,只要我们对查询中的所有string值使用SafeDBString,我们应该是安全的。 对?

有两个原因使用SafeDBString函数。 首先,这是石器时代以来的方式,其次,由于您看到在数据库上运行的excact查询,因此更容易debuggingsql语句。

那么。 我的问题是,是否真的足够使用SafeDBString函数来避免SQL注入攻击。 我一直在试图find打破这种安全措施的代码示例,但我找不到任何示例。

有没有人可以打破这个? 你会怎么做?

编辑:总结到目前为止的答复:

  • 没有人find解决Sql Server 2005或2008中的SafeDBString的方法。 这很好,我想呢?
  • 有几个回复指出,使用参数化查询可以获得性能提升。 原因是查询计划可以重用。
  • 我们也同意使用参数化查询可以提供更易读的代码,更易于维护
  • 此外,总是使用参数比使用各种版本的SafeDBString,string到数字的转换和string到date的转换更容易。
  • 使用参数可以实现自动types转换,这在我们使用date或十进制数时非常有用。
  • 最后: 不要像朱利安(JulianR)写的那样自己去做安全措施 。 数据库供应商花费大量时间和金钱安全。 我们不可能做得更好,没有理由我们应该努力工作。

所以,虽然没有人能够打破SafeDBString函数的简单安全性,但我得到了很多其他的好的论点。 谢谢!

我认为正确的答案是:

不要试图自己做安全 。 使用任何值得信赖的行业标准库来提供您想要做的事情,而不是自己动手做。 无论你对安全做出什么假设,都可能是不正确的。 就像你自己的方法看起来一样安全(而且看起来好像不稳定),那么你就有可能忽视某些事情,并且在安全方面你真的想要抓住这个机会吗?

使用参数。

然后有人去使用“而不是”。参数是,IMO,唯一的安全的方式去。

它也避免了date/数字方面的许多国际性问题; 什么date是01/02/03? 123456多less钱? 你的服务器(app-server和db-server)是否一致?

如果风险因素不能令人信服,那么performance如何呢? 如果使用参数,RDBMS可以重新使用查询计划,从而提高性能。 它不能只用string来完成。

这个论点是不成功的。 如果您确实发现了一个漏洞,那么您的同事将会更改SafeDBString函数来解释它,然后要求您再次certificate它是不安全的。

鉴于参数化查询是无可争议的编程最佳实践,举证责任应该放在他们身上,说明为什么他们不使用既安全又更好的方法。

如果问题在于重写所有的遗留代码,那么简单的妥协就是在所有新代码中使用参数化查询,并且在处理代码时重构旧代码以使用它们。

我的猜测是真正的问题是骄傲和固执,对此你没有什么可以做的。

首先,您的“replace”版本的示例是错误的。 你需要在文字周围加上撇号:

 string sql = "SELECT * FROM Users WHERE Name='" + SafeDBString(name) & "'"; SqlCommand getUser = new SqlCommand(sql, connection); 

所以这是另一个参数为你做的事情:你不需要担心值是否需要用引号引起来。 当然,你可以将它构build到函数中,但是你需要给函数增加很多复杂性:如何知道NULL和NULL之间的区别,如只是一个string,或者是一个数字和一个恰好包含大量数字的string。 这只是错误的另一个来源。

另外一点是性能:参数化查询计划通常比连接计划caching得更好,因此在运行查询时可能会节省服务器一个步骤。

另外, 转义单引号不够好。 许多数据库产品允许攻击者可以利用的angular色的替代方法。 例如,在MySQL中,您也可以使用反斜杠来转义单引号。 所以下面的“name”值只会使用SafeDBString()函数来炸掉MySQL,因为当你将单引号加倍时,第一个字符仍然被反斜线转义,而第二个字符仍然是“active”:

x'或1 = 1; –


另外,JulianR提出了一个很好的观点: 从不尝试自己做安全工作。 即使经过彻底的testing,以微妙的方式获得安全编程也是非常容易的。 然后时间过去了,一年后,你发现你的系统在六个月前被破解了,直到那时你才知道。

总是尽可能地依赖为您的平台提供的安全库。 它们将由那些以安全代码为生的人写出来,如果发现漏洞,那么testing的内容要比你能够pipe理的更好,并且由供应商提供服务。

所以我会说:

1)你为什么要重新实现内置的东西? 它在那里,随时可用,易于使用,并已在全球范围内进行debugging。 如果发现未来的错误,那么它们将会很快被修复,并且可以让所有人都快速使用,而无需做任何事情。

2)哪些stream程可以保证不会错过对SafeDBString的调用? 在一个地方丢失它可能会引发大量问题。 你有多less眼球要注意这些事情,并且考虑当接受的正确答案是如此容易达到的时候,这个努力是多么的浪费。

3)你有多确定你覆盖了微软(数据库和访问库的作者)在你的SafeDBString实现中知道的每个攻击向量。

4)它是如何轻松地阅读SQL的结构? 该示例使用+串联,参数非常类似string.Format,这是更具可读性。

另外,有两种方法可以计算出实际运行的内容 – 滚动您自己的LogCommand函数,一个没有安全考虑的简单函数,甚至可以查看一个sql跟踪来确定数据库正在进行的操作。

我们的LogCommandfunction很简单:

  string LogCommand(SqlCommand cmd) { StringBuilder sb = new StringBuilder(); sb.AppendLine(cmd.CommandText); foreach (SqlParameter param in cmd.Parameters) { sb.Append(param.ToString()); sb.Append(" = \""); sb.Append(param.Value.ToString()); sb.AppendLine("\""); } return sb.ToString(); } 

对或错,它给我们提供了我们需要的信息,而没有安全问题。

使用参数化查询,您可以获得更多的防止sql注入的保护。 你也可以获得更好的执行计划caching潜力。 如果你使用sql server查询分析器,你仍然可以看到“在数据库上运行的确切的sql”,所以你在debuggings​​ql语句方面也没有任何损失。

我已经使用这两种方法来避免SQL注入攻击,绝对偏好参数化查询。 当我使用连接的查询时,我使用了一个库函数来转义variables(如mysql_real_escape_string),并且不能确定我已经在专有实现中覆盖了所有的东西(因为它似乎你也是)。

如果不使用参数,则不能轻松地进行用户input的任何types检查。

如果使用SQLCommand和SQLParameter类来进行数据库调用,仍然可以看到正在执行的SQL查询。 查看SQLCommand的CommandText属性。

对于参数化查询非常容易使用,我一直都希望能够防止SQL注入。 其次,仅仅因为“总是这样做”并不意味着这是正确的做法。

如果你保证你会传递一个string,这是唯一的安全。

如果你没有在某个点上传递string呢? 如果只传递一个数字呢?

 http://www.mywebsite.com/profile/?id=7;DROP DATABASE DB 

最终会成为:

 SELECT * FROM DB WHERE Id = 7;DROP DATABASE DB 

我会使用存储过程或函数的一切,所以这个问题不会出现。

在我必须把SQL放入代码的地方,我使用了唯一有意义的参数。 提醒持不同意见者,有黑客比他们更聪明,并有更好的动机来打破那些试图超越他们的代码。 使用参数,这是不可能的,并不是很难。

同意在安全问题上。
使用参数的另一个原因是效率。

数据库将始终编译您的查询并caching它,然后重新使用caching的查询(对于后续请求显然更快)。 如果使用参数,则即使使用不同的参数,数据库也会在绑定参数之前根据SQLstring重新使用caching的查询。

但是,如果不绑定参数,则SQLstring会在每个请求(具有不同参数)上发生变化,并且永远不会匹配caching中的内容。

从很短的时间内,我不得不调查SQL注入问题,我可以看到,使一个值“安全”也意味着你正在closures的大门,你可能真的需要在你的数据撇号 – 什么人的名字如O'Reilly。

这留下参数和存储过程。

是的,你应该总是试着用你现在知道的最好的方式来实现代码 – 而不是总是这样做。

这里有几篇文章可以帮助你说服你的同事。

http://www.sommarskog.se/dynamic_sql.html

http://unixwiz.net/techtips/sql-injection.html

就个人而言,我宁愿永远不要让任何dynamic代码触及我的数据库,要求所有联系人都是通过sps(而不是使用dynamicSQl的)。 这并不意味着我已经授予用户许可做的事情,并且内部用户(除了为了pipe理目的而拥有生产访问权限的less数人)不能直接访问我的表,并创build破坏,窃取数据或进行欺诈。 如果您运行财务应用程序,这是最安全的方法。

它可以被打破,但手段取决于确切的版本/补丁等。

一个已经提出的溢出/截断错误,可以被利用。

另一个未来的方法是寻找类似于其他数据库的错误 – 例如,MySQL / PHP堆栈遭遇了一个转义问题,因为某些UTF8序列可能被用来操作replace函数 – replace函数会被诱骗引入注入字符。

在一天结束时,replace安全机制依赖于预期的function,而不是预期的function。 由于该function不是代码的预期目的,因此很有可能发现一些发现的怪癖会破坏您的预期function。

如果你有很多遗留的代码,replace方法可以作为一个权宜之计,以避免冗长的重写和testing。 如果你正在编写新的代码,没有任何借口。

由于已经给出的原因,参数是一个非常好的主意。 但是我们讨厌使用它们,因为创buildparam并将其名称分配给一个variables供以后在查询中使用是一个三重间接头部残骸。

以下类包装了您将通常用于构buildSQL请求的stringbuilder。 它使您可以编写参数化查询,而无需创build参数 ,因此您可以专注于SQL。 你的代码看起来像这样

 var bldr = new SqlBuilder( myCommand ); bldr.Append("SELECT * FROM CUSTOMERS WHERE ID = ").Value(myId, SqlDbType.Int); //or bldr.Append("SELECT * FROM CUSTOMERS WHERE NAME LIKE ").FuzzyValue(myName, SqlDbType.NVarChar); myCommand.CommandText = bldr.ToString(); 

代码的可读性,我希望你的同意,大大改善,输出是一个适当的参数化查询。

这个class看起来像这样

 using System; using System.Collections.Generic; using System.Text; using System.Data; using System.Data.SqlClient; namespace myNamespace { /// <summary> /// Pour le confort et le bonheur, cette classe remplace StringBuilder pour la construction /// des requêtes SQL, avec l'avantage qu'elle gère la création des paramètres via la méthode /// Value(). /// </summary> public class SqlBuilder { private StringBuilder _rq; private SqlCommand _cmd; private int _seq; public SqlBuilder(SqlCommand cmd) { _rq = new StringBuilder(); _cmd = cmd; _seq = 0; } //Les autres surcharges de StringBuilder peuvent être implémenté ici de la même façon, au besoin. public SqlBuilder Append(String str) { _rq.Append(str); return this; } /// <summary> /// Ajoute une valeur runtime à la requête, via un paramètre. /// </summary> /// <param name="value">La valeur à renseigner dans la requête</param> /// <param name="type">Le DBType à utiliser pour la création du paramètre. Se référer au type de la colonne cible.</param> public SqlBuilder Value(Object value, SqlDbType type) { //get param name string paramName = "@SqlBuilderParam" + _seq++; //append condition to query _rq.Append(paramName); _cmd.Parameters.Add(paramName, type).Value = value; return this; } public SqlBuilder FuzzyValue(Object value, SqlDbType type) { //get param name string paramName = "@SqlBuilderParam" + _seq++; //append condition to query _rq.Append("'%' + " + paramName + " + '%'"); _cmd.Parameters.Add(paramName, type).Value = value; return this; } public override string ToString() { return _rq.ToString(); } } } 

我没有看到任何其他回答者解决了“为什么自己做的不好”这一方面,但考虑一下SQL截断攻击 。

还有QUOTENAME T-SQL函数,如果你不能说服他们使用参数,可能会有所帮助。 它捕捉到了很多(全部)逃脱的qoute问题。

以下是使用参数化查询的几个原因:

  1. 安全性 – 数据库访问层知道如何删除或转义数据中不允许的项目。
  2. 问题分离 – 我的代码不负责将数据转换成数据库所喜欢的格式。
  3. 没有冗余 – 我不需要在每个执行此数据库格式化/转义的项目中都包含一个程序集或类; 它内置于类库中。

与SQL语句的缓冲区溢出有关的漏洞(我不记得是哪个数据库)没有几个漏洞。

我想说的是,SQL注入更多的是“逃避报价”,你不知道接下来会发生什么。

另一个重要的考虑是跟踪逃逸和未经转义的数据。 有大量的应用程序,Web和其他应用程序,似乎没有正确地跟踪数据是什么时候生成Unicode,编码,格式化的HTML等等。 很明显,要跟踪哪些string是编码的,哪些不是。

当你最终改变一些variables的types时,这也是一个问题 – 也许它曾经是一个整数,但现在它是一个string。 现在你有一个问题。

请尽可能使用参数化查询。 有时即使不使用任何怪异字符的简单input,如果它不被识别为数据库中字段的input,也可以创buildSQL注入。

所以,只要让数据库做好识别input本身的工作,更何况当你需要实际插入奇怪的字符时,它也会节省大量的麻烦,否则这些字符会被转义或者改变。 它甚至可以节省一些宝贵的运行时间,因为不需要计算input。

2年后 ,我recidivated …任何谁发现参数疼痛,欢迎尝试我的VS扩展, QueryFirst 。 您可以使用真正的.sql文件(Validation,Intellisense)编辑您的请求。 要添加一个参数,只需将它直接input到SQL中,以“@”开始。 当你保存文件时,QueryFirst将生成包装类,让你运行查询并访问结果。 它将查找参数的数据库types并将其映射到.nettypes,您将在其中find作为生成的Execute()方法的input。 不能更简单 。 正确的做法比其他任何方式更快更简单,创buildsql注入漏洞变得不可能,或者至less是相反的困难。 还有其他杀手级的优势,比如能够删除数据库中的列,并立即在您的应用程序中看到编译错误。

法律免责声明:我写了QueryFirst