如何在一次执行调用中避免多个查询

时间:2013-09-06 19:17:39

标签: python cursor psycopg2

我刚刚意识到psycopg2允许在一次execute电话中进行多次查询。

例如,此代码实际上会在my_table中插入两行:

>>> import psycopg2
>>> connection = psycopg2.connection(database='testing')
>>> cursor = connection.cursor()
>>> sql = ('INSERT INTO my_table VALUES (1, 2);'
...        'INSERT INTO my_table VALUES (3, 4)')
>>> cursor.execute(sql)
>>> connection.commit()

psycopg2是否有某种方法可以禁用此功能?或者还有其他方法可以防止这种情况发生吗?

我到目前为止搜索查询是否有任何分号(;):

if ';' in sql:
    # Multiple queries not allowed!

但是这个解决方案并不完美,因为它不允许一些有效的查询,如:

SELECT * FROM my_table WHERE name LIKE '%;'

编辑: SQL注入攻击不是问题。我确实想让用户完全访问数据库(他甚至可以删除整个数据库)。

2 个答案:

答案 0 :(得分:1)

如果你想要解决这类问题的一般方法,答案总是“解析格式X,或者至少解析它以满足你的需求”。

在这种情况下,它可能非常简单。 PostgreSQL不允许在列名或表名等中间使用分号;它们可以出现的唯一位置是字符串内部或语句终止符。所以,你不需要一个完整的解析器,只需要一个可以处理字符串的解析器。

不幸的是,即使这不是完全无关紧要的,因为你必须知道PostgreSQL中作为字符串文字的规则。例如,"abc\"def"是字符串abc"def

但是一旦你编写或找到一个可以在PostgreSQL中识别字符串的解析器,就很容易:跳过所有字符串,然后查看是否还有分号。

例如(这可能不是正确的逻辑,*它也是以冗长而低效的方式编写的,只是为了向您展示这个想法):

def skip_quotes(sql):
    in_1, in_2 = False, False
    for c in sql:
        if in_1:
            if c == "'":
                in_1 = False
        elif in_2:
            if c == '"':
                in_2 = False
        else:
            if c == "'":
                in_1 = True
            elif c == '"':
                in_2 = True
            else:
                yield c

然后你可以写:

if ';' in skip_quotes(sql):
    # Multiple queries not allowed!

如果找不到预先制作的解析器,首先要考虑的是:

  • 如果像find这样的简单字符串操作可以正常工作,那么就这样做。
  • 如果是简单的常规语言,请使用re
  • 如果可以描述性地解释逻辑(例如,通过BNF语法),请使用解析库或解析器生成器库,如pyparsingpybison
  • 否则,您可能需要编写状态机,甚至是显式迭代代码(如上面的示例)。但除了教学目的之外,这几乎不是最好的答案。

*这对于接受单引号或双引号字符串的方言是正确的,不会在另一引号中转义一个引用类型,并通过加倍引用来转义引号(我们会错误地将'abc''def'视为两个字符串abcdef,而不是一个字符串abc'def,但由于我们所做的只是跳过字符串,我们得到正确的结果),但没有C风格的反斜杠逃避或其他任何事情。我相信它匹配sqlite3,因为它实际上有效,虽然不是sqlite3,因为它已记录,我不知道它是否与PostgreSQL匹配。

答案 1 :(得分:0)

允许用户进行任意查询(甚至是单个查询)可以使您的程序最多打开SQL injection attacksdenial-of-service (DOS) attacks。处理潜在恶意用户的最安全方法是精确枚举哪些查询是允许的,并且只允许用户提供参数值,而不是整个SQL查询本身。

例如,您可以定义

sql = 'INSERT INTO my_table VALUES (%s, %s)'
args = [1, 2]  # <-- Supplied by the user

然后安全地执行INSERT语句:

cursor.execute(sql, args)

这称为parametrized SQL,因为sql使用%s作为参数placemarkkers,cursor.execute语句使用两个参数。第二个参数应该是一个序列,数据库驱动程序(例如psycopg2)将用args提供的属性引用值替换参数placemarkkers。

这将阻止SQL注入攻击。 您仍然有责任(当您编写允许的SQL时)以防止拒绝服务攻击。例如,您可以通过确保用户提供的参数在合理范围内来尝试保护自己免受DOS攻击。