不幸的是,JDBC 不允许您将表名作为语句中的绑定变量。(这是有其原因的)。
所以你无法写作,或实现这种功能:
connection.prepareStatement("SELECT * FROM ? where id=?", "TUSERS", 123);
并且已经绑定到语句的表名中。TUSER
因此,您唯一安全的方法是验证用户输入。但是,最安全的方法不是验证它并允许用户输入通过数据库,因为从安全的角度来看,您始终可以指望用户比您的验证更聪明。永远不要信任用户生成的动态 String,该字符串在语句中串联。
那么什么是安全的验证模式呢?
模式 1:预构建安全查询
1)在代码中一劳永逸地创建所有有效语句。
Map<String, String> statementByTableName = new HashMap<>();
statementByTableName.put("table_1", "DELETE FROM table_1 where name= ?");
statementByTableName.put("table_2", "DELETE FROM table_2 where name= ?");
如果需要,这种创建本身可以通过声明来动态化。 将返回 SQL 用户有权访问的所有表,您还可以从中获取表名和架构名。select * from ALL_TABLES;
ALL_TABLES
2) 选择映射内的语句
String unsafeUserContent = ...
String safeStatement = statementByTableName.get(usafeUserContent);
conn.prepareStatement(safeStatement, name);
了解变量如何永远不会到达数据库。unsafeUserContent
3)制定某种策略或单元测试,检查所有你对你的模式都是有效的,以便将来演变它,并且没有表丢失。statementByTableName
模式 2:双重检查
你可以1)使用注入免费查询来验证用户输入确实是一个表名(我在这里输入伪sql代码,你必须调整它才能使它工作,因为我没有Oracle实例来实际检查它是否有效):
select * FROM
(select schema_name || '.' || table_name as fullName FROM all_tables)
WHERE fullName = ?
并在此处将您的 fullName 绑定为预准备语句变量。如果您有结果,则它是有效的表名。然后,可以使用此结果生成安全查询。
模式 3
这是1和2之间的混合。创建一个名为“TABLES_ALLOWED_FOR_DELETION”的表,然后用适合删除的所有表静态填充该表。
然后,您将验证步骤设为
conn.prepareStatement(SELECT safe_table_name FROM TABLES_ALLOWED_FOR_DELETION WHERE table_name = ?", unsafeDynamicString);
如果出现结果,则执行safe_table_name。为了提高安全性,标准应用程序用户不应写入此表。
不知何故,我觉得第一种模式更好。