如何避免使用“SELECT*FROM{table_name}”进行SQL注入?

2024-05-29 03:47:32 发布

您现在位置:Python中文网/ 问答频道 /正文

在Python中使用Psycopg2和以下代码:

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())

结果是:

^{pr2}$

有了SQL注入,它工作得很好。在

但我们不想制造安全问题。在

我们阅读了this documentation,其中发现了以下注释:

Only variable values should be bound via this method: it shouldn’t be used to set table or field names. For these elements, ordinary string formatting should be used before running execute().

但如果我们使用“普通字符串格式”,我们也会有SQL注入。在

管理这种特殊情况并避免SQL注入的好方法是什么?在


Tags: nameimportexecutetablessqltablebeconn
3条回答

我认为你混淆了SQL注入的定义。SQL注入是对软件的一种攻击,有人会让你的SQL查询做一些你不想做的事情。字符串插值不是SQL注入。字符串插值有时可以启用SQL注入,但并不总是这样。要了解字符串插值并不总是不安全的,请考虑以下哪项最安全:

  1. sql = 'SELECT name FROM user'
  2. sql = 'SELECT name FROM ' + 'user'
  3. sql = 'SELECT name FROM %s' % ['user']
  4. sql = 'SELECT name FROM {}'.format('user')

这些代码行中的每一行都执行完全相同的操作,因此它们中没有一行比其他行安全。在您的确切示例中,没有SQL注入的危险,因为您只是构建一个硬编码的SQL查询字符串。在

另一方面,如果您的table值来自用户,则可能存在安全问题:

  • 如果他们传递了一个存在的表的名称,但是你不想让他们查询呢?在

    table = 'secrets'
    sql = 'SELECT name FROM %s LIMIT 1' % table
    

    结果:

    ^{2美元
  • 如果他们传递的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)

execute函数中使用psycopg2查询参数是最安全的,并且当参数用作文本时也很容易使用。在

cursor.mogrify("select * from foo where bar = %s", ('example',))
# yields "select * from foo where bar = 'example'"

(注意cursor.mogrify()的行为类似于execute,但只显示格式化的SQL而不实际执行它)

但是,当您希望参数是表、模式或其他标识符时,要做到这一点有点困难。您可以使用AsIs包装参数,但这仍然为SQL注入敞开大门。在

^{pr2}$

看起来psycopg2的新开发(>;=2.7)将有一个标识符类,您可以在其中包装参数,希望是安全的。如果它还没有发布,或者你还没有,这里有一种方法来创建你自己的类。我将在下面给出一些片段,但您也可以看到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。在

我认为您的代码可能只是缺少“;”。你的一行可能看起来像:

cursor.execute('SELECT name FROM %s LIMIT 1;', (table,))

如果所有其他方法都失败了,您可以为输入设置一个过滤器,过滤掉控制字符。听起来我很痛苦,但我能做到。那么你的代码看起来像:

^{pr2}$

也许你可以使用这个项目:
bleach.readthedocs.io

==================================================

非常好的参考: http://bobby-tables.com/

==================================================

相关问题 更多 >

    热门问题