参数化数据库查询

我们经常编写代码,使用应用程序用户提供的参数与数据库进行交互。这些参数包括凭据、资源标识符和其他用户提供的数据。

在动态创建数据库查询时必须小心,以防止它们被用户提供的恶意输入所颠覆,这通常被称为 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,))

后果

  • 数据完全泄露的可能性

  • 远程代码执行的可能性

参考