I was wondering if using PreparedStatement.setString() was a good idea (possible, sensible?) to dynamically build a query.
For example :
sql code:
SELECT * FROM table1 WHERE table1.category = ? ?
java code:
ps.setString(1,"category1");
ps.setString(2,"AND table1.category = 'category2'");
Also, would it be possible to do something like:
ps.setString(1,"category1");
ps.setString(2," AND table1.category = ?");
ps.setString(3,"category2");
Best regards
Unfortunately, NO.
PreparedStatements are strictly for values only. Table Names and Column Names (as well as conditions in your example) are not allowed. So the best way to do is to concatenate it with the string.
String others = " AND table1.category = ?";
String query = "SELECT * FROM table1 WHERE table1.category = ? " + others;
java code:
ps.setString(1,"category1");
ps.setString(2,"category2");
Whatever you put inside setString will go within single quotes ' ' and will not be interpreted as a query.
Related
I was looking at the reference here. We can do this -
String orderSql = "select * from order where id = ?";
jdbcTemplate.query(orderSql, new BeanPropertyRowMapper<>(Order.class), orderId);
This reads from the database and can directly deserialise into the Order object. This is well and good.
What I want to do is -
String updateSql = "update order ? where id = ?";
jdbcTemplate.save(updateSql, new BeanPropertyRowMapper<>(Order.class), order, orderId);
Is there a way of doing this?
No, it's not possible. JdbcTemplate comes with the ability to map an object using query as you've said without having to define columns/attributes, but there is no equivalent usage for update. You will have to pass in your SQL and the relevant parameters. As someone has mentioned if you really want to do this you could consider using hibernate.
Use this query for update :-
String updateSql = "update <table_name> set order= ? where id = ?";
jdbcTemplate.update(updateSql, new Object[]{order, orderId});
It will return affected row update count.
No ! You can only do this !
jdbcTemplate.update("update ordertable set order = ? where id = ?", order, orderId);
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).
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.
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.
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
When I build query I use parameters like:
WAR_REPORT_CALLING_NUMBER = " and cal.calling_number = ? ";
and from code I insert parametr instead ? But now I need insert parameter into this string:
WAR_REPORT_MSG1 = " AND regexp_like(pe.answer_recived,'^[^0-9]*[?][^0-9]*$') ";
and this structure nor work. How do it?
You can assign the regular expression to the parameter and change the condition with :
regexp_like(pe.answer_recived,?) ";
Suppose that I have a query, something like SELECT * FROM my_table where name = 'Debbie O'Brian'. As you may know in SQL Server we just need to put another ' to make it ignore single quotes. Here is what I like to do automatically.
Briefly, I need to replace all occurrences of ' with '' between two enclosing '.
Here is what I have so far:
String text = "SELECT * FROM my_table where name = 'Debbie O'Brian'";
String[] splitArray = text.split(" '");
for (int i = 0; i < splitArray.length; i++) {
String str = splitArray[i];
if (str.endsWith("'"))
str = str + " ";
// replace all single quotes
str = str.replace("'", "''");
// revert last replacement
str = str.replace("'' ", "' ");
splitArray[i] = str;
}
StringBuilder builder = new StringBuilder();
for (int i = 0; i < splitArray.length; i++) {
builder.append(splitArray[i]).append(" '");
}
System.out.println(builder.substring(0, builder.length() - 2).toString());
The problem is that I am relying on existence of a white space before starting '. My question is that how can I do this without this pre-assumption?
Any help (even suggestion of an algorithm) is much appreciated. Please mention that there might be more than one single quoted string in query.
EDIT: I am using Hibernate to execute native SQL and getting the whole SQL query as input.
The best way to handle single quotes and other special characters in an SQL query is by using Parametrized Queries. It is also more secure. I would recommend doing a search on 'SQL Injection'. That will teach you how to protect against and handle the single quotes in your query.
If you use JDBC use
PreparedStatement ps = conn.prepareStatement("SELECT * FROM my_table where name = ?")
ps.setString(1, name);
As you are using Hibernate, similar to what #B.O.B said use parameterised queries:
String queryString = "SELECT * FROM my_table where name = :name";
Query query = getSession().createSQLQuery(queryString);
query.setString("name", name);
OK, as I mentioned above, my problem was not SQL injection. What I was after, at this point seems to be impossible unless I implement a SQL parser which is obviously too much for the requirement. So I am going to stick with my pre-assumption and give up trying to fix the wrong input as two people mentioned in comments.
Thank everybody for your answers.
I have a query like this..
String query = "UPDATE tbl_customer_policies SET "+
"start_date = ?," +
"next_pay_date = ?,"+
"maturity_date = ?, " +
"modified_at = CURRENT_TIMESTAMP,"+
"modifier = ?, status = ? " +
"WHERE id = ?";
Now in place of the place-holder for start_date I want to pass a string like SYSDATE.
What I am doing now is setting that string in a variable called String startDate = "SYSDATE" and binding it to that place-holder. But I guess it does not seem to work. I get this exception
ORA-01858: a non-numeric character was found where a numeric was expected
I have to pass Oracle functions like that. How to achieve that?
If it will always be sysdate you don't need to parameterize it.
Set it directly in the query like this:
String query = "UPDATE tbl_customer_policies SET "+
"start_date = sysdate," + // or "start_date = ADD_MONTHS(SYSDATE, 12),"
"next_pay_date = ?,"+
"maturity_date = ?, " +
"modified_at = CURRENT_TIMESTAMP,"+
"modifier = ?, status = ? " +
"WHERE id = ?";
And set all the others parameters like you did before.
Use function TO_DATE (docs)
for example
"UPDATE tbl_customer_policies SET "+
"start_date = TO_DATE(?, 'DD-MON-RR')," +
"next_pay_date = TO_DATE(?, 'DD-MON-RR'),"+
and don't forget pass parameters in format like '27-OCT-98' in this case.
P.S.
I misunderstood the question so try to correct my answer.
The described problem is for limitations of PreparedStatement Java class.
PreparedStatement object is used for storing a reference to some precompiled (and optimized) SQL statement.
So you have to supply only values of parameters for filling them.
Functions must be evaluated before using their results and they aren't able to be placed there.
I guess you can use a workaround with Oracle Structured Types.
You can pass them as reference types with PreparedStatement's setRef() method.
Using functions can be implemented with a wrapper in constructor of object.
I didn't try it but it seems to me it is a possible solution.