Как работает SQL-запрос параметризации работы?

Я чувствую себя немного глупо, спрашивая об этом, так как я, кажется, единственный человек в мире, который не получает его, но все равно. Я собираюсь использовать Python в качестве примера. Когда я использую необработанные SQL-запросы( обычно я использую ORMs), я использую параметризацию, как в этом примере, используя SQLite:

Метод A:

username = "wayne"
query_params = (username)
cursor.execute("SELECT * FROM mytable WHERE user=?", query_params)

Я знаю, что это работает, и я знаю, что это обычно рекомендуемый способ сделать это. SQL-инъекция-уязвимый способ сделать то же самое что-то вроде этого:--4-->

Метод B:

username = "wayne"
cursor.execute("SELECT * FROM mytable WHERE user='%s'" % username)

насколько я могу сказать, я понимаю SQL-инъекцию, как описано в эта статья в Википедии. Мой вопрос прост: как метод A действительно отличается от метода B? Почему конечный результат метода A не совпадает с методом B? Я предполагаю, что cursor.execute() метод (часть спецификации DB-API Python) заботится о правильном экранировании и проверке типа ввода, но это никогда явно не указано везде. Это все, что параметризация в этом контексте? Для меня, когда мы говорим "параметризация", все это означает" подстановку строк", например %-форматирование. Это неправильно?

5 ответов


параметризованный запрос фактически не выполняет замену строки. Если вы используете подстановку строк, то SQL engine фактически видит запрос, который выглядит как

SELECT * FROM mytable WHERE user='wayne'

Если вы используете ? параметр, затем SQL engine видит запрос, который выглядит как

SELECT * FROM mytable WHERE user=<some value>

это означает, что, прежде чем он увидит строку "wayne", он может полностью проанализировать запрос и понять, как правило, что делает запрос. Он вставляет "wayne" в свое собственное представление запроса, а не строка SQL, описывающая запрос. Таким образом, SQL-инъекция невозможна, так как мы уже прошли этап SQL процесса.

(вышеизложенное обобщено, но оно более или менее передает идею.)


когда вы делаете замену текста (например, ваш метод B), вы должны быть осторожны с кавычками и такими, потому что сервер получит один кусок текста, и он должен определить, где заканчивается значение.

с параметризованными инструкциями OTOH сервер БД получает инструкцию как есть, без параметра. Значение отправляется на сервер в виде другой части данных, используя простой бинарный безопасный протокол. Таким образом, ваша программа не должна ставить кавычки вокруг значения, и конечно, не имеет значения,были ли уже кавычки в самом значении.

аналогия касается исходного и скомпилированного кода: в вашем методе B вы создаете исходный код процедуры, поэтому вы должны быть уверены, что строго следуете синтаксису языка. С помощью метода A вы сначала создаете и компилируете процедуру, а затем (сразу после этого, в вашем примере) вызываете эту процедуру со своим значением в качестве параметра. И, конечно же, значения в памяти не подлежат синтаксису ограничения.

МММ... это не аналогия, это действительно то, что происходит под капотом (ориентировочно).


использование параметризованных запросов-хороший способ выполнить задачу для экранирования и предотвращения инъекций в клиентскую библиотеку БД. Он сделает побег, прежде чем он заменит строку на "?". Это делается в клиентской библиотеке, перед сервером БД.

Если у вас работает MySQL, включите журнал SQL и попробуйте несколько параметризованных запросов,и вы увидите, что сервер MySQL получает полностью замещенные запросы без"?"в нем, но клиентская библиотека MySQL уже избежала любого котировки в вашем "параметре" для вас.

Если вы используете метод B только с заменой строки, " s не будут автоматически экранированы.

синергетически, с MySQL, вы можете подготовить параметризованный запрос раньше времени, а затем использовать подготовленный оператор повторно позже. Когда вы готовите запрос, MySQL анализирует его и возвращает вам подготовленный оператор-некоторое проанализированное представление, которое понимает MySQL. Каждый раз, когда вы используете подготовленное заявление, не только вы защищены от инъекция, но и вы избегаете затрат на разбор запроса снова.

и, если вы действительно хотите быть в безопасности, вы можете изменить уровень доступа к БД/ORM, чтобы 1) код веб-сервера мог использовать только подготовленные операторы, и 2) Вы можете готовить только операторы до запуска веб-сервера. Затем, даже если ваше веб-приложение взломано (скажем, через эксплойт переполнения буфера), хакер может использовать только подготовленные заявления, но не более того. Для этого вам нужно заточить свое веб-приложение и только разрешить доступ к базе данных через уровень DB access/ORM.


просто предостережение здесь. Это ? синтаксис будет работать нормально и правильно экранирует встроенные одинарные или двойные кавычки в строках.

Я нашел один случай, когда он не работает. У меня есть столбец, который отслеживает строку версии формы "n.n.n "например," 1.2.3 " кажется, что формат вызывает ошибку, потому что он выглядит как реальное число до второго ".". Например:
   rec = (some_value, '1.2.3')
   sql = ''' UPDATE some_table
              SET some_column=?
              WHERE version=? '''
    cur = self.conn.cursor()
    cur.execute(sql, rec)

ошибка с ошибкой " неверное количество Привязок, предоставленных. Нынешнее заявление использует 1 и 2 прилагается."

это хорошо работает:

   vers = '1.2.3'
   rec = (some_value)
   sql = ''' UPDATE some_table
              SET some_column=?
              WHERE version='%s' ''' % (vers)
    cur = self.conn.cursor()
    cur.execute(sql, rec)

при отправке запроса через SQL Server сначала проверяется кэш процедур. Если он найдет какой-то запрос точно равным, то он будет использовать тот же план, а не перекомпилировать запрос, просто заменит заполнители (переменные), но на стороне сервера (БД).

проверьте мастер системных таблиц.dbo.syscacheobjects и выполните некоторые тесты, чтобы узнать немного больше об этом разделе.