查DBA_SYS_PRIVS是唯一可靠入口;SELECT grantee, admin_option FROM dba_sys_privs WHERE privilege = 'SELECT ANY TABLE'可定位直接授权用户,admin_option为YES表示可转授;grantee若为角色需递归查DBA_ROLE_PRIVS;还需用SESSION_PRIVS验证当前会话是否生效,并检查近期创建的高危对象。
查 DBA_SYS_PRIVS 是唯一可靠入口
拥有 select any table 权限的用户,一定在 dba_sys_privs 里留下记录——它不藏在角色展开链里,也不混在对象权限视图中。误以为查 dba_role_privs 找到 dba 角色成员就能覆盖,是常见误判。
执行这条语句即可定位:
SELECT grantee, admin_option FROM dba_sys_privs WHERE privilege = 'SELECT ANY TABLE';
admin_option 为 YES 表示该用户还能把此权限转授他人,风险加倍;若为 NO,至少回收时不会级联影响下游。
注意:结果中的 grantee 可能是用户名,也可能是角色名。如果是角色(比如 APP_ADMIN),需再查 DBA_ROLE_PRIVS 确认哪些用户实际持有该角色。
别漏掉 WITH ADMIN OPTION 的隐性传播路径
一个用户哪怕没直接被授予 SELECT ANY TABLE,只要他属于某个被授了 WITH ADMIN OPTION 的中间角色,就可能已间接获得该权限并完成二次分发。
- 先查所有带
WITH ADMIN OPTION的SELECT ANY TABLE授予记录:SELECT grantee, granted_role FROM dba_role_privs WHERE granted_role IN (SELECT role FROM role_sys_privs WHERE privilege = 'SELECT ANY TABLE');
- 再对每个
granted_role,递归查其成员:SELECT grantee FROM dba_role_privs START WITH granted_role = 'XXX' CONNECT BY PRIOR grantee = granted_role;
- 真正高危的是那些既出现在第一层
DBA_SYS_PRIVS结果中、又出现在第二层角色链末端的用户——他们既能读所有表,又能悄悄把权限扩散出去
用 SESSION_PRIVS 验证当前连接是否真生效
很多 DBA 查到某用户有 SELECT ANY TABLE,一试却报 ORA-00942。问题常出在:角色未设为默认、或登录时用了 SET ROLE NONE。
此时 DBA_SYS_PRIVS 显示“有”,但 SESSION_PRIVS 里没有——说明权限当前不可用。
验证方法很简单:
SELECT * FROM session_privs WHERE privilege = 'SELECT ANY TABLE';
只有这条返回一行,才代表当前会话能真正执行 SELECT * FROM sys.user$ 这类操作。别依赖“曾经授过”就认定风险存在。
回收前必须检查是否已用于创建高危对象
SELECT ANY TABLE 不仅能读,还能被用来动态构造提权语句。最典型的是:用户曾用它查询 dba_objects 和 dba_source,然后创建了一个指向 SYS 模式的存储过程,内含 EXECUTE IMMEDIATE 'GRANT DBA TO ...'。
回收前务必运行:
SELECT owner, object_name, object_type FROM dba_objects WHERE status = 'VALID' AND created > SYSDATE - 7 AND owner IN ('SYS', 'SYSTEM') AND object_type IN ('PROCEDURE', 'FUNCTION', 'PACKAGE');重点看这些对象的源码(查 dba_source)是否含 GRANT、ALTER USER、CREATE USER 等关键字。一旦发现,先删对象再回收权限,否则留着后门。


















