Dynamic SQL built by concatenating user input is the classic way to let an attacker rewrite your query. The first defense is bind variables, which keep input as a value. But table and column names cannot be bound, so code that builds names from input needs a second defense: DBMS_ASSERT, a package of functions that check names and quote values before they go into SQL text.
Code for This Guide
The main examples are in the examples/dynamic-bulk folder of the Oracle Database 26ai code repository on GitHub, each with its output. They use NIMBUS, the sample schema of a fictional airline, which you can install with the scripts in the setup/nimbus folder.
They come from Oracle Database 26ai SQL and PL/SQL Book.
Injection, Binding, and Checking Names
The input DXB' or '1'='1 is concatenated into a query for one airport's routes, then passed as a bind variable, then a name containing extra text is checked with DBMS_ASSERT.
Example:
declare
v_input varchar2(100) := 'DXB'' or ''1''=''1'; -- a malicious value from a form
v_count number;
begin
-- concatenated: the input becomes part of the SQL text
execute immediate 'select count(*) from routes where origin = ''' || v_input || ''''
into v_count;
dbms_output.put_line('concatenated: ' || v_count || ' routes');
-- bound: the input is only ever a value
execute immediate 'select count(*) from routes where origin = :o'
into v_count using v_input;
dbms_output.put_line('bound: ' || v_count || ' routes');
-- names can't be bound: check them
execute immediate 'select count(*) from ' || dbms_assert.simple_sql_name('routes; drop')
into v_count;
end;
/Output:
concatenated: 50 routes bound: 0 routes declare * ERROR at line 1: ORA-44003: invalid SQL name ORA-06512: at "SYS.DBMS_ASSERT", line 192 ORA-06512: at line 14
Concatenated, the input turns the query into one that matches every route, returning 50. Bound, it is just a value that matches nothing. And SIMPLE_SQL_NAME rejects 'routes; drop' with ORA-44003 before it reaches the SQL text.
The DBMS_ASSERT Functions
| Function | Checks or returns |
|---|---|
| SIMPLE_SQL_NAME | A valid simple name, such as a table or column name |
| QUALIFIED_SQL_NAME | A valid qualified name, such as schema.table |
| SQL_OBJECT_NAME | The name of an existing object |
| SCHEMA_NAME | The name of an existing schema |
| ENQUOTE_NAME | The name in double quotes, uppercased by default |
| ENQUOTE_LITERAL | The value in single quotes |
Example:
select dbms_assert.enquote_literal('Dubai') as literal,
dbms_assert.enquote_name('flights') as quoted_name,
dbms_assert.sql_object_name('NIMBUS.ROUTES') as existing_object,
dbms_assert.schema_name('NIMBUS') as existing_schema
from dual;
-- an object that does not exist
select dbms_assert.sql_object_name('NO_SUCH_TABLE') as missing from dual;
-- ENQUOTE_LITERAL refuses a value with an unpaired quote inside
select dbms_assert.enquote_literal('O''Hare') as with_quote from dual;Output:
LITERAL QUOTED_NAME EXISTING_OBJECT EXISTING_SCHEMA
__________ ______________ __________________ __________________
'Dubai' "FLIGHTS" NIMBUS.ROUTES NIMBUS
Error starting at line : 8
In command -
select dbms_assert.sql_object_name('NO_SUCH_TABLE') as missing from dual
Error at Command Line : 8 Column : 8
Error report -
SQL Error: ORA-44002: invalid object name
ORA-06512: at "SYS.DBMS_ASSERT", line 452
ORA-06512: at "SYS.DBMS_ASSERT", line 447
Error starting at line : 11
In command -
select dbms_assert.enquote_literal('O''Hare') as with_quote from dual
Error at Command Line : 11 Column : 8
Error report -
SQL Error: ORA-06502: PL/SQL: value or conversion error
ORA-06512: at "SYS.DBMS_ASSERT", line 473
ORA-06512: at "SYS.DBMS_ASSERT", line 563A name that is not an existing object raises ORA-44002. ENQUOTE_LITERAL refuses a value with an unpaired single quote such as O'Hare; values belong in bind variables anyway, and literals only when there is truly no alternative.
Rules for Safe Dynamic SQL
- Pass every value as a bind variable; never concatenate input into SQL text.
- Validate every name that comes from outside with SIMPLE_SQL_NAME or SQL_OBJECT_NAME, or better, check it against a fixed list or the data dictionary.
- Prefer static SQL whenever the statement does not really change.
Related Guides
- Oracle Dynamic SQL Example to Insert a Record Using DBMS_SQL
- How to Return Implicit Results from PL/SQL
Conclusion
Bind variables keep input as values and stop SQL injection for data; DBMS_ASSERT protects the parts that cannot be bound, by validating names and quoting literals before they go into dynamic SQL. Use both whenever SQL text is built at run time.
See my articles more often when you search Google.
