Python / SQLite3在WHERE-Clause中转义

时间:2014-04-28 13:34:47

标签: python sql sqlite escaping sql-like

我应该如何在Python中为SQLite3进行真正的转义?

如果我谷歌(或搜索stackoverflow),那么有很多问题,每次响应都是这样的:

dbcursor.execute("SELECT * FROM `foo` WHERE `bar` like ?", ["foobar"])

这有助于防止SQL注入,如果我只是与" ="进行竞争就足够了。但它当然不会对通配符进行条纹化处理。

所以如果我这样做

cursor.execute(u"UPDATE `cookies` set `count`=? WHERE `nickname` ilike ?", (cookies, name))
某些用户可以提供"%"对于昵称,将用一行替换所有cookie条目。 我自己可以过滤它(呃......我可能会忘记其中一个鲜为人知的通配符),我可以使用小写字母和昵称替换" ilike"与" =",但我真正想做的事情就是:

foo = sqlescape(nick)+"%"
cursor.execute(u"UPDATE `cookies` set `count`=? WHERE `nickname` ilike ?", (cookies, foo))

4 个答案:

答案 0 :(得分:4)

?参数旨在避免SQL字符串(以及其他有问题的数据类型,如浮点数和blob)的格式化问题。

LIKE / GLOB通配符在不同的层面上工作;它们总是字符串本身的一部分。 SQL允许转义它们,但是没有默认的转义字符;你必须选择一些ESCAPE clause

escaped_foo = my_like_escape(foo, "\\")
c.execute("UPDATE cookies SET count = ? WHERE nickname LIKE ? ESCAPE '\',
          (cookies, escaped_foo))

(并且您必须为my_like_escape%(LIKE)或_*(GLOB)编写自己的?函数。)< / p>

答案 1 :(得分:1)

您通过使用参数化查询避免了直接代码注入。现在,您似乎正在尝试与用户提供的数据进行模式匹配,但您希望将用户提供的数据部分视为文字数据(因此没有通配符)。您有几种选择:

  1. 只需过滤输入。 SQLite的LIKEunderstands %_作为通配符,因此很难弄错。只需确保始终过滤输入。 (我的首选方法:在构造查询之前过滤,而不是在读取用户输入时过滤)。

  2. 通常,“白名单”方法被认为比删除特定危险字符更安全,更容易。也就是说,不要删除字符串中的%_(以及任何“鲜为人知的通配符”,如您所说),扫描您的字符串并仅保留您想要的字符。例如,如果你的“昵称”可以包含ASCII字母,数字,“ - ”和“。”,它可以像这样消毒:

    name = re.sub(r"[^A-Za-z\d.-]", "", name)
    

    此解决方案特定于您要匹配的特定字段,并且适用于关键字段和其他标识符。如果我必须使用RLIKE进行搜索,我肯定会这样做,它接受完整的正则表达式,因此需要注意更多的字符。

  3. 如果您不希望用户能够提供通配符,为什么还要在查询中使用LIKE?如果查询的输入来自代码中的许多位置(或者您甚至可能正在编写库),那么如果您完全避免LIKE,则可以使查询更安全:

    • 这是case insensitive matching

      SELECT * FROM ... WHERE name = 'someone' COLLATE NOCASE
      
    • 在您的示例中,您使用前缀匹配(“sqlescape(nick)+"%"”)。以下是使用精确搜索的方法:

      size = len(nick)
      cursor.execute(u"UPDATE `cookies` set `count`=? WHERE substr(`nickname`, 1, ?) = ?", 
                      (cookies, size, nick))
      

答案 2 :(得分:0)

嗯,通常你只想用正常的'='替换'ilike',不能以任何特殊的方式解释'%'。转义(有效地将坏图案列入黑名单)容易出错,例如即使你设法逃脱你使用的sqlLite版本中的所有已知模式,任何未来的升级都会让你处于危险之中等等。

我不清楚为什么你想根据用户名的模糊匹配大量更新cookie。

如果您真的想这样做,我首选的方法是首先选择列表并决定在应用程序级别更新哪些内容以保持最高控制级别。

答案 3 :(得分:-1)

使用字符串format进行此操作有几种非常有趣的方法。

来自Python的Documentation

  

内置的strunicode类可以通过str.format()方法执行复杂的变量替换和值格式化

s = "string"
c = "Cool"
print "This is a {0}. {1}, huh?".format(s,c)
#=> This is a string. Cool, huh? 

你可以用字符串格式化做的其他漂亮技巧:

"First, thou shalt count to {0}".format(3) # References first positional argument
"Bring me a {}".format("shrubbery!")       # Implicitly references the first positional argument
"From {} to {}".format('Africa','Mercia')      # Same as "From {0} to {1}"
"My quest is {name}"                       # References keyword argument 'name'
"Weight in tons {0.weight}"                # 'weight' attribute of first positional arg
"Units destroyed: {players[0]}"            # First element of keyword argument 'players'.`