如何创建参数化 SQL 查询?我为什么要这样做?

How do I create a parameterized SQL query? Why Should I?

提问人:Jim Counts 提问时间:2/13/2009 最后编辑:Visual VincentJim Counts 更新时间:10/4/2021 访问量:103327

问:

我听说“每个人”都在使用参数化的 SQL 查询来防止 SQL 注入攻击,而不必使用每条用户输入。

你是怎么做到的?使用存储过程时是否会自动获得此信息?

所以我的理解是非参数化的:

cmdText = String.Format("SELECT foo FROM bar WHERE baz = '{0}'", fuz)

这会被参数化吗?

cmdText = String.Format("EXEC foo_from_baz '{0}'", fuz)

或者我是否需要做一些像这样更广泛的事情来保护自己免受SQL注入?

With command
    .Parameters.Count = 1
    .Parameters.Item(0).ParameterName = "@baz"
    .Parameters.Item(0).Value = fuz
End With

除了安全考虑之外,使用参数化查询还有其他优点吗?

更新:这篇很棒的文章被链接到Grotok的一个问题参考中。http://www.sommarskog.se/dynamic_sql.html

sql vb.net 准备语句

评论

1赞 Tamas Czinege 2/13/2009
我发现令人震惊的是,显然这个问题以前没有在 Stackoverflow 上被问过。非常好的一个!
3赞 Joel Coehoorn 2/13/2009
哦,它有。当然,措辞非常不同,但它确实如此。
4赞 Lurker Indeed 2/13/2009
With 块有什么不好?
1赞 Jim Counts 2/13/2009
有没有人对“With 块有什么不好”的问题有疑问#?
11赞 zendar 2/13/2009
您应该使用参数化查询来防止 Little Bobby Tables 破坏您的数据。无法抗拒:)

答:

5赞 Andrew Hare 2/13/2009 #1

您希望使用最后一个示例,因为这是唯一一个真正参数化的示例。除了安全问题(比你想象的要普遍得多)之外,最好让 ADO.NET 处理参数化,因为如果不检查每个参数,你就无法确定你传入的值是否需要单引号。Type

[编辑]下面是一个示例:

SqlCommand command = new SqlCommand(
    "select foo from bar where baz = @baz",
    yourSqlConnection
);

SqlParameter parameter = new SqlParameter();
parameter.ParameterName = "@baz";
parameter.Value = "xyz";

command.Parameters.Add(parameter);

评论

3赞 Joel Coehoorn 2/13/2009
请注意这一点:.Net 字符串是 unicode,因此该参数默认采用 NVarChar。如果它真的是 VarChar 列,这可能会导致很大的性能问题。
84赞 Joel Coehoorn 2/13/2009 #2

问题中的示例不会参数化。您需要参数化查询(在某些圈子中是预准备语句)来防止像这样的输入造成损坏:EXEC

';下拉表栏;--

试着把它放在你的变量中(或者不要,如果你重视表)。也可以进行更微妙和破坏性的查询。fuzbar

下面是如何使用 SQL Server 执行参数的示例:Here's an example of how you do parameters with Sql Server:

Public Function GetBarFooByBaz(ByVal Baz As String) As String
    Dim sql As String = "SELECT foo FROM bar WHERE baz= @Baz"

    Using cn As New SqlConnection("Your connection string here"), _
        cmd As New SqlCommand(sql, cn)

        cmd.Parameters.Add("@Baz", SqlDbType.VarChar, 50).Value = Baz
        Return cmd.ExecuteScalar().ToString()
    End Using
End Function

存储过程有时被认为可以防止 SQL 注入。但是,大多数情况下,您仍然必须使用查询参数调用它们,否则它们将无济于事。如果以独占方式使用存储过程,则可以关闭应用程序用户帐户的 SELECT、UPDATE、ALTER、CREATE、DELETE 等权限(除 EXEC 之外的几乎所有权限),并以这种方式获得一些保护。

评论

0赞 Cary Bondoc 8/28/2015
你能进一步解释一下吗?cmd.Parameters.Add("@Baz", SqlDbType.VarChar, 50).Value = Baz
1赞 JB King 11/4/2015
@CaryBondoc,你想知道什么?该行创建一个名为的参数,该参数的类型被分配了字符串的值。@Bazvarchar(50)Baz
0赞 Gavin Perkins 3/17/2016
您也可以说“command.parameters.addiwthvalue(”@Baz“,50)”
4赞 Joel Coehoorn 3/17/2016
@GavinPerkins 假设您的意思是 ,您可以这样做,但您不应该这样做,特别是因为将默认映射到的字符串值转换为实际类型是可以触发该链接中提到的效果的最常见位置之一。AddWithValue("@Baz", Baz)nvarcharvarchar
1赞 Tony Andrews 2/13/2009 #3

您的命令文本需要如下所示:

cmdText = "SELECT foo FROM bar WHERE baz = ?"

cmdText = "EXEC foo_from_baz ?"

然后添加参数值。这种方式可确保值 con 最终仅用作值,而使用其他方法时,如果变量 fuz 设置为

"x'; delete from foo where 'a' = 'a"

你能看到会发生什么吗?

2赞 JAL 2/13/2009 #4

大多数人会通过服务器端编程语言库来做到这一点,比如PHP的PDO或Perl DBI。

例如,在 PDO 中:

$dbh=pdo_connect(); //you need a connection function, returns a pdo db connection

$sql='insert into squip values(null,?,?)';

$statement=$dbh->prepare($sql);

$data=array('my user supplied data','more stuff');

$statement->execute($data);

if($statement->rowCount()==1){/*it worked*/}

这负责转义数据以插入数据库。

一个优点是,您可以使用一个准备好的语句多次重复插入,从而获得速度优势。

例如,在上面的查询中,我可以准备一次语句,然后从一堆数据中循环创建数据数组,并根据需要多次重复 ->execute。

16赞 Tamas Czinege 2/13/2009 #5

绝对是最后一个,即

还是我需要做一些更广泛的事情......?(是的,cmd.Parameters.Add())

参数化查询有两个主要优点:

  • 安全性:这是避免SQL注入漏洞的好方法
  • 性能:如果定期调用相同的查询,只是使用不同的参数,则参数化查询可能允许数据库缓存查询,这是性能提升的重要来源。
  • 额外:您不必担心数据库代码中的日期和时间格式问题。同样,如果您的代码将在具有非英语区域设置的计算机上运行,则不会遇到小数点/小数逗号的问题。

评论

0赞 RooiWillie 4/15/2022
性能通常被忽略,因此您的第二点对于经常使用的查询非常有价值,这些查询将被缓存,从而也会带来服务器端的收益。
0赞 Chillzy 5/27/2018 #6

这是一个从 SQL 开始的简短类,您可以从那里构建并添加到类中。

MySQL数据库

Public Class mysql

    'Connection string for mysql
    Public SQLSource As String = "Server=123.456.789.123;userid=someuser;password=somesecurepassword;database=somedefaultdatabase;"

    'database connection classes

    Private DBcon As New MySqlConnection
    Private SQLcmd As MySqlCommand
    Public DBDA As New MySqlDataAdapter
    Public DBDT As New DataTable
    Public BindSource As New BindingSource
    ' parameters
    Public Params As New List(Of MySqlParameter)

    ' some stats
    Public RecordCount As Integer
    Public Exception As String

    Function ExecScalar(SQLQuery As String) As Long
        Dim theID As Long
        DBcon.ConnectionString = SQLSource
        Try
            DBcon.Open()
            SQLcmd = New MySqlCommand(SQLQuery, DBcon)
            'loads params into the query
            Params.ForEach(Sub(p) SQLcmd.Parameters.AddWithValue(p.ParameterName, p.Value))

            'or like this is also good
            'For Each p As MySqlParameter In Params
            ' SQLcmd.Parameters.AddWithValue(p.ParameterName, p.Value)
            ' Next
            ' clears params
            Params.Clear()
            'return the Id of the last insert or result of other query
            theID = Convert.ToInt32(SQLcmd.ExecuteScalar())
            DBcon.Close()

        Catch ex As MySqlException
            Exception = ex.Message
            theID = -1
        Finally
            DBcon.Dispose()
        End Try
        ExecScalar = theID
    End Function

    Sub ExecQuery(SQLQuery As String)

        DBcon.ConnectionString = SQLSource
        Try
            DBcon.Open()
            SQLcmd = New MySqlCommand(SQLQuery, DBcon)
            'loads params into the query
            Params.ForEach(Sub(p) SQLcmd.Parameters.AddWithValue(p.ParameterName, p.Value))

            'or like this is also good
            'For Each p As MySqlParameter In Params
            ' SQLcmd.Parameters.AddWithValue(p.ParameterName, p.Value)
            ' Next
            ' clears params

            Params.Clear()
            DBDA.SelectCommand = SQLcmd
            DBDA.Update(DBDT)
            DBDA.Fill(DBDT)
            BindSource.DataSource = DBDT  ' DBDT will contain your database table with your records
            DBcon.Close()
        Catch ex As MySqlException
            Exception = ex.Message
        Finally
            DBcon.Dispose()
        End Try
    End Sub
    ' add parameters to the list
    Public Sub AddParam(Name As String, Value As Object)
        Dim NewParam As New MySqlParameter(Name, Value)
        Params.Add(NewParam)
    End Sub
End Class

MS SQL/Express

Public Class MSSQLDB
    ' CREATE YOUR DB CONNECTION
    'Change the datasource
    Public SQLSource As String = "Data Source=someserver\sqlexpress;Integrated Security=True"
    Private DBCon As New SqlConnection(SQLSource)

    ' PREPARE DB COMMAND
    Private DBCmd As SqlCommand

    ' DB DATA
    Public DBDA As SqlDataAdapter
    Public DBDT As DataTable

    ' QUERY PARAMETERS
    Public Params As New List(Of SqlParameter)

    ' QUERY STATISTICS
    Public RecordCount As Integer
    Public Exception As String

    Public Sub ExecQuery(Query As String, Optional ByVal RunScalar As Boolean = False, Optional ByRef NewID As Long = -1)
        ' RESET QUERY STATS
        RecordCount = 0
        Exception = ""
        Dim RunScalar As Boolean = False

        Try
            ' OPEN A CONNECTION
            DBCon.Open()

            ' CREATE DB COMMAND
            DBCmd = New SqlCommand(Query, DBCon)

            ' LOAD PARAMS INTO DB COMMAND
            Params.ForEach(Sub(p) DBCmd.Parameters.Add(p))

            ' CLEAR PARAMS LIST
            Params.Clear()

            ' EXECUTE COMMAND & FILL DATATABLE
            If RunScalar = True Then
                NewID = DBCmd.ExecuteScalar()
            End If
            DBDT = New DataTable
            DBDA = New SqlDataAdapter(DBCmd)
            RecordCount = DBDA.Fill(DBDT)
        Catch ex As Exception
            Exception = ex.Message
        End Try


        ' CLOSE YOUR CONNECTION
        If DBCon.State = ConnectionState.Open Then DBCon.Close()
    End Sub

    ' INCLUDE QUERY & COMMAND PARAMETERS
    Public Sub AddParam(Name As String, Value As Object)
        Dim NewParam As New SqlParameter(Name, Value)
        Params.Add(NewParam)
    End Sub
End Class