关于安全性:在Python sqlite3查询中替换列名

Substituting column names in Python sqlite3 query

本问题已经有最佳答案,请猛点这里访问。

我在sqlite3数据库中有一张宽表,并且希望动态查询Python脚本中的某些列。我知道通过字符串连接注入参数很不好,所以我尝试使用参数替换。

我发现,当我使用参数替换提供列名时,得到了意外的结果。一个最小的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import sqlite3 as lite

db = lite.connect("mre.sqlite")
c = db.cursor()

# Insert some dummy rows
c.execute("CREATE TABLE trouble (value real)")
c.execute("INSERT INTO trouble (value) VALUES (2)")
c.execute("INSERT INTO trouble (value) VALUES (4)")
db.commit()


for row in c.execute("SELECT AVG(value) FROM trouble"):
    print row   # Returns 3

for row in c.execute("SELECT AVG(:name) FROM trouble", {"name" :"value"}):
    print row   # Returns 0

db.close()

有没有比将列名简单地插入字符串并运行更好的方法呢?


正如罗布(Rob)在评论中指出的,有一篇相关的SO帖子包含了我的答案。这些替代构造称为"占位符",这就是为什么我最初没有在SO上找到答案的原因。列名没有占位符模式,因为动态指定列不是代码安全问题:

It comes down to what"safe" means. The conventional wisdom is that
using normal python string manipulation to put values into your
queries is not"safe". This is because there are all sorts of things
that can go wrong if you do that, and such data very often comes from
the user and is not in your control. You need a 100% reliable way of
escaping these values properly so that a user cannot inject SQL in a
data value and have the database execute it. So the library writers do
this job; you never should.

If, however, you're writing generic helper code to operate on things
in databases, then these considerations don't apply as much. You are
implicitly giving anyone who can call such code access to everything
in the database; that's the point of the helper code. So now the
safety concern is making sure that user-generated data can never be
used in such code. This is a general security issue in coding, and is
just the same problem as blindly execing a user-input string. It's a
distinct issue from inserting values into your queries, because there
you want to be able to safely handle user-input data.

因此,解决方案是首先没有问题:使用字符串格式注入值,要快乐,然后继续生活。


为什么不使用字符串格式?

1
2
for row in c.execute("SELECT AVG({name}) FROM trouble".format(**{"name" :"value"})):
    print row # => (3.0,)