提问人:Jim Counts 提问时间:2/13/2009 最后编辑:Visual VincentJim Counts 更新时间:10/4/2021 访问量:103327
如何创建参数化 SQL 查询?我为什么要这样做?
How do I create a parameterized SQL query? Why Should I?
问:
我听说“每个人”都在使用参数化的 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
答:
您希望使用最后一个示例,因为这是唯一一个真正参数化的示例。除了安全问题(比你想象的要普遍得多)之外,最好让 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);
评论
问题中的示例不会参数化。您需要参数化查询(在某些圈子中是预准备语句)来防止像这样的输入造成损坏:EXEC
';下拉表栏;--
试着把它放在你的变量中(或者不要,如果你重视表)。也可以进行更微妙和破坏性的查询。fuz
bar
下面是如何使用 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 之外的几乎所有权限),并以这种方式获得一些保护。
评论
cmd.Parameters.Add("@Baz", SqlDbType.VarChar, 50).Value = Baz
@Baz
varchar(50)
Baz
AddWithValue("@Baz", Baz)
nvarchar
varchar
您的命令文本需要如下所示:
cmdText = "SELECT foo FROM bar WHERE baz = ?"
cmdText = "EXEC foo_from_baz ?"
然后添加参数值。这种方式可确保值 con 最终仅用作值,而使用其他方法时,如果变量 fuz 设置为
"x'; delete from foo where 'a' = 'a"
你能看到会发生什么吗?
大多数人会通过服务器端编程语言库来做到这一点,比如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。
绝对是最后一个,即
还是我需要做一些更广泛的事情......?(是的,
cmd.Parameters.Add()
)
参数化查询有两个主要优点:
- 安全性:这是避免SQL注入漏洞的好方法
- 性能:如果定期调用相同的查询,只是使用不同的参数,则参数化查询可能允许数据库缓存查询,这是性能提升的重要来源。
- 额外:您不必担心数据库代码中的日期和时间格式问题。同样,如果您的代码将在具有非英语区域设置的计算机上运行,则不会遇到小数点/小数逗号的问题。
评论
这是一个从 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
评论