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

Java 当语句具有动态表名时,如何防止SQL注入?,java,jdbc,db2,oracle12c,Java,Jdbc,Db2,Oracle12c,我有这样的代码 final PreparedStatement stmt = connection .prepareStatement("delete from " + fullTableName + " where name= ?"); stmt.setString(1, addressName); fullTableName的计算如下: public String getFullTableName(fin

我有这样的代码

   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注入的攻击

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

有谁能告诉我,处理这个问题的可能方法是什么


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

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

因此,您无法编写或实现这种功能:

connection.prepareStatement("SELECT * FROM ? where id=?", "TUSERS", 123);
并将
TUSER
绑定到语句的表名

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

那么什么是安全的验证模式呢

模式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= ?");
查看
unsafeUserContent
变量如何从不到达数据库

3) 制定某种策略或单元测试,检查所有的
statementByTableName
语句是否对模式有效,以备将来的发展,并且没有表丢失

模式2:双重检查 您可以1)使用无注入查询验证用户输入是否确实是一个表名(我在这里键入伪sql代码,您必须调整它以使其工作,因为我没有Oracle实例来实际检查它的工作):

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

模式3 这是一种介于1和2之间的混合。 您创建了一个名为“TABLES_ALLOWED_FOR_deletation”的表,并用适合删除的所有表静态填充该表

然后将验证步骤设置为

conn.prepareStatement(SELECT safe_table_name FROM TABLES_ALLOWED_FOR_DELETION WHERE table_name = ?", unsafeDynamicString);
如果有结果,则执行safe_table_name。为了安全起见,此表不应由标准应用程序用户写入


我觉得第一种模式更好。

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

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库

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

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

Set<String> 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!
    }
Set validTables=..//你自己准备这一套
if(validTables.contains(fullTableName))
{
最终准备语句stmt=连接
.prepareStatement(“从”+fullTableName中删除
+“其中名称=?”;
//等等
}否则{
//哦,你这个讨厌的哈克人!
}

如果您控制xml文件(即,它不是用户可以更改/提供的内容),您应该可以。您不能在
PreparedStatement
中绑定表名,您必须接受xml文件有效(您可能会验证表名),或者硬编码所有有效表并以这种方式解析。@ElliottFrisch,是的,这就是我最后计划做的(在将表名附加到查询之前验证表名)。但我不能100%确定这是否是防止SQL注入的最佳方法。请确保更安全:为所有表预构建完整语句列表(“从表_1中删除,其中名称=?”、“从表_2中删除,其中名称=?”,等等。将它们放在(哈希)中映射。在连接字符串之前,不要验证表名是否正确。相反,请根据用户生成的条目选择一个预生成的固有安全语句。这样,用户生成的任何语句都不会连接到您的语句。只有开发人员预生成的查询会命中DB。@GPI,通过验证表名,我的意思是s检查字符串是否仅包含字母数字字符(因为我的项目中的表名仅包含字母数字字符)在这里,我不能确定确切的表名。是的。但是,至少在Oracle以外的一些数据库上,表名不可能有带变音符号的吗?美元符号或破折号什么的?西里尔文/中文/日文/什么的?第一个问题,这会改变这个答案的有效性上下文。@GPI,这是大多数泛型的答案如果我正确理解了你的答案,TAB2DEL不是更好地命名为“TABLES\u ALLOWED\u to\u be\u delete”吗?这是一个静态表,对吗?如果不是,我真的不知道从应用程序代码调用过程的方式。这是一个很好的解释,解决了我的问题,但突然意识到我们可以在一段时间后迁移到DB2,所以代码应该与DB2和Oracle数据库兼容(如果我们可以使其独立于数据库,效率会更高)。如果您可以添加任何进一步的注释,它将帮助我找到解决方案。第一种模式不依赖于数据库。第二种模式依赖于数据库,因为
所有表都是特定于Oracle的。我想您不会
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
Set<String> 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!
    }