Как создать параметризованный SQL-запрос? Почему я должен?

94

Я слышал, что «все» используют параметризованные 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. Очень хорошо!
Tamas Czinege,
3
О, это так. Конечно, это очень разные слова, но это так.
Джоэл Коухорн,
10
Вы должны использовать параметризованный запрос, чтобы Little Bobby Tables не разрушал ваши данные. Не удержался :)
zendar
4
Что плохого в блоке With?
Lurker Indeed
1
Есть ли у кого-нибудь вопрос # на вопрос "Что плохого в блоке With"?
Джим

Ответы:

77

Ваш пример EXEC НЕ будет параметризован. Вам нужны параметризованные запросы (подготовленные операторы в некоторых кругах), чтобы предотвратить повреждение такого ввода:

'; Панель DROP TABLE; -

Попробуйте поместить это в свою переменную fuz (или не делайте этого, если вы цените свою столбчатую таблицу). Возможны и более тонкие и опасные вопросы.

Вот пример того, как вы настраиваете параметры с помощью 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?
Кэри Бондок,
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-кода.
  • Производительность: если вы регулярно вызываете один и тот же запрос только с разными параметрами, параметризованный запрос может позволить базе данных кэшировать ваши запросы, что является значительным источником повышения производительности.
  • Дополнительно: вам не придется беспокоиться о проблемах форматирования даты и времени в коде базы данных. Точно так же, если ваш код когда-либо будет работать на машинах с неанглийской локалью, у вас не будет проблем с десятичными точками / запятыми.
Тамаш Чинеге
источник
5

Вы хотите продолжить свой последний пример, так как это единственный действительно параметризованный. Помимо проблем безопасности (которые гораздо более распространены, чем вы можете подумать), лучше всего позволить ADO.NET обрабатывать параметризацию, поскольку вы не можете быть уверены, требует ли передаваемое вами значение одинарные кавычки или нет, не проверив Typeкаждый параметр. .

[Edit] Вот пример:

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, это может вызвать большие проблемы с производительностью.
Джоэл Кохорн,
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*/}

Это позаботится об экранировании ваших данных для вставки в базу данных.

Одним из преимуществ является то, что вы можете повторять вставку много раз с одним подготовленным оператором, что дает преимущество в скорости.

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

JAL
источник
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 / Экспресс

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
источник