SELECT ANY TABLE会绕过对象所有权边界,直接读取所有普通表(含SYS、SYSTEM等),易被用于提权且审计难追溯;回收权限不清理已建跨schema对象,风险残留;应改用角色+细粒度对象授权,并确保授予CREATE SESSION和锁定账户。

SELECT ANY TABLE 会绕过对象所有权边界
它不是“对某些表只读”,而是直接打开数据库所有普通表的读取通道——哪怕那张表属于 SYS、SYSTEM,或你还没建的表。用户执行 SELECT * FROM dba_users 能成功,不是因为他被显式授权了这张表,而是因为 SELECT ANY TABLE 命令级绕过了 schema 隔离。
它能被用来提权,且审计难追溯
拥有该权限的用户可创建指向高权限 schema 的存储过程,再用 EXECUTE IMMEDIATE 执行动态 SQL。例如在 SYS 下建一个 PROCEDURE p_exec,然后调用 EXECUTE IMMEDIATE 'GRANT DBA TO attacker'——整个过程在 DBA_SYS_PRIVS 里只留下一条 SELECT ANY TABLE 记录,看不出提权路径。
回收后风险仍残留
-
REVOKE SELECT ANY TABLE FROM user_name只删权限,不清理已存在的跨 schema 对象(如SYS.p_exec) - 用户可能早用它建了视图或物化视图,这些对象仍能访问敏感数据
- 若当初是脚本批量授予,很可能连谁授了、为何授都查不清,导致回收不全
替代方案必须用角色 + 对象授权
真正可控的只读,靠的是收口到角色里:
先建角色:CREATE ROLE app_readonly;
再生成并执行具体授权语句:GRANT SELECT ON APP_SCHEMA.orders TO app_readonly;
最后赋角色:GRANT app_readonly TO readonly_user。
漏掉 GRANT CREATE SESSION 用户连不上库,漏掉 ALTER USER ... ACCOUNT LOCK 就等于留了交互式入口——这两步常被跳过,但恰恰是生产环境最易被忽略的防线。


















