第 2 阶段
持续时间: 一周
这是安全性和合规性项目的第 2 阶段。 看看如何在 5 个工作日内保护您的基础架构。
尽管“安全保护项目”的第 1 阶段仅持续了 24 小时,但在该阶段您已完成了很多操作。 现在将进入第 2 阶段,持续时间大约为一周。 在本阶段,您将接着上一阶段执行的操作继续执行操作,并取得一些新的进展。 与以前一样,您将采取一些简单但却高效的操作来保护数据库。
您可能会问,为什么本阶段要持续一周的时间呢?
- 某些更改需要设置参数,而这些设置将导致数据库重复启动,并可能执行一些规划。
- 某些更改基于第 1 阶段操作并可能需要一些仔细的规划。
下面我们就开始吧!
这一部分涉及:
· 2.1 删除 utl_file_dir
· 2.2 限制 OS 验证
· 2.3 禁用远程 OS 验证
· 2.4 使用产品配置文件保护 SQL*Plus
· 2.5 控制 SQL*Plus
· 2.6 包装机密代码
· 2.7 将间接授权转换为直接授权
· 2.8 限制表空间限额
· 2.9 监控监听器日志以获知尝试的非法侵入
· 2.10 审计和分析用户访问
2.1 删除 utl_file_dir
背景
Oracle 支持通过 SQL 和/或 PL/SQL 从数据库中操作 OS 文件,而不必访问主机操作系统。 此功能由内置的 PL/SQL 程序包 utl_file 提供。 例如,下面的代码打开目录 /tmp 中一个名为 myfile.txt 的文件并向其中写入行“Some Text”:
1 declare 2 l_filename utl_file.file_type; 3 begin 4 l_filename := utl_file.fopen ('/tmp','myfile.txt','W'); 5 utl_file.put_line (l_filename, 'Some Text', TRUE); 6 utl_file.fclose(l_filename); 7 end;
如果文件 myfile.txt 不存在,则此代码将创建 myfile.txt。 但要使此代码正常运行,您必须通过以下初始化参数将 /tmp 声明为一个可以由 utl_file 程序包打开的目录:
utl_file_dir = '/tmp'
然后重新启动数据库。
只要该目录由 Oracle 软件拥有者拥有,使用此参数表示用户可以在目录 /tmp 中创建文件,也可以覆盖其中的任何文件。 那么,设置该参数有错么?
没有,但大多数系统不会将此参数设置为 /tmp。 相反,为了使开发人员或用户可以操作文件,而事先并不知道创建文件的位置,该设置通常为“*”,如下所示:
utl_file_dir = '*'
这意味着只要 Oracle 软件拥有者拥有文件(甚至是 Oracle 数据文件)的读取或写入权限,用户便可以打开文件。从理论上来讲,这将使恶意攻击者可以写入一个故意破坏数据文件的程序。 如果该文件恰好用于 SYSTEM 表空间,则整个数据库将崩溃。(当然,您可以从备份恢复它,但损害已经在所难免。) 更糟糕的是,恶意攻击者可能会破坏存档的日志文件,从而导致无法通过此存档日志进行恢复。恶意攻击者甚至还可以使用 utl_file.fremove 提供的程序包删除文件。
因此,将 utl_file_dir 设置为“*”并不可取。
策略
在 Oracle9i 数据库和更高版本中,无需使用 utl_file_dir - 程序将使用 directory 对象指定位置,而非绝对目录。 因此,在以上示例中(在该示例中,该应用程序需要打开 /tmp 目录中的文件 myfile.txt),该应用程序可以通过以下代码执行此操作:
1 declare 2 l_filename utl_file.file_type; 3 begin 4 l_filename := utl_file.fopen ('TMP_DIR','myfile.txt','W'); 5 utl_file.put_line (l_filename, 'Some Text', TRUE); 6 utl_file.fclose(l_filename); 7 end;
目录对象 TMP_DIR 必须已在前面通过以下命令创建:
create directory TMP_DIR as '/tmp';
该命令(即 create directory)只提供一次。 随后,任何具有该目录访问权限的用户均可以从中创建或读取文件。
您可以通过精细的方式授予此目录对象。 例如,假设您为警报日志所在的目录创建了一个目录对象,并创建了一个读取和操作此警报日志的应用程序。 您可以将该目录的读取权限授予 SCOTT,而将写入权限授予 DBA ananda。
因此,请从数据库参数中删除 utl_file_dir 并使用目录对象替换此代码。
随后,您还应删除 PUBLIC 以及任何其他用户所拥有的 create directory 系统权限。
可能的影响
可能的影响取决于 utl_file 程序包用于操作外部文件的频次。 如果从未使用此程序包,则可以立即删除它。 如果将要使用它,则首先需要进行规划。
操作计划
-
浏览此代码可以看到以下字符串: utl_file.fopen。
-
收集第一个参数中的所有名称。 例如,当您看到 utl_file.fopen('/tmp'、'myfile.txt'、'W')时,请记录值“/tmp”。 浏览所有文件以获得所有目录位置。可能有 10 个文件使用“/tmp”,有 5 个文件使用“/ftpdata”等等。
-
以用户 SYS 的身份分别为这些目录创建一个目录对象,例如:
create directory TMPDIR for '/tmp';
- 将这些目录授予访问它们的用户,例如:
grant read on directory TMPDIR to SCOTT;
- 从代码中删除目录名,并使用目录对象名称替换它。 例如,行:
utl_file.fopen ('/tmp','myfile.txt','W')
将变为
utl_file.fopen ('TMPDIR','myfile.txt','W')
-
重新编译程序。
- 删除 PUBLIC 以及除 DBA 以外的所有用户所拥有的 create any directory 系统权限。
revoke create any directory from public;
背景
可以通过不同的方式(最常见的方式是通过数据库验证)验证 Oracle 用户。 使用 create user ananda identified by abc123 命令行创建用户后,该用户登录数据库的唯一方法就是传递它的用户 ID 和口令。
一种替带方法是操作系统验证,该方法使用以下命令行创建用户:
create user ops$ananda identified externally;
如果主机操作系统有一个名为“ananda”的用户 ID,则 Oracle 将不再检查它的证书。 它只假设该主机一定已经执行了它的验证,从而允许用户登录数据库而不必进行任何进一步的检查。
这正是问题的所在。 如果主机操作系统的验证机制比较强大,则它可能是安全的;但在某些脆弱的 OS 中,可以用户身份通过破解口令或不输入口令来进行登录:
sqlplus /
注意,以上命令缺少用户 ID 和口令 - 字符串“/”指示数据库接受用户 ID ananda 到数据库帐户 ops$ananda 的连接。
此类验证通常用于 shell 脚本,因此您不必在脚本中嵌入口令,而只需使用 sqlplus / 调用它。 由于不存在口令,因此该方法不但方便而且在某种程度上比较安全。 但请注意以下情形: 在安全性薄弱的 OS 中,某人可以创建一个名为 ananda 的帐户,然后使用它登录到帐户 ops$ananda。
该帐户一定为 ops$ 么? 不一定;您可以通过设置初始化参数更改它。 在以下示例中,我已将它设置为 osauthent$。
os_authent_prefix = 'osauthent$'
可以使用以下查询找到这些用户:
SQL> select username, password from dba_users 2 where password = 'EXTERNAL' 3 / USERNAME PASSWORD ------------------------------ ------------------------------ OPS$ANANDA EXTERNAL OPS$ORACLE EXTERNAL
按如上所示设置初始化参数时,帐户 ops$ananda 将不起作用;相反,您需要将这些帐户(OS 验证的帐户)创建为 osauthent$ananda。 有趣的是,您还可以将它设置为 ""(空)。 这时,OS 用户 ananda 将映射到 Oracle 用户 ananda。 您甚至还可以为此帐户设置口令:
alter user ops$ananda identified by oracle;
这种情况下,用户可以通过以下两种方法登录数据库:
sqlplus / sqlplus ops$ananda/oracle
那么,这样做有什么不妥么? 先来看一看下面的情形。 假设参数 os_authent_prefix 设置为 ""(空)。 在安全性薄弱的 OS 中,某人可以创建一个名为 SYSTEM 的用户并使用以下命令登录
sqlplus /
这将把该用户记录为 Oracle 用户 SYSTEM! 登录后,该用户可以执行所需的任何操作 - 创建用户、删除数据文件、查看机密数据以及大量其他操作。 看似方便的事情突然间成了一个大麻烦。
策略
您可以看到,此问题只会在所出现的情况的某些组合中发生。 其中之一是 OS_AUTHENT_PREFIX 不为空,另一个是为 OS 验证的帐户设置口令。 因此,第一个需要检查的对象就是 OS 验证前缀。
SQL> select value 2 from v$parameter 3 where name = 'os_authent_prefix'; VALUE ----------------------- ops$
如果以上查询返回空,则应制订规划来更改它。 实际值并不重要,但您必须包含某个非字母字符。 这样,OS 验证的用户名将始终不会匹配实际用户。
其次,您需要确保 OS 验证的帐户应完全按照 OS 所采用的方式得到验证,并从不包含口令。 例如,如果 OS_AUTHENT_PREFIX 设置为 OPS$,则您将使用以下查询查明是否设置了口令:
SQL> select username, password from dba_users 2 where username like 'OPS$%'; USERNAME PASSWORD ------------------------------ ------------------------------ OPS$ORACLE 17C96FEC14DC431F OPS$ANANDA EXTERNAL
该查询表明用户 OPS$ORACLE 无法通过 OS 验证路由或口令路由登录。 这正是您希望避免的情况;应只存在一种验证方法。 要更改 OPS$ORACLE 的验证模式,应使用:
alter user OPS$ORACLE identified externally;
该命令将 PASSWORD 列更改为 EXTERNAL。
可能的影响
根据这些帐户的使用情况,这些更改的影响可能会比较广泛。 如果您拥有此类帐户的任何一个,请浏览程序以便了解如何轻松地更改它们。
操作计划
查明哪些程序正在使用 OPS$ 帐户。 如果没有,则 | ||
检查初始化参数 os_authent_prefix 如果它为空,则 | ||
将它更改为 OPS$(需要重新启动数据库) | ||
检查 OPS$ 帐户的口令 如果不为 EXTERNAL,则 | ||
将其更改为 EXTERNAL | ||
如果找到一些这样的程序,则 | ||
检查它们是否还将该帐户用作口令(例如,OPS$ORACLE/mypass)。 如果使用了口令,则删除它 - 例如,行 sqlplus OPS$ORACLE/mypass 应变为 sqlplus /。 |
背景
远程 OS 验证是一个相似的过程。 在 OS 验证中,您可以创建在本地操作系统中进行验证的用户。 例如,如果 UNIX 服务器上有一个名为 ananda 的用户,而同一服务器上运行的数据库中有一个名为 OPS$ANANDA 的用户,则用户 ananda 只需发出以下命令即可登录到该数据库:
sqlplus /
该方法适用于本地用户 - 数据库运行所在的同一服务器上的用户。但当远程服务器的用户尝试连接到此数据库时,情况将如何? 例如,假设有两个服务器 - node1 和 node2,且数据库在 node1 服务器上运行。 该数据库有一个在外部标识的用户 OPS$ANANDA。 服务器 node2(而非 node1)上有一个用户 ananda。当服务器 node2 上的用户 ananda 通过发出以下命令尝试连接到 node1 上的数据库时:
sqlplus /@node1
他能否连接成功?
答案取决于数据库初始化参数 remote_os_authent 的设置。 如果该参数设置为 TRUE,则此连接尝试将取得成功;如果为 FALSE,则不允许来自远程服务器的 OS 验证连接。 默认值为 FALSE。
为什么? 理由很充分: 在您的数据库服务器上,您可能拥有很高的安全性,因此 OS 验证的用户可能并不算是问题。 而客户端计算机则不然。根据 OS 的不同,恶意攻击者能够在远程服务器上创建一个名为 ananda 的帐户,当该帐户连接到数据库服务器时,它可以在无需口令情况下连接到 ops$ananda。 由于您无法轻松地控制客户机,因此该功能将为您带来一个不小的漏洞。
策略
此处的任务很简单。 检查该参数是否设置为 TRUE。 如果为 true,则将其更改为 FALSE,然后重新启动数据库。
可能的影响
在许多数据库中,该参数可能已经设置为 FALSE。 但如果它设置为 TRUE,且某些客户机以 OS 验证的用户身份连接,则这些帐户将不起作用。 您可以在进行此更改之前找到它们:
select username, machine, osuser, terminal from v$session where username like 'OPS$%' /
输出可能显示类似如下的内容:
USERNAME MACHINE OSUSER TERMINAL ------------- -------------------- ------------ ----------- OPS$ANANDA PRONT/ANANDAT42 ananda ANANDAT42
此处我们看到,用户 ananda 已经以远程 OS 验证的用户身份从计算机 PRONT/ANANDAT42 登录。 此处请注意,ananda 是计算机 PRONT/ANANDAT42(而非数据库运行所在的服务器)中的用户。
操作计划
- 如果某些用户使用远程验证连接,则删除他们的无口令功能。
- 在初始化参数文件中,将 remote_os_authent 更改为 FALSE,然后重新启动数据库。
背景
使用 SQL*Plus 时,您可能注意到,您可以发出 host 命令转到 *nix 提示符,或发出 run 命令执行某个脚本。一个很少使用但却有可能造成致命破坏的攻击便源自此功能。 您可能编写了某个从 shell 中调用的 SQL*Plus 脚本,如下所示:
sqlplus someuser/somepass@remotedb @myscript.sql
此处的 myscript.sql 是一个先执行然后退出的 SQL*Plus 脚本文件。 在该文件执行过程中,用户可以按 Control-C(或特定 OS 的 break 键序列)中断程序流。 这种情况下,用户将获得 SQL*Plus 提示符(以 someuser 的身份连接)。 在此阶段,用户几乎可以执行从 SQL*Plus 提示符中所能执行的所有操作,并因此而造成某种程度的破坏。
这是一个应立即修复的安全漏洞。
策略
修复方法很简单: 您所要做的就是在运行命令之前,将这些命令限制到一个由 SQL*Plus 引用的“特殊位置”。 此特殊位置是 SYSTEM 模式中一个名为 SQLPLUS_PRODUCT_PROFILE 的表。 如果该表不存在,则您在每次启动 SQL*Plus 时将获得一个类似“Product User Profile Not Loaded”这样的警告。
SQL> select * from system.SQLPLUS_PRODUCT_PROFILE 2 / no rows selected SQL> insert into system.SQLPLUS_PRODUCT_PROFILE 2 values ('SQL*Plus','%','HOST',null,null,'DISABLED',null,null) 3 / 1 row created. SQL> commit; Commit complete.
现在,当您启动 SQL*Plus 并输入 host 命令(或它的等价形式“!”)时,您将收到以下错误:
SQL> host; SP2-0544: Command "host" disabled in Product User Profile
如果要对某个用户禁用此功能,只需使用要限制的用户名替换上面的 insert 命令中的“%”(表示所有用户)。 例如,如果只希望对用户 SCOTT 禁用此功能,请使用以下语句:
SQL> insert into system.SQLPLUS_PRODUCT_PROFILE 2 values ('SQL*Plus','SCOTT','HOST',null,null,'DISABLED',null,null) 3 /
还可以在用户名中使用通配府 - 例如,使用 APP% 表示所有以 APP 开头的用户(APPUSER1、APPUSER2)。
该方法也适用于其他命令(及其缩写或简写形式):
accept edit repheader append execute run archive log exit save attribute quit set break get show btitle help shutdown change host spool clear input start column list startup compute password store connect pause timing copy print ttitle define prompt undefine del recover variable describe remark whenever oserror disconnect repfooter whenever sqlerror
除了 SQL*Plus 以外,该方法还适用于 SQL 命令。 例如,如果要禁用 lock 命令,则需要插入:
insert into system.SQLPLUS_PRODUCT_PROFILE values ('SQL*Plus','%','LOCK',null,null,'DISABLED',null,null) /
注意,SQL 命令 lock 已被禁用,此外我们还在上面使用了“SQL*Plus”关键字。
可以禁用的 SQL 命令如下:
alter drop revoke analyze explain rollback associate flashback savepoint audit grant select call insert set constraints comment lock set role commit merge set transaction create noaudit truncate delete purge update disassociate rename validate
可能的影响
开始前,请注意一个重要问题: 该方法适用于在服务器本身中运行的 SQL*Plus, 而不会影响在其他位置运行(如在客户端中运行)的 SQL*Plus。
该操作需要仔细的规划。 如果 shell 脚本依赖于 SQL*Plus 中的 host 命令,则禁用它们将会明显停止这些作业。 一个非常典型的例子就是旧式的 host backup 命令。 一个典型的 SQL 脚本可能如下所示:
alter tablespace xyz begin backup; host cp /fs1/xyz.dbf /backup/xyz.dbf alter tablespace xyz end backup;
等等(适用于所有表空间)。 该脚本将注定失败 - 除非以 SYS 用户的身份运行它,这时命令禁用将不起作用。
操作计划
当所有这些命令使用的可能性最小时,从 SQL*Plus 中禁用它们。
背景
根据 SQL*Plus 脚本用法的不同,您可能会也可能不会受到前面描述的威胁。 无论在哪种情况下,采取预防性措施降低威胁级别都是很有好处的。 方法之一是按照前面所述在产品配置文件中禁用命令,而自从 Oracle9i 数据库推出后,一个更简单的可用方法是使用 SQL*Plus 限制。 在该方法中,您必须使用一个新参数 restrict 调用 SQL*Plus。 限制分多个不同的级别。 级别 1 只禁用 host 和 edit 命令。
sqlplus a€“restrict 1 scott/tiger
如果用户在 shell 内部调用 host 命令,则将收到:
SQL> host SP2-0738: Restricted command "host" not available
注意,此错误消息不同于产品用户配置文件消息。
使用 -restrict 2 除了禁用 host 和 edit 以外还将禁用 save、store 和 spool 命令。 使用 -restrict 3 可以将 get 和 start 添加到现有列表。
最重要的差别体现在,所有用户(甚至是 SYS)都将受到此限制的影响。 产品配置文件限制不适用于 SYS。
策略
这可能是一个比使用产品用户配置文件更好的方法。 在本示例中,在 $ORACLE_HOME/bin 中创建一个名为“sqlplus”的 shell 脚本,并将 sqlplus 可执行文件重命名为类似“sqlplus_orig”这样的名称。 在脚本“sqlplus”中放置以下行。
$ORACLE_HOME/bin/sqlplus_orig a€“restrict 1
这将与命令行参数具有相同的效果。 当然,您可以向 restrict 中添加所需的任何参数。
可能的影响
该任务可能产生的影响与前一个任务可能产生的影响类似,但由于它们还适用于 SYS,因此其范围略大一些。 如果脚本调用受限制的命令,则您可能需要修改它们或使用一个非限制版本。
操作计划
标识调用受限制命令的 SQL 脚本。 如果找到它们,则 | |
修改它们,或 使用非限制版本 | |
否则 | |
将 sqlplus 重命名为 sqlplus_orig 创建具有限制的文件 sqlplus |
背景
您可能在存储过程和函数的内部放置了口令或密钥等机密信息。 这种情况下,恶意攻击者可以轻松地从代码中选择该信息。 以下是我最近审计的一个网站中的示例:
l_password := 'GobbleDGook';
恶意攻击者可以使用以下查询选择源代码:
select text from dba_source where name = 'MYFUNC';
消除此风险的最佳方法是使用 wrap 实用程序。 创建脚本文件以创建过程或函数后,请使用以下代码包装它:
wrap iname=myfunc.sql oname=myfunc.plb
此代码将创建一个采用无法破解的方式存储源代码的文件 myfunc.plb,从而保护您的代码。
在 Oracle 数据库 10g 第 2 版中,您可以通过调用支持的存储过程直接创建包装代码,而不使用脚本:
begin dbms_ddl.create_wrapped ('create or replace function myfunc ...') end;
也可以使用此代码编写您自己的包装实用程序:
1 declare 2 l_input_code dbms_sql.varchar2s; 3 begin 4 l_input_code (1) := 'Array to hold the MYP'; 5 l_input_code (2) := 'create or replace procedure myproc as '; 6 l_input_code (3) := ' l_key VARCHAR2(200);'; 7 l_input_code (4) := 'begin '; 8 l_input_code (5) := ' ...;'; 9 l_input_code (6) := 'end;'; 10 l_input_code (7) := 'the end'; 11 sys.dbms_ddl.create_wrapped ( 12 ddl => l_input_code, 13 lb => 2, 14 ub => 6 15 ); 16* end;
然后将所有代码置于输入可变数组中以进行包装。
另一个需要注意的问题是: 在 Oracle9i 中,包装实用程序并不包装变量值。 因此,password 和 keys 等机密单词清晰地显示在包装代码的内部。
让我们来看一个示例。 以下是文件 a.sql 的内容:
create or replace procedure myproc as l_v varchar2(200); begin l_v := 'SecretKey'; end;
我将机密单词的值用作 SecretKey。 下面将包装源代码:
$ wrap iname=a.sql oname=a.plb
现在,如果您在包装代码的内部检查,则仍将看到明文中的值:
$ grep SecretKey a.plb 1SecretKey:
Oracle 数据库 10g 第 1 版和更高版本中并不存在此问题。
要保护变量值,应降低代码内部的明文值的可读性。 例如,代码可能显示如下:
create or replace procedure myproc as l_v1 varchar2(200); l_v2 varchar2(200); l_v3 varchar2(200); l_lv varchar2(200); begin l_v1 := 'LotsOfJunktoMakeSureSecurityIsWaterTight'; l_v2 := 'AdditionalValueToInterpretTheSecretWord'; l_v3 := 'WowWeDontStopAtTheKeyDoWe'; l_lv := substr(l_v1,21,3); l_lv := l_lv||substr(l_v2,24,3); l_lv := l_lv||substr(l_v3,19,3); end; /
注意我们如何将机密单词的各个部分嵌套在字符串中并在代码内部使用位置。 您在第一个字符串的内部看不到代码的第一部分。
策略
无法清楚地标识引用此机密数据的源代码。 您最好询问开发组以了解哪些用户可能使用机密单词。 也可以执行初步检查以查明代码是否包含 PASSWORD、SECRET、ENC(针对 ENCrypted、ENCryption、ENCiphered、ENCoded)、DEC 等单词。
select name, text from dba_source where upper(text) like '%PASSWORD%' or upper(text) like '%ENC%' or upper(text) like '%DEC%' or upper(text) like '%SECRET%' or upper(text) like '%PASS%' /
这可能会提供一些线索来帮助您标识可能要检查的代码段。 标识所有此类代码后,应使用 wrap 实用程序包装它并运行包装代码。
可能的影响
不存在任何可能的影响。 但您应该知道一个非常重要的问题: 包装是单项进行的;您可以包装明文代码,但不能从包装代码中创建明文。 因此,应将明文代码保存在某个安全的位置以便进一步编辑。 如果丢失明文代码,则将永远无法更改代码。
操作计划
标识包含机密数据的代码。 如果在 Oracle9i 中,则 | |
将值分解为多个部分并将每个部分嵌套在短语中。 创建一个变量以提取短语中的各个部分。 重新构造代码中的值。 | |
否则 | |
不执行任何操作。 | |
ENDIF 从明文中创建脚本文件。 包装脚本。 运行包装脚本。 |
背景
授予权限时,可以选择使用 with grant option 子句,以便被授权者可以进一步授予权限。 以下是一个有关授予用户 A 拥有的表 TAB1 的权限的示例。
Step1 Connect A/****** Grant select on tab1 to B;
Step 2 Connect B/****** Grant select on a.tab1 to C;
用户收到错误
ORA-01031:insufficient privileges
原因是用户 B 无权授予它本身从某个用户那里收到的权限。 但在第 1 步中,如果语句为
Grant select on tab1 to b with grant option;
则用户 B 将有权进一步授予该权限,且第 2 步将成功。
同样,B 也可以使用 grant option 将该权限授予 C,而 C 随后可以将该权限授予 D,依此类推。
表面看来它似乎是一个不错的规划。 最初所有者 A 不必担心向哪个用户授予或撤销权限;该过程是按需进行自我管理的。 那么,问题是什么呢?
来看看以下情形:
Connect A/***** Revoke select on tab1 from B.
注意,C 从 B 那里获得它对 TAB1 的权限,而并非从 A 那里直接获得;因此,如果 B 丢失了这些权限,那么 C 的权限将如何? C 也将丢失它的权限,这是因为该权限是一个派生的权限。
我们进一步假设 A 已经向 C 直接授予了对 TAB1 的 select 权限。 C 现在对 TAB1 有两个权限,一个来自 B,一个来自 A。当您撤销某个权限时,另一个权限仍然有效,从而使您错误地认为该权限不存在。
尽管该过程表面看来很不错,但实际上缺产生混淆和安全漏洞,并带来难于跟踪的错误。 一种更有意义的做法是在无中间方的情况下直接授予权限。
策略
您的目标是标识哪些权限是通过其他用户授予的,然后直接授予权限。 可以从视图 DBA_TAB_PRIVS 中清楚地看到这些权限,其中的列 grantor 显示了授予权限的用户。
SQL> col grantee format a15 SQL> col privilege format a15 SQL> col owner format a20 SQL> col table_name format a20 SQL> select grantee, privilege, owner, table_name 2 from dba_tab_privs 3* where grantor != owner 4 /
下面显示了一个简单的输出。
PUBLIC EXECUTE XDB.DBMS_XMLSCHEMA SYS PUBLIC EXECUTE XDB.XDB_PRIVILEGES SYS PUBLIC EXECUTE XDB.DBMS_XMLSCHEMA_INT SYS APP1 SELECT ANANDA.MP RUSER
前三行可以忽略,在这三行中授权是由用户 SYS 向角色 PUBLIC 发出的。 该权限是模式 XDB 拥有的程序包 DBMS_XMLSCHEMA 的权限。 作为由 Oracle 提供的特殊程序包,这样做是允许的;但应注意第四行。 由 ANANDA 拥有的表 MP 是由 RUSER 授予的,因此应对其进行更正。 修复方法其实很简单: 将该对象的 select 直接授予 APP1,即使 RUSER 拥有 with grant option 权限。
执行该操作有两种方法:
- 对象拥有者直接授予该权限。
- SYS 这样的超级用户授予该权限。
第二种方法更易于实施。 SYS 用户实际上并不继承授权;它通过使用系统权限 grant any object privilege 授予权限。 当 SYS 使用以下代码授予权限时:
grant select on a.tab1 to c;
GRANTOR 列将显示 A 而不是 SYS;这正是您所需要的。
set lines 300 set pages 0 spool grant_direct.sql select 'grant '||privilege||' on '||owner|| '.'||table_name||' to '||grantee||';' from dba_tab_privs where grantor != owner / spool off
现在,运行文件 grant_direct.sql 以直接授予权限。
成功授予权限后,必须撤销间接授予的权限。 该操作无法在单个语句中实现,原因是您还必须以授权者的身份连接。
break on conn skip 2 select 'connect '||grantor conn, 'revoke '||privilege||' on '||owner|| '.'||table_name||' from '||grantee||';' line from dba_tab_privs where GRANTOR != 'SYS' and grantor != owner order by 1,2 /
将该脚本假脱机到某个文件,编辑该脚本以为每个用户提供口令,然后执行它以撤销授权。
可能的影响
有两个可能的影响。 首先,由于您要撤销权限并重新授予它们,因此可能由于无法重新授予权限而引起错误。 因此,必须在此更改的前后获得权限快照以确保成功。 使用以下脚本查找权限:
SQL> select grantee, privilege, owner, table_name 2 from dba_tab_privs 3* where grantor != owner 4 /
在更改的前后运行该脚本,保存输出,然后比较它们以确保权限保持原样。
第二个可能的影响更明显。 授予和撤销周期将使这些对象的游标在库缓存中无效并将迫使对游标进行重新分析,从而将临时降低性能。
此外,某些相关对象将失效。 由于再次授予了权限,因此对象在被引用时将正常编译,但您可能需要采取某个主动式操作并事先重新编译它们。
操作计划
- 查找由其他用户使用 grantable 选项授予的权限。
- 撤销权限。
- 在不使用 grant 选项的情况下重新授予权限。
- 检查无效对象并重新编译它们。
背景
用户可以使用表空间内部多大的空间?用户可以写入多少个表空间? 答案取决于可供用户使用的表空间的份额。 可以按如下所示指定份额:
alter user ananda quota 12M on users;
此代码允许用户 ananda 创建总大小不超过 12MB 的存储对象,如表、索引以及物化视图。 要确认或查明用户已经使用的空间大小,请发出以下查询
SQL> col used format 999,999.999 head "Used (MB)" SQL> col quota format 999,999.999 head "Quota (MB)" SQL> col tablespace_name format a15 SQL> select username, tablespace_name, 2 bytes/1024/1024 used, 3 max_bytes/1024/1024 quota 4 from dba_ts_quotas 5 order by username 6 /
示例输出如下。
USERNAME TABLESPACE_NAME Used (MB) Quota (MB) ------------------- --------------- ------------ ------------ USER1 USERS .000 100.000 USER1 APP1_INDEX 504.875 -.000 USER2 USERS .125 5.000
我们需要对以上输出进行一下说明。 该输出表明用户 USER1 在表空间 USERS 中的份额为 100MB(显示在列 Quota 的下面)。 而用户未使用该分额中的任何表空间(显示在列 Used 的下面)。 第二行很有趣 - 您可以看到 Quota 列显示“-0”。 它指示用户在该表空间 (APP1_INDEX) 中具有无限的表空间权限。 用户 USER2 在表空间 USERS 中的份额为 5MB,并只使用了其中的 0.125 MB。
您应监视的是无限的表空间。 可以使用以下代码向用户提供无限份额:
alter user ananda quota unlimited on users;
但该操作可能对安全性产生影响;如果常规用户对业务关键的表空间具有无限的表空间份额,则该用户可能会用尽该表空间 - 这一点类似于拒绝服务攻击。
一个更严重的风险是系统权限 UNLIMITED TABLESPACE,它使用户在所有表空间中均具有无限份额,而没有向他们授予特定份额。 请让我再次重申一下: 用户在所有表空间(包括 SYSTEM)中均具有无限的份额,因此用户可以从中创建对象。 这样并不好。
首先,检查 SYSTEM 上的任何显式表空间份额:
select username, bytes/1024/1024 used, max_bytes/1024/1024 quota from dba_ts_quotas where tablespace_name = 'SYSTEM' order by username;
如果该查询显示某些内容,则应对其进行评估并在必要情况下撤销该份额。
下一步是标识具有无限表空间系统权限的用户。
select grantee from dba_sys_privs where privilege = 'UNLIMITED TABLESPACE';
由于该系统权限也包含 SYSTEM 表空间的权限,因此应对该列表进行仔细评估。
策略
您现在已经标识了所有用户及其表空间份额,下一个任务就是降低它们的风险。 此处有两个任务,一个任务比另一个任务更具破坏性。
首先,尝试删除 SYSTEM 表空间中的无限份额。 执行该操作不会对应用程序造成严重的破坏。 但在执行该操作之前,您需要确保 SYSTEM 表空间不包含 SYS 模式外部的对象。 以下查询可以实现此目的。
select owner, segment_type, segment_name from dba_segments where tablespace_name = 'SYSTEM' and owner not in ('SYS','SYSTEM');
输出如下
OWNER SEGMENT_TYPE SEGMENT_NAME --------------- --------------- -------------- OUTLN INDEX OL$HNT_NUM OUTLN INDEX OL$SIGNATURE OUTLN INDEX OL$NAME OUTLN TABLE OL$NODES OUTLN TABLE OL$HINTS OUTLN TABLE OL$
在该示例中,只有 OUTLN 对象位于 SYSTEM 表空间中,这是可以接受的。 如果看到任何其他对象,则应移动它们。
该问题的根本原因可能是
select username from dba_users where default_tablespace = 'SYSTEM';
它将只返回以下内容。
USERNAME ---------- SYSTEM SYS OUTLN
如果它显示其他用户名,请将该用户更改到其他表空间。 例如,要将用户 SCOTT 的默认表空间更改为 USER_DATA,请发出以下命令
alter user scott default tablespace user_data;
然后将所有对象移出系统表空间。
alter table scott.tab1 move tablespace user_data;
现在,您的下一个任务是确保所有用户在 SYSTEM 表空间中的份额为 0。 份额无限有两个基本原因,其中之一就是直接授予了无限的表空间。 另一个原因是授予了角色 RESOURCE,该角色在 Oracle9i 数据库和更早版本中具有系统权限 UNLIMITED TABLESPACE。 相比之下,Oracle 数据库 10g 并不将系统权限授予 RESOURCE 角色。
对于 Oracle9i 数据库
确保实际上已将 UNLIMITED TABLESPACE 授予 RESOURCE 角色。
SQL> select * 2 from dba_sys_privs 3 where grantee = 'RESOURCE'; GRANTEE PRIVILEGE ADM ------------------------------ ---------------------------------------- --- RESOURCE CREATE TYPE NO RESOURCE CREATE TABLE NO RESOURCE CREATE CLUSTER NO RESOURCE CREATE TRIGGER NO RESOURCE CREATE OPERATOR NO RESOURCE CREATE SEQUENCE NO RESOURCE CREATE INDEXTYPE NO RESOURCE CREATE PROCEDURE NO RESOURCE UNLIMITED TABLESPACE NO
如果未列出 UNLIMITED TABLESPACE,则您不必在此阶段执行任何操作。 向前转至“常见任务”。
对于 Oracle 数据库 10g
确保未将 UNLIMITED TABLESPACE 授予 RESOURCE 角色。
SQL> select * 2 from dba_sys_privs 3 where grantee = 'RESOURCE'; GRANTEE PRIVILEGE ADM ------------------------------ ---------------------------------------- --- RESOURCE CREATE TYPE NO RESOURCE CREATE TABLE NO RESOURCE CREATE CLUSTER NO RESOURCE CREATE TRIGGER NO RESOURCE CREATE OPERATOR NO RESOURCE CREATE SEQUENCE NO RESOURCE CREATE INDEXTYPE NO RESOURCE CREATE PROCEDURE NO
常见任务
标识具有 UNLIMITED TABLESPACE 权限的用户,并将其份额更改为在所有表空间中均为 unlimited。
set lines 300 set pages 0 spool quota.sql select 'alter user '||grantee||' quota unlimited on '|| tablespace_name||';' from dba_sys_privs p, dba_tablespaces t where p.grantee in ( select username from dba_users ) and p.privilege = 'UNLIMITED TABLESPACE' and t.tablespace_name not in ('SYSTEM','SYSAUX') order by grantee, tablespace_name / spool off
此代码创建一个包含类似如下内容的文件
alter user ORAAGENT quota unlimited on INDEX01; alter user ORAAGENT quota unlimited on INDEX02; alter user ORADBA quota unlimited on INDEX02;
接下来,您可以执行此脚本文件以拥有这些用户的无限份额。 最后,删除 UNLIMITED TABLESPACE。
set lines 300 set pages 0 spool revoke_ut.sql select 'revoke unlimited tablespace from '||grantee||';' from dba_sys_privs where privilege = 'UNLIMITED TABLESPACE' / spool off
然后,执行此脚本文件以撤销权限。
可能的影响
删除这些权限以及将 SYSTEM 表空间的份额减少为 0 不会产生影响。 但如果 SYSTEM 表空间中存在段,并且您将其移动到其他表空间,则将产生两个结果:
- rowid 将因为物理移动而更改。 如果您有一个基于 rowid 的应用程序,则应注意这一点。
- 表索引将不可用 - 您必须重建它们。
此更改还可以使某些独立过程无效。
操作计划
找到 SYS、SYSTEM 和 OUTLN 之外的用户的默认表空间。 | |
将它们移出到它们的表空间 | |
ENDIF 找到具有 UNLIMITED TABLESPACE 系统权限的用户 如果找到,则 | |
向他们授予所有表空间(SYSTEM 和 SYSAUX 除外)的无限份额 撤销系统权限 UNLIMITED TABLESPACE | |
ENDIF |
背景
在阶段 1.7 中,您了解了如何通过限制在线更改参数的能力来保护 Oracle Listener。 这样做很不错,但您如何知道是否有人尝试非法入侵以及何时尝试非法入侵? 防止入侵只是预防措施的一部分;跟踪防御的有效性同样重要。
您可以从监听器日志文件中浏览已尝试的失败登录。 当用户提供错误口令并尝试修改监听器时,以下消息将写入监听器日志:
12-NOV-2005 23:23:12 * (CONNECT_DATA=(CID=(PROGRAM=)(HOST=prolin01)(USER=ananda ))(COMMAND=stop)(ARGUMENTS=64)(SERVICE=LISTENER_PROLIN01)(VERSION=168821760)) * stop * 1190 TNS-01190: The user is not authorized to execute the requested listener command
该消息表明 11 月 12 日晚上 11:23,用户“ananda”尝试停止监听器(注意 (COMMAND=stop) 的存在),但提供了错误的口令。 这是否表示尝试的非法入侵? 有可能。 ananda 也可能是一个合法用户,但在输入口令时出现输入错误,从而产生此错误。 但如果您多次看到此错误,则它实际上可能指示尝试的非法入侵。应访问用户 ananda 并验证它实际上是否尝试停止监听器。
同样,在监听器中限制 admin 选项时,用户无法只从命令行设置参数。 如果用户尝试使用以下命令:
$ lsnrctl LSNRCTL> set trc_level support
他将立即获得错误。
TNS-12508: TNS:listener could not resolve the COMMAND given
以下条目将出现在监听器日志文件中。
12-NOV-2005 23:26:34 * trc_level * 12508 TNS-12508: TNS:listener could not resolve the COMMAND given
该条目指示某人曾尝试直接对 LSNRCTL 提示设置 trc_level。 这可能也是个正常的错误,但重复尝试可能表示存在攻击。
策略
最好的方法是定期检查这些模式的监听器日志文件。 可以通过多种方法执行该操作。
首先,您可以使用以下代码编写一个 shell 脚本:
$ grep "(COMMAND=stop)" listener.log | cut -f4 -d"*"
以下输出将指示监听器命令 STOP 已发出三次:
0 0 0
还可以使用复杂工具(如 awk)或脚本语言(如 PERL)使该脚本提供更丰富的信息。 但如果您对 SQL 最熟悉(这种情况很有可能),则使用 SQL 从此日志文件中提取信息将更具有吸引力。
此处的技巧是将监听器日志用作外部表。 首先,为监听器日志所在的目录创建一个 directory 对象。
create directory listener_log_dir as '/u01/app/oracle/10.1/db1/network/log' /
然后,为日志文件创建外部表。 仔细留意日志文件的内容;它通常包含六个由“*”字符分隔的信息部分。 这些部分将成为外部表的列。
create table listener_log ( log_date date, connect_data varchar2(300), protocol_data varchar2(300), command varchar2(15), service_name varchar2(15), return_code number(10) ) organization external ( type oracle_loader default directory LISTENER_LOG_DIR access parameters ( records delimited by newline nobadfile nologfile nodiscardfile fields terminated by "*" lrtrim missing field values are null ( log_datechar(30) date_format date mask "DD-MON-YYYY HH24:MI:SS", connect_data, protocol_data, command, service_name, return_code ) ) location ('listener_prolin01.log') ) reject limit unlimited /
创建表后,可以从中进行选择以确保定义正确。
这些行的描述性很高,但嵌入的命令(如 (COMMAND=stop))可能使它难于破解。 这种情况下,编写另一个函数以提取字符串中的值:
create or replace function extract_value ( p_in varchar2, p_param in varchar2 ) return varchar2 as l_begin number(3); l_end number(3); l_val varchar2(2000); begin l_begin := instr (upper(p_in), '('||p_param||'='); l_begin := instr (upper(p_in), '=', l_begin); l_end := instr (upper(p_in), ')', l_begin); l_val := substr (p_in, l_begin+1, l_end - l_begin - 1); return l_val; end;
这样,监控将变得非常简单。 发现失败登录尝试所要执行的操作就是发出
col l_user format a10with the embedde col service format a20 col logdate format a20 col host format a10 col RC format a5 select to_char(log_date,'mm/dd/yy hh24:mi:ss') logdate, extract_value (connect_data,'HOST') host, extract_value (connect_data,'USER') l_user, extract_value (connect_data,'SERVICE') service, action RC from listener_log where extract_value (connect_data, 'COMMAND') in ( 'password', 'rawmode', 'displaymode', 'trc_file', 'trc_directory', 'trc_level', 'log_file', 'log_directory', 'log_status', 'current_listener', 'inbound_connect_timeout', 'startup_waittime', 'save_config_on_stop', 'start', 'stop', 'status', 'services', 'version', 'reload', 'save_config', 'trace', 'spawn', 'change_password', 'quit', 'exit' )
这返回类似如下所示的输出
LOGDATE COMMAND HOST L_USER SERVICE RC -------------------- --------------- ---------- ---------- -------------------- ----- 10/02/05 02:57:36 stop prlddb01 oraprld LISTENER_PRLDDB01 0 10/02/05 04:47:03 stop prlddb01 oraprld listener_prlddb01 0 10/03/05 15:14:53 stop prlddb01 oraprld LISTENER_PRLDDB01 0 11/18/05 23:48:26 reload prlddb01 oraprld LISTENER_PRLDDB01 0
您可以看到,该输出显示了命令的日期和时间以及返回代码。 也可以修改此查询以只显示返回代码不为 0 的值。此外,还可以添加一个谓词以只显示某个日期之后的记录,以便只显示当天的尝试。 如果每天均运行此脚本,则只能看到当日的尝试。
上面只显示了无效口令的数据。 对于管理限制的监听器,该错误字符串只显示三个字段,因此表 LISTENER_LOG 的列具有不同的含义: 第二列显示用户发出的命令,第三列显示返回代码。
select log_date, connect_data command, protocol_data return_code from listener_log where connect_data in ( 'password', 'rawmode', 'displaymode', 'trc_file', 'trc_directory', 'trc_level', 'log_file', 'log_directory', 'log_status', 'current_listener', 'inbound_connect_timeout', 'startup_waittime', 'save_config_on_stop', 'start', 'stop', 'status', 'services', 'version', 'reload', 'save_config', 'trace', 'spawn', 'change_password', 'quit', 'exit' ) /
这返回:
LOG_DATE COMMAND RETURN_CODE --------- -------------------- --------------- 06-NOV-05 change_password 0 06-NOV-05 save_config 0 06-NOV-05 log_file 0 06-NOV-05 trc_level 12508 06-NOV-05 save_config_on_stop 12508 06-NOV-05 log_directory 12508 06-NOV-05 log_directory 12508 06-NOV-05 stop 1169 06-NOV-05 stop 1169 06-NOV-05 services 1169 06-NOV-05 status 1169 06-NOV-05 reload 1169 06-NOV-05 status 1169 06-NOV-05 stop 1169 06-NOV-05 status 1169 06-NOV-05 stop 1169
可能的影响
无;该活动只是一个诊断活动。
操作计划
- 创建监听器日志外部表。
- 选择在无口令情况下发出管理命令的记录(可以由非零返回值标识)。
- 选择根据监听器控制提示发出命令的记录。
- 如果找到无法由任何 DBA 执行的活动解释的记录,则您可能标识了错误内容。
背景
您对用户的了解程度如何? 您是否知道他们从哪些计算机连接、他们在连接时的活动等问题?或许您并不知道。 但请注意,一个成功的安全性规划需要了解这些详细信息,或至少了解重要信息。 这正是 Oracle 数据库中的审计功能的用途所在。
Oracle 数据库中的审计功能非常全面。 此处,您只需要启用该功能的一部分来创建数据库的“配置文件”。您将尝试的所有操作就是查看用户连接、用户连接所使用的用户 ID 以及所使用的验证类型。 您还将发现无效的登录尝试 - 例如,用户 ID/口令组合何时错误。 正如以上所介绍的,查找这些异常事件的模式可以为发现可能的攻击提供线索。
策略
要启用审计,请在数据库初始化参数文件中设置以下参数。
audit_trail = db
这是一个静态参数;您必须重新利用数据库才能使其生效。 执行此操作后,发出以下命令以执行审计。
AUDIT SESSION;
该命令将在用户登录或注销时创建一个记录。 即使登录失败也将创建登录跟踪。 在数据库运行一段时间后,可以在审计跟踪中搜索模式。 列 RETURNCODE 记录用户在执行该操作时收到的 Oracle 错误代码。
SQL> select returncode, count(1) 2 from dba_audit_trail 3 group by returncode 4 / RETURNCODE COUNT(1) ---------- ---------- 0 1710880 604 3 955 17 987 2 1013 2 1017 1428 1045 1 1555 4 1652 4 1950 1 2002 1 2004 4 28000 4 28009 3
以上代码显示了错误模式;大多数操作均成功(其中的返回代码为 0)。 对于剩余代码,您可以通过从 *nix 提示符中发出以下命令获得描述
oerr ora <errorno>
。 例如,要查明错误代码 1017 的含义,请发出
oerr ora 1017
这将返回
01017, 00000, "invalid username/password; logon denied" // *Cause: // *Action:
这个最常见的错误是分析的目标,因为它将最有效地显示攻击模式。 无效/口令组合的偶然性如果很高,则可能指示尝试的意外入侵。
现在,您将看到这些会话的来源。 特定用户 ID 的口令无效可能指示对该用户 ID 的攻击。 可以通过以下代码查看用户 ID:
select username, count(1) from dba_audit_trail where returncode = 1017 group by username order by 2 desc;
该输出如下所示:
USERNAME COUNT(1) ------------------------------ ---------- ARAO 569 DBSNMP 381 DW_DQS 181
此处,我们看到用户 ARAO(表面看来是一个实际用户)已经尝试使用无效口令 569 次。 下一个用户 ID DBSNMP(381 次无效的口令尝试)不是一个实际用户;它的用户 ID 为 Enterprise Manager。 这应立即引发警报信号 - —DBSNMP 是一个首选的攻击目标。
为进一步检查它,我们将查看这些攻击的来源:
select userhost, terminal, os_username, count(1) from dba_audit_trail where returncode = 1017 and username = 'DBSNMP' group by userhost, terminal, os_username;
输出如下:
USERHOST TERMINAL OS_USERNAM COUNT(1) ------------------------- --------------- ---------- ---------- prlpdb01 oraprlp 199 prlpdb01 pts/2 oraprlp 4 prlpdb01 pts/7 oraprlp 9 prlpdb02 oraprlp 130 PRONT/PRANANDAT42 PRANANDAT42 ananda 3 progcpdb unknown oracle 34
注意,此数据库运行所在的服务器为 prlpdb01。 由于这是一个 RAC 数据库,因此还存在第二个节点,且服务器名称为 prlpdb02。 大多数错误连接尝试均来自这些服务器,并使用作为 Oracle 软件拥有者的 OS 用户 (oraprlp)。 如果这是实际的攻击,则用户可以访问 Oracle 软件所有者并可以 SYSDBA 的身份登录。 不需要以 DBSNMP 的身份登录,该口令明显是错误的。 因此,它并不像攻击。
您还可以看到无效登录来自其他两个计算机: PRONT/PRANANDAT42 和 progcpdb。 它们看似可疑,我们可以确认这些计算机的身份 - 第一个计算机属于名为“ananda”的 DBA,另一个计算机是网格控制服务器,它应使用此用户 ID 连接。
接下来,请分析这些失败的模式。 如果这些失败集中在特定时间发生,则可能指示攻击。
SQL> select to_char (timestamp,'mm/dd/yy') ts, count(1) 2 from dba_audit_trail 3 where returncode = 1017 4 and username = 'DBSNMP' 5 group by to_char (timestamp,'mm/dd/yy') 6 / TS COUNT(1) -------------------- ---------- 10/14/05 9 10/16/05 222 10/27/05 15 10/28/05 125 11/09/05 4 11/11/05 2 11/12/05 2 11/14/05 2
您可以看到,有两个频繁出现失败的时间: 10/16 和 10/28。您应进行全面的调查。
可能的影响
审计对性能的影响最低;但仍存在一定程度的影响。 此外,请注意,审计跟踪写入表空间 SYSTEM(可能已被填满)中。 因此,您必须注意 SYSTEM 表空间内部的可用空间。
操作计划
- 通过设置 AUDIT_TRAIL 初始化参数打开审计。
- 对会话启用审计。
- 查找无效或失败的登录尝试。
- 检查失败的模式尝试(日期集群)。