我想查找文本列以用户给定字符串开头的行,例如,SELECT * FROM users WHERE name LIKE 'rob%'
但“rob”是未经验证的用户输入。如果用户写入一个包含特殊模式字符(如“rob_”)的字符串,它将匹配“robert42”和“rob_the_man”。我需要确保字符串在字面上匹配,我该怎么做?我是否需要在应用程序级别处理转义,还是一种更漂亮的方式?
我正在使用 PostgreSQL 9.1 和go-pgsql for Go。
我想查找文本列以用户给定字符串开头的行,例如,SELECT * FROM users WHERE name LIKE 'rob%'
但“rob”是未经验证的用户输入。如果用户写入一个包含特殊模式字符(如“rob_”)的字符串,它将匹配“robert42”和“rob_the_man”。我需要确保字符串在字面上匹配,我该怎么做?我是否需要在应用程序级别处理转义,还是一种更漂亮的方式?
我正在使用 PostgreSQL 9.1 和go-pgsql for Go。
_ 和 % 字符必须被引用才能在 LIKE 语句中进行字面匹配,这是没有办法的。选择是在客户端还是服务器端(通常使用 SQL replace(),见下文)。此外,为了在一般情况下 100% 正确,还有一些事情需要考虑。
默认情况下,在 _ 或 % 之前使用的引号字符是反斜杠 (\),但可以使用紧跟在 LIKE 子句之后的 ESCAPE 子句来更改它。在任何情况下,引号字符都必须在模式中重复两次才能按字面意思作为一个字符进行匹配。
示例:... WHERE field like 'john^%node1^^node2.uucp@%' ESCAPE '^'
将匹配john%node1^node2.uccp@后跟任何内容。
反斜杠的默认选择存在问题:当standard_conforming_strings为 OFF时,它已经用于其他目的(PG 9.1 默认将其设置为 ON,但以前的版本仍在广泛使用,这是需要考虑的一点)。
此外,如果在用户输入注入场景中对 LIKE 通配符的引用是在客户端完成的,那么除了用户输入中已经需要的正常字符串引用之外,它还会出现。
看一下 go-pgsql 示例就知道它使用 $N 样式的占位符来表示变量...所以这里尝试以某种通用的方式编写它:它与 standard_conforming_strings 一起工作,无论是 ON 还是 OFF,使用服务器端替换[%_],另一种引号字符,引号字符的引用,避免sql注入:
db.Query("SELECT * from USERS where name like replace(replace(replace($1,'^','^^'),'%','^%'),'_','^_') ||'%' ESCAPE '^'",
variable_user_input);
要转义like
表达式中要在模式中使用的下划线和百分比,请使用转义字符:
SELECT * FROM users WHERE name LIKE replace(replace(user_input, '_', '\\_'), '%', '\\%');
据我所知,LIKE 运算符唯一的特殊字符是百分比和下划线,这些可以很容易地使用反斜杠手动转义。它不是很漂亮,但它有效。
SELECT * FROM users WHERE name LIKE
regexp_replace('rob', '(%|_)', '\\\1', 'g') || '%';
我觉得奇怪的是 PostgreSQL 没有附带这样的功能。谁希望他们的用户编写自己的模式?
最好的答案是您根本不应该将用户输入插入到您的 sql 中。即使转义 sql 仍然很危险。
下面使用 go 的 db/sql 库说明了一种更安全的方法。用您的 go postgresql 库的等效项替换 Prepare 和 Exec 调用。
// The question mark tells the database server that we will provide
// the LIKE parameter later in the Exec call
sql := "SELECT * FROM users where name LIKE ?"
// no need to escape since this won't be interpolated into the sql string.
value := "%" + user_input
// prepare the completely safe sql string.
stmt, err := db.Prepare(sql)
// Now execute that sql with the values for every occurence of the question mark.
result, err := stmt.Exec(value)
这样做的好处是可以安全地使用用户输入,而不必担心它将 sql 注入到您运行的语句中。您还可以获得为多个查询重用准备好的 sql 的好处,这在某些情况下可能更有效。