PyMySQL - экранирование идентификаторов

Я использую PyMySQL, и когда я пишу запросы, мне нужно избегать определенных идентификаторов, предоставленных пользователем (имена таблиц и т. д.). Например, вот плохой пример, показывающий, что я имею в виду:

sql = "SELECT * FROM {}".format(table_name)
cursor.execute(sql)

В этом случае table_name предоставляется пользователем, поэтому приведенный выше код явно уязвим для атаки с использованием SQL-инъекции.

Сначала я попробовал сделать что-то вроде этого:

sql = "SELECT * FROM %s"
cursor.execute(sql, (table_name,))

Кажется, что приведенный выше код не работает при экранировании идентификаторов, он работает только со значениями. Это не удивительно, потому что я видел подобное поведение в других библиотеках MySQL на других языках.

Я использовал другие библиотеки, такие как mysql в Node.js, в которых есть функция для экранирования идентификаторов. Я не вижу ничего в Документация PyMySQL, что указывает на аналогичную функциональность. Я обнаружил, что этот StackOverflow вопрос задает аналогичный вопрос, но ответ был примерно таким: «Вам не нужно этого делать». Что ж, мне нужно это сделать! ;)

Какие у меня есть варианты экранирования идентификаторов при использовании MySQL в Python? Спасибо!

«Это не удивительно, потому что я видел подобное поведение в других библиотеках MySQL на других языках». Затем это моделируется фреймворком. Собственный протокол подготовки MySQL позволяет подготавливать только значения, а не имена баз данных, таблиц или столбцов.

Raymond Nijland 16.08.2018 01:39
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
3
1
1 221
2

Ответы 2

Драйвер python имитирует параметры запроса, но следует аналогичным правилам. Заполнитель, такой как %s, будет заменен строкой цитируется, при этом строка будет экранирована, поэтому в любые буквальные символы апострофа будет вставлен \.

sql = "SELECT * FROM mytable WHERE name = %s"
cursor.execute(sql, (myname,))

Результатом будет SQL:

SELECT * FROM mytable WHERE name = 'O\'Reilly'

Обратная косая черта необходима для предотвращения внедрения SQL.

Но это бесполезно для имен таблиц, потому что одинарные кавычки предназначены для строковых литералов или литералов даты, а не для идентификаторов.

Вы можете сделать это так:

sql = "SELECT * FROM `{}`".format(table_name)

Обратные галочки ограничивают идентификатор, поэтому вы можете использовать зарезервированные слова, пробелы или другие специальные символы в имени вашей таблицы.

Вы должны убедиться, что значение table_name не включает в себя буквальные символы обратной галочки, или же вставьте обратную косую черту, если у вас есть буквальная обратная косая черта в имени таблицы.

Расширяя ответ от @ Билл Карвин, вам нужно будет самостоятельно избегать обратных кавычек, потому что pymysql не делает этого изначально, даже его метод escape_string().

Однако, вопреки тому, что сказал Билл, чтобы избежать возможных обратных кавычек в имени таблицы, вам нужно будет их удвоить, а не использовать \. От документация mysql:

Identifier quote characters can be included within an identifier if you quote the identifier. If the character to be included within the identifier is the same as that used to quote the identifier itself, then you need to double the character.

Это в основном означает, что для того, чтобы избежать обратной кавычки, вам понадобится двойная обратная кавычка. Вот к чему я в итоге прибег:

table = 'table`; drop table users; -- '
sane_table = pymysql.escape_string(table).strip('`').replace('`', '``')
cursor.execute(f"SELECT * FROM `{sane_table}`"))

Что будет правильно экранировать имя таблицы и сообщать правильную ошибку:

pymysql.err.ProgrammingError: (1103, "Incorrect table name 'table`; drop table users; -- '")

Другие вопросы по теме