Лучшие практики Python и самые безопасные для подключения к MySQL и выполнения запросов

Каков самый безопасный способ выполнения запросов на mysql? Я знаю об опасностях, связанных с MySQL и SQL-инъекциями.

Однако я не знаю, как мне выполнять свои запросы, чтобы предотвратить внедрение переменных, которыми могут манипулировать другие пользователи (веб-клиенты). Раньше я писал свою собственную escape-функцию, но, видимо, это «не сделано».

Что мне следует использовать и как использовать его для запросов и безопасных вставок в базу данных MySQL через python без риска внедрения mysql?


person Lucas Kauffman    schedule 28.10.2011    source источник


Ответы (3)


Чтобы избежать инъекций, используйте execute с %s вместо каждой переменной, затем передайте значение через список или кортеж в качестве второго параметра execute. Вот пример из документации:

c=db.cursor()
max_price=5
c.execute("""SELECT spam, eggs, sausage FROM breakfast
          WHERE price < %s""", (max_price,))

Обратите внимание, что здесь используется запятая, а не % (это была бы прямая подстановка строки, без экранирования). Не делайте этого:

c.execute("""SELECT spam, eggs, sausage FROM breakfast
          WHERE price < %s""" % (max_price,))

Кроме того, вам не нужны кавычки вокруг держателя позиции ('%s'), если параметр является строкой.

person Bruno    schedule 28.10.2011
comment
Также используйте %s (см. Пример max_price выше). - person Bruno; 28.10.2011
comment
почему после max_price стоит запятая? Что это значит ? Извините, если мои вопросы кажутся глупыми, но я новичок в python :) - person Lucas Kauffman; 30.10.2011
comment
Запятая после max_price - это запись для одноэлементного кортежа: docs. python.org/tutorial/ - person Bruno; 30.10.2011
comment
Вы сказали Note that this is using a comma, not % (which would be a direct string substitution, not escaped). Don't do this Вы уверены? Потому что я использовал запятую, и она экранирует строку - person Hussain; 23.05.2014
comment
@HussainTamboli, да, это именно то, что я сказал: запятая - это правильный способ использования заполнителей параметров (она выполняет все необходимые экранирования), % не экранирует параметры. - person Bruno; 23.05.2014
comment
@ Бруно Ой! Я понял. Так что я делаю это правильно. Между прочим, простите меня. Я неправильно это понял. - person Hussain; 23.05.2014
comment
Python преобразует значения в кортеже в тип данных, который понимает MySQL, и добавляет необходимые кавычки. - person shadow0359; 03.04.2017
comment
А когда это имя таблицы? мне кажется, что %s вводит одинарные кавычки типа 'breakfast', а не `breakfast` - person lucidbrot; 25.08.2019
comment
@lucidbrot Имена таблиц не являются параметрами. Если вам нужно создать запрос динамически с именами таблиц, полученными из переменных, вам необходимо дезинфицировать эти переменные вручную, прежде чем помещать их в строку запроса (не через заполнители параметров). Например, разрешить только имена таблиц, соответствующие [a-z0-9_]+. - person Bruno; 25.08.2019
comment
Да, это работает. Но почему он защищает от SQL-инъекций? - person RicarHincapie; 08.08.2020
comment
@RicarHincapie Он защищает от SQL-инъекций, поскольку рассматривает %s как параметр, не имеющий ничего общего с подстановкой строк в самом запросе: либо драйвер будет правильно экранировать значения, либо сам протокол БД учтет это. - person Bruno; 09.08.2020

В качестве расширения ответа Бруно ваша клиентская библиотека MySQL может поддерживать любой из нескольких различных форматов для указания именованных параметров. Из PEP 249 (DB-API) вы можете написать свои запросы, например:

qmark

>>> cursor.execute("SELECT spam FROM eggs WHERE lumberjack = ?", (lumberjack,))

'числовой'

>>> cursor.execute("SELECT spam FROM eggs WHERE lumberjack = :1", (lumberjack,))

'по имени'

>>> cursor.execute("SELECT spam FROM eggs WHERE lumberjack = :jack", {'jack': lumberjack})

'формат'

>>> cursor.execute("SELECT spam FROM eggs WHERE lumberjack = %s", (lumberjack,))

"пиформат"

>>> cursor.execute("SELECT spam FROM eggs WHERE lumberjack = %(jack)s", {'jack': lumberjack})

Вы можете увидеть, что поддерживает ваша клиентская библиотека, посмотрев на переменную уровня модуля paramstyle:

>>> clientlibrary.paramstyle
'pyformat'

Любой из вышеперечисленных вариантов должен действовать правильно в отношении обработки ваших, возможно, небезопасных данных. Как заметил Бруно, никогда не пытайтесь самостоятельно вставлять параметры. Часто используемые клиентские библиотеки обрабатывают данные намного лучше, чем мы, простые смертные.

person Kirk Strauser    schedule 28.10.2011
comment
Какие клиентские библиотеки поддерживают "named"? PyMySQL и MySQLdb поддерживают формат, а наш SQL поддерживает qmark. - person Martin Burch; 10.04.2015
comment
sqlite3 как минимум поддерживает named. У меня нет установленных адаптеров MySQL для проверки наличия в них «именованной» поддержки. - person Kirk Strauser; 10.04.2015
comment
Я знаю, что это старый вопрос, но я пытаюсь правильно закодировать свою веб-страницу и не имею большого опыта в безопасном SQL. Достаточно ли использовать описанный выше метод для предотвращения внедрения SQL-кода или мне нужно сделать что-то еще в дополнение к этому? Спасибо. - person jonesy19; 22.05.2018
comment
Это само по себе достаточно хорошо. Я очень, очень рекомендую проверить ORM, например SQLAlchemy, и позволить ему обрабатывать детали за вас. - person Kirk Strauser; 23.05.2018

Если вы используете mysqldb, вы можете использовать встроенную функцию escape_string. Нравится.

sql = "SELECT spam FROM eggs WHERE lumberjack = '" + MySQLdb.escape_string(str(lumberjack)) + "';"
cursor.execute(sql)

Я всегда предпочитаю использовать escape-функцию соединителя базы данных - она ​​работает, как задумано, и ручное кодирование escape-функций самостоятельно представляет собой угрозу безопасности.

person Wayne Workman    schedule 03.07.2018
comment
Поскольку MySQLdb в Python в значительной степени является прямым отображением C API, и поскольку Документ MySQL говорит: Не используйте эту функцию., вероятно, использовать ее - не лучшая идея. Даже с real_escape_string, могут быть уязвимости (в основном это примеры PHP, но основанные на одном и том же C API в обоих случаях). - person Bruno; 13.11.2018