在JDBC查询中安全使用表名作为参数的方法

6
什么是将表名作为参数放入SQL查询的安全方法?您不能使用PreparedStatement将表名作为参数放入。使用Statement连接字符串以执行具有动态表名的查询是可能的,但由于SQL注入的风险,不建议这样做。最佳方法是什么?

2
最好的方法是修复你的设计,这样你就不必动态地放置表名。 - Kayaman
因为不安全的原因是你失去了对将要在数据库上执行的内容的控制。即使你进行了强大的检查,但你的设计仍然薄弱,这将在性能、稳定性和安全性方面给你带来代价。那么,你为什么要这样做呢? - Mario Santini
我想,如果你限制输入只包含字母、数字和下划线,那么你应该是安全的(至少在注入方面是这样的,但仍然很奇怪,有人可以访问任何表)。 - Thilo
查询的其余部分怎么样?列名之类的呢?你不需要让它们也非常动态吗? - Thilo
目标数据库是什么? - Nicolas Filotto
显示剩余2条评论
3个回答

6
最好的方法是:
  1. 将表名放在用于分隔表名的字符之间,这些字符在不同的数据库中有所不同。
  2. 并相应地转义提供的表名,以使 SQL 注入不再可能。

例如,在 MySQL 中,表名的分隔符是反引号字符,我们通过简单地将其加倍来转义它。

如果您的查询是 SELECT foo from bar,您可以将查询重写为:

 String query = String.format("SELECT foo from `%s`", tableName.replace("`", "``"));

这样做可以注入表名,而不必担心被注入恶意代码的风险。


1
如果tableName的值是:"table'; GRANT ......; PRINT '......",为了使其起作用,还应检查tablename变量是否包含除字母数字以外的其他字符... - Martin
1
不行,这样做不行。你会得到SELECT foo from "table'; GRANT ......; PRINT '......", 它会把反引号中的内容作为表名使用。 - Nicolas Filotto
1
@Bakus123 不错的问题,我并不了解太多关于 SQLite 的知识,但是从 文档 中看到,你可以使用双引号或重音符号(为兼容 MySQL)来引用标识符,因此你可以使用相同的方法(加倍以转义)在两者中都适用。 - Nicolas Filotto
@NicolasFilotto,谢谢,但方括号怎么办?在文档中,他们写道它还可以用于引用标识符。 - Bakus123
@Bakus123,确实也可以使用单引号,但如果我是你,我会使用双引号,因为在SQLite中似乎更标准。 - Nicolas Filotto
@NicolasFilotto,那这样就可以了 - String.format("SELECT foo from \"%s\"", tableName.replace("\"", "\"\"")); - Bakus123

2
我会尝试解决设计问题,这样您就不必动态设置表名。如果不可能,我会采用一种设计,在其中管理可用表格的列表,用户从中选择一个BY ID,这样您可以从所选ID检索实际表名,并用其替换表名占位符,避免任何SQL注入在表名替换中的机会。

我认为这是一个设计问题。这是一个后台运行的应用程序。普通用户无法选择值,它们由管理员配置。我喜欢配置ID和TABLENAME值对的想法,并在执行之前检查真实表是否存在。 - Martin

1
只允许动态JDBC查询中使用实际参数背后有一个合理的原因:参数可以来自外部并且可以取任何值,而表名和列名是静态的。当不同的表具有几乎相同的结构且由于DRY原则你不想多次重复相同的查询(仅更改表格或列名)时,可以对表格或列名进行参数化,但在这种情况下,程序员完全控制将被替换的名称,并且应仔细测试其中没有任何拼写错误。在此处不存在SQL注入的可能性,可以安全地替换查询字符串中的表名。这对于在Internet上公开的Web应用程序来说是非常不同的,因为查询将使用在表单字段中输入的内容,因为在这里任何事情都可能发生,包括分号以终止原始无害的查询并伪造新的有害查询=>如果只是连接字符串而不是正确构建参数化查询,则会发生SQL注入。我无法想象表名或列名可以是由用户在表单字段中键入的字符串的用例,这将是允许将它们参数化的唯一原因。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接