Làm cách nào để tạo một truy vấn SQL được tham số hóa? Tại sao phải là tôi?


93

Tôi đã nghe nói rằng "mọi người" đang sử dụng các truy vấn SQL được tham số hóa để bảo vệ khỏi các cuộc tấn công SQL injection mà không cần phải vailidate mọi thông tin đầu vào của người dùng.

Làm thế nào để bạn làm điều này? Bạn có tự động nhận được điều này khi sử dụng các thủ tục được lưu trữ không?

Vì vậy, sự hiểu biết của tôi đây là điều không tham số:

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

Điều này sẽ được tham số hóa?

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

Hay tôi cần thực hiện một số việc mở rộng hơn như thế này để tự bảo vệ mình khỏi SQL injection?

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

Có những lợi ích nào khác khi sử dụng truy vấn được tham số hóa ngoài những cân nhắc về bảo mật không?

Cập nhật: Bài viết tuyệt vời này được liên kết trong một trong những tài liệu tham khảo câu hỏi của Grotok. http://www.sommarskog.se/dynamic_sql.html


Tôi thấy thật sốc khi rõ ràng câu hỏi này chưa từng được hỏi trên Stackoverflow trước đây. Rất tốt!
Tamas Czinege

3
Ồ, nó có. Tất nhiên, lời nói rất khác nhau, nhưng nó có.
Joel Coehoorn

9
Bạn nên sử dụng truy vấn tham số hóa để ngăn Little Bobby Tables phá hủy dữ liệu của bạn. Không thể cưỡng lại :)
zendar

4
Có gì tệ về khối With?
Lurker Indeed 12/02/09

1
Có ai có câu hỏi # cho câu hỏi "Điều gì tồi tệ về khối Với" không?
Jim Counts

Câu trả lời:


76

Ví dụ EXEC của bạn sẽ KHÔNG được tham số hóa. Bạn cần các truy vấn được tham số hóa (các câu lệnh chuẩn bị sẵn trong một số vòng kết nối) để ngăn việc nhập như thế này gây ra thiệt hại:

'; Thanh DROP TABLE; -

Hãy thử đặt nó vào biến fuz của bạn (hoặc không, nếu bạn coi trọng bảng thanh của mình). Các truy vấn phức tạp hơn và có hại hơn cũng có thể xảy ra.

Đây là một ví dụ về cách bạn thực hiện các tham số với 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

Các thủ tục được lưu trữ đôi khi được ghi nhận là ngăn chặn việc đưa vào SQL. Tuy nhiên, hầu hết thời gian bạn vẫn phải gọi chúng bằng cách sử dụng các tham số truy vấn hoặc chúng không giúp ích gì. Nếu bạn sử dụng riêng các thủ tục được lưu trữ , thì bạn có thể tắt các quyền cho CHỌN, CẬP NHẬT, ALTER, TẠO, XÓA, v.v. (chỉ về mọi thứ trừ EXEC) cho tài khoản người dùng ứng dụng và nhận được một số bảo vệ theo cách đó.


Bạn có thể giải thích thêm về điều này được cmd.Parameters.Add("@Baz", SqlDbType.VarChar, 50).Value = Bazkhông?
Cary Bondoc

1
@CaryBondoc, bạn muốn biết điều gì? Dòng đó tạo ra một tham số được gọi @Bazlà kiểu varchar(50)được gán giá trị của Bazchuỗi.
JB King

bạn cũng có thể nói "command.parameters.addiwthvalue (" @ Baz ", 50)"
Gavin Perkins

2
@GavinPerkins Giả sử bạn muốn nói AddWithValue("@Baz", Baz), bạn có thể làm điều đó, nhưng bạn không nên , đặc biệt là vì việc chuyển đổi các giá trị chuỗi ánh xạ theo mặc định thành loại nvarcharthực tế varcharlà một trong những nơi phổ biến nhất có thể kích hoạt các hiệu ứng được đề cập trong liên kết đó.
Joel Coehoorn

15

Chắc chắn là người cuối cùng, tức là

Hay tôi cần mở rộng hơn ...? (Có, cmd.Parameters.Add())

Các truy vấn được tham số hóa có hai ưu điểm chính:

  • Bảo mật: Đó là một cách tốt để tránh các lỗ hổng SQL Injection
  • Hiệu suất: Nếu bạn thường xuyên gọi cùng một truy vấn chỉ với các tham số khác nhau, một truy vấn được tham số hóa có thể cho phép cơ sở dữ liệu lưu vào bộ nhớ cache các truy vấn của bạn, đây là một nguồn tăng hiệu suất đáng kể.
  • Thêm: Bạn sẽ không phải lo lắng về các vấn đề định dạng ngày và giờ trong mã cơ sở dữ liệu của mình. Tương tự, nếu mã của bạn từng chạy trên các máy có ngôn ngữ không phải là tiếng Anh, bạn sẽ không gặp vấn đề với dấu phẩy / dấu phẩy thập phân.

5

Bạn muốn đi với ví dụ cuối cùng của mình vì đây là ví dụ duy nhất thực sự được tham số hóa. Bên cạnh những lo ngại về bảo mật (mà bạn có thể nghĩ là phổ biến hơn nhiều), tốt nhất bạn nên để ADO.NET xử lý tham số hóa vì bạn không thể chắc chắn liệu giá trị bạn đang chuyển vào có yêu cầu các dấu ngoặc kép xung quanh nó hay không nếu không kiểm tra Typetừng tham số .

[Chỉnh sửa] Đây là một ví dụ:

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
Hãy cẩn thận với điều này: Chuỗi .Net là unicode và do đó, tham số sẽ giả sử NVarChar theo mặc định. Nếu đó thực sự là một cột VarChar, điều này có thể gây ra các vấn đề lớn về hiệu suất.
Joel Coehoorn

2

Hầu hết mọi người sẽ làm điều này thông qua thư viện ngôn ngữ lập trình phía máy chủ, như PDO của PHP hoặc Perl DBI.

Ví dụ, trong 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*/}

Điều này sẽ xử lý việc thoát dữ liệu của bạn để chèn cơ sở dữ liệu.

Một ưu điểm là bạn có thể lặp lại một đoạn chèn nhiều lần với một câu lệnh đã chuẩn bị sẵn, đạt được lợi thế về tốc độ.

Ví dụ: trong truy vấn trên, tôi có thể chuẩn bị câu lệnh một lần, sau đó lặp lại việc tạo mảng dữ liệu từ một loạt dữ liệu và lặp lại -> thực thi nhiều lần nếu cần.


1

Văn bản lệnh của bạn cần phải như sau:

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

cmdText = "EXEC foo_from_baz ?"

Sau đó, thêm các giá trị tham số. Cách này đảm bảo rằng giá trị con cuối cùng chỉ được sử dụng như một giá trị, trong khi với phương pháp khác nếu biến fuz được đặt thành

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

bạn có thể thấy những gì có thể xảy ra?


0

Đây là một lớp ngắn để bắt đầu với SQL và bạn có thể xây dựng từ đó và thêm vào lớp.

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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.