当语句具有动态表名时,如何防止SQL注入?

我有这样的代码。

final PreparedStatement stmt = connection .prepareStatement("delete from " + fullTableName + " where name= ?"); stmt.setString(1, addressName); 

fullTableName计算类似于:

  public String getFullTableName(final String table) { if (this.schemaDB != null) { return this.schemaDB + "." + table; } return table; } 

这里schemaDB是环境的名称(可以随时间更改), table是表名(将被修复)。

schemaDB值来自XML文件,这使得查询容易受到SQL注入的攻击。

查询:我不确定如何将表名用作预准备语句(如本示例中使用的name ),这是针对SQL注入的100%安全措施。

任何人都可以建议我,可能有什么方法可以解决这个问题?

注意:我们将来可以迁移到DB2,因此该解决方案应该与Oracle和DB2兼容(如果可能的话,与数据库无关)。

遗憾的是,JDBC不允许您将表名作为语句中的绑定变量。 (这有其原因)。

所以你不能写,或实现这种function:

 connection.prepareStatement("SELECT * FROM ? where id=?", "TUSERS", 123); 

并且将TUSER绑定到语句的表名。

因此,您唯一安全的方法是validation用户输入。 但最安全的方法不是validation它并允许用户输入通过数据库,因为从安全的角度来看,您总是可以指望用户比validation更聪明。 永远不要相信在您的语句中连接的动态的,用户生成的String。

那么什么是安全的validation模式?

模式1:预建安全查询

1)在代码中一劳永逸地创建所有有效语句。

 Map statementByTableName = new HashMap<>(); statementByTableName.put("table_1", "DELETE FROM table_1 where name= ?"); statementByTableName.put("table_2", "DELETE FROM table_2 where name= ?"); 

如果需要,可以使用select * from ALL_TABLES;将此创建本身设置为动态select * from ALL_TABLES; 声明。 ALL_TABLES将返回SQL用户有权访问的所有表,您还可以从中获取表名和模式名称。

2)选择地图内的语句

 String unsafeUserContent = ... String safeStatement = statementByTableName.get(usafeUserContent); conn.prepareStatement(safeStatement, name); 

了解unsafeUserContent变量如何永远不会到达DB。

3)制定某种策略或unit testing,检查所有statementByTableName是否对您的模式有效,以便将来进行演变,并且不会丢失任何表。

模式2:仔细检查

你可以1)使用免注入查询validation用户输入确实是一个表名(我在这里输入伪sql代码,你必须调整它才能使它工作,因为我没有Oracle实例来实际检查有用) :

 select * FROM (select schema_name || '.' || table_name as fullName FROM all_tables) WHERE fullName = ? 

并在此处将您的fullName绑定为预准备语句变量。 如果您有结果,那么它是一个有效的表名。 然后,您可以使用此结果来构建安全查询。

模式3

它是1到2之间的混合。您创建一个名为“TABLES_ALLOWED_FOR_DELETION”的表,并使用适合删除的所有表静态填充它。

然后进行validation步骤

 conn.prepareStatement(SELECT safe_table_name FROM TABLES_ALLOWED_FOR_DELETION WHERE table_name = ?", unsafeDynamicString); 

如果有结果,则执行safe_table_name。 为了更加安全,标准应用程序用户不应该写入此表。

我不知何故觉得第一种模式更好。

 create table MYTAB(n number); insert into MYTAB values(10); commit; select * from mytab; N 10 create table TABS2DEL(tname varchar2(32)); insert into TABS2DEL values('MYTAB'); commit; select * from TABS2DEL; TNAME MYTAB create or replace procedure deltab(v in varchar2) is LvSQL varchar2(32767); LvChk number; begin LvChk := 0; begin select count(1) into LvChk from TABS2DEL where tname = v; if LvChk = 0 then raise_application_error(-20001, 'Input table name '||v||' is not a valid table name'); end if; exception when others then raise; end; LvSQL := 'delete from '||v||' where n = 10'; execute immediate LvSQL; commit; end deltab; begin deltab('MYTAB'); end; select * from mytab; 

没有找到行

 begin deltab('InvalidTableName'); end; ORA-20001: Input table name InvalidTableName is not a valid table name ORA-06512: at "SQL_PHOYNSAMOMWLFRCCFWUMTBQWC.DELTAB", line 21 ORA-06512: at "SQL_PHOYNSAMOMWLFRCCFWUMTBQWC.DELTAB", line 16 ORA-06512: at line 2 ORA-06512: at "SYS.DBMS_SQL", line 1721 

您可以使用正则表达式检查表名来避免攻击:

 if (fullTableName.matches("[_a-zA-Z0-9\\.]+")) { final PreparedStatement stmt = connection .prepareStatement("delete from " + fullTableName + " where name= ?"); stmt.setString(1, addressName); } 

使用这样一组受限制的字符注入SQL是不可能的。

此外,我们可以从表名中转义任何引号,并将其安全地添加到我们的查询中:

 fullTableName = StringEscapeUtils.escapeSql(fullTableName); final PreparedStatement stmt = connection .prepareStatement("delete from " + fullTableName + " where name= ?"); stmt.setString(1, addressName); 

StringEscapeUtils附带Apache的commons-lang库。

我认为最好的方法是创建一组可能的表名,并在创建查询之前检查此集中是否存在。

 Set validTables=.... // prepare this set yourself if(validTables.contains(fullTableName)) { final PreparedStatement stmt = connection .prepareStatement("delete from " + fullTableName + " where name= ?"); //and so on }else{ // ooooh you nasty haker! }