Oracle允许使用几个PL/SQL API(UTL_TCP, UTL_SMTP, UTL_MAIL, UTL_HTTP和 UTL_INADDR)访问外部网络服务,这些API都使用TCP协议,在上一个数据库版本中(10g)是通过一个基于用户是否被授予执行某个包的许可的 on/off开关来实现的,Oracle 11g引入了细粒度访问网络服务,
通过在XML DB 数据库中使用访问控制列表(ACL)来实现,允许控制哪个用户能够访问哪个网络资源,而不关心包的授权。
使用FTP或WebDav可以直接在XML DB 数据库中创建、修改和删除访问控制列表,Oracle提供了DBMS_NETWORK_ACL_ADMIN和 DBMS_NETWORK_ACL_UTILITY程序包允许从PL/SQL管理访问控制列表,这些API就是本文的主角。
创建一个访问控制列表(ACL)
访问控制列表是使用DBMS_NETWORK_ACL_ADMIN程序包来操作的,CREATE_ACL存储过程使用下面的参数来创建一个新的访问控制列表:
? acl - 访问控制列表XML文件的名字,产生在XML DB 数据库中的/sys/acls目录下? description - 访问控制列表的描述信息? principal - 第一个被授予或拒绝的用户账号,大小写敏感? is_grant - TRUE意味着授予了权限,FALSE意味着权限被拒绝? privilege - 给UTL_TCP, UTL_SMTP, UTL_MAIL和UTL_HTTP授予connect权限,给UTL_INADDR名称/ip解析授予resolve权限,大小写敏感? start_date - 默认值是NULL,当指定了一个值后,访问控制列表只有在指定的日期到达时或到达后才被激活? en_date - 访问控制列表结束日期(可选的)
下面的代码创建了两个测试用户充当委托人,然后又创建了一个新的访问控制列表。
CONN sys/password@db11g AS SYSDBA
CREATE USER test1 IDENTIFIED BY test1;
GRANT CONNECT TO test1;
CREATE USER test2 IDENTIFIED BY test2;
GRANT CONNECT TO test2;
BEGIN
DBMS_NETWORK_ACL_ADMIN.create_acl (
acl => 'test_acl_file.xml',
description => 'A test of the ACL functionality',
principal => 'TEST1',
is_grant => TRUE,
privilege => 'connect',
start_date => SYSTIMESTAMP,
end_date => NULL);
COMMIT;
END;
/
一旦创建完毕,访问控制列表就能够在http://host:port/sys/acls目录下看到。
使用ADD_PRIVILEGE存储过程将其他的用户或角色添加到访问控制列表中,它的参数与CREATE_ACL存储过程的参数类似,省略了DESCRIPTION参数,同时增加了POSITION参数,它用于设置优先顺序。
BEGIN
DBMS_NETWORK_ACL_ADMIN.add_privilege (
acl => 'test_acl_file.xml',
principal => 'TEST2',
is_grant => FALSE,
privilege => 'connect',
position => NULL,
start_date => NULL,
end_date => NULL);
COMMIT;
END;
/
每个委托人在访问控制列表中都被作为一个独立的访问控制单元(ACE)进行定义,当定义了多条原则时,他们按照从上到下的顺序被评估,直到最后一条定义 权限的原则,这就意味着一个拒绝访问某个资源的角色可以被授予一个用户,但是如果这个用户又作为一个委托人定义在文件中时,这个定义将覆盖角色的定义,使 用POSITION参数保证权限是按顺序进行评估的。
使用DELETE_PRIVILEGE存储过程移除权限,如果IS_GRANT或PRIVILEGE参数的值是NULL,将移除所有授予的权限。
BEGIN
DBMS_NETWORK_ACL_ADMIN.delete_privilege (
acl => 'test_acl_file.xml',
principal => 'TEST2',
is_grant => FALSE,
privilege => 'connect');
COMMIT;
END;
/
使用DROP_ACL删除访问控制列表
BEGIN
DBMS_NETWORK_ACL_ADMIN.drop_acl (
acl => 'test_acl_file.xml');
COMMIT;
END;
/ 转贴 给网络分配一个访问控制列表
使用ASSIGN_ACL存储过程给网络分配访问控制列表,它有下面一些参数:
? acl - 访问控制列表XML文件的名字? host - 主机名,域名,ip地址或分配的子网,主机名大小写敏感,ip地址和域名允许使用通配符? lower_port - 默认值是NULL,为connect权限指定低端口范围? upper_port - 默认值是NULL,如果指定了lower_port,同时upper_port的值为 NULL,它就认为upper_port等同于lower_port
下面的代码展示了前面创建的访问控制列表被分配一个特定的ip地址和一个子网。
BEGIN
DBMS_NETWORK_ACL_ADMIN.assign_acl (
acl => 'test_acl_file.xml',
host => '192.168.2.3',
lower_port => 80,
upper_port => NULL);
DBMS_NETWORK_ACL_ADMIN.assign_acl (
acl => 'test_acl_file.xml',
host => '10.1.10.*',
lower_port => NULL,
upper_port => NULL);
COMMIT;
END;
/
只有一个访问控制列表可以分配给一个特殊的主机+端口范围的组合,给主机+端口范围分配一个新的访问控制列表将导致前面分配的访问控制列表被删除,在你 开始一个新的分配操作时要特别留意前一个访问控制列表关闭的端口现在又被你打开了,因此分配给192.168.2.3:80的访问控制列表的优先级比分配 给192.168.2.*的访问控制列表要高。
UNASSIGN_ACL存储过程允许你手动删除访问控制列表,它使用的参数与ASSIGN_ACL存储过程相同,使用NULL参数作为通配符。
BEGIN
DBMS_NETWORK_ACL_ADMIN.unassign_acl (
acl => 'test_acl_file.xml',
host => '192.168.2.3',
lower_port => 80,
upper_port => NULL);
COMMIT;
END;
/
访问控制列表视图
DBA_NETWORK_ACLS, DBA_NETWORK_ACL_PRIVILEGES和USER_NETWORK_ACL_PRIVILEGES视图显示当前的访问控制列表设置,下面预期的输出认为没有执行过delete/drop/unassign操作。
DBA_NETWORK_ACLS视图显示关于网络和访问控制列表分配的信息。
COLUMN host FORMAT A30
COLUMN acl FORMAT A30
SELECT host, lower_port, upper_port, acl
FROM dba_network_acls;
HOST
------------------------------
10.1.10.*
192.168.2.3
LOWER_PORT UPPER_PORT ACL---------- ---------- ------------------------------ /sys/acls/test_acl_file.xml
80 80 /sys/acls/test_acl_file.xml
2 rows selected.
SQL>
DBA_NETWORK_ACL_PRIVILEGES视图显示关于与访问控制列表联合的权限信息。
COLUMN acl FORMAT A30
COLUMN principal FORMAT A30
SELECT acl,
principal,
privilege,
is_grant,
TO_CHAR(start_date, 'DD-MON-YYYY') AS start_date,
TO_CHAR(end_date, 'DD-MON-YYYY') AS end_date
FROM dba_network_acl_privileges;
ACL PRINCIPAL
------------------------------ ------------------------------
/sys/acls/test_acl_file.xml TEST1
/sys/acls/test_acl_file.xml TEST2
PRIVILE IS_GR START_DATE END_DATE------- ----- ----------- -----------connect true 02-APR-2008connect false2 rows selected.
SQL>
USER_NETWORK_ACL_PRIVILEGES视图显示当前用户网络访问控制列表设置。
CONN test1/test1@db11g
COLUMN host FORMAT A30
SELECT host, lower_port, upper_port, privilege, status
FROM user_network_acl_privileges;
HOST LOWER_PORT UPPER_PORT PRIVILE STATUS
------------------------------ ---------- ---------- ------- -------
10.1.10.* connect GRANTED
192.168.2.3 80 80 connect GRANTED
2 rows selected.
SQL>
CONN test2/test2@db11g
COLUMN host FORMAT A30
SELECT host, lower_port, upper_port, privilege, status
FROM user_network_acl_privileges;
HOST LOWER_PORT UPPER_PORT PRIVILE STATUS
------------------------------ ---------- ---------- ------- -------
10.1.10.* connect DENIED
192.168.2.3 80 80 connect DENIED
2 rows selected.
SQL> 转权限检查
处理访问控制列表视图外,还可以使用DBMS_NETWORK_ACL_ADMIN包中的CHECK_PRIVILEGE和CHECK_PRIVILEGE_ACLID函数来检查权限。
CONN sys/password@db11g AS SYSDBA
SELECT DECODE(
DBMS_NETWORK_ACL_ADMIN.check_privilege('test_acl_file.xml', 'TEST1', 'connect'),
1, 'GRANTED', 0, 'DENIED', NULL) privilege
FROM dual;
PRIVILE
-------
GRANTED
1 row selected.
SQL>
COLUMN acl FORMAT A30
COLUMN host FORMAT A30
SELECT acl,
host,
DECODE(
DBMS_NETWORK_ACL_ADMIN.check_privilege_aclid(aclid, 'TEST2', 'connect'),
1, 'GRANTED', 0, 'DENIED', NULL) privilege
FROM dba_network_acls;
PRIVILE
-------
DENIED
1 row selected.
SQL>
DBMS_NETWORK_ACL_UTILITY包包括了帮助判断可能匹配的域的函数,DOMAINS表函数按顺序返回所有可能受影响的主机,域,ip地址或子网的集合。
SELECT *
FROM TABLE(DBMS_NETWORK_ACL_UTILITY.domains('oel5-11g.localdomain'));
COLUMN_VALUE
-------------------------------
oel5-11g.localdomain
*.localdomain
*
3 rows selected.
SQL>
SELECT *
FROM TABLE(DBMS_NETWORK_ACL_UTILITY.domains('192.168.2.3'));
COLUMN_VALUE
-------------------------------
192.168.2.3
192.168.2.*
192.168.*
192.*
*
5 rows selected.
SQL>
DOMAIN_LEVEL函数返回主机,域,ip地址或子网的级数。
SELECT DBMS_NETWORK_ACL_UTILITY.domain_level('oel5-11g.localdomain')
FROM dual;
DBMS_NETWORK_ACL_UTILITY.DOMAIN_LEVEL('OEL5-11G.LOCALDOMAIN')
-------------------------------------------------------------
2
1 row selected.
SQL>
SELECT DBMS_NETWORK_ACL_UTILITY.domain_level('192.168.2.3')
FROM dual;
DBMS_NETWORK_ACL_UTILITY.DOMAIN_LEVEL('192.168.2.3')
----------------------------------------------------
4
1 row selected.
SQL>
在为可能匹配的主机,域,ip地址或子网查询访问控制列表视图是这些函数可能非常有用。
SELECT host,
lower_port,
upper_port,
acl,
DECODE(
DBMS_NETWORK_ACL_ADMIN.check_privilege_aclid(aclid, 'TEST1', 'connect'),
1, 'GRANTED', 0, 'DENIED', null) PRIVILEGE
FROM dba_network_acls
WHERE host IN (SELECT *
FROM TABLE(DBMS_NETWORK_ACL_UTILITY.domains('10.1.10.191')))
ORDER BY
DBMS_NETWORK_ACL_UTILITY.domain_level(host) desc, lower_port, upper_port;
HOST LOWER_PORT UPPER_PORT ACL PRIVILE
------------------------------ ---------- ---------- ------------------------------ -------
10.1.10.* /sys/acls/test_acl_file.xml GRANTED
1 row selected.
SQL> 测试访问控制列表
用户TEST1和TEST2分别拥有了允许的和拒绝的访问控制列表,这就意味着我们可以开始通过对比对访问外部网络服务时它们的响应来测试访问控制列表的功能,下面的代码授予了这两个用户都可以执行UTL_HTTP包的权限,然后尝试从每个用户访问一个web页面。
CONN sys/password@db11g AS SYSDBA
GRANT EXECUTE ON UTL_HTTP TO test1, test2;
CONN test1/test1@db11g
DECLARE
l_url VARCHAR2(50) := 'http://192.168.2.3:80';
l_http_request UTL_HTTP.req;
l_http_response UTL_HTTP.resp;
BEGIN
-- Make a HTTP request and get the response.
l_http_request := UTL_HTTP.begin_request(l_url);
l_http_response := UTL_HTTP.get_response(l_http_request);
UTL_HTTP.end_response(l_http_response);
END;
/
PL/SQL procedure successfully completed.
SQL>
CONN test2/test2@db11g
DECLARE
l_url VARCHAR2(50) := 'http://192.168.2.3:80';
l_http_request UTL_HTTP.req;
l_http_response UTL_HTTP.resp;
BEGIN
-- Make a HTTP request and get the response.
l_http_request := UTL_HTTP.begin_request(l_url);
l_http_response := UTL_HTTP.get_response(l_http_request);
UTL_HTTP.end_response(l_http_response);
END;
/
DECLARE
*
ERROR at line 1:
ORA-29273: HTTP request failed
ORA-06512: at "SYS.UTL_HTTP", line 1029
ORA-24247: network access denied by access control list (ACL)
ORA-06512: at line 7
SQL>
从返回的信息我们不难看出用户TEST1能够访问web页面,而用户TEST2被访问控制列表拒绝了,服务器的默认行为是拒绝访问外部网络服务,下面显示了一个新用户的测试情况。
CONN sys/password@db11g AS SYSDBA
CREATE USER test3 IDENTIFIED BY test3;
GRANT CONNECT TO test3;
GRANT EXECUTE ON UTL_HTTP TO test3;
CONN test3/test3@db11g
DECLARE
l_url VARCHAR2(50) := 'http://192.168.2.3:80';
l_http_request UTL_HTTP.req;
l_http_response UTL_HTTP.resp;
BEGIN
-- Make a HTTP request and get the response.
l_http_request := UTL_HTTP.begin_request(l_url);
l_http_response := UTL_HTTP.get_response(l_http_request);
UTL_HTTP.end_response(l_http_response);
END;
/
DECLARE
*
ERROR at line 1:
ORA-29273: HTTP request failed
ORA-06512: at "SYS.UTL_HTTP", line 1029
ORA-24247: network access denied by access control list (ACL)
ORA-06512: at line 7
SQL>
在从10g升级到11g时,访问外部网络服务时可能会产生一些混乱,在那种情况下,你需要实现合理的访问控制列表。
其他安全因素
Pete Finnigan在它的博客上和关于访问控制列表的安全陈述只没有附上具体的程序包,这就意味着通过UTL_TCP, UTL_SMTP, UTL_MAIL和UTL_HTTP加上connect权限就能在服务器上打开一个端口。牢记这一点并考虑以下事项:
◆细粒度访问网络服务的使用不能作为忽略基本的安全评估的借口,如收回与网络服务有关程序包的不必要的权限。
◆通过限制对特定端口的访问控制你的服务是可用的,如果你仅仅需要访问http 80端口,指定这个端口比在服务器上开放所有端口的访问要好得多。
◆授权时使用通配符比不使用通配符安全性更差,也更危险。
◆你必须保护你的访问控制列表,如果有人能够修改它们,因为保护机制问题它们变得毫无用处,阻止直接访问存储在XML DB 数据库中的访问控制列表,确保用户不能访问管理API.