CREATE OR REPLACE PACKAGE fuzzor
-------------------------------------------------------------------------------
-- A fuzzer framework in PL/SQL to fuzz Oracle code and store results and runs
-- in fuzz tables.
--
-- Copyright (C) 2008 Slavik Markovich
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program. If not, see .
--
-- Prerequisites:
-- The user running this package should be directly (not through a role) granted the 'create table', 'create sequence' privs.
--
-- Usage (of course, you should choose a different username/password):
-- SYS> CREATE USER fuzz IDENTIFIED BY fuzz DEFAULT TABLESPACE users TEMPORARY TABLESPACE temp;
-- -- Granting the execute any procedure is optional - and dangerous. Never do this on production. You can grant specific rights based on fuzzing needs.
-- SYS> GRANT create session, create table, create sequence, create procedure, execute any procedure TO fuzz;
-- SYS> ALTER USER fuzz QUOTA 300m ON users;
-- SYS> CONN fuzz/fuzz
-- -- Make sure that fuzzor.sql is on the SQL PATH
-- FUZZ> set serveroutput on
-- FUZZ> @fuzzor
-- -- Create the fuzzor tables in the default schema (current) and on the default tablespace
-- FUZZ> exec fuzzor.create_fuzz_tables
-- -- Let's run a simple fuzzing on a package
-- FUZZ> exec fuzzor.run_package('Test of dbms_defer_sys', 'SYS', 'DBMS_DEFER_SYS')
-- FUZZ> exec fuzzor.report('%defer%')
-- -- To remove all fuzzing tables you should run exec fuzzor.drop_fuzz_tables
--
-- Author: Slavik Markovich
-- Disclaimer: Use at your own risk and never on a production environment
-------------------------------------------------------------------------------
IS
C_TYPE_PROCEDURE CONSTANT NUMBER(4) := 1;
C_TYPE_FUNCTION CONSTANT NUMBER(4) := 2;
C_TYPE_PACKAGE CONSTANT NUMBER(4) := 4;
C_TYPE_OBJECT CONSTANT NUMBER(4) := 8;
C_TYPE_JAVA CONSTANT NUMBER(4) := 16;
-----------------------------------------------------------------------------
-- Set the owner of the fuzzing tables globally for the entire fuzzing process
-----------------------------------------------------------------------------
PROCEDURE set_fuzz_owner(
p_fuzz_owner IN VARCHAR2);
-----------------------------------------------------------------------------
-- Return the current fuzz owner of the tables
-----------------------------------------------------------------------------
FUNCTION get_fuzz_owner
RETURN VARCHAR2;
-----------------------------------------------------------------------------
-- Create the relevant tables for the fuzzing framework
-----------------------------------------------------------------------------
PROCEDURE create_fuzz_tables(
p_tablespace IN VARCHAR2 := NULL);
-----------------------------------------------------------------------------
-- Drop the relevant tables for the fuzzing framework
-----------------------------------------------------------------------------
PROCEDURE drop_fuzz_tables;
-----------------------------------------------------------------------------
-- This procedure is a wrapper around the entire process of creating a
-- new cycle of discovery, fuzzing loop and simple report generation
-----------------------------------------------------------------------------
PROCEDURE run(
p_name IN VARCHAR2,
p_owner IN VARCHAR2 := NULL,
p_type IN NUMBER := 7,
p_only_suspect IN BOOLEAN := TRUE);
-----------------------------------------------------------------------------
-- This procedure will fuzz a specific package
-----------------------------------------------------------------------------
PROCEDURE run_package(
p_name IN VARCHAR2,
p_owner IN VARCHAR2,
p_package_name IN VARCHAR2,
p_method_name IN VARCHAR2 := NULL,
p_overload IN VARCHAR2 := NULL);
-----------------------------------------------------------------------------
-- This procedure will fuzz a specific stand-alone function or procedure
-----------------------------------------------------------------------------
PROCEDURE run_proc(
p_name IN VARCHAR2,
p_owner IN VARCHAR2,
p_proc_name IN VARCHAR2);
-----------------------------------------------------------------------------
-- This procedure will restart a previous test either by continueing
-- where we left of or by reruning the entire test
-----------------------------------------------------------------------------
PROCEDURE run(
p_fuzz_run IN NUMBER,
p_continue IN BOOLEAN);
-----------------------------------------------------------------------------
-- This procedure will run a test of specific object
-----------------------------------------------------------------------------
PROCEDURE run(
p_fuzz_run IN NUMBER,
p_obj_id IN NUMBER,
p_method_name IN VARCHAR2 := NULL);
-----------------------------------------------------------------------------
-- This procedure will create the discovered records in the test tables
-----------------------------------------------------------------------------
PROCEDURE discover(
p_name IN VARCHAR2,
p_owner IN VARCHAR2 := NULL,
p_type IN NUMBER := 7,
p_only_suspect IN BOOLEAN := TRUE);
-----------------------------------------------------------------------------
-- This procedure will create a simple report and will print it to dbms_output
-- buffer
-----------------------------------------------------------------------------
PROCEDURE report(
p_fuzz_run IN NUMBER);
-----------------------------------------------------------------------------
-- This procedure will create a simple report and will print it to dbms_output
-- buffer
-----------------------------------------------------------------------------
PROCEDURE report(
p_fuzz_name IN VARCHAR2);
-----------------------------------------------------------------------------
-- This procedure will create a simple report and will print it to dbms_output
-- buffer for a specific object
-----------------------------------------------------------------------------
PROCEDURE report(
p_fuzz_run IN NUMBER,
p_obj_id IN NUMBER);
-----------------------------------------------------------------------------
-- This procedure will create a simple report and will print it to dbms_output
-- buffer
-----------------------------------------------------------------------------
PROCEDURE report(
p_fuzz_run IN NUMBER,
p_obj_id IN NUMBER,
p_method_name IN VARCHAR2,
p_overload IN VARCHAR2 := NULL);
-----------------------------------------------------------------------------
-- This procedure will delete all fuzz runs with the given name (supports LIKE)
-----------------------------------------------------------------------------
PROCEDURE delete_run(
p_fuzz_name IN VARCHAR2);
-----------------------------------------------------------------------------
-- This procedure will delete all fuzz runs with the given id
-----------------------------------------------------------------------------
PROCEDURE delete_run(
p_fuzz_id IN NUMBER);
-----------------------------------------------------------------------------
-- Turn on debug printing
-----------------------------------------------------------------------------
PROCEDURE debug(
p_debug IN BOOLEAN);
END fuzzor;
/
CREATE OR REPLACE PACKAGE BODY fuzzor
-------------------------------------------------------------------------------
-- A fuzzer framework in PL/SQL to fuzz Oracle code and store results and runs
-- in fuzz tables.
--
-- Copyright (C) 2008 Slavik Markovich
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program. If not, see .
--
-- Prerequisites:
-- The user running this package should be directly (not through a role) granted the 'create table', 'create sequence' privs.
--
-- Exmaple:
-- 1. Choosing schema for fuzz tables, by defautlt, the current schema will be used - exec fuzzor.set_fuzz_owner('SCHEMA_NAME')
-- 2. Creating fuzzing tables - exec fuzzor.create_fuzz_tables
-- 3. Removing fuzzing tables, will drop all fuzzing tables including content - exec fuzzor.drop_fuzz_tables
-- 4. Fuzzing specific schema procedures, functions and packages - exec fuzzor.run('Name for the fuzz cycle', 'TESTED_SCHEMA_NAME', 3, true)
--
-- Author: Slavik Markovich
-- Disclaimer: Use at your own risk and never on a production environment
-------------------------------------------------------------------------------
IS
-- The owner of the fuzzing tables
g_fuzz_owner VARCHAR2(50);
g_encoded_fuzz_owner VARCHAR2(50);
-- A flag to tell us to print debug messages or not
g_debug BOOLEAN := FALSE;
-- An in-memory table of all_arguments type
TYPE args_typ IS TABLE OF all_arguments%ROWTYPE
INDEX BY PLS_INTEGER;
TYPE input_typ IS TABLE OF VARCHAR2(32767)
INDEX BY PLS_INTEGER;
TYPE input_hash_typ IS TABLE OF input_typ
INDEX BY VARCHAR2(30);
g_inputs input_hash_typ;
TYPE input_defaults_typ IS TABLE OF VARCHAR2(32767)
INDEX BY VARCHAR2(30);
g_input_defaults input_defaults_typ;
PROCEDURE p(
p_message IN VARCHAR2)
IS
BEGIN
-- Since 10gR2 no limit on line size and anyhow those messages are short
dbms_output.put_line(p_message);
END p;
PROCEDURE d(
p_message IN VARCHAR2)
IS
BEGIN
IF g_debug
THEN
p(p_message);
END IF;
END d;
PROCEDURE set_fuzz_owner(
p_fuzz_owner IN VARCHAR2)
IS
BEGIN
IF p_fuzz_owner IS NULL
THEN
g_fuzz_owner := USER;
ELSE
g_fuzz_owner := sys.dbms_assert.schema_name(UPPER(p_fuzz_owner));
END IF;
g_encoded_fuzz_owner := sys.dbms_assert.enquote_name(g_fuzz_owner);
END set_fuzz_owner;
-----------------------------------------------------------------------------
-- Return the current fuzz owner of the tables
-----------------------------------------------------------------------------
FUNCTION get_fuzz_owner
RETURN VARCHAR2
IS
BEGIN
RETURN g_fuzz_owner;
END get_fuzz_owner;
FUNCTION encode_simple_name(
p_name IN VARCHAR2)
RETURN VARCHAR2
IS
BEGIN
RETURN sys.dbms_assert.enquote_name(sys.dbms_assert.simple_sql_name(p_name));
END encode_simple_name;
-----------------------------------------------------------------------------
-- A helper procedure to check if a table exists and create one if it doesn't
-----------------------------------------------------------------------------
PROCEDURE create_table(
p_table_name IN VARCHAR2,
p_create_cmd IN VARCHAR2)
IS
l_exists NUMBER(1);
BEGIN
d('Create table ' || p_table_name);
SELECT 1
INTO l_exists
FROM all_tables
WHERE owner = g_fuzz_owner
AND table_name = UPPER(p_table_name);
EXCEPTION
WHEN NO_DATA_FOUND THEN
EXECUTE IMMEDIATE p_create_cmd;
END create_table;
PROCEDURE create_sequence(
p_sequence_name IN VARCHAR2)
IS
l_exists NUMBER(1);
BEGIN
d('Create sequence ' || p_sequence_name);
SELECT 1
INTO l_exists
from all_sequences
WHERE sequence_owner = g_fuzz_owner
AND sequence_name = UPPER(p_sequence_name);
EXCEPTION
WHEN NO_DATA_FOUND THEN
-- Since it is an internal function with constant sequence names, no need to escape the sequence name from SQL injection
EXECUTE IMMEDIATE 'CREATE SEQUENCE ' || g_encoded_fuzz_owner || '.' || p_sequence_name || ' MINVALUE 1 MAXVALUE 99999 NOCYCLE';
END create_sequence;
PROCEDURE create_fuzz_tables(
p_tablespace IN VARCHAR2 := NULL)
IS
l_tablespace VARCHAR2(50);
l_index_tablespace VARCHAR2(50);
BEGIN
d('Creating fuzz tables...');
IF p_tablespace IS NULL
THEN
l_tablespace := '';
l_index_tablespace := '';
ELSE
l_tablespace := 'TABLESPACE ' || encode_simple_name(p_tablespace);
l_index_tablespace := 'USING INDEX ' || l_tablespace;
END IF;
create_table('FUZZ_RUN',
'CREATE TABLE ' || g_encoded_fuzz_owner || '.' || 'fuzz_run
(
id NUMBER(5),
name VARCHAR2(4000) NOT NULL,
start_ts DATE NOT NULL,
end_ts DATE,
CONSTRAINT fuzz_run_pk PRIMARY KEY (id),
CONSTRAINT fuzz_run_name_uk UNIQUE(name) ' || l_index_tablespace || ') ' ||
l_tablespace);
create_table('FUZZED_OBJ',
'CREATE TABLE ' || g_encoded_fuzz_owner || '.' || 'fuzzed_obj
(
id NUMBER(5),
fuzz_run_id NUMBER(5) NOT NULL,
obj_id NUMBER NOT NULL,
method_name VARCHAR2(30),
overload VARCHAR2(40),
start_ts DATE,
end_ts DATE,
is_vul VARCHAR2(1) DEFAULT ''N'',
CONSTRAINT fuzzed_obj_pk PRIMARY KEY(id),
CONSTRAINT fuzzed_obj_fuzz_run_fk FOREIGN KEY (fuzz_run_id) REFERENCES ' || g_encoded_fuzz_owner || '.' || 'fuzz_run (id)) ' ||
l_tablespace);
create_table('FUZZED_EXEC',
'CREATE TABLE ' || g_encoded_fuzz_owner || '.' || 'fuzzed_exec
(
id NUMBER(5),
fuzzed_obj_id NUMBER(5) NOT NULL,
is_vul VARCHAR2(1),
result VARCHAR2(4000),
CONSTRAINT fuzzed_exec_pk PRIMARY KEY(id),
CONSTRAINT fuzzed_exec_obj_id_fk FOREIGN KEY (fuzzed_obj_id) REFERENCES ' || g_encoded_fuzz_owner || '.' || 'fuzzed_obj (id)) ' ||
l_tablespace);
create_table('FUZZED_EXEC_PARAM',
'CREATE TABLE ' || g_encoded_fuzz_owner || '.' || 'fuzzed_exec_param
(
id NUMBER(5),
fuzzed_exec_id NUMBER(5) NOT NULL,
position NUMBER NOT NULL,
VAL VARCHAR2(4000),
CONSTRAINT fuzzed_exec_param_pk PRIMARY KEY(id),
CONSTRAINT fuzzed_exec_param_exec_fk FOREIGN KEY (fuzzed_exec_id) REFERENCES ' || g_encoded_fuzz_owner || '.' || 'fuzzed_exec (id)) ' ||
l_tablespace);
create_table('FUZZ_INPUT',
'CREATE TABLE ' || g_encoded_fuzz_owner || '.' || 'fuzz_input
(
data_type VARCHAR2(30) NOT NULL,
val VARCHAR2(4000),
dynamic VARCHAR2(1) DEFAULT ''N'',
CONSTRAINT fuzz_input_pk PRIMARY KEY(data_type, val)) ' ||
l_tablespace);
create_table('FUZZ_INPUT_DEFAULTS',
'CREATE TABLE ' || g_encoded_fuzz_owner || '.' || 'fuzz_input_defaults
(
data_type VARCHAR2(30) NOT NULL,
val VARCHAR2(4000),
CONSTRAINT fuzz_input_defaults_pk PRIMARY KEY(data_type)) ' ||
l_tablespace);
create_table('FUZZ_ERRORS',
'CREATE TABLE ' || g_encoded_fuzz_owner || '.' || 'fuzz_errors
(
err_code NUMBER(5),
CONSTRAINT fuzz_errors_pk PRIMARY KEY(err_code)) ' ||
l_tablespace);
create_table('FUZZ_EXCLUDES',
'CREATE TABLE ' || g_encoded_fuzz_owner || '.' || 'fuzz_excludes
(
obj_id NUMBER NOT NULL,
method_name VARCHAR2(30),
overload VARCHAR2(40)) ' ||
l_tablespace);
create_table('FUZZ_SEARCH_TERMS',
'CREATE TABLE ' || g_encoded_fuzz_owner || '.' || 'fuzz_search_terms
(
search_term VARCHAR2(4000) NOT NULL,
CONSTRAINT fuzz_search_terms_pk PRIMARY KEY(search_term)) ' ||
l_tablespace);
create_sequence('SEQ_FUZZ_RUN');
create_sequence('SEQ_FUZZED_OBJ');
create_sequence('SEQ_FUZZED_EXEC');
create_sequence('SEQ_FUZZED_EXEC_PARAM');
d('Inserting some default values into the tables');
-- Insert by default some interesting search terms you'd like to search for
EXECUTE IMMEDIATE 'INSERT INTO ' || g_encoded_fuzz_owner || '.fuzz_search_terms VALUES (''execute'')';
EXECUTE IMMEDIATE 'INSERT INTO ' || g_encoded_fuzz_owner || '.fuzz_search_terms VALUES (''dbms_sql'')';
EXECUTE IMMEDIATE 'INSERT INTO ' || g_encoded_fuzz_owner || '.fuzz_search_terms VALUES (''ref cursor'')';
EXECUTE IMMEDIATE 'INSERT INTO ' || g_encoded_fuzz_owner || '.fuzz_search_terms VALUES (''wrapped'')';
-- Insert some interesting errors
-- Let's start with SQL injection error codes
EXECUTE IMMEDIATE 'INSERT INTO ' || g_encoded_fuzz_owner || '.fuzz_errors VALUES (103)';
EXECUTE IMMEDIATE 'INSERT INTO ' || g_encoded_fuzz_owner || '.fuzz_errors VALUES (604)';
EXECUTE IMMEDIATE 'INSERT INTO ' || g_encoded_fuzz_owner || '.fuzz_errors VALUES (900)';
EXECUTE IMMEDIATE 'INSERT INTO ' || g_encoded_fuzz_owner || '.fuzz_errors VALUES (903)';
EXECUTE IMMEDIATE 'INSERT INTO ' || g_encoded_fuzz_owner || '.fuzz_errors VALUES (906)';
EXECUTE IMMEDIATE 'INSERT INTO ' || g_encoded_fuzz_owner || '.fuzz_errors VALUES (907)';
EXECUTE IMMEDIATE 'INSERT INTO ' || g_encoded_fuzz_owner || '.fuzz_errors VALUES (923)';
EXECUTE IMMEDIATE 'INSERT INTO ' || g_encoded_fuzz_owner || '.fuzz_errors VALUES (933)';
EXECUTE IMMEDIATE 'INSERT INTO ' || g_encoded_fuzz_owner || '.fuzz_errors VALUES (970)';
EXECUTE IMMEDIATE 'INSERT INTO ' || g_encoded_fuzz_owner || '.fuzz_errors VALUES (1476)';
EXECUTE IMMEDIATE 'INSERT INTO ' || g_encoded_fuzz_owner || '.fuzz_errors VALUES (1742)';
EXECUTE IMMEDIATE 'INSERT INTO ' || g_encoded_fuzz_owner || '.fuzz_errors VALUES (1756)';
EXECUTE IMMEDIATE 'INSERT INTO ' || g_encoded_fuzz_owner || '.fuzz_errors VALUES (1789)';
EXECUTE IMMEDIATE 'INSERT INTO ' || g_encoded_fuzz_owner || '.fuzz_errors VALUES (29257)';
-- Now with crashes - we will probably not be able to catch those
EXECUTE IMMEDIATE 'INSERT INTO ' || g_encoded_fuzz_owner || '.fuzz_errors VALUES (3113)';
EXECUTE IMMEDIATE 'INSERT INTO ' || g_encoded_fuzz_owner || '.fuzz_errors VALUES (3114)';
EXECUTE IMMEDIATE 'INSERT INTO ' || g_encoded_fuzz_owner || '.fuzz_errors VALUES (1012)';
EXECUTE IMMEDIATE 'INSERT INTO ' || g_encoded_fuzz_owner || '.fuzz_errors VALUES (7445)';
-- Insert some interting strings you'd like to work with
EXECUTE IMMEDIATE 'INSERT INTO ' || g_encoded_fuzz_owner || '.fuzz_input VALUES (''VARCHAR2'', :1, ''N'')' USING ''' --';
EXECUTE IMMEDIATE 'INSERT INTO ' || g_encoded_fuzz_owner || '.fuzz_input VALUES (''VARCHAR2'', :1, ''N'')' USING ''')';
EXECUTE IMMEDIATE 'INSERT INTO ' || g_encoded_fuzz_owner || '.fuzz_input VALUES (''VARCHAR2'', :1, ''N'')' USING ''''')';
EXECUTE IMMEDIATE 'INSERT INTO ' || g_encoded_fuzz_owner || '.fuzz_input VALUES (''VARCHAR2'', :1, ''N'')' USING 'xxx''yyy';
EXECUTE IMMEDIATE 'INSERT INTO ' || g_encoded_fuzz_owner || '.fuzz_input VALUES (''VARCHAR2'', :1, ''N'')' USING '"';
EXECUTE IMMEDIATE 'INSERT INTO ' || g_encoded_fuzz_owner || '.fuzz_input VALUES (''VARCHAR2'', :1, ''N'')' USING 'X"';
EXECUTE IMMEDIATE 'INSERT INTO ' || g_encoded_fuzz_owner || '.fuzz_input VALUES (''VARCHAR2'', :1, ''Y'')' USING 'LPAD(''x'', 32676, ''x'')';
-- Some input defaults
EXECUTE IMMEDIATE 'INSERT INTO ' || g_encoded_fuzz_owner || '.fuzz_input_defaults VALUES (''VARCHAR2'', NULL)';
EXECUTE IMMEDIATE 'INSERT INTO ' || g_encoded_fuzz_owner || '.fuzz_input_defaults VALUES (''VARCHAR'', NULL)';
EXECUTE IMMEDIATE 'INSERT INTO ' || g_encoded_fuzz_owner || '.fuzz_input_defaults VALUES (''NUMBER'', ''0'')';
EXECUTE IMMEDIATE 'INSERT INTO ' || g_encoded_fuzz_owner || '.fuzz_input_defaults VALUES (''BOOLEAN'', ''FALSE'')';
EXECUTE IMMEDIATE 'INSERT INTO ' || g_encoded_fuzz_owner || '.fuzz_input_defaults VALUES (''FLOAT'', ''0.0'')';
-- Exclude ourselves from fuzzing
EXECUTE IMMEDIATE 'INSERT INTO ' || g_encoded_fuzz_owner || '.fuzz_excludes SELECT object_id, null, null FROM all_objects ' ||
'WHERE object_name = ''FUZZOR'' AND object_type = ''PACKAGE''';
END create_fuzz_tables;
PROCEDURE drop_table(
p_table_name IN VARCHAR2)
IS
E_NO_SUCH_TABLE EXCEPTION;
PRAGMA EXCEPTION_INIT(E_NO_SUCH_TABLE, -942);
BEGIN
d('DROP TABLE ' || g_encoded_fuzz_owner || '.' || p_table_name || ' PURGE');
-- No need to check the parameters - internal use only
EXECUTE IMMEDIATE 'DROP TABLE ' || g_encoded_fuzz_owner || '.' || p_table_name || ' PURGE';
EXCEPTION
WHEN E_NO_SUCH_TABLE THEN
d('No such table ' || p_table_name);
END drop_table;
PROCEDURE drop_sequence(
p_sequence_name IN VARCHAR2)
IS
BEGIN
d('DROP SEQUENCE ' || p_sequence_name);
EXECUTE IMMEDIATE 'DROP SEQUENCE ' || p_sequence_name;
END drop_sequence;
PROCEDURE drop_fuzz_tables
IS
l_owner VARCHAR2(50);
BEGIN
d('Dropping fuzz tables...');
drop_table('fuzz_search_terms');
drop_table('fuzz_excludes');
drop_table('fuzz_errors');
drop_table('fuzz_input');
drop_table('fuzz_input_defaults');
drop_table('fuzzed_exec_param');
drop_table('fuzzed_exec');
drop_table('fuzzed_obj');
drop_table('fuzz_run');
drop_sequence('SEQ_FUZZ_RUN');
drop_sequence('SEQ_FUZZED_OBJ');
drop_sequence('SEQ_FUZZED_EXEC');
drop_sequence('SEQ_FUZZED_EXEC_PARAM');
END drop_fuzz_tables;
PROCEDURE load_inputs
IS
TYPE c_type IS REF CURSOR;
l_cv c_type;
l_data_type VARCHAR2(30);
l_val VARCHAR2(4000);
l_dyn_val VARCHAR2(32767);
l_dynamic VARCHAR2(1);
l_curr_size PLS_INTEGER;
BEGIN
d('load_inputs');
-- Make sure that the inputs is clean
g_inputs.DELETE;
g_input_defaults.DELETE;
OPEN l_cv FOR 'SELECT data_type, val FROM ' || g_encoded_fuzz_owner || '.fuzz_input_defaults';
LOOP
FETCH l_cv INTO l_data_type, l_val;
EXIT WHEN l_cv%NOTFOUND;
g_input_defaults(l_data_type) := l_val;
END LOOP;
CLOSE l_cv;
OPEN l_cv FOR 'SELECT data_type, val, dynamic FROM ' || g_encoded_fuzz_owner || '.fuzz_input';
LOOP
FETCH l_cv INTO l_data_type, l_val, l_dynamic;
EXIT WHEN l_cv%NOTFOUND;
IF NOT g_inputs.EXISTS(l_data_type) THEN
l_curr_size := 1;
ELSE
l_curr_size := g_inputs(l_data_type).COUNT + 1;
END IF;
IF l_dynamic = 'Y'
THEN
-- This is valnurable if anyone has access to the inputs table
EXECUTE IMMEDIATE 'SELECT ' || l_val || ' FROM DUAL' INTO l_dyn_val;
ELSE
l_dyn_val := l_val;
END IF;
g_inputs(l_data_type)(l_curr_size) := l_dyn_val;
END LOOP;
CLOSE l_cv;
d('end load_inputs');
EXCEPTION
WHEN OTHERS THEN
IF l_cv%ISOPEN
THEN
CLOSE l_cv;
END IF;
RAISE;
END load_inputs;
PROCEDURE run(
p_name IN VARCHAR2,
p_owner IN VARCHAR2,
p_type IN NUMBER,
p_only_suspect IN BOOLEAN)
IS
l_fuzz_run_id NUMBER(5);
BEGIN
d('Running a fuzz cycle ' || p_name || ' on ' || p_owner);
discover(p_name, p_owner, p_type, p_only_suspect);
-- A bad hack for now that assumes we will not fuzz in parallel
-- so select the max fuzz_run_id
EXECUTE IMMEDIATE 'SELECT max(id) FROM ' || g_encoded_fuzz_owner || '.fuzz_run' INTO l_fuzz_run_id;
run(l_fuzz_run_id, FALSE);
END run;
FUNCTION generate_test_run(
p_name IN VARCHAR2)
RETURN NUMBER
IS
l_seq_fuzz_run NUMBER(5);
BEGIN
EXECUTE IMMEDIATE 'SELECT ' || g_encoded_fuzz_owner || '.seq_fuzz_run.nextval FROM dual' INTO l_seq_fuzz_run;
-- Create an initial entry that describes the run
EXECUTE IMMEDIATE 'INSERT INTO ' || g_encoded_fuzz_owner || '.fuzz_run (id, name, start_ts) VALUES (:1, :2, SYSDATE)'
USING l_seq_fuzz_run, p_name;
RETURN l_seq_fuzz_run;
END generate_test_run;
PROCEDURE run_proc(
p_name IN VARCHAR2,
p_owner IN VARCHAR2,
p_proc_name IN VARCHAR2)
IS
l_owner VARCHAR2(30);
l_seq_fuzz_run NUMBER(5);
BEGIN
d('Fuzzing procedure or function ' || p_owner || '.' || p_proc_name);
l_seq_fuzz_run := generate_test_run(p_name);
IF p_owner IS NOT NULL
THEN
l_owner := sys.dbms_assert.schema_name(UPPER(p_owner));
END IF;
EXECUTE IMMEDIATE 'INSERT INTO ' || g_encoded_fuzz_owner ||
'.fuzzed_obj (id, fuzz_run_id, obj_id) ' ||
'SELECT ' || g_encoded_fuzz_owner || '.seq_fuzzed_obj.NEXTVAL, :1, object_id ' ||
'FROM all_objects ' ||
'WHERE owner = :2 ' ||
' AND object_name = :3 '
USING l_seq_fuzz_run, l_owner, p_proc_name;
run(l_seq_fuzz_run, FALSE);
END run_proc;
PROCEDURE run_package(
p_name IN VARCHAR2,
p_owner IN VARCHAR2,
p_package_name IN VARCHAR2,
p_method_name IN VARCHAR2 := NULL,
p_overload IN VARCHAR2 := NULL)
IS
l_owner VARCHAR2(30);
l_seq_fuzz_run NUMBER(5);
BEGIN
d('Fuzzing package ' || p_owner || '.' || p_package_name);
l_seq_fuzz_run := generate_test_run(p_name);
IF p_owner IS NOT NULL
THEN
l_owner := sys.dbms_assert.schema_name(UPPER(p_owner));
END IF;
IF p_method_name IS NOT NULL AND p_overload IS NOT NULL
THEN
EXECUTE IMMEDIATE 'INSERT INTO ' || g_encoded_fuzz_owner ||
'.fuzzed_obj (id, fuzz_run_id, obj_id, method_name, overload) ' ||
'SELECT ' || g_encoded_fuzz_owner || '.seq_fuzzed_obj.NEXTVAL, :1, object_id, object_name, overload ' ||
'FROM all_arguments ' ||
'WHERE owner = :2 ' ||
' AND object_name = :3 ' ||
' AND package_name = :4' ||
' AND overload = :5' ||
' AND position = 1 ' ||
' AND argument_name IS NOT NULL'
USING l_seq_fuzz_run, l_owner, p_method_name, p_package_name, p_overload;
ELSIF p_method_name IS NOT NULL
THEN
EXECUTE IMMEDIATE 'INSERT INTO ' || g_encoded_fuzz_owner ||
'.fuzzed_obj (id, fuzz_run_id, obj_id, method_name, overload) ' ||
'SELECT ' || g_encoded_fuzz_owner || '.seq_fuzzed_obj.NEXTVAL, :1, object_id, object_name, overload ' ||
'FROM all_arguments ' ||
'WHERE owner = :2 ' ||
' AND object_name = :2 ' ||
' AND package_name = :4' ||
' AND position = 1 ' ||
' AND argument_name IS NOT NULL'
USING l_seq_fuzz_run, l_owner, p_method_name, p_package_name;
ELSE
EXECUTE IMMEDIATE 'INSERT INTO ' || g_encoded_fuzz_owner ||
'.fuzzed_obj (id, fuzz_run_id, obj_id, method_name, overload) ' ||
'SELECT ' || g_encoded_fuzz_owner || '.seq_fuzzed_obj.NEXTVAL, :1, object_id, object_name, overload ' ||
'FROM all_arguments ' ||
'WHERE owner = :2 ' ||
' AND package_name = :3 ' ||
' AND position = 1 ' ||
' AND argument_name IS NOT NULL'
USING l_seq_fuzz_run, l_owner, p_package_name;
END IF;
run(l_seq_fuzz_run, FALSE);
END run_package;
PROCEDURE run(
p_fuzz_run IN NUMBER,
p_continue IN BOOLEAN)
IS
TYPE c_type IS REF CURSOR;
l_cv c_type;
l_obj_id NUMBER;
l_id NUMBER(5);
BEGIN
IF p_continue
THEN
d('Running fuzz id ' || p_fuzz_run || ' from last stop');
ELSE
d('Running fuzz id ' || p_fuzz_run || ' from the start');
END IF;
IF p_continue
THEN
EXECUTE IMMEDIATE 'SELECT MIN(id) FROM ' || g_encoded_fuzz_owner || '.fuzzed_obj WHERE fuzz_run_id = :1 AND end_ts IS NULL' INTO l_id USING p_fuzz_run;
OPEN l_cv FOR 'SELECT DISTINCT obj_id FROM ' || g_encoded_fuzz_owner || '.fuzzed_obj WHERE fuzz_run_id = :1 AND id >= :2' USING p_fuzz_run, l_id;
ELSE
OPEN l_cv FOR 'SELECT DISTINCT obj_id FROM ' || g_encoded_fuzz_owner || '.fuzzed_obj WHERE fuzz_run_id = :1' USING p_fuzz_run;
END IF;
LOOP
FETCH l_cv INTO l_obj_id;
EXIT WHEN l_cv%NOTFOUND;
run(p_fuzz_run, l_obj_id);
END LOOP;
CLOSE l_cv;
EXECUTE IMMEDIATE 'UPDATE fuzz_run SET end_ts = SYSDATE WHERE id = :1' USING p_fuzz_run;
COMMIT;
EXCEPTION
WHEN OTHERS THEN
IF l_cv%ISOPEN
THEN
CLOSE l_cv;
END IF;
RAISE;
END run;
FUNCTION get_type_str(
p_args IN all_arguments%ROWTYPE)
RETURN VARCHAR2
IS
l_type VARCHAR2(100);
BEGIN
l_type := p_args.pls_type;
IF l_type IN ('VARCHAR2', 'NVARCHAR2', 'NCHAR VARYING', 'VARCHAR', 'NCHAR', 'CHAR')
THEN
l_type := l_type || '(32767)';
END IF;
RETURN l_type;
END get_type_str;
FUNCTION collect_types(p_args args_typ)
RETURN VARCHAR2
IS
l_types VARCHAR2(32767) := '(';
BEGIN
FOR l_i IN 1..p_args.COUNT
LOOP
IF INSTR(l_types, p_args(l_i).pls_type) = 0
THEN
l_types := l_types || '''' || p_args(l_i).pls_type || ''',';
END IF;
END LOOP;
l_types := SUBSTR(l_types, 1, LENGTH(l_types) - 1) || ')';
return l_types;
END collect_types;
FUNCTION add_execution(
p_fuzzed_obj_id IN NUMBER)
RETURN NUMBER
IS
l_fuzz_exec_id NUMBER(5);
BEGIN
EXECUTE IMMEDIATE 'INSERT INTO ' || g_encoded_fuzz_owner || '.fuzzed_exec (id, fuzzed_obj_id) VALUES (seq_fuzzed_exec.NEXTVAL, :1) RETURNING id INTO :2'
USING p_fuzzed_obj_id, OUT l_fuzz_exec_id;
RETURN l_fuzz_exec_id;
END add_execution;
PROCEDURE add_execution_param(
p_fuzz_exec_id IN NUMBER,
p_position IN NUMBER,
p_val IN VARCHAR2)
IS
BEGIN
EXECUTE IMMEDIATE 'INSERT INTO ' || g_encoded_fuzz_owner || '.fuzzed_exec_param (id, fuzzed_exec_id, position, val) ' ||
'VALUES (seq_fuzzed_exec_param.NEXTVAL, :1, :2, :3)'
USING p_fuzz_exec_id, p_position, SUBSTR(p_val, 1, 4000);
END add_execution_param;
PROCEDURE execute_object(
p_fuzz_run IN NUMBER,
p_obj_id IN NUMBER,
p_method_name IN VARCHAR2,
p_overload IN VARCHAR2,
p_fuzzed_obj_id IN NUMBER)
IS
-- The query on all_arguments
l_query VARCHAR2(200);
-- The all_arguments cursor
TYPE c_type IS REF CURSOR;
l_cv c_type;
-- The error code from execution
l_err_code NUMBER;
-- The error message from execution
l_err_msg VARCHAR2(2000);
-- Dummy var used to load interesting error codes
l_dummy NUMBER;
-- A flag to mark if the invoked object is vul.
l_vul VARCHAR2(1) := 'N';
-- The declare part of the anonymous block
l_declare VARCHAR2(32000);
-- The begin part of the anonymous block
l_begin VARCHAR2(32000);
-- The whole block
l_block VARCHAR2(32000);
-- All arguments variable to hold all the argument data
l_args args_typ;
-- An exception used for not supported functions
e_not_supported EXCEPTION;
-- The name of the executed object
l_executed_object VARCHAR2(100);
-- The bind variable number
l_cnt NUMBER;
-- The type of current argument
l_type VARCHAR2(100);
-- Current counter for each data type
TYPE count_typ IS TABLE OF PLS_INTEGER INDEX BY VARCHAR2(30);
l_counters count_typ;
TYPE exists_typ IS TABLE OF VARCHAR2(1) INDEX BY VARCHAR2(30);
l_type_exists exists_typ;
l_block_cr NUMBER;
l_input_value VARCHAR2(32767);
-- The current execution number
l_exec_id NUMBER(5);
BEGIN
d('Executing object...');
-- Update the start timestamp for the object
EXECUTE IMMEDIATE 'UPDATE ' || g_encoded_fuzz_owner || '.fuzzed_obj SET start_ts = SYSDATE WHERE id = :1' USING p_fuzzed_obj_id;
COMMIT;
-- Get the object_name - we can get it from all_arguments but it's easier
-- to pre-load it here from all_objects
SELECT object_name INTO l_executed_object FROM all_objects WHERE object_id = p_obj_id;
-- Get all the arguments for the attacked object
l_query := 'SELECT * FROM all_arguments WHERE object_id = :1 ';
IF p_method_name IS NOT NULL AND p_overload IS NOT NULL
THEN
l_query := l_query || 'AND object_name = :2 AND overload = :3 ORDER BY position';
OPEN l_cv FOR l_query USING p_obj_id, p_method_name, p_overload;
l_executed_object := l_executed_object || '.' || p_method_name;
ELSIF p_method_name IS NOT NULL
THEN
l_query := l_query || 'AND object_name = :2 ORDER BY position';
OPEN l_cv FOR l_query USING p_obj_id, p_method_name;
l_executed_object := l_executed_object || '.' || p_method_name;
ELSE
l_query := l_query || 'ORDER BY position';
OPEN l_cv FOR l_query USING p_obj_id;
END IF;
d(l_query);
-- Build the PL/SQL block for the invocation
-- Start with the basic building blocks of declare and begin
l_declare := 'DECLARE';
l_begin := 'BEGIN';
FETCH l_cv BULK COLLECT INTO l_args;
CLOSE l_cv;
FOR l_indx IN 1..l_args.COUNT
LOOP
l_type := get_type_str(l_args(l_indx));
-- If this is a function, we need to create a return variable
IF l_args(l_indx).position = 0
THEN
l_declare := l_declare || ' l_res ' || l_type || ';';
l_begin := l_begin || ' l_res := ' || l_executed_object || '(';
ELSE
-- If we are not in a function
IF LENGTH(l_begin) < 7
THEN
l_begin := l_begin || ' ' || l_executed_object || '(';
END IF;
IF l_args(l_indx).in_out = 'IN'
THEN
IF l_type = 'BOOLEAN'
THEN
l_declare := l_declare || ' l_' || l_args(l_indx).argument_name || ' ' || l_type || ':= ''TRUE'' = :' || l_args(l_indx).pls_type || ';';
l_begin := l_begin || l_args(l_indx).argument_name || ' => l_' || l_args(l_indx).argument_name || ',';
ELSE
l_begin := l_begin || l_args(l_indx).argument_name || ' => :' || l_args(l_indx).pls_type || ',';
END IF;
ELSIF l_args(l_indx).in_out = 'IN/OUT'
THEN
IF l_type = 'BOOLEAN'
THEN
l_declare := l_declare || ' l_' || l_args(l_indx).argument_name || ' ' || l_type || ':= ''TRUE'' = :' || l_args(l_indx).pls_type || ';';
ELSE
l_declare := l_declare || ' l_' || l_args(l_indx).argument_name || ' ' || l_type || ':= :' || l_args(l_indx).pls_type || ';';
END IF;
l_begin := l_begin || l_args(l_indx).argument_name || ' => l_' || l_args(l_indx).argument_name || ',';
ELSE
l_declare := l_declare || ' l_' || l_args(l_indx).argument_name || ' ' || l_type || ';';
l_begin := l_begin || l_args(l_indx).argument_name || ' => l_' || l_args(l_indx).argument_name || ',';
END IF;
END IF;
END LOOP;
-- Strip the last comma
l_begin := SUBSTR(l_begin, 1, LENGTH(l_begin) - 1) || '); END;';
-- If there is a declare block, collect it
IF LENGTH(l_declare) > 8
THEN
l_block := l_declare || ' ';
END IF;
l_block := l_block || l_begin;
d(l_block);
-- Now it's time to execute the block with the bind variable options
-- First, select the number of iterations based on the types of arguments we are expecting
-- and the number of inputs
EXECUTE IMMEDIATE 'SELECT MAX(cnt) FROM (SELECT count(*) cnt FROM ' || g_encoded_fuzz_owner || '.fuzz_input WHERE data_type IN ' ||
collect_types(l_args) || ' GROUP BY data_type)' INTO l_cnt;
l_block_cr := dbms_sql.open_cursor;
dbms_sql.parse(l_block_cr, l_block, dbms_sql.native);
-- Run at least once even if no inputs - with defaults
IF l_cnt IS NULL THEN l_cnt := 1; END IF;
FOR l_i IN 1..l_cnt
LOOP
l_exec_id := add_execution(p_fuzzed_obj_id);
COMMIT;
BEGIN
-- Bind each data type only once with the relevant input
l_type_exists.DELETE;
FOR l_j IN 1..l_args.COUNT
LOOP
l_type := l_args(l_j).pls_type;
-- If this is an input parameter and we did not bind this type before
-- then we need to bind a value to it
IF INSTR('IN', l_args(l_j).in_out) > 0 AND
NOT l_type_exists.EXISTS(l_type)
THEN
l_type_exists(l_type) := 'Y';
IF NOT l_counters.EXISTS(l_type)
THEN
l_counters(l_type) := 0;
END IF;
-- If there is at least one input for this type then bind the relevant one
IF g_inputs(l_type).COUNT > 0
THEN
-- For now, do auto-conversion from VARCHAR2 to the relevant type
l_input_value := g_inputs(l_type)(MOD(l_counters(l_type), g_inputs(l_type).COUNT) + 1);
ELSE
IF g_input_defaults.EXISTS(l_type)
THEN
l_input_value := g_input_defaults(l_type);
ELSE
l_input_value := g_input_defaults('VARCHAR2');
END IF;
END IF;
add_execution_param(l_exec_id, l_j, l_input_value);
COMMIT;
dbms_sql.bind_variable(l_block_cr, l_type, l_input_value);
l_counters(l_type) := l_counters(l_type) + 1;
END IF;
END LOOP;
COMMIT;
-- Now let's execute
l_dummy := dbms_sql.execute(l_block_cr);
EXCEPTION
-- TODO
-- Update the invocation table to reflect that this method is not supported
WHEN e_not_supported THEN
NULL;
-- Check the error code and if it is one of the intersting ones, report this
WHEN OTHERS THEN
l_err_code := sqlcode;
l_err_msg := sqlerrm;
IF l_cv%ISOPEN
THEN
CLOSE l_cv;
END IF;
BEGIN
d('Testing err ' || l_err_code);
EXECUTE IMMEDIATE 'SELECT err_code FROM ' || g_encoded_fuzz_owner || '.fuzz_errors WHERE err_code = :1' INTO l_dummy USING ABS(l_err_code);
EXECUTE IMMEDIATE 'UPDATE ' || g_encoded_fuzz_owner || '.fuzzed_obj SET is_vul = ''Y'' WHERE id = :1' USING p_fuzzed_obj_id;
-- Let's update the error result
EXECUTE IMMEDIATE 'UPDATE ' || g_encoded_fuzz_owner || '.fuzzed_exec SET is_vul = ''Y'', result = :1 WHERE id = :2'
USING SUBSTR(sqlerrm, 1, 4000), l_exec_id;
COMMIT;
EXCEPTION
-- This is not an interesting error, we will ignore it for now
WHEN NO_DATA_FOUND THEN
d('Not interesting...');
-- Let's update the error result
EXECUTE IMMEDIATE 'UPDATE ' || g_encoded_fuzz_owner || '.fuzzed_exec SET result = :1 WHERE id = :2'
USING SUBSTR(sqlerrm, 1, 4000), l_exec_id;
COMMIT;
END;
END;
END LOOP;
-- Update the end timestamp for the object
EXECUTE IMMEDIATE 'UPDATE ' || g_encoded_fuzz_owner || '.fuzzed_obj SET end_ts = SYSDATE WHERE id = :1' USING p_fuzzed_obj_id;
COMMIT;
dbms_sql.close_cursor(l_block_cr);
EXCEPTION
WHEN OTHERS THEN
IF l_cv%ISOPEN
THEN
CLOSE l_cv;
END IF;
IF dbms_sql.is_open(l_block_cr)
THEN
dbms_sql.close_cursor(l_block_cr);
END IF;
RAISE;
END execute_object;
PROCEDURE run(
p_fuzz_run IN NUMBER,
p_obj_id IN NUMBER,
p_method_name IN VARCHAR2 := NULL)
IS
TYPE c_type IS REF CURSOR;
l_cv c_type;
l_obj_id NUMBER;
l_fuzzed_obj_id NUMBER(5);
l_method_name VARCHAR2(30);
l_overload VARCHAR2(40);
BEGIN
d('Running object ' || p_obj_id);
load_inputs;
IF p_method_name IS NOT NULL
THEN
OPEN l_cv FOR
'SELECT id, method_name, overload FROM ' || g_encoded_fuzz_owner || '.fuzzed_obj ' ||
'WHERE fuzz_run_id = :1 AND obj_id = :2 AND method_name = :3' USING p_fuzz_run, p_obj_id, p_method_name;
ELSE
OPEN l_cv FOR
'SELECT id, method_name, overload FROM ' || g_encoded_fuzz_owner || '.fuzzed_obj ' ||
'WHERE fuzz_run_id = :1 AND obj_id = :2' USING p_fuzz_run, p_obj_id;
END IF;
LOOP
FETCH l_cv INTO l_fuzzed_obj_id, l_method_name, l_overload;
EXIT WHEN l_cv%NOTFOUND;
execute_object(p_fuzz_run, p_obj_id, l_method_name, l_overload, l_fuzzed_obj_id);
END LOOP;
CLOSE l_cv;
-- Update the end of the run even if we are about to run other objects because the external run
-- will update the end_ts as well
EXECUTE IMMEDIATE 'UPDATE fuzz_run SET end_ts = SYSDATE WHERE id = :1' USING p_fuzz_run;
COMMIT;
EXCEPTION
WHEN OTHERS THEN
IF l_cv%ISOPEN
THEN
CLOSE l_cv;
END IF;
RAISE;
END run;
PROCEDURE discover(
p_name IN VARCHAR2,
p_owner IN VARCHAR2,
p_type IN NUMBER,
p_only_suspect IN BOOLEAN)
IS
-- The dynamic statement we are going to create
l_owner VARCHAR2(30);
l_seq_fuzz_run NUMBER(5);
BEGIN
d('Discovering the fuzzable objects...');
l_seq_fuzz_run := generate_test_run(p_name);
IF p_owner IS NOT NULL
THEN
l_owner := sys.dbms_assert.schema_name(UPPER(p_owner));
ELSE
l_owner := USER;
END IF;
IF bitand(p_type, C_TYPE_PROCEDURE) = C_TYPE_PROCEDURE
THEN
IF p_only_suspect
THEN
EXECUTE IMMEDIATE 'INSERT INTO ' || g_encoded_fuzz_owner ||
'.fuzzed_obj (id, fuzz_run_id, obj_id, method_name, overload) ' ||
'SELECT ' || g_encoded_fuzz_owner || '.seq_fuzzed_obj.NEXTVAL, :1, object_id, null, null ' ||
'FROM all_objects ao ' ||
'WHERE owner = :2 AND object_name IN ' ||
'(SELECT DISTINCT alls.name ' ||
'FROM all_source alls, ' || g_encoded_fuzz_owner || '.fuzz_search_terms fst ' ||
'WHERE alls.owner = :3 AND alls.type = ''PROCEDURE'' AND UPPER(alls.text) LIKE ''%'' || UPPER(fst.search_term) || ''%'')' ||
'AND object_id NOT IN ' ||
'(SELECT obj_id FROM ' || g_encoded_fuzz_owner || '.fuzz_excludes) ' ||
'AND EXISTS (SELECT 1 FROM all_arguments WHERE object_id = ao.object_id AND position = 1)'
USING l_seq_fuzz_run, l_owner, l_owner;
ELSE
EXECUTE IMMEDIATE 'INSERT INTO ' || g_encoded_fuzz_owner ||
'.fuzzed_obj (id, fuzz_run_id, obj_id, method_name, overload) ' ||
'SELECT ' || g_encoded_fuzz_owner || '.seq_fuzzed_obj.NEXTVAL, :1, object_id, null, null ' ||
'FROM all_objects ao ' ||
'WHERE owner = :2 ' ||
'AND object_type = ''PROCEDURE'' ' ||
'AND object_id NOT IN ' ||
'(SELECT obj_id FROM ' || g_encoded_fuzz_owner || '.fuzz_excludes) ' ||
'AND EXISTS (SELECT 1 FROM all_arguments WHERE object_id = ao.object_id AND position = 1)'
USING l_seq_fuzz_run, l_owner;
END IF;
END IF;
IF bitand(p_type, C_TYPE_FUNCTION) = C_TYPE_FUNCTION
THEN
IF p_only_suspect
THEN
EXECUTE IMMEDIATE 'INSERT INTO ' || g_encoded_fuzz_owner ||
'.fuzzed_obj (id, fuzz_run_id, obj_id, method_name, overload) ' ||
'SELECT ' || g_encoded_fuzz_owner || '.seq_fuzzed_obj.NEXTVAL, :1, object_id, null, null ' ||
'FROM all_objects ao ' ||
'WHERE owner = :2 AND object_name IN ' ||
'(SELECT DISTINCT alls.name ' ||
'FROM all_source alls, ' || g_encoded_fuzz_owner || '.fuzz_search_terms fst ' ||
'WHERE alls.owner = :3 AND alls.type = ''FUNCTION'' AND UPPER(alls.text) LIKE ''%'' || UPPER(fst.search_term) || ''%'')' ||
'AND object_id NOT IN ' ||
'(SELECT obj_id FROM ' || g_encoded_fuzz_owner || '.fuzz_excludes) ' ||
'AND EXISTS (SELECT 1 FROM all_arguments WHERE object_id = ao.object_id AND position = 1)'
USING l_seq_fuzz_run, l_owner, l_owner;
ELSE
EXECUTE IMMEDIATE 'INSERT INTO ' || g_encoded_fuzz_owner ||
'.fuzzed_obj (id, fuzz_run_id, obj_id, method_name, overload) ' ||
'SELECT ' || g_encoded_fuzz_owner || '.seq_fuzzed_obj.NEXTVAL, :1, object_id, null, null ' ||
'FROM all_objects ao ' ||
'WHERE owner = :2 ' ||
'AND object_type = ''FUNCTION'' ' ||
'AND object_id NOT IN ' ||
'(SELECT obj_id FROM ' || g_encoded_fuzz_owner || '.fuzz_excludes) ' ||
'AND EXISTS (SELECT 1 FROM all_arguments WHERE object_id = ao.object_id AND position = 1)'
USING l_seq_fuzz_run, l_owner;
END IF;
END IF;
IF bitand(p_type, C_TYPE_PACKAGE) = C_TYPE_PACKAGE
THEN
IF p_only_suspect
THEN
EXECUTE IMMEDIATE 'INSERT INTO ' || g_encoded_fuzz_owner ||
'.fuzzed_obj (id, fuzz_run_id, obj_id, method_name, overload) ' ||
'SELECT ' || g_encoded_fuzz_owner || '.seq_fuzzed_obj.NEXTVAL, :1, object_id, object_name, overload ' ||
'FROM (' ||
'SELECT DISTINCT object_id, object_name, overload ' ||
'FROM all_arguments aa ' ||
'WHERE owner = :2 AND package_name IN ' ||
'(SELECT DISTINCT alls.name ' ||
'FROM all_source alls, ' || g_encoded_fuzz_owner || '.fuzz_search_terms fst ' ||
'WHERE alls.owner = :3 AND alls.type = ''PACKAGE BODY'' AND UPPER(alls.text) LIKE ''%'' || UPPER(fst.search_term) || ''%'')' ||
'AND (object_id, object_name, overload) NOT IN ' ||
'(SELECT obj_id, method_name, overload FROM ' || g_encoded_fuzz_owner || '.fuzz_excludes) ' ||
'AND EXISTS (SELECT 1 FROM all_arguments WHERE object_id = aa.object_id AND object_name = aa.object_name AND ' ||
'NVL(overload, ''x'') = NVL(aa.overload, ''x'') AND argument_name IS NOT NULL AND position = 1))'
USING l_seq_fuzz_run, l_owner, l_owner;
ELSE
EXECUTE IMMEDIATE 'INSERT INTO ' || g_encoded_fuzz_owner ||
'.fuzzed_obj (id, fuzz_run_id, obj_id, method_name, overload) ' ||
'SELECT ' || g_encoded_fuzz_owner || '.seq_fuzzed_obj.NEXTVAL, :1, object_id, object_name, overload ' ||
'FROM all_arguments aa' ||
'WHERE owner = :2 ' ||
'AND object_type = ''PACKAGE'' ' ||
'AND (object_id, object_name, overload) NOT IN ' ||
'(SELECT obj_id, method_name, overload FROM ' || g_encoded_fuzz_owner || '.fuzz_excludes) ' ||
'AND EXISTS (SELECT 1 FROM all_arguments WHERE object_id = aa.object_id AND object_name = aa.object_name AND ' ||
'NVL(overload, ''x'') = NVL(aa.overload, ''x'') AND argument_name IS NOT NULL AND position = 1))'
USING l_seq_fuzz_run, l_owner, l_owner;
END IF;
END IF;
END discover;
PROCEDURE report(
p_fuzz_run IN NUMBER)
IS
l_name VARCHAR2(4000);
l_start_ts DATE;
l_end_ts DATE;
l_total_obj PLS_INTEGER;
l_vul_obj PLS_INTEGER;
l_total_exec PLS_INTEGER;
TYPE c_type IS REF CURSOR;
l_cv c_type;
l_obj_id NUMBER;
BEGIN
EXECUTE IMMEDIATE 'SELECT name, start_ts, end_ts FROM ' || g_encoded_fuzz_owner || '.fuzz_run WHERE id = :1'
INTO l_name, l_start_ts, l_end_ts USING p_fuzz_run;
EXECUTE IMMEDIATE 'SELECT count(*) FROM ' || g_encoded_fuzz_owner || '.fuzzed_obj WHERE fuzz_run_id = :1'
INTO l_total_obj USING p_fuzz_run;
EXECUTE IMMEDIATE 'SELECT count(*) FROM ' || g_encoded_fuzz_owner || '.fuzzed_obj WHERE fuzz_run_id = :1 AND is_vul = ''Y'''
INTO l_vul_obj USING p_fuzz_run;
EXECUTE IMMEDIATE 'SELECT count(*) FROM ' || g_encoded_fuzz_owner || '.fuzzed_obj, ' || g_encoded_fuzz_owner || '.fuzzed_exec ' ||
'WHERE fuzz_run_id = :1 AND fuzzed_obj.id = fuzzed_exec.fuzzed_obj_id'
INTO l_total_exec USING p_fuzz_run;
p('Results for run ' || l_name || ' with ID ' || p_fuzz_run || ' started at ' ||
to_char(l_start_ts, 'DD/MON/YYYY HH24:MI:SS') || ' and ended at ' || to_char(l_start_ts, 'DD/MON/YYYY HH24:MI:SS'));
p('Total number of fuzzed objects is: ' || l_total_obj);
p('Total number of executions is: ' || l_total_exec);
p('Total number of vulnerable objects is: ' || l_vul_obj);
OPEN l_cv FOR 'SELECT DISTINCT obj_id FROM ' || g_encoded_fuzz_owner || '.fuzzed_obj WHERE fuzz_run_id = :1' USING p_fuzz_run;
LOOP
FETCH l_cv INTO l_obj_id;
EXIT WHEN l_cv%NOTFOUND;
report(p_fuzz_run, l_obj_id);
END LOOP;
CLOSE l_cv;
EXCEPTION
WHEN OTHERS THEN
IF l_cv%ISOPEN
THEN
CLOSE l_cv;
END IF;
RAISE;
END report;
PROCEDURE report(
p_fuzz_name IN VARCHAR2)
IS
TYPE c_type IS REF CURSOR;
l_cv c_type;
l_id NUMBER(5);
BEGIN
OPEN l_cv FOR 'SELECT id FROM ' || g_encoded_fuzz_owner || '.fuzz_run WHERE UPPER(name) LIKE UPPER(:1)' USING p_fuzz_name;
LOOP
FETCH l_cv INTO l_id;
EXIT WHEN l_cv%NOTFOUND;
report(l_id);
END LOOP;
CLOSE l_cv;
EXCEPTION
WHEN OTHERS THEN
IF l_cv%ISOPEN
THEN
CLOSE l_cv;
END IF;
RAISE;
END report;
PROCEDURE report(
p_fuzz_run IN NUMBER,
p_obj_id IN NUMBER)
IS
TYPE c_type IS REF CURSOR;
l_cv c_type;
l_method_name VARCHAR2(30);
l_overload VARCHAR2(40);
BEGIN
OPEN l_cv FOR 'SELECT method_name, overload FROM ' || g_encoded_fuzz_owner || '.fuzzed_obj WHERE fuzz_run_id = :1 AND obj_id = :2' USING p_fuzz_run, p_obj_id;
LOOP
FETCH l_cv INTO l_method_name, l_overload;
EXIT WHEN l_cv%NOTFOUND;
report(p_fuzz_run, p_obj_id, l_method_name, l_overload);
END LOOP;
CLOSE l_cv;
EXCEPTION
WHEN OTHERS THEN
IF l_cv%ISOPEN
THEN
CLOSE l_cv;
END IF;
RAISE;
END report;
PROCEDURE report(
p_fuzz_run IN NUMBER,
p_obj_id IN NUMBER,
p_method_name IN VARCHAR2,
p_overload IN VARCHAR2)
IS
l_name VARCHAR2(30);
TYPE c_type IS REF CURSOR;
l_cv c_type;
l_cv1 c_type;
l_id NUMBER(5);
l_start_ts DATE;
l_end_ts DATE;
l_is_vul VARCHAR2(1);
l_fuzzed_exec_id NUMBER(5);
l_prev_fuzzed_exec_id NUMBER(5) := 0;
l_position NUMBER;
l_val VARCHAR2(4000);
l_first BOOLEAN := TRUE;
BEGIN
SELECT object_name INTO l_name FROM all_objects WHERE object_id = p_obj_id;
p('Results for ' || l_name || '(' || p_method_name || ')');
OPEN l_cv FOR 'SELECT id, start_ts, end_ts, is_vul FROM ' || g_encoded_fuzz_owner || '.fuzzed_obj WHERE fuzz_run_id = :1 AND ' ||
'obj_id = :2 AND NVL(method_name, ''!'') = NVL(:3, ''!'') AND NVL(overload, ''!'') = NVL(:4, ''!'')'
USING p_fuzz_run, p_obj_id, p_method_name, p_overload;
LOOP
FETCH l_cv INTO l_id, l_start_ts, l_end_ts, l_is_vul;
EXIT WHEN l_cv%NOTFOUND;
p('The test started at ' || to_char(l_start_ts, 'DD/MON/YYYY HH24:MI:SS') || ' and ended ' ||
to_char(l_end_ts, 'DD/MON/YYYY HH24:MI:SS') || ' and vul = ' || l_is_vul);
IF l_end_ts IS NULL
THEN
p('********************** The test did not finish!!!******************************');
p('Values that crashed the test are:');
OPEN l_cv1 FOR 'SELECT fuzzed_exec_id, position, val FROM ' || g_encoded_fuzz_owner || '.fuzzed_exec_param WHERE fuzzed_exec_id = ' ||
'(SELECT MAX(id) FROM fuzzed_exec WHERE fuzzed_obj_id = :1) ORDER BY position'
USING l_id;
LOOP
FETCH l_cv1 INTO l_fuzzed_exec_id, l_position, l_val;
EXIT WHEN l_cv1%NOTFOUND;
-- This is a new fuzz execution
IF l_first
THEN
l_first := FALSE;
p('Execution number: ' || l_fuzzed_exec_id);
END IF;
p('Param' || l_position || ' = ' || SUBSTR(l_val, 1, 50));
END LOOP;
CLOSE l_cv1;
END IF;
IF l_is_vul = 'Y'
THEN
p('Inputs of interest are:');
OPEN l_cv1 FOR 'SELECT fuzzed_exec_id, position, val FROM ' || g_encoded_fuzz_owner || '.fuzzed_exec_param WHERE fuzzed_exec_id IN ' ||
'(SELECT id FROM fuzzed_exec WHERE fuzzed_obj_id = :1 AND is_vul = ''Y'') ORDER BY fuzzed_exec_id, position'
USING l_id;
LOOP
FETCH l_cv1 INTO l_fuzzed_exec_id, l_position, l_val;
EXIT WHEN l_cv1%NOTFOUND;
-- This is a new fuzz execution
IF l_prev_fuzzed_exec_id <> l_fuzzed_exec_id
THEN
l_prev_fuzzed_exec_id := l_fuzzed_exec_id;
p('Execution number: ' || l_fuzzed_exec_id);
END IF;
p('Param' || l_position || ' = ' || SUBSTR(l_val, 1, 50));
END LOOP;
CLOSE l_cv1;
END IF;
END LOOP;
CLOSE l_cv;
EXCEPTION
WHEN OTHERS THEN
IF l_cv%ISOPEN
THEN
CLOSE l_cv;
END IF;
IF l_cv1%ISOPEN
THEN
CLOSE l_cv1;
END IF;
RAISE;
END report;
PROCEDURE delete_run(
p_fuzz_id IN NUMBER)
IS
BEGIN
d('Deleting fuzz_run_id = ' || p_fuzz_id);
EXECUTE IMMEDIATE 'DELETE FROM ' || g_encoded_fuzz_owner || '.fuzzed_exec_param WHERE fuzzed_exec_id IN (' ||
'SELECT id FROM ' || g_encoded_fuzz_owner || '.fuzzed_exec WHERE fuzzed_obj_id IN (' ||
'SELECT id FROM ' || g_encoded_fuzz_owner || '.fuzzed_obj WHERE fuzz_run_id = :1))' USING p_fuzz_id;
EXECUTE IMMEDIATE 'DELETE FROM ' || g_encoded_fuzz_owner || '.fuzzed_exec WHERE fuzzed_obj_id IN (' ||
'SELECT id FROM ' || g_encoded_fuzz_owner || '.fuzzed_obj WHERE fuzz_run_id = :1)' USING p_fuzz_id;
EXECUTE IMMEDIATE 'DELETE FROM ' || g_encoded_fuzz_owner || '.fuzzed_obj WHERE fuzz_run_id = :1' USING p_fuzz_id;
EXECUTE IMMEDIATE 'DELETE FROM ' || g_encoded_fuzz_owner || '.fuzz_run WHERE id = :1' USING p_fuzz_id;
END delete_run;
PROCEDURE delete_run(
p_fuzz_name IN VARCHAR2)
IS
TYPE c_type IS REF CURSOR;
l_cv c_type;
l_id NUMBER(5);
BEGIN
OPEN l_cv FOR 'SELECT id FROM ' || g_encoded_fuzz_owner || '.fuzz_run WHERE UPPER(name) LIKE UPPER(:1)' USING p_fuzz_name;
LOOP
FETCH l_cv INTO l_id;
EXIT WHEN l_cv%NOTFOUND;
delete_run(l_id);
END LOOP;
CLOSE l_cv;
EXCEPTION
WHEN OTHERS THEN
IF l_cv%ISOPEN
THEN
CLOSE l_cv;
END IF;
RAISE;
END delete_run;
PROCEDURE debug(
p_debug IN BOOLEAN)
IS
BEGIN
g_debug := p_debug;
END debug;
BEGIN
set_fuzz_owner(NULL);
END fuzzor;
/