为什么我们总是喜欢在SQL语句中使用参数?


114

我对使用数据库非常陌生。现在我可以写SELECTUPDATEDELETE,和INSERT命令。但是我看过很多我们喜欢写的论坛:

SELECT empSalary from employee where salary = @salary

...代替:

SELECT empSalary from employee where salary = txtSalary.Text

为什么我们总是喜欢使用参数,我将如何使用它们?

我想知道第一种方法的用途和好处。我什至听说过SQL注入,但我并不完全了解。我什至不知道SQL注入是否与我的问题有关。


2
没错,这与SQL注入有关。无论您的程序运行何种语言/框架,如何处理参数通常都是责任,并且可能取决于语言。请同时发布您的RDBMS(有帮助)和ORM框架(必要)。
Clockwork-Muse

1
我正在使用C#作为编程语言,并使用Sql Server 2008作为数据库。我正在使用Microsoft dotNet framework 4.0。我真的很抱歉,我不确定您要问什么(RDBMS或ORM),也许您现在可以给我我的RDBMS和ORM框架版本:-)。非常感谢
Sandy

1
RDBMS是您的数据库,在您的情况下是SQL Server2008。ORM是访问数据库的方法,在本例中是ADO.NET。其他包括LINQ到SQL实体框架。实际上,一旦您学习了ADO.NET和SQL的基础知识,我建议您使用诸如LINQ或EF之类的ORM,因为它们可以解决您通过手动编写SQL所遇到的许多问题。
乍得利维

Answers:


129

当数据库与程序界面(如桌面程序或网站)结合使用时,使用参数有助于防止SQL注入攻击

在您的示例中,用户可以通过编写中的语句直接在数据库上运行SQL代码txtSalary

例如,如果他们要编写0 OR 1=1,则执行的SQL将是

 SELECT empSalary from employee where salary = 0 or 1=1

从而所有empSalaries都将被退回。

此外,用户可能会对您的数据库执行更差的命令,包括删除以下命令0; Drop Table employee

SELECT empSalary from employee where salary = 0; Drop Table employee

employee然后将删除该表。


就您而言,您似乎正在使用.NET。使用参数非常简单:

C#

string sql = "SELECT empSalary from employee where salary = @salary";

using (SqlConnection connection = new SqlConnection(/* connection info */))
using (SqlCommand command = new SqlCommand(sql, connection))
{
    var salaryParam = new SqlParameter("salary", SqlDbType.Money);
    salaryParam.Value = txtMoney.Text;

    command.Parameters.Add(salaryParam);
    var results = command.ExecuteReader();
}

VB.NET

Dim sql As String = "SELECT empSalary from employee where salary = @salary"
Using connection As New SqlConnection("connectionString")
    Using command As New SqlCommand(sql, connection)
        Dim salaryParam = New SqlParameter("salary", SqlDbType.Money)
        salaryParam.Value = txtMoney.Text

        command.Parameters.Add(salaryParam)

        Dim results = command.ExecuteReader()
    End Using
End Using

编辑2016-4-25:

根据George Stocker的评论,我将示例代码更改为not use AddWithValue。另外,通常建议将IDisposables 包装在using语句中。


很好的解决方案。但是您能否进一步解释一下,为什么以及如何使用参数是安全的。我的意思是看起来sql命令仍然会一样
Sandy

我们可以向sql命令添加多个参数吗?就像我们在INSERT命令中可能需要的一样?
桑迪

2
SQL Server仅将参数内的文本视为输入,并且永远不会执行它。
乍得利维

3
是的,您可以添加多个参数:Insert Into table (Col1, Col2) Values (@Col1, @Col2)。在您的代码中,您将添加多个AddWithValue
乍得利维

1
请不要使用AddWithValue!它可能导致隐式转换问题。始终明确设置大小,并使用添加参数值parameter.Value = someValue
乔治·斯托克2015年

75

没错,这与SQL注入有关,这是一个漏洞,允许malicioius用户对您的数据库执行任意语句。这本古老的最喜欢的XKCD漫画说明了这个概念:

她的女儿叫“帮助我”,被困在驾驶执照工厂。


在您的示例中,如果您仅使用:

var query = "SELECT empSalary from employee where salary = " + txtSalary.Text;
// and proceed to execute this query

您愿意接受SQL注入。例如,假设有人输入txtSalary:

1; UPDATE employee SET salary = 9999999 WHERE empID = 10; --
1; DROP TABLE employee; --
// etc.

当您执行此查询时,它将执行SELECT和和UPDATEDROP或他们想要的任何内容。将--在年底只需注释掉查询的其余部分,如果你串联后什么这将是在进攻中非常有用txtSalary.Text


正确的方法是使用参数化查询,例如(C#):

SqlCommand query =  new SqlCommand("SELECT empSalary FROM employee 
                                    WHERE salary = @sal;");
query.Parameters.AddWithValue("@sal", txtSalary.Text);

这样,您可以安全地执行查询。

有关如何避免使用其他几种语言进行SQL注入的参考,请访问bobby-tables.com(由SO用户维护的网站)。


1
很好的解决方案。但是您能否进一步解释一下,为什么以及如何使用参数是安全的。我的意思是看起来仍然像sql命令一样。
桑迪

1
@ user815600:一个常见的误解-您仍然相信带有参数的查询会接受值并将参数替换为实际值-对吗?不,这没有发生!-相反,带有参数的SQL语句将被发送到SQL Server,用的参数列表和它们的值- SQL语句是不是将是相同的
marc_s

1
这意味着sql注入受到sql server内部机制或安全性的监视。谢谢。
桑迪

4
就像我喜欢卡通一样,如果您以足够的特权运行代码来删除表,则可能会遇到更广泛的问题。
菲利普

9

除了其他答案外,还需要补充一点,参数不仅有助于防止sql注入,而且可以提高查询性能。SQL Server缓存参数化的查询计划,并在重复执行查询时重用它们。如果未参数化查询,则如果查询文本不同,则sql server将在每个查询(带有某些排除项)上编译新计划

有关查询计划缓存的更多信息


1
这比人们想像的更相关。甚至一个“小”查询也可以执行数千或数百万次,从而有效刷新整个查询缓存。
詹姆斯(James)

5

我第一次去两年后,我正在沉沦……

为什么我们更喜欢参数?SQL注入显然是一个重要原因,但这可能是因为我们秘密地渴望回到SQL 作为一种语言。字符串文字中的SQL已经是一种怪异的文化习惯,但是至少您可以将请求复制并粘贴到Management Studio中。当SQL具有条件和控制结构时,使用宿主语言条件和控制结构动态构建的SQL只是0级野蛮行为。您必须在调试中或通过跟踪运行应用程序,以查看其生成的SQL。

不要仅仅因为参数而停止。一直使用QueryFirst(免责声明:我写的)。您的SQL 位于.sql文件中。您可以在神话般的TSQL编辑器窗口中对其进行编辑,并对表和列进行语法验证和Intellisense。您可以在特殊注释部分中分配测试数据,然后单击“播放”以在窗口中的此处直接运行查询。创建参数就像在SQL中放入“ @myParam”一样简单。然后,每次保存时,QueryFirst都会为您的查询生成C#包装器。您的参数将弹出,并作为Execute()方法的参数进行强类型输入。结果以IEnumerable或强类型POCO列表的形式返回,这些类型是由查询返回的实际模式生成的。如果您的查询未运行,则您的应用将无法编译。如果您的数据库架构更改并且查询运行,但是某些列消失,则编译错误将指向代码中的行试图访问丢失的数据。并且还有许多其他优点。您为什么要以其他方式访问数据?


4

在Sql中,当任何单词包含@符号时,表示它是变量,我们使用此变量在其中设置值,并在同一sql脚本的数字区域中使用它,因为它仅限于单个脚本,而您可以声明很多变量在许多脚本上具有相同的类型和名称。我们在存储过程批次中使用此变量,因为存储过程是预编译的查询,我们可以从脚本,桌面和网站传递这些变量中的值以获取更多信息,以阅读声明本地变量Sql存储过程sql注入

另请阅读“ 防止sql注入”,它将指导您如何保护数据库。

希望它也可以帮助您理解任何问题,对我进行评论。


3

其他答案包括为什么参数很重要,但是有缺点!在.net中,有几种创建参数的方法(Add,AddWithValue),但是它们都需要您不必担心参数名称,它们都降低了代码中SQL的可读性。就在您尝试冥想SQL时,您需要在上方或下方四处寻找以查看参数中使用了哪个值。

我谦虚地声称我的SqlBuilder小类是编写参数化查询的最优雅的方法。您的代码将如下所示:

C#

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

您的代码将更短并且更具可读性。您甚至不需要多余的行,并且在回读时,无需四处寻找参数的值。您需要的课程在这里...

using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Data.SqlClient;

public class SqlBuilder
{
private StringBuilder _rq;
private SqlCommand _cmd;
private int _seq;
public SqlBuilder(SqlCommand cmd)
{
    _rq = new StringBuilder();
    _cmd = cmd;
    _seq = 0;
}
public SqlBuilder Append(String str)
{
    _rq.Append(str);
    return this;
}
public SqlBuilder Value(Object value)
{
    string paramName = "@SqlBuilderParam" + _seq++;
    _rq.Append(paramName);
    _cmd.Parameters.AddWithValue(paramName, value);
    return this;
}
public SqlBuilder FuzzyValue(Object value)
{
    string paramName = "@SqlBuilderParam" + _seq++;
    _rq.Append("'%' + " + paramName + " + '%'");
    _cmd.Parameters.AddWithValue(paramName, value);
    return this;
}
public override string ToString()
{
    return _rq.ToString();
}
}

在分析服务器正在运行的查询时,命名参数无疑会有所帮助。
Dave R.

我的老板也这么说。如果有意义的参数名称对您很重要,请在value方法中添加paramName参数。我怀疑您不必要地使事情复杂化。
bbsimonbb

馊主意。如前所述,AddWithValue可能会导致隐式转换问题。
亚当·卡尔维特·布尔

@Adam,您是对的,但这并不能阻止AddWithValue()的广泛使用,而且我认为这不会使这个想法无效。但是与此同时,我想出了一种更好的方法来编写参数化查询,并且该方法不使用AddWithValue():-)
bbsimonbb

对!答应,我很快会看的!
亚当·卡维特·布尔

3

旧帖子,但想确保新来者了解存储过程

我的10美分价值是,如果您能够将SQL语句作为存储过程编写,那么我认为这是最佳方法。我总是使用存储的proc,并且永远不会遍历我的主代码中的记录。例如:SQL Table > SQL Stored Procedures > IIS/Dot.NET > Class

使用存储过程时,可以仅将用户限制为EXECUTE权限,从而降低了安全风险

您的存储过程本质上是微调的,您可以指定输入和输出参数。

SELECT可以使用与SELECT代码中的常规语句完全相同的方式来访问和读取存储过程(如果它通过语句返回数据)。

由于它是在SQL Server上编译的,因此运行速度也更快。

我是否还提到过您可以执行多个步骤,例如,update一个表,检查另一台DB服务器上的值,然后最终完成,将数据返回到客户端,所有操作均在同一服务器上,并且无需与客户端交互。因此,这比在您的代码中编码此逻辑要快得多。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.