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

久未见 提交于 2019-12-30 02:20:08

问题


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:

 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).

Value for schemaDB is coming from an XML file which makes the query vulnerable to SQL injection.

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.

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).


回答1:


JDBC, sort of unfortunately, does not allow you to make the table name a bound variable inside statements. (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.

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

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; 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.

2) Select the statement inside the map

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

See how the unsafeUserContent variable never reaches the DB.

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.

Pattern 2 : double check

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) :

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

And bind your fullName as a prepared statement variable here. 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

It's sort of a mix between 1 and 2. You create a table that is named, e.g., "TABLES_ALLOWED_FOR_DELETION", and you statically populate it with all tables that are fit 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. For extra safety, this table should not be writable by the standard application user.

I somehow feel the first pattern is better.




回答2:


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



回答3:


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.

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.




回答4:


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


来源:https://stackoverflow.com/questions/49171540/how-to-prevent-sql-injection-when-the-statement-has-a-dynamic-table-name

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!