【发布时间】:2009-11-04 23:46:28
【问题描述】:
对于某些 sql 语句我不能使用准备好的语句,例如:
SELECT MAX(AGE) FROM ?
例如,当我想改变表格时。是否有在 Java 中清理 sql 的实用程序?红宝石中有一个。
【问题讨论】:
-
如果表名直接来自用户输入,那么与清理 SQL 相比,您需要担心的问题要大得多(如果不清理,则无需清理)
标签: java sql jdbc sql-injection
对于某些 sql 语句我不能使用准备好的语句,例如:
SELECT MAX(AGE) FROM ?
例如,当我想改变表格时。是否有在 Java 中清理 sql 的实用程序?红宝石中有一个。
【问题讨论】:
标签: java sql jdbc sql-injection
正确,准备好的语句查询参数只能在您将使用单个文字值的地方使用。您不能将参数用于表名、列名、值列表或任何其他 SQL 语法。
因此,您必须将应用程序变量插入 SQL 字符串并适当地引用该字符串。请使用 quoting 来分隔您的表名标识符,并通过加倍来转义引号字符串:
java.sql.DatabaseMetaData md = conn.getMetaData();
String q = md.getIdentifierQuoteString();
String sql = "SELECT MAX(AGE) FROM %s%s%s";
sql = String.format(sql, q, tablename.replaceAll(q, q+q), q);
例如,如果您的表名的字面意思是table"name,而您的RDBMS 标识符引号字符是",那么sql 应该包含如下字符串:
SELECT MAX(AGE) FROM "table""name"
我也同意@ChssPly76 的评论——最好是您的用户输入实际上不是文字表名,而是您的代码映射到表名的符号,然后您将其插入到 SQL 查询中。这让您更加确信不会发生 SQL 注入。
HashMap h = new HashMap<String,String>();
/* user-friendly table name maps to actual, ugly table name */
h.put("accounts", "tbl_accounts123");
userTablename = ... /* user input */
if (h.containsKey(userTablename)) {
tablename = h.get(userTablename);
} else {
throw ... /* Exception that user input is invalid */
}
String sql = "SELECT MAX(AGE) FROM %s";
/* we know the table names are safe because we wrote them */
sql = String.format(sql, tablename);
【讨论】:
不可能。最好的办法是使用String#format()。
String sql = "SELECT MAX(AGE) FROM %s";
sql = String.format(sql, tablename);
请注意,这并不能避免 SQL 注入风险。如果tablename 是用户/客户端控制的值,则需要使用String#replaceAll() 对其进行清理。
tablename = tablename.replaceAll("[^\\w]", "");
希望这会有所帮助。
[编辑] 我应该补充一点:不要将它用于列值,您可以使用 PreparedStatement 来表示。只需继续以通常的方式对任何列值使用它即可。
[Edit2] 最好不要让用户/客户端能够以它想要的方式输入表名,但最好在 UI 中显示一个包含所有有效表名(您可以通过DatabaseMetaData#getCatalogs() 获取)的下拉列表,以便用户/客户可以选择它。如果选择有效,请不要忘记检查服务器端,因为可能会欺骗请求参数。
【讨论】:
在这种情况下,您可以通过从 DatabaseMetaData 获取表列表来根据可用表列表验证表名。实际上,在使用类似 String.format 来构建完整的 sql 语句之前,使用正则表达式从字符串中去除空格,也许还有一些 sql 保留字“;”等可能会更容易。
您不能使用preparedStatement 的原因是它可能将表名包含在's 中并像字符串一样将其转义。
【讨论】: