在使用Psycopg2的Python中使用以下代码:
import psycopg2
import getpass
conn = psycopg2.connect("dbname=mydb user=%s" % getpass.getuser())
cursor = conn.cursor()
tables = ["user", "group", "partner", "product"]
for table in tables:
# with sql injection
cursor.execute("SELECT name FROM %s LIMIT 1" % (table,))
print "table", table, "result", len(cursor.fetchone())
# without sql injection
cursor.execute("SELECT name FROM %s LIMIT 1", (table,))
print "table", table, "result", len(cursor.fetchone())
输出结果为:
table res_partner result 1
Traceback (most recent call last):
File "my_psycopg2_example.py", line 16, in <module>
cursor.execute("SELECT name FROM %s LIMIT 1", (table,))
psycopg2.ProgrammingError: syntax error at or near "'res_partner'"
LINE 1: SELECT name FROM 'res_partner' LIMIT 1
使用SQL注入它可以正常工作。
但我们不想创建安全问题。
我们阅读了this documentation,并在其中找到了以下评论:
只应通过此方法绑定变量值:它不应用于设置表或字段名称。对于这些元素,应在运行
execute()
之前使用普通的字符串格式。
但是如果我们使用“普通字符串格式化”,我们也会使用SQL注入。
管理这种特殊情况的好方法是什么,避免SQL注入?
答案 0 :(得分:3)
我认为你混淆了SQL注入的定义。 SQL注入是您软件上的攻击,有人会让您的SQL查询执行您不想要的操作。字符串插值不是SQL注入。字符串插值有时可以启用SQL注入,但并非总是如此。要查看字符串插值并不总是不安全,请考虑以下哪项是最安全的:
sql = 'SELECT name FROM user'
sql = 'SELECT name FROM ' + 'user'
sql = 'SELECT name FROM %s' % ['user']
sql = 'SELECT name FROM {}'.format('user')
这些代码行中的每一行都完全相同,因此它们中没有一个比其他代码更安全或更不安全。在您的确切示例中,没有SQL注入的危险,因为您只是构建一个硬编码的SQL查询字符串。
另一方面,如果您的table
值来自用户,则可能存在安全问题:
如果他们传递了一个存在的表的名称,但您不希望他们查询该怎么办?
table = 'secrets'
sql = 'SELECT name FROM %s LIMIT 1' % table
结果:
SELECT name FROM secrets LIMIT 1
如果他们传递的something实际上不是表名怎么办?
table = 'product; DROP TABLE user; --'
sql = 'SELECT name FROM %s LIMIT 1' % table
结果:
SELECT name FROM product;
DROP TABLE user;
-- LIMIT 1
您可以通过检查是否允许表名来阻止这种情况:
if table.lower() not in ["user", "group", "partner", "product"]:
raise Something('Bad table name: %r' % table)
答案 1 :(得分:0)
在execute
函数中使用psycopg2查询参数是最安全的,并且在将参数用作文字时很容易使用。
cursor.mogrify("select * from foo where bar = %s", ('example',))
# yields "select * from foo where bar = 'example'"
(请注意, cursor.mogrify()
的行为类似于执行,但只显示格式化的SQL而不实际执行它。
但是,当您希望参数为表,模式或其他标识符时,这样做有点棘手。您可以使用AsIs来包装您的参数,但这仍然为SQL注入打开了大门。
from psycopg2.extensions import AsIs
cur.mogrify('select %s from foo;', (AsIs('* from dual; drop table students; --'),))
# yields 'select * from dual; drop table students; -- from foo;'
看起来psycopg2(&gt; = 2.7)的新开发将有一个Identifier类,您可以将参数包装进来并希望是安全的。如果它还没有发布,或者你没有它,那么这是一种创建自己的类的方法。我将在下面给出一些片段,但您也可以看到my gist。
import re
import psycopg2.extensions
class NotSqlIdentifierError(Exception):
pass
valid_pattern = r'^[a-zA-Z_][a-zA-Z0-9_\$]*$'
class QuotedIdentifier(object):
def __init__(self, obj_str):
self.obj_str = obj_str
def getquoted(self):
if re.match(valid_pattern, self.obj_str):
return self.obj_str
else:
raise NotSqlIdentifierError(repr(self.obj_str))
psycopg2.extensions.register_adapter(QuotedIdentifier, lambda x: x)
如果你已经有一个psycopg2游标实例,你可以这样测试/使用它:
# Test that a valid identifier formats into string
cursor.mogrify('select %s from foo;', (QuotedIdentifier('bar'),))
# returns 'select bar from foo;'
# Test formatting both an identifier and a literal
cursor.mogrify(
'select * from foo where %s = %s;',
(
QuotedIdentifier('bar'),
'example'
)
)
# returns "select * from foo where bar = 'example';"
# Test that a non-valid identifier fails with exception
cursor.mogrify('select %s from foo;', (QuotedIdentifier('* from dummy; drop table students; --'),))
"""Returns following:
---------------------------------------------------------------------------
NotSqlIdentifierError Traceback (most recent call last)
<ipython-input-14-d6a960dc458a> in <module>()
----> 1 cur.mogrify('select %s from foo;', (QuotedIdentifier('* from dummy; drop table students; --'),))
<ipython-input-12-0a1327cbaf78> in getquoted(self)
18 return self.obj_str
19 else:
---> 20 raise NotSqlIdentifierError(repr(self.obj_str))
21
22 psycopg2.extensions.register_adapter(QuotedIdentifier, lambda x: x)
NotSqlIdentifierError: '* from dummy; drop table students; --'
"""
有关自定义类包装SQL参数的机制的更多信息,请参阅文档中的this section。
答案 2 :(得分:0)
我认为您的代码可能只是缺少“;”。您的一行可能如下所示:
cursor.execute('SELECT name FROM %s LIMIT 1;', (table,))
如果所有其他方法都失败了,您可以为该输入设置一个过滤掉控制字符的过滤器。对我来说听起来很痛苦,但可以做到。然后你的代码看起来像:
cursor.execute('SELECT name FROM %s LIMIT 1;' % (functionToSanitizeInput(table)))
也许你可以使用这个项目:
bleach.readthedocs.io
=============================================== =
非常好参考: http://bobby-tables.com/
=============================================== =
答案 3 :(得分:0)
这是一篇有关注入和python代码的不错的文章 https://realpython.com/prevent-python-sql-injection/
您可以变换
tables = ["user", "group", "partner", "product"]
for table in tables:
cursor.execute("SELECT name FROM %s LIMIT 1" % (table,))
到
from psycopg2 import sql
tables = ["user", "group", "partner", "product"]
for table_name in tables:
stmt = sql.SQL("SELECT name FROM {table_name} LIMIT 1").format(
table_name = sql.Identifier(table_name),
)
cursor.execute(stmt)