如何在不使用预准备语句的情况下清理SQL

mr.*_*ffo 15 java sql sql-injection jdbc

对于某些sql语句,我不能使用准备好的语句,例如:

SELECT MAX(AGE) FROM ?
Run Code Online (Sandbox Code Playgroud)

例如,当我想改变表格时.是否有一个实用程序可以在Java中清理sql?红宝石中有一个.

Bil*_*win 17

没错,准备好的语句查询参数只能在你使用单个文字值的地方使用.您不能将参数用于表名,列名,值列表或任何其他SQL语法.

因此,您必须将应用程序变量插入到SQL字符串中并适当地引用字符串.使用引号来分隔您的表名称标识符,并通过加倍来转义引号字符串:

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);
Run Code Online (Sandbox Code Playgroud)

例如,如果您的表名是字面意思table"name,并且您的RDBMS标识符引用字符是",那么sql应该包含如下字符串:

SELECT MAX(AGE) FROM "table""name"
Run Code Online (Sandbox Code Playgroud)

我也同意@ 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); 
Run Code Online (Sandbox Code Playgroud)


Bal*_*usC 2

不可能。你能做的最好的事情就是使用String#format().

String sql = "SELECT MAX(AGE) FROM %s";
sql = String.format(sql, tablename);
Run Code Online (Sandbox Code Playgroud)

请注意,这并不能避免 SQL 注入风险。如果tablename是用户/客户端控制的值,您需要使用 对其进行清理String#replaceAll()

tablename = tablename.replaceAll("[^\\w]", "");
Run Code Online (Sandbox Code Playgroud)

希望这可以帮助。

[编辑] 我应该添加:不要将其用于可以使用的列值PreparedStatement。只需继续以通常的方式对任何列值使用它即可。

[Edit2] 最好不要让用户/客户端能够按照自己想要的方式输入表名,但最好DatabaseMetaData#getCatalogs()在 UI 中呈现一个包含所有有效表名(您可以通过 获取)的下拉列表,以便用户/客户端可以选择它。不要忘记检查服务器端的选择是否有效,因为可能会欺骗请求参数。