Як створити параметризований 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

Чи є інші переваги використання параметризованих запитів, окрім міркувань безпеки?

Оновлення: Ця чудова стаття була пов’язана в одному з посилань на запитання Гротока. http://www.sommarskog.se/dynamic_sql.html


Мені здається шокуючою те, що, мабуть, це питання раніше не задавались в Stackoverflow. Дуже хороший!
Тамас Цінеге

3
О, це є. Сказано дуже інакше, звичайно, але це є.
Joel Coehoorn

9
Вам слід використовувати параметризований запит, щоб запобігти знищенню ваших даних Little Bobby Tables . Не втримався :)
zendar

4
Що такого поганого в блоці With?
Lurker Дійсно,

1
У когось є питання № для питання "Що так погано в блоці With"?
Джим рахує

Відповіді:


76

Ваш приклад EXEC НЕ буде параметризований. Вам потрібні параметризовані запити (підготовлені висловлювання в деяких колах), щоб уникнути подібного введення:

'; DROP TABLE bar; -

Спробуйте помістити це у свою змінну fuz (або ні, якщо ви цінуєте свою барну таблицю). Також можливі більш тонкі та згубні запити.

Ось приклад того, як ви робите параметри за допомогою сервера Sql:

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)"
Гевін Перкінс

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

Більшість людей зробить це через бібліотеку мов програмування на стороні сервера, наприклад, PDO для PHP або 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*/}

Це допоможе уникнути даних для вставки в базу даних.

Одна перевага полягає в тому, що ви можете повторити вставку багато разів з одним підготовленим висловлюванням, отримуючи перевагу в швидкості.

Наприклад, у вищезазначеному запиті я міг підготувати оператор один раз, а потім перетворити цикл на створення масиву даних з купки даних і повторити -> виконати стільки разів, скільки потрібно.


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 / 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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.