Как работает параметризация SQL-запроса? - PullRequest
33 голосов
/ 12 августа 2009

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

Метод A:

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

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

Метод B:

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

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

Ответы [ 5 ]

41 голосов
/ 12 августа 2009

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

SELECT * FROM mytable WHERE user='wayne'

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

SELECT * FROM mytable WHERE user=<some value>

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

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

4 голосов
/ 12 августа 2009

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

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

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

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

2 голосов
/ 12 августа 2009

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

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

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

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

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

0 голосов
/ 05 декабря 2017

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

Однако я нашел один случай, когда он не работает. У меня есть столбец, который отслеживает строку версии в форме "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)
0 голосов
/ 12 августа 2009

Когда вы отправляете запрос через SQL Server, он сначала проверяет кэш процедур. Если он обнаружит, что запрос ТОЧНО равен, то он будет использовать тот же план и не перекомпилировать запрос, а просто заменит заполнители (переменные), но на стороне сервера (дБ).

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...