【问题标题】:How do I create a parameterized SQL query? Why Should I?如何创建参数化 SQL 查询?我为什么要?
【发布时间】:2010-10-07 06:00:09
【问题描述】:

我听说“每个人”都在使用参数化 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 上从未有人问过这个问题。非常好!
  • 哦,它有。当然,措辞非常不同,但确实如此。
  • 您应该使用参数化查询来防止Little Bobby Tables 破坏您的数据。忍不住:)
  • With 块有什么不好?
  • 有人对“With 块有什么不好”的问题有疑问吗?

标签: sql vb.net sql-parametrized-query


【解决方案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);

【讨论】:

  • 注意这一点:.Net 字符串是 unicode,因此默认情况下该参数将假定为 NVarChar。如果它真的是 VarChar 列,这可能会导致很大的性能问题。
【解决方案2】:

问题中的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吗?
  • @CaryBondoc,你想知道什么?该行创建了一个名为@Baz 的参数,该参数的类型为varchar(50),分配了Baz 字符串的值。
  • 你也可以说“command.parameters.addiwthvalue("@Baz",50)"
  • @GavinPerkins 假设您的意思是 AddWithValue("@Baz", Baz),您可以这样做,but you shouldn't,尤其是因为将默认映射到 nvarchar 的字符串值转换为实际的 @ 987654332@ 类型是最常见的可以触发该链接中提到的效果的地方之一。
【解决方案3】:

你的命令文本应该是这样的:

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

cmdText = "EXEC foo_from_baz ?"

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

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

你能看到会发生什么吗?

【讨论】:

    【解决方案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*/}
    

    这负责转义您的数据以进行数据库插入。

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

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

    【讨论】:

      【解决方案5】:

      肯定是最后一个,即

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

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

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

      【讨论】:

        【解决方案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
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多