SQLAlchemy + SQL 注入

在使用 SQLAlchemy 时,减轻 SQL 注入攻击的最佳实践是什么?

55550 次浏览

如果您有任何“特殊”字符(如分号或 在你的数据,他们会 自动为你报价 SQLEngine 对象,所以您不必 担心引用。这也意味着 除非你故意绕过 SQLAlchemy 的引用机制, SQL 注入攻击基本上是 不可能。

[按 http://www.rmunn.com/sqlalchemy-tutorial/tutorial.html]

Tldr: 尽可能避免使用原始 SQL。

公认的答案是懒惰和不正确的。Filter 方法接受原始 SQL,如果以这种方式使用,则完全容易受到 SQL 注入攻击。例如,如果您接受一个来自 url 的值,并将其与过滤器中的原始 sql 组合在一起,那么您将受到攻击:

session.query(MyClass).filter("foo={}".format(getArgs['val']))

使用上面的代码和下面的 url,您将在过滤器语句中注入 SQL。上面的代码将返回数据库中的所有行。

网址编码:

https://example.com/?val=2%20or%201%20=%201

更容易理解(URL 解码) :

https://example.com/?val=2 or 1 = 1

添加到 @ 温柔的回答。我做了一个小小的调查,用的是安静天真的方法。filter方法有 *criterion作为参数,其他几个 ORM 查询方法也有类似的参数。

如果 filter方法的 *criterion参数最终传递给 _ literal _ as _ text,而 _ literal _ as _ text将其标记为安全 sql (如果我错了,请纠正我)。所以它不安全。

以下是 ORM 查询类方法与 *criterion参数研究的结果:

filter   - uses _literal_as_text (NOT SAFE)
having   - uses _literal_as_text (NOT SAFE)


distinct - uses _literal_as_label_reference (NOT SAFE)
group_by - uses _literal_as_label_reference (NOT SAFE)
order_by - uses _literal_as_label_reference (NOT SAFE)


join     - uses model attributes to resolve relation (SAFE)

可能的方法错误使用 的示例(为了保持简单,跳过字符串格式化) :

db.session.query(User.login).group_by('login').having('count(id) > 4; select name from roles').all()
db.session.query(User.login).distinct('name) name from roles /*').order_by('*/').all()
db.session.query(User.login).order_by('users_login; select name from roles').all()
db.session.query(User.login).group_by('login union select name from roles').all()

注意 ,只有当字符串文字传递时,这些方法才是不安全的。

我倾向于同意@坦德丽德的回答。

如果你这样写:

session.query(MyClass).filter("foo={}".format(getArgs['val']))

你正在制造一个注射漏洞。

SqlAlchemy 的方法是使用绑定参数来避免这些注入攻击 。使用 filter()的方法就是写:

session.query(MyClass).filter(MyClass.foo == getArgs['va'])

因为 SqlAlchemy 已经重载了 python 的操作符(如 ==)以正确地转义 SQL (并避免注入)

在 SqlAlchemy 给你的文档中有一个关于这个问题的警告:

始终使用绑定参数

正如本节开头所提到的,文本 SQL 不是 我们使用 SQLAlchemy 的通常方式 Python 文本值,即使是非字符串(如整数或日期) ,也应该 不要直接将字符串化为 SQL 字符串; 参数应该 这就是众所周知的如何避免 SQL 注入攻击时,数据是不可信的。但它也允许 SQLAlchemy 方言和/或 DBAPI 来正确处理传入的 在纯文本 SQL 用例之外, 否则,SQLAlchemy 的核心表达式 API 将确保 Python 文本 值在适当的地方作为绑定参数传递。

在术语表中有一个关于绑定参数 给你的部分

上面写着:

绑定参数是将数据传递给 要调用的操作基于 SQL 语句字符串,则数据值本身被传递 其中,驱动程序包含逻辑,将安全地处理 这些字符串并将它们传递给后端数据库服务器 要么将参数格式化为 SQL 字符串本身, 或者使用单独的协议将它们传递到数据库。

数据库驱动程序执行此操作的特定系统不应该 问题的关键是,在外部,数据应该 总是单独传递,而不是作为 SQL 字符串本身的一部分传递。 这对于拥有足够的 SQL 安全性是不可或缺的 注射,以及让司机有最好的 表演。

基本上就是说:

session.query(MyClass).filter("foo={}".format(getArgs['val']))

... 是因为将数据与 SQL 语句 foo=<data>一起传递给 filter()

你应该始终将语句和数据分开。例如:

session.query(MyClass).filter(MyClass.foo == getArgs['va'])

或者

session.query(MyClass).filter_by(foo=getArgs['va'])

这样 SqlAlchemy 就可以神奇地工作,并使用绑定参数进行转义。