将表名作为参数传递给psycopg2

时间:2012-12-10 00:23:11

标签: python sql postgresql sql-injection psycopg2

我有以下代码,使用pscyopg2:

sql = 'select %s from %s where utctime > %s and utctime < %s order by utctime asc;'
data = (dataItems, voyage, dateRangeLower, dateRangeUpper)
rows = cur.mogrify(sql, data)

输出:

select 'waterTemp, airTemp, utctime' from 'ss2012_t02' where utctime > '2012-05-03T17:01:35+00:00'::timestamptz and utctime < '2012-05-01T17:01:35+00:00'::timestamptz order by utctime asc;

当我执行此操作时,它会崩溃 - 这是可以理解的,因为表名周围的引号是非法的。

有没有办法合法地将表名作为参数传递,或者我是否需要进行(显式警告)字符串连接,即:

voyage = 'ss2012_t02'
sql = 'select %s from ' + voyage + ' where utctime > %s and utctime < %s order by utctime asc;'

为任何见解干杯。

9 个答案:

答案 0 :(得分:53)

根据官方文件:

  

如果需要动态生成SQL查询(例如)   动态选择表名)您可以使用这些设施   由psycopg2.sql模块提供。

sql模块是psycopg2版本2.7中的新模块。它具有以下语法:

from psycopg2 import sql

cur.execute(
    sql.SQL("insert into {} values (%s, %s)")
        .format(sql.Identifier('my_table')),
    [10, 20])

更多信息:http://initd.org/psycopg/docs/sql.html#module-psycopg2.sql

[更新2017-03-24:AsIs不应用于表示表格或字段名称,而应使用新的sql模块:https://stackoverflow.com/a/42980069/5285608]

另外,根据psycopg2文档:

  

警告:从不,从不从不使用Python字符串连接(+)或字符串参数插值({{1将变量传递给SQL查询字符串。甚至在枪口下也没有。

答案 1 :(得分:30)

this answer你可以这样做:

import psycopg2
from psycopg2.extensions import AsIs

#Create your connection and cursor...

cursor.execute("SELECT * FROM %(table)s", {"table": AsIs("my_awesome_table")})

答案 2 :(得分:18)

表名不能作为参数传递,但其他一切都可以。因此,表名应该在您的应用程序中进行硬编码(不要输入或使用程序之外的任何名称作为名称)。您拥有的代码应该适用于此。

如果您有正当理由获取外部表名,请确保您不允许用户直接输入它。也许可以传递一个索引来选择一个表,或者可以用其他方式查找表名。但是,你应该警惕这样做。这是有效的,因为周围的表名相对较少。找到一种验证表名的方法,你应该没问题。

可以做这样的事情,看看表名是否存在。这是参数化版本。只需确保执行此操作并在运行SQL代码之前验证输出。部分想法来自this answer

SELECT 1 FROM information_schema.tables WHERE table_schema = 'public' and table_name=%s LIMIT 1

答案 3 :(得分:2)

我已经创建了一个小实用程序来预处理带有变量表(...)名称的SQL语句:

from string import letters
NAMECHARS = frozenset(set(letters).union('.'))

def replace_names(sql, **kwargs):
    """
    Preprocess an SQL statement: securely replace table ... names
    before handing the result over to the database adapter,
    which will take care of the values.

    There will be no quoting of names, because this would make them
    case sensitive; instead it is ensured that no dangerous chars
    are contained.

    >>> replace_names('SELECT * FROM %(table)s WHERE val=%(val)s;',
    ...               table='fozzie')
    'SELECT * FROM fozzie WHERE val=%(val)s;'
    """
    for v in kwargs.values():
        check_name(v)
    dic = SmartDict(kwargs)
    return sql % dic

def check_name(tablename):
    """
    Check the given name for being syntactically valid,
    and usable without quoting
    """
    if not isinstance(tablename, basestring):
        raise TypeError('%r is not a string' % (tablename,))
    invalid = set(tablename).difference(NAMECHARS)
    if invalid:
        raise ValueError('Invalid chars: %s' % (tuple(invalid),))
    for s in tablename.split('.'):
        if not s:
            raise ValueError('Empty segment in %r' % tablename)

class SmartDict(dict):
    def __getitem__(self, key):
        try:
            return dict.__getitem__(self, key)
        except KeyError:
            check_name(key)
            return key.join(('%(', ')s'))

SmartDict对象为每个未知%(key)s返回key,保留它们以进行值处理。该函数可以检查是否缺少任何引号字符,因为现在应该处理所有引用...

答案 4 :(得分:1)

如果要将表名作为参数传递,可以使用此包装器:

class Literal(str):
    def __conform__(self, quote):
        return self

    @classmethod
    def mro(cls):
        return (object, )

    def getquoted(self):
        return str(self)

用法:cursor.execute("CREATE TABLE %s ...", (Literal(name), ))

答案 5 :(得分:1)

这是我过去使用的解决方法

query = "INSERT INTO %s (col_1, col_2) VALUES (%%s, %%s)" % table_name
cur.execute(query, (col_1_var, col_2_var))

希望有所帮助:)

答案 6 :(得分:0)

您可以使用表名的模块格式,然后使用常规参数化执行:

xlist = (column, table)
sql = 'select {0} from {1} where utctime > %s and utctime < %s order by utctime asc;'.format(xlist)

请记住,如果这是向最终用户公开的,除非您为此编写,否则不会受到SQL注入的保护。

答案 7 :(得分:0)

这是@Antoine Dusséaux 回答的一个小补充。如果你想在 SQL 查询中传递两个(不带引号的)参数,你可以这样做:-

query = sql.SQL("select {field} from {table} where {pkey} = %s").format(
    field=sql.Identifier('my_name'),
    table=sql.Identifier('some_table'),
    pkey=sql.Identifier('id'))

根据文档,

<块引用>

通常您应该将查询的模板表示为 SQL 带有 {} 样式占位符的实例并使用 format() 合并 可变部分,所有这些都必须是可组合的子类。 您仍然可以在查询中使用 %s 样式的占位符并传递值 to execute():format() 不会触及这些值占位符

来源:https://www.psycopg.org/docs/sql.html#module-usage

另外,在编写查询时请记住 this

答案 8 :(得分:-6)

惊讶没有人提到这样做:

sql = 'select {} from {} where utctime > {} and utctime < {} order by utctime asc;'.format(dataItems, voyage, dateRangeLower, dateRangeUpper)
rows = cur.mogrify(sql)

format将字符串放入不带引号的字符串中。