在 Python 中处理此问题的惯用方法是使用正在使用的数据库驱动程序提供的 cursor 的 executemany 方法。
例如,对于使用标准库中sqlite3模块的sqlite
conn = sqlite3.connect('/path/to/file.db')
cursor = conn.cursor()
sql = """INSERT INTO mytable (ID, Speed, Power) VALUES (?, ?, ?)"""
values = [(1,7,3000),(1,8,3500),(1,9,3900)]
cursor.executemany(stmt, values)
VALUES 子句中使用的占位符因特定驱动程序而异。正确的值可以在驱动程序的文档中找到,也可以通过查找驱动程序模块的paramstyle 属性来找到。
使用这种方法而不是字符串插值/格式化或 f 字符串可确保正确引用值,从而防止 SQL 注入和其他错误:
>>> conn = sqlite3.connect(':memory:')
>>> cur = conn.cursor()
>>> date = '2020-11-23'
>>> # Correctly quoted input is returned as the selected value
>>> cur.execute("""SELECT ? AS today""", (date,)) # <- execute requires a tuple as values
<sqlite3.Cursor object at 0x7f1fa205e1f0>
>>> cur.fetchone()
('2020-11-23',)
>>> # Unquoted input is evaluated as an expression!
>>> cur.execute(f"""SELECT {date} AS today""")
<sqlite3.Cursor object at 0x7f1fa205e1f0>
>>> cur.fetchone()
(1986,)
这是一个使用字符串格式的 SQL 注入示例。因为值“name”没有转义,所以当程序员的意图只是返回一个时,查询会返回表中的所有用户名和密码。
NAMES = [('Alice', 'apple'), ('Bob', 'banana'), ('Carol', 'cherry')]
conn = sqlite3.connect(':memory:')
cur = conn.cursor()
cur.execute("""CREATE TABLE users (name text, password text)""")
cur.executemany("""INSERT INTO users (name, password) VALUES (?, ?)""", NAMES)
conn.commit()
cur.execute("""SELECT name, password FROM users WHERE name = {}""".format('name'))
for row in cur.fetchall():
print(row)
如果值被正确转义:
cur.execute("""SELECT name, password FROM users WHERE name = ?""", ('name',))
不会返回任何行,从而阻止攻击。