JDBC, unfortunately, does not seem to allow you to make a table name a related variable inside statements. (This has its own reasons for this).
Thus, you cannot write or perform such a function:
connection.prepareStatement("SELECT * FROM ? where id=?", "TUSERS", 123);
And bind TUSER to the statement table name.
Therefore, your only safe way forward is to verify user input. However, the safest way is not to check it and allow the user to enter data through the database, because from a security point of view, you can always count on the user being smarter than your check. Never trust a dynamic, user-created String, merged inside your statement.
So what is a safe validation pattern?
Sample 1: Secure Query Advanced Add-Ins
1) Create all your virtual 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 necessary, this creation itself can be made dynamic, with the expression select * from ALL_TABLES; . ALL_TABLES will return all the tables that the SQL user has access to, and you can also get the table name and schema name from this.
2) Select the operator inside the map
String unsafeUserContent = ... String safeStatement = statementByTableName.get(usafeUserContent); conn.prepareStatement(safeStatement, name);
See how the variable unsafeUserContent never reaches the database.
3) Make some kind of policy or unit test that checks that all your statementByTableName are valid for your schemas for its future evolutions and that there is no table.
Sample 2: double check
You can: 1) verify that user input is indeed the name of the table using a query without injections (I type pseudo sql code here, you will have to adapt it to make it work, because I don't have an Oracle instance to check if it works this is):
select * FROM (select schema_name || '.' || table_name as fullName FROM all_tables) WHERE fullName = ?
And bind your full name as a prepared statement variable here. If you have a result, then this is a valid table name. You can then use this result to create a secure query.
Sample 3
This is a kind of mixture between 1 and 2. You create a table with the name, for example, "TABLES_ALLOWED_FOR_DELETION", and you statically populate it with all the tables suitable for deletion.
Then you take the verification step
conn.prepareStatement(SELECT safe_table_name FROM TABLES_ALLOWED_FOR_DELETION WHERE table_name = ?", unsafeDynamicString);
If this is the result, you execute the name safe_table_name. For added security, this table should not be writable by the standard user of the application.
I somehow feel that the first sample is better.