数据库基础体系 · 第 111/139 篇。文章以各产品官方稳定版本的公开语义为准;示例会明确引擎、事务与部署边界。
Oracle 安全治理:用户、角色、Profile、TDE、审计和最小权限
Oracle 的安全治理不是给数据库“加一层密码”这么简单,而是同时处理以下问题:
- 谁可以连接数据库:用户身份认证与账户状态。
- 连接后可以做什么:系统权限、对象权限和角色。
- 权限如何被约束:最小权限、授权链和存储程序执行权限。
- 账户如何被限制:Profile 对密码、登录失败和资源的控制。
- 数据如何防止介质泄露:Transparent Data Encryption,简称 TDE。
- 谁在什么时候做了什么:统一审计、细粒度审计和审计数据保护。
- 发生异常时如何判断边界:权限错误、账户锁定、钱包未打开、审计策略未命中等。
这些机制的保护对象并不相同。TDE 主要保护数据库文件、备份或其他静态介质;审计负责记录行为;角色负责组织授权;Profile 负责账户和部分资源约束;最小权限负责从整体上限制操作集合。把其中任意一个机制当成其他机制的替代品,都会产生安全盲区。
一、先建立 Oracle 权限模型
1. 用户、Schema 与账户
在 Oracle 中,**用户(user)**是可以认证、拥有权限和建立会话的数据库主体;Schema 是与用户同名的对象命名空间以及该用户拥有的数据库对象集合。
例如:
CREATE USER app_user
IDENTIFIED BY "Strong-Password-Example-1"
DEFAULT TABLESPACE app_data
TEMPORARY TABLESPACE temp
QUOTA 100M ON app_data;
GRANT CREATE SESSION TO app_user;
这里有三个不同层次:
app_user是账户主体;app_user通常也是名为APP_USER的 Schema;CREATE SESSION只允许建立数据库会话,并不表示可以访问业务表;QUOTA规定该用户在指定表空间中最多可以使用多少空间。
如果没有对象权限,用户虽然可以登录,但执行查询仍可能失败:
SELECT * FROM app_owner.orders;
可能返回:
ORA-00942: table or view does not exist
这个错误不一定表示表不存在,也可能是当前用户没有访问该对象的权限。
Oracle 的对象所有权和权限是分离的。对象属于创建它的 Schema,而其他用户需要显式获得对象权限:
GRANT SELECT ON app_owner.orders TO app_user;
对于业务用户,通常不应让其成为业务表的所有者。更常见的分工是:
APP_OWNER:拥有表、索引、序列、存储程序;APP_RUNTIME:应用运行账户,只获得运行时需要的权限;APP_MIGRATION:发布或迁移账户,获得受控的 DDL 权限;- DBA 或运维账户:承担管理任务,但不作为应用连接账户。
这样做的原因是:应用账户一旦被注入或凭据泄露,攻击者取得的是运行时权限,而不是整个业务 Schema 的所有权。
2. 系统权限、对象权限和角色
Oracle 中常见的授权类型如下。
系统权限
系统权限描述数据库级能力,例如:
GRANT CREATE SESSION TO app_user;
GRANT CREATE TABLE TO developer_user;
CREATE TABLE 不仅表示“能执行一条 CREATE TABLE 语句”,还涉及用户在目标表空间中的配额。没有配额时,即使拥有创建表的系统权限,也可能因为无法分配空间而失败。
类似地,CREATE ANY TABLE 的范围远大于 CREATE TABLE。ANY 类系统权限通常允许用户在其他 Schema 中创建或操作对象,授权风险显著更高。
对象权限
对象权限作用于特定对象:
GRANT SELECT ON app_owner.orders TO app_runtime;
GRANT INSERT, UPDATE ON app_owner.orders TO app_runtime;
GRANT EXECUTE ON app_owner.order_api TO app_runtime;
应优先授予明确的对象权限,而不是使用范围过大的系统权限。例如,应用只需要读取一个表时,不应授予:
GRANT SELECT ANY TABLE TO app_runtime;
角色
角色是权限集合,可以包含系统权限、对象权限,也可以包含其他角色:
CREATE ROLE app_read_role;
GRANT SELECT ON app_owner.orders TO app_read_role;
GRANT app_read_role TO app_runtime;
角色的价值不只是减少 GRANT 语句数量,更重要的是把“岗位或应用身份”和“权限集合”分离:
- 权限变化时修改角色;
- 人员变化时修改角色成员;
- 审查时查看角色定义,而不是逐个用户分析。
用户获得角色后,角色可以是默认角色,也可以只在特定会话中启用:
ALTER USER app_runtime DEFAULT ROLE app_read_role;
查看授权关系:
SELECT grantee, granted_role, default_role
FROM dba_role_privs
WHERE grantee IN ('APP_RUNTIME', 'APP_OWNER');
SELECT grantee, privilege
FROM dba_sys_privs
WHERE grantee = 'APP_READ_ROLE';
SELECT grantee, owner, table_name, privilege
FROM dba_tab_privs
WHERE grantee = 'APP_READ_ROLE';
DBA_* 视图通常需要较高权限。普通用户可以查看的范围取决于其数据字典访问权限。
3. 授权选项会改变权限边界
以下两种授权尤其需要谨慎:
GRANT CREATE SESSION TO developer_user WITH ADMIN OPTION;
GRANT SELECT ON app_owner.orders TO reporting_user WITH GRANT OPTION;
WITH ADMIN OPTION允许被授权者把系统权限或角色继续授予其他主体,也可以撤销自己授予的权限。WITH GRANT OPTION允许被授权者继续授予对象权限。
如果 reporting_user 将 SELECT ON app_owner.orders 授予 external_user,之后撤销 reporting_user 的权限,并不应简单假定授权链会自动按业务意图收敛。实际影响取决于授权关系和被授予方式,因此生产授权应避免无必要的转授权。
可将授权关系看成一个有向图:
其中:
- 是用户和角色;
- 是“授予角色”“授予系统权限”或“授予对象权限”的边。
当允许转授权时,一个节点不仅拥有权限,还拥有增加图中边的能力。权限审查因此必须检查两件事:
- 当前主体实际能执行哪些操作;
- 当前主体能否把这些操作继续传播给其他主体。
二、最小权限:从口号变成可验证条件
1. 最小权限的形式化定义
设某个业务功能 在完整执行过程中实际需要的权限集合为:
某用户通过直接授权、角色、公共权限以及存储程序执行上下文最终获得的有效权限集合为:
最小权限的基本条件是:
同时,理想状态是:
也就是说:
- 缺少 中任何权限,功能会失败;
- 多出不属于 的权限,攻击面会扩大。
实际工程中,往往不能做到完全相等,因为同一账户可能运行多个功能。此时应按职责拆分账户或角色,使每个主体的权限集合更接近单一业务功能。
2. 一个完整的授权算例
假设订单应用只需要:
- 读取
APP_OWNER.PRODUCTS; - 新增订单;
- 通过存储程序取消订单;
- 不允许直接更新订单金额。
可以这样设计:
CREATE ROLE order_runtime_role;
GRANT SELECT ON app_owner.products
TO order_runtime_role;
GRANT INSERT ON app_owner.orders
TO order_runtime_role;
GRANT EXECUTE ON app_owner.order_api
TO order_runtime_role;
CREATE USER order_runtime
IDENTIFIED BY "Another-Strong-Password-1"
DEFAULT TABLESPACE app_data
TEMPORARY TABLESPACE temp
QUOTA 0 ON app_data;
GRANT CREATE SESSION TO order_runtime;
GRANT order_runtime_role TO order_runtime;
这里的 QUOTA 0 表示该用户不能在 APP_DATA 中创建需要占用配额的对象。它仍然可以访问被授予的对象,但不能因为意外获得 CREATE TABLE 后在业务表空间中任意建表。
如果应用还需要调用 ORDER_API.CANCEL_ORDER,可以把取消操作封装为存储程序:
CREATE OR REPLACE PACKAGE app_owner.order_api
AUTHID DEFINER
AS
PROCEDURE cancel_order(p_order_id NUMBER);
END;
/
CREATE OR REPLACE PACKAGE BODY app_owner.order_api
AS
PROCEDURE cancel_order(p_order_id NUMBER)
AS
BEGIN
UPDATE app_owner.orders
SET status = 'CANCELLED',
cancelled_at = SYSTIMESTAMP
WHERE order_id = p_order_id
AND status = 'PAID';
IF SQL%ROWCOUNT = 0 THEN
RAISE_APPLICATION_ERROR(
-20001,
'Order does not exist or is not cancellable'
);
END IF;
END;
END;
/
AUTHID DEFINER 表示默认的定义者权限模型:程序主要使用程序所有者的直接权限执行。随后只授予运行账户执行权限:
GRANT EXECUTE ON app_owner.order_api TO order_runtime;
这样,运行账户可以取消符合业务条件的订单,但不需要直接获得:
GRANT UPDATE ON app_owner.orders TO order_runtime;
这比直接授予整张表的 UPDATE 更接近最小权限。
3. 角色权限与存储程序的常见陷阱
在定义者权限的 PL/SQL 单元中,角色授予的权限通常不能替代所需的直接权限。也就是说,下面这种设计可能导致程序编译或运行失败:
GRANT SELECT ON app_owner.orders TO app_runtime_role;
GRANT app_runtime_role TO app_owner;
APP_OWNER 作为程序所有者通过角色得到权限,但定义者权限程序需要的对象权限应直接授予 APP_OWNER:
GRANT SELECT ON app_owner.orders TO app_owner;
这不是角色失效,而是存储程序权限解析与普通 SQL 会话权限解析存在差异。排查 ORA-01031: insufficient privileges 时,应分别检查:
SELECT grantee, owner, table_name, privilege
FROM dba_tab_privs
WHERE grantee = 'APP_OWNER';
SELECT grantee, granted_role
FROM dba_role_privs
WHERE grantee = 'APP_OWNER';
此外,动态 SQL、AUTHID CURRENT_USER、对象所有者、同义词和跨 Schema 引用会改变实际权限路径。权限审计不能只看“用户拥有哪个角色”,还要看具体 SQL 和执行上下文。
4. 最小权限的验证方法
先用业务账户测试允许的操作:
CONNECT order_runtime/"Another-Strong-Password-1"
SELECT product_id, product_name
FROM app_owner.products;
INSERT INTO app_owner.orders(order_id, product_id, quantity, status)
VALUES (1001, 10, 2, 'NEW');
UPDATE app_owner.orders
SET amount = 0
WHERE order_id = 1001;
预期结果:
SELECT成功;INSERT成功,前提是约束和序列等依赖已正确配置;- 直接
UPDATE失败,通常为ORA-01031。
再测试存储程序:
BEGIN
app_owner.order_api.cancel_order(1001);
END;
/
如果程序成功而直接更新失败,说明接口边界确实生效。若程序失败,应沿以下路径排查:
ORDER_RUNTIME是否拥有EXECUTE;APP_OWNER是否直接拥有程序访问的对象权限;- 程序中的对象是否使用了正确的 Schema;
- 是否存在动态 SQL;
- 数据行是否满足状态条件;
- 是否因并发更新导致
SQL%ROWCOUNT = 0。
三、Profile:账户策略,不是权限集合
1. Profile 的职责
Profile 是分配给用户的一组资源限制和密码管理参数。它不授予 SELECT、UPDATE 或 CREATE SESSION,也不能代替角色。
创建用户时,默认使用 DEFAULT Profile:
CREATE PROFILE app_profile LIMIT
FAILED_LOGIN_ATTEMPTS 5
PASSWORD_LOCK_TIME 1/24
PASSWORD_LIFE_TIME 90
PASSWORD_REUSE_TIME 365
PASSWORD_REUSE_MAX 10
PASSWORD_VERIFY_FUNCTION ora12c_strong_verify_function;
然后分配给用户:
ALTER USER order_runtime PROFILE app_profile;
查询配置:
SELECT profile, resource_name, resource_type, limit
FROM dba_profiles
WHERE profile = 'APP_PROFILE'
ORDER BY resource_type, resource_name;
Profile 的常见参数包括:
FAILED_LOGIN_ATTEMPTS:连续认证失败达到多少次后锁定账户;PASSWORD_LOCK_TIME:账户锁定后的锁定时长;PASSWORD_LIFE_TIME:密码有效期;PASSWORD_REUSE_TIME、PASSWORD_REUSE_MAX:密码历史复用约束;PASSWORD_VERIFY_FUNCTION:密码复杂度校验函数;SESSIONS_PER_USER:每个用户允许的并发会话数;IDLE_TIME:会话空闲时间限制;CONNECT_TIME:单次会话连接时长限制;CPU_PER_SESSION、CPU_PER_CALL:CPU 使用限制。
密码参数与资源参数不是同一类控制。一个 Profile 可以要求复杂密码,但不限制并发会话;也可以限制会话数,但不代表密码足够安全。
2. Profile 的状态变化
当用户连续输入错误密码时,账户可能经历:
OPEN
-- 达到 FAILED_LOGIN_ATTEMPTS -->
LOCKED(TIMED)
-- 等待 PASSWORD_LOCK_TIME -->
OPEN
管理员也可以手工锁定:
ALTER USER order_runtime ACCOUNT LOCK;
解锁:
ALTER USER order_runtime ACCOUNT UNLOCK;
查询状态:
SELECT username,
account_status,
lock_date,
expiry_date,
profile
FROM dba_users
WHERE username = 'ORDER_RUNTIME';
常见状态包括:
OPEN:可正常认证;LOCKED:被显式锁定;LOCKED(TIMED):因失败次数被临时锁定;EXPIRED:密码过期;EXPIRED & LOCKED:密码过期且账户被锁定。
典型失败表现:
ORA-28000: the account is locked:账户被锁定;ORA-28001: the password has expired:密码过期;ORA-01017: invalid username/password; logon denied:认证信息错误,也可能由认证方式或连接到错误容器引起。
解锁并重置密码:
ALTER USER order_runtime
IDENTIFIED BY "New-Strong-Password-2"
ACCOUNT UNLOCK;
生产环境中不能只执行 ACCOUNT UNLOCK 而忘记密码状态。如果账户已经过期,仍可能无法登录。
3. Profile 的边界与风险
Profile 不是防暴力破解系统
FAILED_LOGIN_ATTEMPTS 可以限制单个数据库账户的连续失败次数,但无法完全阻止:
- 攻击者轮换多个账户;
- 从连接池或中间层发起请求;
- 使用已泄露的正确密码;
- 在数据库前置网络层持续发送连接请求。
所以它是数据库账户层面的控制,不是完整的身份安全系统。
IDLE_TIME 不等于请求超时
IDLE_TIME 主要针对数据库会话空闲状态。应用连接池可能长期保持连接,却频繁执行轻量 SQL;此时不能把 IDLE_TIME 当作接口级超时或事务超时。
资源限制需要确认实例语义
传统资源限制参数是否生效,与实例参数和部署架构有关。应检查:
SHOW PARAMETER resource_limit;
在需要启用资源限制的环境中:
ALTER SYSTEM SET resource_limit = TRUE;
不同版本、CDB/PDB 架构和参数管理方式可能存在差异,不能仅凭 Profile 中出现了参数就断定运行时一定受限。修改后要用实际会话和监控视图验证,而不是只查看配置文本。
4. CDB、PDB 与用户范围
在多租户架构中,用户可能是:
- 本地用户:只存在于某个 PDB;
- 公共用户:在 CDB 范围创建,名称通常带
C##前缀,可跨容器存在。
例如,业务应用通常应连接到目标 PDB,并在该 PDB 中创建本地用户:
ALTER SESSION SET CONTAINER = app_pdb;
CREATE USER app_runtime
IDENTIFIED BY "Pdb-Password-Example-1";
GRANT CREATE SESSION TO app_runtime;
如果在错误的容器中执行 CREATE USER,可能出现:
- 用户实际创建在 CDB Root,而不是业务 PDB;
- 连接到 PDB 时找不到用户;
- 授权对象属于另一个容器;
- 查询
DBA_USERS时观察到的结果与应用连接端不同。
因此排查认证和授权问题时,必须同时确认:
SHOW CON_NAME;
SELECT SYS_CONTEXT('USERENV', 'CON_NAME') FROM dual;
SELECT SYS_CONTEXT('USERENV', 'SESSION_USER') FROM dual;
四、TDE:保护静态数据,不是保护查询结果
1. TDE 解决什么问题
Transparent Data Encryption,透明数据加密,用于加密数据库中的静态数据,例如:
- 数据文件;
- 表空间;
- 某些列;
- 备份或复制出的数据库文件,在相应配置下受到密钥保护。
“透明”是指应用通常不需要修改 SQL:
SELECT card_token FROM app_owner.payment;
应用看到的是正常数据,Oracle 在底层完成加密和解密。
TDE 主要防范的是:
- 直接窃取数据文件;
- 盗取部分备份介质;
- 未经授权读取存储层文件。
TDE 不直接防范:
- 已登录且有
SELECT权限的用户读取明文; - 被注入的应用执行合法查询;
- 拥有足够数据库管理权限的人通过数据库接口读取数据;
- 应用日志、导出文件、缓存或屏幕中出现的明文;
- 弱授权导致的数据外泄。
因此:
更准确地说,TDE 保护的是“存储介质上的表示”,而最小权限保护的是“数据库操作能力”。
2. TDE 的密钥层次
TDE 通常涉及以下概念:
- Keystore/Wallet:保存密钥材料的密钥库;
- 主密钥:用于保护数据库或表空间密钥;
- 表空间密钥或列密钥:用于实际数据加密;
- 加密数据:落盘时以密文形式保存。
可以简化为:
其中:
- 是明文数据;
- 是数据加密密钥;
- 是落盘密文。
而数据密钥本身还需要由密钥库中的更高层密钥保护。这样做的目的,是在不重新加密全部数据的情况下完成密钥轮换。
3. 配置 TDE 的关键状态
TDE 不是执行一条 ALTER TABLESPACE ... ENCRYPTION 就结束了。至少要确认:
- 密钥库已创建;
- 密钥库已经打开;
- 主密钥已经设置;
- 目标表空间或列已完成加密;
- 重启、备份和恢复流程能够重新获得密钥;
- CDB/PDB 的密钥库范围符合部署设计。
下面是一个典型的管理流程。路径和密码必须替换为组织自己的安全值,示例中的值不能直接用于生产:
-- 在具备 TDE 管理权限的管理会话中执行
ADMINISTER KEY MANAGEMENT CREATE KEYSTORE
'/opt/oracle/admin/DB1/tde'
IDENTIFIED BY "Keystore-Password-Example-1";
ADMINISTER KEY MANAGEMENT SET KEYSTORE OPEN
IDENTIFIED BY "Keystore-Password-Example-1";
ADMINISTER KEY MANAGEMENT SET KEY
IDENTIFIED BY "Keystore-Password-Example-1"
WITH BACKUP USING 'initial_tde_key_backup';
这些语句的具体可用性取决于数据库版本、密钥库类型、CDB/PDB 配置以及部署方式。生产执行前应以目标版本 SQL Language Reference 和 Advanced Security 相关文档为准。尤其不能把文件系统密码写入脚本、命令历史或普通配置仓库。
查看状态:
SELECT wallet_type,
status,
wallet_order,
keystore_mode
FROM v$encryption_wallet;
在支持相应视图列的版本中,还可以查询加密表空间:
SELECT tablespace_name,
encrypted
FROM dba_tablespaces;
部分版本也提供:
SELECT tablespace_name,
encrypted,
encryptionalg
FROM v$encrypted_tablespaces;
如果密钥库没有打开,常见表现是:
ORA-28365: wallet is not open
数据库可能仍然可以启动,但访问依赖 TDE 密钥的数据时失败。这个故障不是通过给业务用户加权限解决的,而是要检查:
- 数据库实例实际使用的 keystore 配置;
- wallet 文件或外部密钥管理服务是否可访问;
- 密钥库密码或自动登录配置是否正确;
- CDB Root 和目标 PDB 的密钥库状态;
- 数据库重启后的自动打开策略。
4. 表空间加密与列加密
表空间加密适合保护一组表及其相关段,运维边界比较清晰:
CREATE TABLESPACE app_secure
DATAFILE '/opt/oracle/oradata/DB1/app_secure01.dbf'
SIZE 1G
ENCRYPTION USING 'AES256'
DEFAULT STORAGE (ENCRYPT);
也可以对已有表空间进行加密,具体语法和在线操作限制取决于版本及存储环境。执行前要确认:
- 是否需要额外空间;
- 是否会产生较大 I/O;
- 是否影响备份窗口;
- 是否存在无法在线完成的对象;
- 加密后的表空间是否已纳入恢复演练。
列级 TDE 适合只保护少数敏感列,例如身份证号或支付标识。它的边界更细,但可能带来:
- 索引和查询行为变化;
- 等值查询、排序或连接能力受限;
- 应用改造或密文索引设计;
- 列迁移和密钥轮换复杂度增加。
不要把“列已加密”理解成“数据库中所有出现过的敏感数据都安全”。同一数据可能还存在于:
- 非加密临时表;
- 非加密表空间;
- 导出文件;
- 审计内容;
- 应用日志;
- 备份或归档副本。
5. TDE 的备份与恢复边界
备份数据库文件而不备份相应密钥材料,恢复出的文件可能无法解密。密钥库备份必须具备:
- 独立的访问控制;
- 与数据库备份不同的保护边界;
- 可验证的恢复流程;
- 明确的密钥轮换记录;
- 防止单人同时获得数据库备份和密钥库的控制措施。
但把密钥库和数据库备份放在同一个普通目录,并不能形成有效隔离。攻击者如果同时得到数据文件和密钥材料,TDE 的介质保护目标就被破坏。
TDE 还涉及许可证和版本差异。Oracle 不同版本、版本包和部署形态对 TDE 功能、外部密钥管理以及授权要求可能不同,生产采购和许可判断必须以目标版本的官方许可文档为准,不能依据旧版本经验推断。
五、审计:记录行为,但不自动阻止行为
1. 审计的定义和数据流
**审计(auditing)**是把安全相关事件记录为审计记录。一个审计记录通常至少需要表达:
- 谁执行了操作;
- 什么时候执行;
- 从哪里执行;
- 执行了什么动作;
- 作用于哪个对象;
- 是否成功;
- 可能的 SQL 文本、绑定信息或返回码。
可以把一次审计事件抽象为:
其中:
- :执行主体;
- :时间;
- :会话或来源信息;
- :对象;
- :动作;
- :结果。
审计的核心是可追溯性,不是实时阻断。即使审计记录显示某用户执行了危险 SQL,该 SQL 通常已经执行完成;要阻止操作,应使用权限、视图、VPD、Database Vault、网络控制或应用层策略等机制。
2. 统一审计与传统审计
现代 Oracle 数据库提供统一审计(Unified Auditing),将多个审计来源统一到统一审计轨迹。不同版本可能处于:
- 混合模式:传统审计能力与统一审计并存;
- 纯统一审计:统一审计功能完全启用。
可先查看相关配置和轨迹:
SELECT policy_name,
enabled_option,
entity_name,
success,
failure
FROM audit_unified_enabled_policies;
不同版本视图列可能有所差异,查询前应对照目标版本数据字典文档。
审计记录通常可以从:
SELECT event_timestamp,
dbusername,
action_name,
object_schema,
object_name,
return_code,
unified_audit_policies
FROM unified_audit_trail
WHERE event_timestamp >= SYSTIMESTAMP - INTERVAL '1' HOUR
ORDER BY event_timestamp DESC;
查看。普通业务用户通常不能直接读取完整统一审计轨迹,审计查询应由独立的审计或安全管理角色完成。
3. 创建和启用统一审计策略
例如,只审计运行账户对订单表的修改:
CREATE AUDIT POLICY order_change_audit
ACTIONS
INSERT ON app_owner.orders,
UPDATE ON app_owner.orders,
DELETE ON app_owner.orders;
AUDIT POLICY order_change_audit
BY order_runtime
WHENEVER NOT SUCCESSFUL;
这里的语义是:
- 策略定义要关注的动作;
BY order_runtime将策略范围限制到指定主体;WHENEVER NOT SUCCESSFUL只记录失败操作。
如果要同时记录成功和失败,可以按目标版本支持的语法启用相应策略。审计策略设计应明确“要回答什么问题”,而不是无差别记录所有 SQL。例如:
- 谁修改了支付状态?
- 谁尝试读取敏感表但失败?
- 谁创建、删除或修改了数据库用户?
- 谁改变了 TDE 密钥或审计策略?
- 哪个应用账户在短时间内产生大量失败访问?
取消策略:
NOAUDIT POLICY order_change_audit
BY order_runtime;
删除策略:
DROP AUDIT POLICY order_change_audit;
删除前需要先确认该策略没有被依赖或被组织的变更流程引用。
4. 审计策略的命中验证
仅执行 CREATE AUDIT POLICY 不足以证明审计生效。应执行端到端验证:
CONNECT order_runtime/"Another-Strong-Password-1"
UPDATE app_owner.orders
SET status = 'PAID'
WHERE order_id = 1001;
然后由审计管理员查询:
SELECT event_timestamp,
dbusername,
action_name,
object_schema,
object_name,
return_code
FROM unified_audit_trail
WHERE dbusername = 'ORDER_RUNTIME'
AND object_schema = 'APP_OWNER'
AND object_name = 'ORDERS'
ORDER BY event_timestamp DESC;
如果更新成功,预期可以看到对应的成功事件;如果用没有权限的账户执行失败操作,并且策略配置了失败审计,则应看到非零 RETURN_CODE 的记录。若查不到记录,应依次排查:
- 当前连接的是不是目标 PDB;
- 策略是否启用在正确的用户和容器;
- 审计条件是成功、失败还是两者;
- 查询时间范围和时区是否正确;
- 审计记录是否尚未刷新或已被归档;
- 当前查询者是否有读取审计轨迹的权限;
- 实际执行的是不是预期对象,是否经过了同义词或视图。
5. 细粒度审计
统一审计可以记录对象和动作,但某些需求需要根据行条件或访问上下文审计。例如,只在读取高价值订单、特定列或特定客户端模块时审计,可以使用 Fine-Grained Auditing,通常通过 DBMS_FGA 配置。
概念示例:
BEGIN
DBMS_FGA.ADD_POLICY(
object_schema => 'APP_OWNER',
object_name => 'PAYMENT',
policy_name => 'PAYMENT_SENSITIVE_READ',
audit_condition => 'payment_status = ''PAID''',
audit_column => 'CARD_TOKEN',
statement_types => 'SELECT'
);
END;
/
该调用的精确参数和行为受数据库版本影响,部署前必须按目标版本的 DBMS_FGA 文档验证。细粒度审计不是“加上条件就一定记录每一次逻辑访问”,还要考虑:
- 查询是否实际访问了指定列;
- 优化器是否消除了某些列访问;
- 条件是否为真或为
NULL; - 视图、同义词和底层表的对象边界;
- 审计记录中的 SQL 是否包含敏感值。
6. 审计本身也需要保护
审计轨迹可能快速增长,并占用数据库空间。应监控:
- 审计表所在表空间;
- 审计记录增长速度;
- 清理与归档策略;
- 审计管理员的访问权限;
- 审计记录是否转发到独立日志平台;
- 数据库管理员是否能修改审计策略或删除记录。
审计数据不应只存在于被审计的同一个数据库中。否则攻击者取得高权限后,可能同时影响业务数据和审计证据。更稳妥的做法是定期抽取到受控的外部平台,并验证传输完整性、时间同步和保留周期。
审计失败时还要明确系统选择:
- 是允许业务继续但记录可能丢失;
- 还是在审计不可用时阻断关键操作。
两者没有普遍适用的答案。金融、支付等场景可能要求“审计不可用则拒绝关键操作”;普通业务可能优先保证可用性。这个取舍必须在设计阶段明确,而不是等审计表空间满后被动决定。
六、TDE、访问控制、脱敏和注入防护的边界
这些机制经常被混为一谈,但它们位于不同层次。
| 机制 | 主要保护对象 | 主要解决的问题 |
|---|---|---|
| 用户认证 | 身份 | 谁能建立会话 |
| 角色和权限 | 操作能力 | 能访问哪些对象、执行哪些动作 |
| Profile | 账户与部分资源 | 密码、锁定、并发和会话限制 |
| TDE | 静态介质 | 数据文件或备份被直接读取 |
| 审计 | 行为证据 | 谁在什么时候做了什么 |
| 数据脱敏 | 返回结果 | 某些主体看到部分或替换后的值 |
| SQL 注入防护 | 输入到 SQL 的解析边界 | 防止输入改变 SQL 语义 |
例如,TDE 加密了 CARD_TOKEN,但拥有 SELECT 权限的应用仍会得到明文。若客服只应看到部分值,应使用数据脱敏、视图或专门 API:
CREATE OR REPLACE VIEW app_owner.payment_for_support AS
SELECT payment_id,
CASE
WHEN SYS_CONTEXT('USERENV', 'SESSION_USER') = 'SUPPORT_USER'
THEN '****' || SUBSTR(card_token, -4)
ELSE card_token
END AS card_token_display
FROM app_owner.payment;
这个示例展示了思路,但在生产中不应仅依赖字符串判断用户。还应考虑:
- 角色或应用上下文;
- 视图是否被绕过;
- 导出和报表路径;
- 直接访问基表的权限;
- 连接池复用会话造成的上下文残留。
SQL 注入则是另一类问题。下面的拼接方式具有风险:
String sql = "SELECT * FROM orders WHERE order_id = " + input;
应使用绑定变量:
PreparedStatement ps =
connection.prepareStatement(
"SELECT order_id, status FROM app_owner.orders WHERE order_id = ?");
ps.setLong(1, orderId);
绑定变量防止输入改变 SQL 结构,但它不替代最小权限。即使没有注入,应用账户如果拥有 SELECT ANY TABLE,凭据泄露后的后果仍然很大。
七、生产环境中的权限审查与诊断
1. 查看用户有效权限
常用视图包括:
SELECT username, account_status, profile, default_tablespace
FROM dba_users
WHERE username = 'ORDER_RUNTIME';
SELECT grantee, granted_role, default_role
FROM dba_role_privs
WHERE grantee = 'ORDER_RUNTIME';
SELECT grantee, privilege
FROM dba_sys_privs
WHERE grantee = 'ORDER_RUNTIME';
SELECT grantee, owner, table_name, privilege
FROM dba_tab_privs
WHERE grantee = 'ORDER_RUNTIME';
但这些查询仍可能漏掉几个来源:
PUBLIC权限;- 角色嵌套;
- 代理认证;
- 定义者权限程序;
ANY系统权限;- 通过视图、同义词和数据库链接间接访问;
- 外部身份映射或企业用户权限。
因此,权限审查必须以实际业务动作测试为最终验证,而不能只生成一份角色清单。
2. 使用会话视图确认当前权限
在目标用户会话中:
SELECT * FROM session_roles;
SELECT privilege
FROM session_privs
ORDER BY privilege;
如果某角色不是默认角色,它可能不会出现在 SESSION_ROLES 中。可以在允许的前提下测试:
SET ROLE app_read_role;
但生产应用不应依赖人工 SET ROLE 来弥补错误的默认角色设计。角色启用策略必须和连接池生命周期一致。
3. 常见故障的因果关系
ORA-01031
可能原因:
- 没有直接对象权限;
- 只授予了角色,但代码运行在定义者权限程序中;
- 当前角色未启用;
- 访问了错误 Schema;
- 缺少
EXECUTE或底层依赖权限; - 在错误的 PDB 中执行。
恢复方法不是盲目授予 DBA,而是先定位失败语句和执行身份,再补充最窄权限。
ORA-28000
说明账户被锁定。应查询:
SELECT username, account_status, lock_date, profile
FROM dba_users
WHERE username = 'ORDER_RUNTIME';
恢复前还要查找为什么持续失败:
- 连接池中仍保存旧密码;
- Secret 已更新但实例未刷新;
- 多个服务共享同一账户;
- 健康检查使用了错误凭据;
- 攻击者正在尝试密码。
只解锁不修复连接池,账户可能立即再次锁定。
ORA-28365
说明依赖的 wallet/keystore 未打开。应检查 V$ENCRYPTION_WALLET、数据库告警日志、文件权限、外部密钥服务和容器范围。不要通过关闭加密或绕过密钥管理来恢复业务,这会破坏原有安全边界。
4. 撤销权限后的真实效果
撤销权限通常影响后续权限检查,但已有会话、已打开游标、连接池和缓存会使观察结果复杂化:
- 新 SQL 可能立即失败;
- 已经执行的事务不会因为撤销而自动回滚;
- 存储程序和对象依赖可能需要重新编译;
- 连接池中的会话可能仍保留旧角色状态;
- 应用中间层可能缓存授权结果。
因此,权限变更应包含:
- 变更前的授权快照;
- 变更脚本;
- 新旧会话验证;
- 应用连接池刷新策略;
- 失败时的精确回滚脚本。
八、一个可落地的分层设计
对于一个典型订单系统,可以将安全边界划分为:
身份层
- 应用使用独立的运行账户;
- 人员账户不直接供应用使用;
- 管理员使用个人账户和可追溯认证;
- 业务用户连接到正确的 PDB;
- Profile 约束密码和账户状态。
授权层
- 表由
APP_OWNER所有; - 应用只获得必要对象权限;
- 高风险修改通过存储程序封装;
- 禁止无必要的
ANY权限; - 禁止无必要的
ADMIN OPTION和GRANT OPTION; - 定期检查角色嵌套和
PUBLIC权限。
数据层
- 敏感表空间使用 TDE;
- 备份、密钥库和恢复流程分别保护;
- 客服、报表等场景使用脱敏视图或 API;
- 业务代码使用绑定变量。
证据层
- 审计关键 DDL、敏感表访问和高风险修改;
- 记录成功与失败的边界符合调查目标;
- 审计管理员与数据库管理员职责分离;
- 审计轨迹归档到独立平台;
- 定期验证审计策略确实命中。
验证层
对每个业务角色建立“允许矩阵”和“禁止矩阵”:
| 主体 | 动作 | 对象 | 预期 |
|---|---|---|---|
ORDER_RUNTIME |
SELECT |
APP_OWNER.PRODUCTS |
成功 |
ORDER_RUNTIME |
INSERT |
APP_OWNER.ORDERS |
成功 |
ORDER_RUNTIME |
直接 UPDATE amount |
APP_OWNER.ORDERS |
失败 |
ORDER_RUNTIME |
EXECUTE |
APP_OWNER.ORDER_API |
成功 |
SUPPORT_USER |
读取完整支付标识 | 基表 | 失败或仅返回脱敏值 |
| 审计管理员 | 查询审计轨迹 | 统一审计视图 | 成功 |
这个矩阵的意义在于把“最小权限”转化为可重复的测试,而不是停留在配置意图上。
九、与 SQLite 的边界
Oracle 的用户、角色、Profile、TDE 和统一审计是数据库服务器安全模型的一部分。SQLite 是嵌入式数据库库,原生模型通常没有 Oracle 意义上的:
- 数据库用户;
- 服务器端角色;
- Profile;
- 统一审计轨迹;
- 数据库进程级访问控制。
SQLite 的安全通常依赖:
- 操作系统文件权限;
- 应用进程隔离;
- 加密扩展或外部加密方案;
- 应用层认证与授权;
- 事务和参数绑定;
- 文件备份与密钥管理策略。
因此,不能把 Oracle 的:
CREATE ROLE ...
CREATE PROFILE ...
AUDIT POLICY ...
直接类比为 SQLite 的功能。两者都能执行 SQL,但安全边界不同:Oracle 主要在数据库服务端实施主体和权限控制;SQLite 很大程度上由宿主进程和文件系统承担控制责任。
Oracle 安全治理的核心不是某一个参数,而是让身份、权限、密钥、审计和数据访问边界相互对应:
- 用户回答“是谁”;
- 角色和权限回答“能做什么”;
- Profile 回答“账户如何被限制”;
- TDE 回答“文件被拿走后是否能直接读”;
- 审计回答“发生过什么”;
- 最小权限回答“为什么这个主体不应该拥有更多能力”。
只有把这些机制放在各自正确的边界内,并通过实际成功与失败测试验证,安全配置才真正成为可审查、可诊断、可恢复的治理体系。
系列导航与关联阅读
- 系列入口:数据库完整学习路线:从关系模型、事务索引到分布式与向量检索
- 上一篇:Oracle 物化视图与查询重写:刷新、日志、一致性和性能
- 下一篇:Oracle 监控与调优:AWR、ASH、等待事件、统计和容量
- 延伸:数据库安全治理:最小权限、加密、审计、脱敏与注入防护
官方资料
本文依据数据库官方文档重新梳理;正文、示例与生产检查清单由 WR BLOG 编写。

评论
0 条讨论