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


93

我听说“每个人”都在使用参数化的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


我感到震惊的是,显然之前在Stackoverflow上还没有问过这个问题。非常好一个!
2009年

3
哦,有。当然,措辞大相径庭,但确实如此。
乔尔·科洪

9
您应该使用参数化查询来防止Little Bobby Tables破坏您的数据。无法抗拒:)
zendar

4
With块有什么不好?
潜伏者确实于2009年

1
是否有人对“ With块有什么不好”问题有疑问#?
Jim Counts

Answers:


76

您的EXEC示例不会被参数化。您需要参数化查询(有些情况下为预备语句),以防止类似这样的输入引起损坏:

'; DROP TABLE栏;-

尝试将其放在fuz变量中(如果您重视bar表,则不要这样做)。更细微和破坏性的查询也是可能的。

这是有关如何使用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之外,几乎所有其他权限),并以此方式获得一些保护。


您能进一步解释cmd.Parameters.Add("@Baz", SqlDbType.VarChar, 50).Value = Baz一下吗?
卡里·邦多克

1
@CaryBondoc,您想知道什么?该行将创建一个名为的参数@Baz,该参数的类型varchar(50)为该Baz字符串的值。
JB King

您也可以说“ command.parameters.addiwthvalue(“ @ Baz”,50)“
Gavin Perkins

2
@GavinPerkins假设你的意思是AddWithValue("@Baz", Baz),你做到这一点,但你不应该,尤其是因为将字符串值映射在默认情况下nvarchar的实际varchar类型是最常见的地方,可以触发该链接中提到的效应之一。
Joel Coehoorn

15

绝对是最后一个,即

还是我需要做更多的事情……?(是的cmd.Parameters.Add()

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

  • 安全性:这是避免SQL Injection漏洞的好方法
  • 性能:如果您仅使用不同的参数定期调用同一查询,则参数化查询可能会使数据库缓存您的查询,这是获得性能的重要来源。
  • 另外:您不必担心数据库代码中的日期和时间格式问题。同样,如果您的代码可以在非英语语言环境的机器上运行,则小数点/小数逗号也不会出现问题。

5

您想使用最后一个示例,因为这是唯一真正参数化的示例。除了安全性问题(可能比您想象的要普遍得多)之外,最好让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
请注意:.Net字符串是unicode,因此默认情况下该参数将采用NVarChar。如果确实是VarChar列,则可能会导致严重的性能问题。
Joel Coehoorn

2

大多数人会通过服务器端编程语言库(例如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次。


1

您的命令文本必须类似于:

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

cmdText = "EXEC foo_from_baz ?"

然后添加参数值。这样可以确保只将值con最终用作值,而如果将变量fuz设置为

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

你知道会发生什么吗?


0

这是一个以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 /快速

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
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.