CREATE TABLE customers
AS
SELECT * FROM sh.customers
WHERE cust_id BETWEEN 1 and 1000
Table created.
CREATE TABLE employees
AS
SELECT * FROM hr.employees
Table created.
CREATE TABLE departments
AS
SELECT * FROM hr.departments
Table created.
Super Cool Generic Dynamic Procedure
CREATE OR REPLACE PROCEDURE process_one_row (table_in IN VARCHAR2,
where_in IN VARCHAR2)
IS
l_block VARCHAR2 (32767)
:= 'DECLARE l_row '
|| table_in
|| '%ROWTYPE; BEGIN SELECT * INTO l_row FROM '
|| table_in
|| ' WHERE '
|| where_in
|| '; /* Then do stuff with that row... */'
|| ' END;';
BEGIN
DBMS_OUTPUT.put_line (l_block);
EXECUTE IMMEDIATE l_block;
EXCEPTION
WHEN OTHERS
THEN
Raise_Application_Error (
-20000,
'Unable to execute dynamic statement for table-where '
|| table_in
|| '-'
|| where_in
|| ' = '
|| l_block
|| ' Error: '
|| DBMS_UTILITY.FORMAT_ERROR_STACK);
END;
Procedure created.
Used as Intended
BEGIN
process_one_row ('CUSTOMERS', 'cust_id=709; ');
END;
DECLARE l_row CUSTOMERS%ROWTYPE; BEGIN SELECT * INTO l_row FROM CUSTOMERS WHERE cust_id=709; END;
Inject a Table
BEGIN
process_one_row (
'CUSTOMERS',
'cust_id=709;
EXECUTE IMMEDIATE ''CREATE TABLE nasty_data (mycol NUMBER)'';');
END;
DECLARE l_row CUSTOMERS%ROWTYPE; BEGIN SELECT * INTO l_row FROM CUSTOMERS WHERE cust_id=709;
EXECUTE IMMEDIATE 'CREATE TABLE nasty_data (mycol NUMBER)'; END;
Inject a Backdoor Procedure
BEGIN
process_one_row ('CUSTOMERS',
'cust_id=709;
EXECUTE IMMEDIATE
''CREATE PROCEDURE backdoor (str VARCHAR2)
AS BEGIN EXECUTE IMMEDIATE str; END;'';');
END;
DECLARE l_row CUSTOMERS%ROWTYPE; BEGIN SELECT * INTO l_row FROM CUSTOMERS WHERE cust_id=709;
EXECUTE IMMEDIATE
'CREATE PROCEDURE backdoor (str VARCHAR2)
AS BEGIN EXECUTE IMMEDIATE str; END;'; END;
More secure: Put Subprograms in Packages
CREATE OR REPLACE PACKAGE dynamic_hr
AS
PROCEDURE process_one_row (table_in IN VARCHAR2,
value1_in IN VARCHAR2,
value2_in IN DATE);
END;
Package created.
More secure: Maximize Use of Bind Variables
CREATE OR REPLACE PACKAGE BODY dynamic_hr
AS
PROCEDURE process_one_row (table_in IN VARCHAR2,
value1_in IN VARCHAR2,
value2_in IN DATE)
IS
l_where VARCHAR2 (32767);
l_block VARCHAR2 (32767);
BEGIN
IF table_in = 'EMPLOYEES'
THEN
l_where := 'last_name = :name AND hire_date < :hdate';
ELSIF table_in = 'DEPARTMENTS'
THEN
l_where := 'name LIKE :name AND incorporation_date = :hdate';
ELSE
raise_application_error (
-20000,
'Invalid table name for process_one_row: ' || table_in);
END IF;
l_block :=
'DECLARE l_row '
|| table_in
|| '%ROWTYPE; BEGIN SELECT * INTO l_row FROM '
|| table_in
|| ' WHERE '
|| l_where
|| '; END;';
EXECUTE IMMEDIATE l_block
USING value1_in, value2_in;
EXCEPTION
WHEN OTHERS
THEN
Raise_Application_Error (
-20000,
'Unable to execute dynamic statement for table-where '
|| table_in
|| '-'
|| l_where
|| ' = '
|| l_block
|| ' Error: '
|| DBMS_UTILITY.FORMAT_ERROR_STACK);
END;
END;
Package Body created.
More secure: Vaidate the Table Name w/DBMS_ASSERT
CREATE OR REPLACE PACKAGE BODY dynamic_hr
AS
PROCEDURE process_one_row (table_in IN VARCHAR2,
value1_in IN VARCHAR2,
value2_in IN DATE)
IS
l_where VARCHAR2 (32767);
l_table VARCHAR2 (32767);
l_block VARCHAR2 (32767);
BEGIN
l_table := SYS.DBMS_ASSERT.sql_object_name (table_in);
IF l_table = 'EMPLOYEES'
THEN
l_where := 'last_name = :name AND hire_date < :hdate';
ELSIF l_table = 'DEPARTMENTS'
THEN
l_where := 'name LIKE :name AND incorporation_date = :hdate';
ELSE
raise_application_error (
-20000,
'Invalid table name for process_one_row: ' || table_in);
END IF;
l_block :=
'DECLARE l_row '
|| l_table
|| '%ROWTYPE; BEGIN SELECT * INTO l_row FROM '
|| l_table
|| ' WHERE '
|| l_where
|| ';'
|| 'END;';
EXECUTE IMMEDIATE l_block
USING value1_in, value2_in;
EXCEPTION
WHEN OTHERS
THEN
Raise_Application_Error (
-20000,
'Unable to execute dynamic statement for table-where '
|| table_in
|| '-'
|| l_where
|| ' = '
|| l_block
|| ' Error: '
|| SQLERRM);
END;
END;
Package Body created.
More secure: Do Not Provide Sensitive Information in Error Messages
CREATE OR REPLACE PACKAGE BODY dynamic_hr
AS
PROCEDURE process_one_row (table_in IN VARCHAR2,
value1_in IN VARCHAR2,
value2_in IN DATE)
IS
l_where VARCHAR2 (32767);
l_table VARCHAR2 (32767);
BEGIN
l_table := SYS.DBMS_ASSERT.sql_object_name (table_in);
IF l_table = 'EMPLOYEES'
THEN
l_where := 'last_name = :name AND hire_date < :hdate';
ELSIF l_table = 'DEPARTMENTS'
THEN
l_where := 'name LIKE :name AND incorporation_date = :hdate';
ELSE
raise_application_error (
-20000,
'Invalid table name for process_one_row: ' || table_in);
END IF;
EXECUTE IMMEDIATE
'DECLARE l_row '
|| l_table
|| '%ROWTYPE; BEGIN SELECT * INTO l_row FROM '
|| l_table
|| ' WHERE '
|| l_where
|| ';'
|| 'END;'
USING value1_in, value2_in;
EXCEPTION
WHEN OTHERS
THEN
DECLARE
l_id INTEGER;
BEGIN
l_id := 1000;
/* Use error management package
help_desk.pkg_app_errors.f_insert_errors (
p_app => $$PLSQL_UNIT,
p_line => $$PLSQL_LINE,
p_errorcode => SQLCODE,
p_parms => table_in
|| ' '
|| value1_in
|| ' '
|| value2_in); */
Raise_Application_Error (
-20000,
'Error processing request. Contact help desk with ID ' || l_id);
END;
END;
END;
Package Body created.
More secure: Use Invoker Rights to Narrow Impact of Dynamic SQL
CREATE OR REPLACE PACKAGE dynamic_hr
AUTHID CURRENT_USER
AS
PROCEDURE process_one_row (table_in IN VARCHAR2,
value1_in IN VARCHAR2,
value2_in IN DATE);
END;
Package created.
More secure: Whitelist Access to Code
CREATE OR REPLACE PACKAGE dynamic_hr
AUTHID CURRENT_USER
ACCESSIBLE BY (public_facing_pkg)
AS
PROCEDURE process_one_row (table_in IN VARCHAR2,
value1_in IN VARCHAR2,
value2_in IN DATE);
END;
Package created.