C# 避免无参数的SQL注入
我们正在进行另一次讨论,讨论如何在代码中使用参数化sql查询。在讨论中我们有两个方面:我和其他一些人说我们应该始终使用参数来防止sql注入,而其他人则认为没有必要这样做。相反,他们希望在所有字符串中用两个撇号替换单撇号,以避免sql注入。我们的数据库都运行Sql Server 2005或2008,我们的代码库运行在.NET framework 2.0上 让我给你举一个C#的简单例子: 我想让我们用这个:C# 避免无参数的SQL注入,c#,asp.net,sql-server,sql-injection,C#,Asp.net,Sql Server,Sql Injection,我们正在进行另一次讨论,讨论如何在代码中使用参数化sql查询。在讨论中我们有两个方面:我和其他一些人说我们应该始终使用参数来防止sql注入,而其他人则认为没有必要这样做。相反,他们希望在所有字符串中用两个撇号替换单撇号,以避免sql注入。我们的数据库都运行Sql Server 2005或2008,我们的代码库运行在.NET framework 2.0上 让我给你举一个C#的简单例子: 我想让我们用这个: string sql = "SELECT * FROM Users WHERE Name=@
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?
其中,SafeDstring函数定义如下:
string SafeDBString(string inputValue)
{
return "'" + inputValue.Replace("'", "''") + "'";
}
现在,只要我们在查询中对所有字符串值使用SafeDBString,我们就应该是安全的。对吧?
使用SafeDBString函数有两个原因。首先,这是石器时代以来的做法,其次,由于您看到在数据库上运行的EXACT查询,因此调试sql语句更容易
那么。我的问题是,使用SafeDBString函数来避免sql注入攻击是否真的足够。我一直在寻找破坏这种安全措施的代码示例,但我找不到任何这样的示例
有人能打破这个吗?你会怎么做
编辑:
总结迄今为止的答复:
- 目前还没有人找到在SQLServer2005或2008上绕过SafeDstring的方法。我想这很好吧
- 一些回复指出,当使用参数化查询时,可以获得性能增益。原因是查询计划可以重用
- 我们还同意使用参数化查询可以提供更易于维护的可读代码
- 此外,始终使用参数比使用各种版本的SafeBString、字符串到数字的转换和字符串到日期的转换更容易李>
- 使用参数可以实现自动类型转换,这在处理日期或十进制数时特别有用
- 最后:正如朱利安所写。数据库供应商在安全性方面花费了大量的时间和金钱。我们没有办法做得更好,也没有理由去做他们的工作
因此,虽然没有人能够破坏SafeDBString函数的简单安全性,但我得到了许多其他好的参数。谢谢 我会对所有内容使用存储过程或函数,这样问题就不会出现了
在我必须将SQL放入代码的地方,我使用参数,这是唯一有意义的事情。提醒持不同政见者,有比他们更聪明的黑客,他们有更好的动机去破解试图比他们聪明的代码。使用参数是不可能的,也不是很困难。我使用了这两种方法来避免SQL注入攻击,并且肯定更喜欢参数化查询。当我使用串联查询时,我使用了一个库函数来转义变量(比如mysql\u real\u escape\u string),我不相信我已经在一个专有实现中涵盖了所有内容(看起来你也是)。然后有人去使用“而不是”。在我看来,参数是唯一安全的方法 它还避免了许多关于日期/数字的i18n问题;什么日期是03年2月1日?123456是多少?您的服务器(应用服务器和数据库服务器)是否彼此一致
如果风险因素不能让他们信服,那么性能如何?如果使用参数,RDBMS可以重复使用查询计划,这有助于提高性能。它不能仅使用字符串来实现这一点。首先,您的“替换”版本示例是错误的。您需要在文本周围加上撇号:
string sql = "SELECT * FROM Users WHERE Name='" + SafeDBString(name) & "'";
SqlCommand getUser = new SqlCommand(sql, connection);
因此,参数为您做的另一件事是:您不必担心值是否需要用引号括起来。当然,您可以将其构建到函数中,但随后您需要给函数增加很多复杂性:如何知道作为NULL的“NULL”与作为字符串的“NULL”之间的区别,或者一个数字之间的区别和一个恰好包含很多数字的字符串。这只是另一个bug的来源
另一件事是性能:参数化的查询计划通常比串联的计划缓存得更好,因此可能在运行查询时为服务器节省了一个步骤
此外,转义单引号还不够好。许多DB产品都允许使用其他方法转义攻击者可以利用的字符。例如,在MySQL中,您也可以使用反斜杠转义单引号。因此,以下“name”值将仅使用SafeDBString()来炸开MySQL
函数,因为当您将单引号加倍时,第一个引号仍然由反斜杠转义,第二个引号保持“活动”:
x\'或1=1--
此外,朱利安在下面提出了一个很好的观点:永远不要尝试自己做安全工作。即使经过彻底的测试,安全编程也很容易以看似有效的微妙方式出错。然后时间一天天过去,一年后,你发现你的系统在六个月前就被破解了,直到那时你才知道。
始终尽可能多地依赖为您的平台提供的安全库。这些安全库将由从事安全代码工作的人编写,比您能够管理的更好,如果发现漏洞,将由供应商提供服务。通过参数化查询,您可以获得的不仅仅是针对sql注入的保护。您可以o获得更好的执行计划缓存潜力。如果使用sql server查询探查器,您仍然可以看到“在数据库上运行的确切sql”,因此在调试sql语句方面也不会丢失任何东西。如果不使用参数,您无法轻松地对用户输入进行任何类型检查
http://www.mywebsite.com/profile/?id=7;DROP DATABASE DB
SELECT * FROM DB WHERE Id = 7;DROP DATABASE DB
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();
}
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();
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();
}
}
}