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; /