最准确的方法是查询DBA_ROLE_PRIVS视图并排除Oracle内置用户,再结合ROLE_ROLE_PRIVS递归检查嵌套授权,同时校验V$PWFILE_USERS和组合权限风险。
查哪些用户被授予了DBA角色
直接查 dba_role_privs 视图是最准的,它记录所有角色授予关系,不依赖当前会话权限。重点筛出非系统用户(即用户名不是 oracle 内置账户),避免把 system、sys、outln 这类账号混进来。
常见错误是只查 SESSION_ROLES 或靠 CURRENT_USER 判断——这只能看到当前登录用户的权限,漏掉其他账号;还有人用 DBA_USERS 的 ACCOUNT_STATUS 过滤,但状态正常≠不该有DBA权限。
- 执行:
SELECT grantee FROM dba_role_privs WHERE granted_role = 'DBA' AND grantee NOT IN ('SYS','SYSTEM','OUTLN','DBSNMP','ORDDATA','ORDSYS','MDSYS','CTXSYS','XDB','WMSYS','APPQOSSYS','DVSYS','AUDSYS','GSMADMIN_INTERNAL'); - 更稳妥的做法是排除所有以
Oracle默认前缀开头的用户:grantee NOT LIKE 'APEX\_%' ESCAPE '\'、NOT LIKE 'FLOWS\_%'、NOT LIKE 'MDDATA'等 - 注意:
GRANTEE可能是角色名(比如某应用角色被授了DBA),所以还得递归查该角色下有哪些用户——但日常监控优先盯住直接授予DBA的用户
为什么不能只看DBA_ROLE_PRIVS就完事
因为 DBA 权限可能通过嵌套角色间接获得。比如用户 A 没被直授 DBA,但被授了角色 R,而 R 被授了 DBA——这种链式授权 DBA_ROLE_PRIVS 本身不展开,得靠 ROLE_ROLE_PRIVS 追踪。
典型踩坑场景:安全扫描工具只扫一层授权,报告“无非系统用户持DBA”,结果上线后发现某中间件账号通过自定义运维角色拿到了等效DBA权限。
- 简单递归检查命令(需有访问
ROLE_ROLE_PRIVS权限):SELECT DISTINCT r1.grantee FROM dba_role_privs r1 JOIN role_role_privs r2 ON r1.granted_role = r2.role WHERE r2.granted_role = 'DBA';
- 如果数据库版本 ≥ 12c,可用
WITH RECURSIVE查完整路径,但多数巡检脚本用两层 JOIN 就够用 - 注意性能:
ROLE_ROLE_PRIVS数据量小,但嵌套过深(>3 层)时建议加LEVEL <= 3限制,避免误触发全表扫描
定期自动检查的最小可行方案
不用上 ELK 或商业审计平台,一个带时间戳的 SQL 脚本 + cron 就能跑通。关键是输出要包含「谁、什么时候、怎么拿到的」三要素,否则出问题回溯不了。
容易被忽略的是权限回收后的残留痕迹:比如用户已被 DROP USER,但 DBA_ROLE_PRIVS 里还留着记录(实际已失效),或者用户被 ALTER USER ... ACCOUNT LOCK 但权限没清理。
- 推荐组合查询(含来源和状态):
SELECT d.grantee, d.granted_role, d.admin_option, u.account_status, SYSDATE AS check_time FROM dba_role_privs d LEFT JOIN dba_users u ON d.grantee = u.username WHERE d.granted_role = 'DBA' AND d.grantee NOT IN (/*同上系统用户列表*/);
- 把结果重定向到文件时,务必加
SET PAGESIZE 0 LINESIZE 32767 TRIMSPOOL ON FEEDBACK OFF VERIFY OFF,不然 spool 出来全是格式符 - 别用
TO_DATE(SYSDATE)当检查时间——会丢精度;直接用SYSDATE或SYSTIMESTAMP
DBA权限后门的真实风险点
很多人以为只要没开远程登录就没事,其实 DBA 角色自带 CREATE ANY PROCEDURE 和 ALTER SYSTEM,意味着能写存储过程调用 UTL_FILE 写 shell 脚本、或通过 ORACLE_HOME 下的外部程序提权。真正的高危动作不是“连得上”,而是“改得了”。
最常被绕过的检查是密码文件用户(REMOTE_LOGIN_PASSWORDFILE=EXCLUSIVE 时):这类用户即使不在 DBA_ROLE_PRIVS 里,也可能凭 SYSDBA 登录后直接赋权——所以必须同步查 V$PWFILE_USERS。
- 顺手加一条检查:
SELECT username FROM v$pwfile_users WHERE sysdba = 'TRUE' AND username NOT IN ('SYS','INTERNAL'); - 注意:19c 后
INTERNAL已废弃,但某些老迁移库还残留,别漏判 - 如果数据库启用了 Oracle Wallet 或 TDE,还要确认
ENCRYPTION_WALLET_LOCATION配置没被恶意覆盖——DBA 用户可改这个参数
真正难防的不是明面上的DBA账号,而是那些被悄悄赋予 SELECT_CATALOG_ROLE + EXECUTE_CATALOG_ROLE + CREATE ANY TRIGGER 的组合权限。它们凑一起,能力逼近DBA,却不会出现在 DBA_ROLE_PRIVS 的 ‘DBA’ 行里。










