C# 避免无参数的SQL注入

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=@

我们正在进行另一次讨论,讨论如何在代码中使用参数化sql查询。在讨论中我们有两个方面:我和其他一些人说我们应该始终使用参数来防止sql注入,而其他人则认为没有必要这样做。相反,他们希望在所有字符串中用两个撇号替换单撇号,以避免sql注入。我们的数据库都运行Sql Server 2005或2008,我们的代码库运行在.NET framework 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?
其中,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();
        }
    }
}