如何在 Python 中安全地将值传递给 SQLite PRAGMA 语句?

问题描述 投票:0回答:1

我目前正在用 Python 编写一个应用程序,将其数据存储在 SQLite 数据库中。我希望将数据库文件加密存储在磁盘上,我发现最常见的解决方案是SQLCipher。我将 sqlcipher3 添加到我的项目中以提供 DB-API,并开始使用。使用 SQLCipher,数据库加密密钥以 PRAGMA 语句的形式提供,必须在执行数据库的第一个操作之前提供该语句。

PRAGMA key='hunter2';  -- like this

当我的程序运行时,它会提示用户输入数据库密码。我担心的是,由于这是用户输入的来源,因此它可能容易受到 SQL 注入的攻击。例如,提供密钥的简单方法可能如下所示:

from getpass import getpass
import sqlcipher3

con = sqlcipher3.connect(':memory:')
cur = con.cursor()
password = getpass('Password: ')
cur.execute(f"PRAGMA key='{password}';")

### do stuff with the unencrypted database here

如果有人在密码提示中输入类似“

hunter2'; DROP TABLE secrets;--
”的内容,则替换后生成的 SQL 语句将如下所示:

PRAGMA key='hunter2'; DROP TABLE secrets;--';

通常,解决此问题的方法是使用 DB-API 的参数替换。来自sqlite3文档

SQL 语句可以使用两种占位符之一:问号(qmark 样式)或命名占位符(命名样式)。对于 qmark 样式,parameters 必须是一个序列,其长度必须与占位符的数量匹配,否则会引发

ProgrammingError
。对于命名样式,parameters必须是
dict
(或子类)的实例,它必须包含所有命名参数的键;任何额外的项目都会被忽略。这是两种风格的示例:

con = sqlite3.connect(":memory:")
cur = con.execute("CREATE TABLE lang(name, first_appeared)")

# This is the named style used with executemany():
data = (
    {"name": "C", "year": 1972},
    {"name": "Fortran", "year": 1957},
    {"name": "Python", "year": 1991},
    {"name": "Go", "year": 2009},
)
cur.executemany("INSERT INTO lang VALUES(:name, :year)", data)

# This is the qmark style used in a SELECT query:
params = (1972,)
cur.execute("SELECT * FROM lang WHERE first_appeared = ?", params)
print(cur.fetchall())

这按照文档示例代码中的预期工作,但是在 PRAGMA 语句中使用占位符时,我们得到一个

OperationalError
告诉我们存在语法错误。两种类型的参数替换都是这种情况。

# these will both fail

cur.execute('PRAGMA key=?;', (password,))
cur.execute('PRAGMA key=:pass;', {'pass': password})

我不知道从这里该去哪里。如果我们实际上在密码提示符下输入恶意字符串,它将不起作用,并产生以下错误:

回溯(最近一次调用最后一次):
  文件“”,第 1 行,位于 
sqlcipher3.ProgrammingError:您一次只能执行一条语句。

那么早期的“幼稚”代码安全吗?我不确定答案是“是”,只是因为我能想出的 one 恶意字符串不起作用,但似乎没有更好的方法来做到这一点。这里唯一问这个问题的人的答案是我可以找到建议的等效解决方案(python + sqlite 将变量插入 PRAGMA 语句)。我也不想使用 ORM,特别是如果它只是针对这种情况。任何建议将不胜感激,谢谢。

python sqlite sql-injection sqlcipher
1个回答
0
投票

根据“执行中的Python sqlite3字符串变量”的接受的答案,可以使用DB-API替换的地方存在限制:

参数标记只能用于表达式,即值。您不能将它们用于表名和列名等标识符。

看到这一点,我认为

PRAGMA
的参数必须属于与“表和列名称”相同的类别。事实上,我的具体用例是
PRAGMA table_info
,其中参数 is 是表名。

进一步深入研究,我发现Python的

sqlite3
模块依赖于SQLite自己的
sqlite3_bind_*
函数
来进行参数替换。例如,以下是替换字符串值的代码。我发现进一步证实替换不适用于
PRAGMA
参数

“但是等等,”我想。 “Sam 的参数是键,而不是表名。”如果不深入挖掘,我只能推测这并不重要,而且 SQLite(或 SQLCipher)只是不允许将值绑定到

PRAGMA
语句。

也许您可以通过 SQLCipher 的 C API 而不是通过 SQL 提供密钥?它不能修复我的用例,但可能对你的用例有所帮助!

对于我以及其他任何试图以编程方式向

PRAGMA table_info
提供表名的人来说,我想官方的解决方案是双重和三重检查变量 cannot possible 包含用户输入,验证并转义它。以防万一,交叉手指、脚趾、膝盖和鼻子,然后进行弦替换! 可能会出现什么问题...

© www.soinside.com 2019 - 2024. All rights reserved.