最准方法是直接查dba_sys_privs视图,执行select grantee, admin_option from dba_sys_privs where privilege in ('select any table', 'drop any table'),重点关注admin_option='yes'的记录,并顺查dba_role_privs确认实际持有者,同时严查public及sys下新创建的高危对象。

查哪些用户被授予了 SELECT ANY TABLE 或 DROP ANY TABLE
直接查 DBA_SYS_PRIVS 是最准的起点,别依赖角色继承或间接授权链——ANY 权限是系统级的,只在这一张表里明明白白列着。
- 执行
SELECT GRANTEE, ADMIN_OPTION FROM DBA_SYS_PRIVS WHERE PRIVILEGE IN ('SELECT ANY TABLE', 'DROP ANY TABLE'),重点关注ADMIN_OPTION = 'YES'的记录,这类用户能继续转授,风险扩散更快 - 注意:
GRANTEE可能是角色名(如APP_ADMIN),得顺藤摸瓜查DBA_ROLE_PRIVS确认谁实际持有该角色 - 别漏掉 PUBLIC —— 执行
SELECT * FROM DBA_SYS_PRIVS WHERE GRANTEE = 'PUBLIC' AND PRIVILEGE LIKE '%ANY%',哪怕只有一条,也得立刻处理
确认这些权限是否已被用于创建高危对象
回收权限前,必须先看它有没有“留下后门”:用户可能用 SELECT ANY TABLE 访问 SYS 表并创建了跨 Schema 的存储过程,回收权限后对象仍可执行。
- 运行
SELECT OBJECT_NAME, OBJECT_TYPE, STATUS FROM DBA_OBJECTS WHERE OWNER = 'SYS' AND CREATED > SYSDATE - 7 AND OBJECT_TYPE IN ('PROCEDURE', 'FUNCTION', 'PACKAGE'),重点盯新创建的PROCEDURE - 对可疑对象,用
SELECT TEXT FROM DBA_SOURCE WHERE OWNER = 'SYS' AND NAME = 'xxx' ORDER BY LINE检查是否含EXECUTE IMMEDIATE或GRANT类动态语句 - 别只查
CREATED时间,还要查LAST_DDL_TIME,有些对象是后期被重编译注入逻辑的
回收权限时绕不开的两个盲点
REVOKE SELECT ANY TABLE FROM user_name 看似干净,但现实里常踩两个坑:下游对象没失效,权限来源记不清。
- 视图、物化视图、函数体内的 SQL 仍能跑通——因为它们绑定的是“定义者权限”,不是调用者权限;得手动
ALTER VIEW xxx COMPILE或重建 - 如果当初是批量脚本授的权,很可能
DBA_AUDIT_TRAIL里没留记录,或日志已归档;建议翻查运维操作台账、Ansible playbooks 或 SQL*Plus 历史命令缓存 - 回收后立即执行
SELECT COUNT(*) FROM DBA_TAB_PRIVS WHERE GRANTEE = 'user_name',确认没残留的SELECT对象级权限被误当 ANY 权限漏掉
替代方案落地时最容易卡住的环节
最小权限不是喊口号,是得把每张表、每个 Schema 都列清楚,否则替换就变成“换汤不换药”。
- 别直接建
hr_reader角色然后GRANT SELECT ON HR.EMPLOYEES TO hr_reader就完事——漏掉HR.DEPARTMENTS或HR.JOBS,应用一连就报错 - 用
SELECT TABLE_NAME FROM DBA_TABLES WHERE OWNER = 'HR' AND TEMPORARY = 'N'拉全量清单,再人工筛出业务真正读的表(别信开发说的“都用”,要抓 AWR 或 ASH 中真实执行过的 SQL) - 如果应用连接用的是统一账号(如
app_user),务必同步执行ALTER USER app_user PASSWORD EXPIRE+ACCOUNT LOCK,防止有人拿账号交互登录去试权限边界
真正麻烦的从来不是查和 revoke,而是你不知道那个用 SELECT ANY TABLE 创建的 SYS.SOME_PROC 还在后台 quietly 执行着 GRANT DBA TO hacker。











