PL/SQL Security: Defending Against SQL Injection
Dynamic SQL is powerful, but it is the number one vector for database attacks. Learn how to sanitize inputs, enforce bind variables, and correctly configure execution rights.
The Threat of SQL Injection
When you use EXECUTE IMMEDIATE to run dynamic SQL, you open the door to SQL Injection. If you directly concatenate user input into the SQL string, a malicious user can manipulate the query structure.
Consider this search procedure:
If a hacker provides the input admin' OR '1'='1, the string becomes:
SELECT COUNT(*) FROM users WHERE username = 'admin' OR '1'='1'
This bypasses the security check entirely, returning data they shouldn't see. Worse, they could inject '; DROP TABLE users; --.
The Ultimate Defense: Bind Variables
The only way to guarantee prevention of SQL injection on data values is to use Bind Variables. When you use bind variables, the database engine treats the input strictly as a literal value, never as executable code.
Even if p_username contains malicious SQL, Oracle simply searches for a user whose literal name is "admin' OR '1'='1".
Sanitizing Identifiers (DBMS_ASSERT)
Bind variables only work for values in the WHERE or VALUES clause. You cannot bind table names or column names.
If you are writing a dynamic utility that drops a table based on user input, you must sanitize the input using DBMS_ASSERT.
Execution Rights (AUTHID DEFINER vs CURRENT_USER)
When User A creates a package, and User B executes it, whose privileges are used?
- Definer's Rights (Default): The code executes with the privileges of the user who created the package. If User A has
DROP ANY TABLErights, and User B calls User A's package, User B temporarily gains the power to drop tables! This is a massive security risk if the package contains dynamic SQL. - Invoker's Rights: The code executes with the privileges of the user who called the package.
To enforce Invoker's rights, add AUTHID CURRENT_USER to your package spec.
Data Encryption (DBMS_CRYPTO)
For storing sensitive data like passwords or API keys, never store them in plain text. Use DBMS_CRYPTO to hash or encrypt them.
Common Gotchas
Important Gotchas
- !
You cannot use bind variables in Data Definition Language (DDL) like
CREATE,ALTER, orDROP. You must use concatenation, makingDBMS_ASSERTabsolutely mandatory. - !
By default, most users do not have access to the
DBMS_CRYPTOpackage. The DBA must explicitly runGRANT EXECUTE ON SYS.DBMS_CRYPTO TO your_user;.
Key Takeaways
Key Takeaways
- Never concatenate user input directly into a dynamic SQL string.
- Always use the
USINGclause and Bind Variables to pass values intoEXECUTE IMMEDIATE. - Use
DBMS_ASSERTto sanitize dynamic table and column names. - Use
AUTHID CURRENT_USERon utility packages to prevent privilege escalation.


