【问题标题】:imploding a list for use in a python MySQLDB IN clause内爆列表以在 python MySQLDB IN 子句中使用
【发布时间】:2020-04-28 22:25:38
【问题描述】:

我知道如何将列表映射到字符串:

foostring = ",".join( map(str, list_of_ids) )

而且我知道我可以使用以下方法将该字符串放入 IN 子句中:

cursor.execute("DELETE FROM foo.bar WHERE baz IN ('%s')" % (foostring))

我需要的是使用 MySQLDB 安全地完成同样的事情(避免 SQL 注入)。在上面的示例中,因为 foostring 没有作为参数传递给执行,所以它很容易受到攻击。我还必须在 mysql 库之外引用和转义。

(有一个related SO question,但那里列出的答案要么不适用于 MySQLDB,要么容易受到 SQL 注入的攻击。)

【问题讨论】:

标签: python mysql


【解决方案1】:

直接使用list_of_ids

format_strings = ','.join(['%s'] * len(list_of_ids))
cursor.execute("DELETE FROM foo.bar WHERE baz IN (%s)" % format_strings,
                tuple(list_of_ids))

这样就避免了自己引用,也避免了各种sql注入。

请注意,数据(list_of_ids)作为参数(不在查询文本中)直接进入 mysql 的驱动程序,因此没有注入。您可以在字符串中保留任何您想要的字符,无需删除或引用字符。

【讨论】:

  • @heikogerlach:我没有引用 %s... 第一行创建了一个字符串 "%s,%s,%s"... 与 list_of_ids 长度相同。跨度>
  • 这也可以在 sqlite 中使用吗?因为我刚试过,它似乎指出了语法错误。
  • @Sohaib 在 sqlite 中替换字符是 ? 而不是 %s 所以如果你将第一行更改为 format_strings = ','.join('?' * len(list_of_ids)) 它将起作用。
  • 根据这样的语句与不同数量的参数一起使用的频率,我喜欢对参数号进行分组并执行多个以确保数据库只看到有限数量的变体(对于 sql 缓存) .this 也有助于避免过多的争论。
  • @kdas 在您的情况下,您不希望 % format_strings 部分更改查询中的其他 %s 占位符,仅更改 IN (%s) 占位符-实现此目的的方法是加倍所有% 字符,您要替换的字符除外:query = ("select distinct cln from vcf_commits where branch like %%s and repository like %%s and filename in (%s) and author not like %%s" % format_strings,); cursor.execute(query, (branch, repository) + tuple(fname_list) + (invalid_author,))
【解决方案2】:
list_of_ids = [ 1, 2, 3]
query = "select * from table where x in %s" % str(tuple(list_of_ids))
print query

如果您不希望关心必须传递参数以完成查询字符串的方法并且只想调用cursror.execute(query),这可能适用于某些用例。

另一种可能是:

"select * from table where x in (%s)" % ', '.join(str(id) for id in list_of_ids)

【讨论】:

    【解决方案3】:

    很简单:只需使用下面的格式

    rules_id = ["9","10"]

    sql1 = "SELECT * FROM admission_rules_staff WHERE id in("+", ".join(map(str, rules_id))+")"

    ", ".join(map(str, rules_id))

    【讨论】:

    • 它在哪里做 sql 引用,这不是使用文字而不是绑定变量吗?
    • 不需要,它工作正常。您可以测试因为元组形成直接转换为带有第一个大括号 ("9", "10") 的字符串。其中调整sql格式。所以你不需要其他的形成是 sql adjastable
    • 如果rules_id 包含"); DROP TABLES Bobby --
    • 已经告诉过“内爆列表”而不是“)......所以在查询之前你需要验证
    • 或使用:sql1 = "SELECT * FROM admission_rules_staff WHERE id in("+", ".join(map(str, rules_id))+")"
    【解决方案4】:

    如果你使用Django 2.0 or 2.1Python 3.6,这是正确的方式:

    from django.db import connection
    RESULT_COLS = ['col1', 'col2', 'col3']
    RESULT_COLS_STR = ', '.join(['a.'+'`'+i+'`' for i in RESULT_COLS])
    QUERY_INDEX = RESULT_COLS[0]
    
    TABLE_NAME = 'test'
    search_value = ['ab', 'cd', 'ef']  # <-- a list
    query = (
        f'SELECT DISTINCT {RESULT_COLS_STR} FROM {TABLE_NAME} a '
        f'WHERE a.`{RESULT_COLS[0]}` IN %s '
        f'ORDER BY a.`{RESULT_COLS[0]}`;'
    )  # <- 'SELECT DISTINCT a.`col1`, a.`col2`, a.`col3` FROM test a WHERE a.`col1` IN %s ORDER BY a.`col1`;'
    with connection.cursor() as cursor:
        cursor.execute(query, params=[search_value])  # params is a list with a list as its element
    

    参考:https://stackoverflow.com/a/23891759/2803344 https://docs.djangoproject.com/en/2.1/topics/db/sql/#passing-parameters-into-raw

    【讨论】:

      【解决方案5】:

      虽然这个问题已经很老了,但我认为最好留下回复,以防其他人正在寻找我想要的东西

      当我们有很多参数或者我们想使用命名参数时,接受的答案会变得混乱

      经过一番尝试

      ids = [5, 3, ...]  # list of ids
      cursor.execute('''
      SELECT 
      ...
      WHERE
        id IN %(ids)s
        AND created_at > %(start_dt)s
      ''', {
        'ids': tuple(ids), 'start_dt': '2019-10-31 00:00:00'
      })
      

      python2.7pymysql==0.7.11测试

      【讨论】:

      • 这不适用于 python 3 和 mysql-connector-python 8.0.21。返回错误“Python tuple cannot be convert to MySQL type”。
      【解决方案6】:

      另一个使用列表理解的简单解决方案:

      # creating a new list of strings and convert to tuple
      sql_list = tuple([ key.encode("UTF-8") for key in list_of_ids ])
      
      # replace "{}" with "('id1','id2',...'idlast')"
      cursor.execute("DELETE FROM foo.bar WHERE baz IN {}".format(sql_list))
      

      【讨论】:

        【解决方案7】:

        虽然这个问题已经很老了。如果它可以帮助某人,我将分享我的解决方案。

        list_to_check = ['A', 'B'] cursor.execute("DELETE FROM foo.bar WHERE baz IN ({})".format(str(list_to_check)[1:-1])

        Python=3.6测试

        【讨论】:

        • 我担心这个解决方案容易受到 SQL 注入攻击,因为提供的 list_to_check 没有被 SQL 转义。这就是为什么将值作为参数传递给execute 更合适的原因。请谨慎使用此解决方案(也就是说,输入 ID 不会作为参数从应用程序外部接收),因为有人可能会使用它来攻击您的系统并访问您的数据库。
        【解决方案8】:

        这似乎仍然是 2021 年 Python3 的问题,正如 Rubms 对 markk 答案的评论中所指出的那样。

        在mysql连接器包的“cursor.py”中的“_process_params_dict”方法中添加大约9行代码来处理元组解决了我的问题:

        def _process_params_dict(self, params):
            """Process query parameters given as dictionary"""
            try:
                to_mysql = self._connection.converter.to_mysql
                escape = self._connection.converter.escape
                quote = self._connection.converter.quote
                res = {}
                for key, value in list(params.items()):
                    if type(value) is tuple: ### BEGIN MY ADDITIONS
                        res[key.encode()] = b''
                        for subvalue in value:
                            conv = subvalue
                            conv = to_mysql(conv)
                            conv = escape(conv)
                            conv = quote(conv)
                            res[key.encode()] = res[key.encode()] + b',' + conv if len(res[key.encode()]) else conv
                    else: ### END MY ADDITIONS
                        conv = value
                        conv = to_mysql(conv)
                        conv = escape(conv)
                        conv = quote(conv)
                        res[key.encode()] = conv
            except Exception as err:
                raise errors.ProgrammingError(
                    "Failed processing pyformat-parameters; %s" % err)
            else:
                return res
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2022-01-02
          • 1970-01-01
          • 2020-07-15
          • 1970-01-01
          • 2016-05-15
          相关资源
          最近更新 更多