Bagaimana cara membuat kueri SQL berparameter? Kenapa harus saya?

94

Saya pernah mendengar bahwa "semua orang" menggunakan kueri SQL berparameter untuk melindungi dari serangan injeksi SQL tanpa harus memvalidasi setiap masukan pengguna.

Bagaimana kamu melakukan ini? Apakah Anda mendapatkan ini secara otomatis saat menggunakan prosedur tersimpan?

Jadi pemahaman saya ini adalah non-parameterized:

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

Apakah ini akan dijadikan parameter?

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

Atau apakah saya perlu melakukan sesuatu yang lebih ekstensif seperti ini untuk melindungi diri saya dari injeksi SQL?

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

Apakah ada keuntungan lain menggunakan kueri berparameter selain pertimbangan keamanan?

Pembaruan: Artikel bagus ini ditautkan di salah satu referensi pertanyaan oleh Grotok. http://www.sommarskog.se/dynamic_sql.html

Jim Hitungan
sumber
Saya merasa terkejut bahwa ternyata pertanyaan ini belum pernah ditanyakan di Stackoverflow sebelumnya. Sangat bagus!
Tamas Czinege
3
Oh, sudah. Kata-katanya sangat berbeda, tentu saja, tetapi memang demikian.
Joel Coehoorn
10
Anda harus menggunakan kueri parametrized untuk mencegah Little Bobby Tables merusak data Anda. Tidak bisa menahan :)
zendar
4
Apa buruknya blok With?
Lurker Memang
1
Apakah ada yang punya pertanyaan # untuk pertanyaan "Apa yang buruk tentang blok Dengan"?
Jim Menghitung

Jawaban:

77

Contoh EXEC Anda TIDAK akan dijadikan parameter. Anda memerlukan kueri berparameter (pernyataan yang disiapkan di beberapa lingkaran) untuk mencegah masukan seperti ini menyebabkan kerusakan:

'; TETAPKAN bilah TABEL; -

Coba letakkan itu di variabel fuz Anda (atau jangan, jika Anda menghargai tabel batang Anda). Kueri yang lebih halus dan merusak juga dimungkinkan.

Berikut adalah contoh bagaimana Anda melakukan parameter dengan 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

Prosedur yang disimpan terkadang dikreditkan dengan mencegah injeksi SQL. Namun, seringkali Anda masih harus memanggilnya menggunakan parameter kueri atau mereka tidak membantu. Jika Anda menggunakan prosedur tersimpan secara eksklusif , maka Anda dapat mematikan izin untuk PILIH, PERBARUI, ALTER, BUAT, HAPUS, dll (hampir semuanya kecuali JALANKAN) untuk akun pengguna aplikasi dan mendapatkan perlindungan dengan cara itu.

Joel Coehoorn
sumber
Bisakah Anda menjelaskan lebih lanjut ini cmd.Parameters.Add("@Baz", SqlDbType.VarChar, 50).Value = Baz?
Cary Bondoc
1
@CaryBondoc, apa yang ingin Anda ketahui? Baris itu membuat parameter yang disebut @Baztipe varchar(50)yang diberi nilai Bazstring.
JB King
Anda juga bisa mengatakan "command.parameters.addiwthvalue (" @ Baz ", 50)"
Gavin Perkins
2
@GavinPerkins Dengan asumsi Anda bermaksud AddWithValue("@Baz", Baz), Anda dapat melakukannya, tetapi Anda tidak boleh melakukannya , terutama karena mengonversi nilai string yang dipetakan secara default ke tipe nvarcharaktual varcharadalah salah satu tempat paling umum yang dapat memicu efek yang disebutkan dalam tautan itu.
Joel Coehoorn
15

Pasti yang terakhir, yaitu

Atau apakah saya perlu melakukan sesuatu yang lebih ekstensif ...? (Ya, cmd.Parameters.Add())

Kueri yang diparameterisasi memiliki dua keunggulan utama:

  • Keamanan: Ini adalah cara yang baik untuk menghindari SQL Injection kerentanan
  • Kinerja: Jika Anda secara teratur menjalankan kueri yang sama hanya dengan parameter berbeda, kueri parametrized mungkin memungkinkan database untuk menyimpan kueri Anda yang merupakan sumber perolehan kinerja yang cukup besar.
  • Ekstra: Anda tidak perlu khawatir tentang masalah format tanggal dan waktu dalam kode database Anda. Demikian pula, jika kode Anda pernah berjalan pada mesin dengan lokal non-Inggris, Anda tidak akan mengalami masalah dengan titik desimal / koma desimal.
Tamas Czinege
sumber
5

Anda ingin menggunakan contoh terakhir Anda karena ini adalah satu-satunya yang benar-benar parametrized. Selain masalah keamanan (yang jauh lebih umum daripada yang mungkin Anda pikirkan), yang terbaik adalah membiarkan ADO.NET menangani parametrization karena Anda tidak dapat memastikan apakah nilai yang Anda berikan memerlukan tanda kutip tunggal di sekitarnya atau tidak tanpa memeriksaType setiap parameter .

[Sunting] Berikut adalah contohnya:

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);
Andrew Hare
sumber
3
Berhati-hatilah dengan ini:. String bersih adalah unicode, sehingga parameter akan menganggap NVarChar secara default. Jika ini benar-benar kolom VarChar, ini dapat menyebabkan masalah kinerja yang besar.
Joel Coehoorn
2

Kebanyakan orang akan melakukan ini melalui pustaka bahasa pemrograman sisi server, seperti PDO PHP atau Perl DBI.

Misalnya, di 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*/}

Ini menangani pelolosan data Anda untuk penyisipan database.

Salah satu keuntungannya adalah Anda dapat mengulangi penyisipan berkali-kali dengan satu pernyataan yang disiapkan, mendapatkan keuntungan kecepatan.

Misalnya, dalam kueri di atas saya dapat menyiapkan pernyataan sekali, dan kemudian mengulang membuat larik data dari sekumpulan data dan ulangi -> mengeksekusi sebanyak yang diperlukan.

JAL
sumber
1

Teks perintah Anda harus seperti ini:

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

cmdText = "EXEC foo_from_baz ?"

Kemudian tambahkan nilai parameter. Cara ini memastikan bahwa nilai con hanya akan digunakan sebagai nilai, sedangkan dengan metode lain jika variabel fuz disetel ke

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

dapatkah kamu melihat apa yang mungkin terjadi?

Tony Andrews
sumber
0

Berikut adalah kelas singkat untuk memulai dengan SQL dan Anda dapat membangun dari sana dan menambahkannya ke kelas.

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
Chillzy
sumber