简体   繁体   English

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

[英]How to prevent SQL injection when the statement has a dynamic table name?

I am having code something like this.我有这样的代码。

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

Calculation of fullTableName is something like: fullTableName计算类似于:

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

Here schemaDB is the name of the environment(which can be changed over time) and table is the table name(which will be fixed).这里schemaDB是环境的名称(可以随时间更改), table是表名(将被修复)。

Value for schemaDB is coming from an XML file which makes the query vulnerable to SQL injection. schemaDB值来自一个XML文件,这使得查询容易受到 SQL 注入的攻击。

Query: I am not sure how the table name can be used as a prepared statement(like the name used in this example), which is the 100% security measure against SQL injection.查询:我不确定如何将表名用作准备好的语句(如本示例中使用的name ),这是针对 SQL 注入的 100% 安全措施。

Could anyone please suggest me, what could be the possible approach to deal with this?任何人都可以建议我,处理这个问题的可能方法是什么?

Note: We can be migrated to DB2 in future so the solution should compatible with both Oracle and DB2(and if possible database independent).注意:我们将来可以迁移到 DB2,因此该解决方案应该与 Oracle 和 DB2 兼容(如果可能的话,与数据库无关)。

JDBC, sort of unfortunately, does not allow you to make the table name a bound variable inside statements.不幸的是,JDBC 不允许您将表名设为语句内的绑定变量。 (It has its reasons for this). (这样做是有原因的)。

So you can not write, or achieve this kind of functionnality :所以你不能写,或实现这种功能:

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

And have TUSER be bound to the table name of the statement.并将TUSER绑定到语句的表名。

Therefore, your only safe way forward is to validate the user input.因此,您唯一安全的方法是验证用户输入。 The safest way, though, is not to validate it and allow user-input go through the DB, because from a security point of view, you can always count on a user being smarter than your validation.但是,最安全的方法不是验证它并允许用户输入通过数据库,因为从安全角度来看,您总是可以指望用户比您的验证更聪明。 Never trust a dynamic, user generated String, concatenated inside your statement.永远不要相信一个动态的、用户生成的字符串,在你的语句中连接。

So what is a safe validation pattern ?那么什么是安全的验证模式?

Pattern 1 : prebuild safe queries模式 1:预构建安全查询

1) Create all your valid statements once and for all, in code. 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= ?");

If need be, this creation itself can be made dynamic, with a select * from ALL_TABLES;如果需要,可以使用select * from ALL_TABLES;使这个创建本身动态select * from ALL_TABLES; statement.陈述。 ALL_TABLES will return all the tables your SQL user has access to, and you can also get the table name, and schema name from this. ALL_TABLES将返回您的 SQL 用户有权访问的所有表,您还可以从中获取表名和模式名。

2) Select the statement inside the map 2) 选择地图里面的语句

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

See how the unsafeUserContent variable never reaches the DB.查看unsafeUserContent变量如何永远不会到达数据库。

3) Make some kind of policy, or unit test, that checks that all you statementByTableName are valid against your schemas for future evolutions of it, and that no table is missing. 3) 制定某种策略或单元测试,以检查所有statementByTableName是否对您的架构有效,以便将来对其进行演变,并且没有丢失任何表。

Pattern 2 : double check模式 2:双重检查

You can 1) validate that the user input is indeed a table name, using an injection free query (I'm typing pseudo sql code here, you'd have to adapt it to make it work cause I have no Oracle instance to actually check it works) :您可以 1) 验证用户输入确实是一个表名,使用无注入查询(我在这里输入伪 sql 代码,您必须对其进行调整以使其工作,因为我没有实际检查的 Oracle 实例有用) :

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

And bind your fullName as a prepared statement variable here.并在此处将您的 fullName 绑定为准备好的语句变量。 If you have a result, then it is a valid table name.如果你有结果,那么它就是一个有效的表名。 Then you can use this result to build a safe query.然后您可以使用此结果来构建安全查询。

Pattern 3模式3

It's sort of a mix between 1 and 2. You create a table that is named, eg, "TABLES_ALLOWED_FOR_DELETION", and you statically populate it with all tables that are fit for deletion.它有点像 1 和 2 之间的混合。您创建一个名为“TABLES_ALLOWED_FOR_DELETION”的表,然后用所有适合删除的表静态填充它。

Then you make your validation step be然后你让你的验证步骤是

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

If this has a result, then you execute the safe_table_name.如果有结果,则执行 safe_table_name。 For extra safety, this table should not be writable by the standard application user.为了更加安全,标准应用程序用户不应写入该表。

I somehow feel the first pattern is better.我不知何故觉得第一个模式更好。

You can avoid attack by checking your table name using regular expression:您可以通过使用正则表达式检查表名来避免攻击:

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

It's impossible to inject SQL using such a restricted set of characters.使用如此受限制的字符集来注入 SQL 是不可能的。

Also, we can escape any quotes from table name, and safely add it to our query:此外,我们可以转义表名中的任何引号,并将其安全地添加到我们的查询中:

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

StringEscapeUtils comes with Apache's commons-lang library. StringEscapeUtils 带有 Apache 的 commons-lang 库。

I think that the best approach is to create a set of possible table names and check for existance in this set before creating query.我认为最好的方法是创建一组可能的表名并在创建查询之前检查该组中是否存在。

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!
    }
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;

no rows found没有找到行

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM