参数化数据库查询¶
我们经常编写代码,使用应用程序用户提供的参数与数据库进行交互。这些参数包括凭据、资源标识符和其他用户提供的数据。
在动态创建数据库查询时必须小心,以防止它们被用户提供的恶意输入所颠覆,这通常被称为 SQL 注入 (SQLi)。SQL 注入之所以有效,是因为用户输入改变了 SQL 查询的逻辑,导致了应用程序开发人员不希望出现的行为。
成功的 SQL 注入攻击可能导致敏感信息(如用户密码)的泄露、数据的修改或删除,以及获得执行权限,从而允许攻击者在数据库服务器上运行任意命令。
SQL 注入通常可以通过结合使用预处理语句、存储过程和对用户提供输入进行转义来缓解。大多数安全的 Web 应用程序都会使用这三种方法,我们将在下面描述它们的用法。
代码示例¶
SQLAlchemy¶
错误¶
此示例使用内置的参数替换机制“%”将值插入查询字符串中,这将执行不安全的字面插入,并且不提供任何转义。
import sqlalchemy
connection = engine.connect()
myvar = 'jsmith' # our intended usage
myvar = 'jsmith or 1=1' # this will return all users
myvar = 'jsmith; DROP TABLE users' # this drops (removes) the users table
query = "select username from users where username = %s" % myvar
result = connection.execute(query)
for row in result:
print "username:", row['username']
connection.close()
正确¶
此示例使用 SQLAlchemy 内置的参数替换机制安全地将“:name”变量替换为提供的值。
import sqlalchemy
connection = engine.connect()
myvar = 'jsmith' # our intended usage
myvar = 'jsmith or 1=1' # only matches this odd username
query = "select username from users where username = :name"
result = connection.execute(query, name = myvar)
for row in result:
print "username:", row['username']
connection.close()
MySQL¶
不正确¶
不使用任何转义机制可能会创建不安全的查询。
import MySQLdb
query = "select username from users where username = '%s'" % name
con = MySQLdb.connect('localhost', 'testuser', 'test623', 'testdb');
with con:
cur = con.cursor()
cur.execute(query)
更好¶
在此示例中,查询是使用 Python 标准的不安全“%”运算符创建的。MySQL 的“escape_string”方法用于在将用户输入字符串包含到字符串中之前对其进行转义。
import MySQLdb
query = "select username from users where username = '%s'" % MySQLdb.escape_string(name)
con = MySQLdb.connect('localhost', 'testuser', 'test623', 'testdb');
with con:
cur = con.cursor()
cur.execute(query)
正确¶
使用参数化查询的正确方法可能如下所示
import MySQLdb
query = "select username from users where username = '%s'"
con = MySQLdb.connect('localhost', 'testuser', 'test623', 'testdb');
with con:
cur = con.cursor()
cur.execute(query, (username_value,))
这之所以有效,是因为查询的逻辑在考虑用户输入之前已编译。
PostgreSQL (Psycopg2)¶
不正确¶
此示例使用 Python 不安全的默认参数替换机制来构建查询字符串。这将不执行任何转义,与下面正确的示例不同,字符串被处理并作为单个参数传递给“execute”。
import psycopg2
conn = psycopg2.connect("dbname=test user=postgres")
cur = conn.cursor()
cur.execute("select username from users where username = '%s'" % name)
正确¶
此示例使用 Psycopg2 的参数替换机制来构建查询字符串。尽管使用“%”来指示替换标记,但它与 Python 的内置字符串运算符 % 不同。请注意,值作为参数单独传递给“execute”。
import psycopg2
conn = psycopg2.connect("dbname=test user=postgres")
cur = conn.cursor()
cur.execute("select username from users where username = '%s'", (name,))
后果¶
数据完全泄露的可能性
远程代码执行的可能性