PostgreSQL PL/Perl 钩子安全性分析

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云原生数据库 PolarDB 分布式版,标准版 2核8GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
简介: 背景 plperl 是PostgreSQL支持的函数语言之一。 在使用plperl时,可以使用plperl提供的钩子功能,满足一些特殊场景的需求。 钩子分2种,一种是加载plperl.so库时的钩子,一种是加载perl语言解释器时的钩子。 钩子用法介绍 加载plperl.so库

背景

plperl 是PostgreSQL支持的函数语言之一。

在使用plperl时,可以使用plperl提供的钩子功能,满足一些特殊场景的需求。

钩子分2种,一种是加载plperl.so库时的钩子,一种是加载perl语言解释器时的钩子。

钩子的使用有安全问题吗?

钩子用法介绍

加载plperl.so库时的钩子

相关参数
plperl.on_init (string)

Specifies Perl code to be executed when a Perl interpreter is first initialized, before it is specialized for use by plperl or plperlu. 
The SPI functions are not available when this code is executed. 
If the code fails with an error it will abort the initialization of the interpreter and propagate out to the calling query, causing the current transaction or subtransaction to be aborted.

The Perl code is limited to a single string. 
Longer code can be placed into a module and loaded by the on_init string. Examples:

plperl.on_init = 'require "plperlinit.pl"'
plperl.on_init = 'use lib "/my/app"; use MyApp::PgInit;'

Any modules loaded by plperl.on_init, either directly or indirectly, will be available for use by plperl. 
This may create a security risk. 
To see what modules have been loaded you can use:

DO 'elog(WARNING, join ", ", sort keys %INC)' LANGUAGE plperl;

Initialization will happen in the postmaster if the plperl library is included in shared_preload_libraries, in which case extra consideration should be given to the risk of destabilizing the postmaster. 
The principal reason for making use of this feature is that Perl modules loaded by plperl.on_init need be loaded only at postmaster start, and will be instantly available without loading overhead in individual database sessions. 

However, keep in mind that the overhead is avoided only for the first Perl interpreter used by a database session — either PL/PerlU, or PL/Perl for the first SQL role that calls a PL/Perl function. 
Any additional Perl interpreters created in a database session will have to execute plperl.on_init afresh. Also, on Windows there will be no savings whatsoever from preloading, since the Perl interpreter created in the postmaster process does not propagate to child processes.

This parameter can only be set in the postgresql.conf file or on the server command line.

当设置了 shared_preload_libraries = 'plperl' 预加载时,plperl.on_init 只会被调用一次。

当没有设置 shared_preload_libraries = 'plperl' 预加载时,plperl.on_init 会在每个会话第一次装载plperl.so时被调用。

代码
src/pl/plperl/plperl.c

        /*
         * plperl.on_init is marked PGC_SIGHUP to support the idea that it might
         * be executed in the postmaster (if plperl is loaded into the postmaster
         * via shared_preload_libraries).  This isn't really right either way,
         * though.
         */
        DefineCustomStringVariable("plperl.on_init",
                                                           gettext_noop("Perl initialization code to execute when a Perl interpreter is initialized."),
                                                           NULL,
                                                           &plperl_on_init,
                                                           NULL,
                                                           PGC_SIGHUP, 0,
                                                           NULL, NULL, NULL);


/*
 * Create a new Perl interpreter.
 *
 * We initialize the interpreter as far as we can without knowing whether
 * it will become a trusted or untrusted interpreter; in particular, the
 * plperl.on_init code will get executed.  Later, either plperl_trusted_init
 * or plperl_untrusted_init must be called to complete the initialization.
 */
static PerlInterpreter *
plperl_init_interp(void)
{

...
        if (plperl_on_init && *plperl_on_init)
        {
                embedding[nargs++] = "-e";
                embedding[nargs++] = plperl_on_init;
        }
...

/*
 * _PG_init()                   - library load-time initialization
 *
 * DO NOT make this static nor change its name!
 */
void
_PG_init(void)
{
...
        /*
         * Create the first Perl interpreter, but only partially initialize it.
         */
        plperl_held_interp = plperl_init_interp();
...

plperl.on_init (string) 只能设置在配置文件中,或者在启动postgres时命令行指定。
对只开放普通数据库用户的环境来说没有安全问题。

加载perl语言解释器时的钩子

plperl 函数语言钩子,当在会话中第一次加载perl语言解释器时,perl 函数解释器将自动调用

plperl.on_plperl_init (string)  
plperl.on_plperlu_init (string)  

或设置的串。
具体调用哪个,和函数语言有关,plperl则调用on_plperl_init, plperlu则调用on_plperlu_init。

需要注意的是,这两个函数可以在参数中设置,也能在会话中设置,但是在会话中设置的话,如果perl解释器已经加载了,不会触发修改后的值。

另外需要注意on_plperl_init是在plperl安全化后执行的,所以即使在这里配置了不安全的属性,也不怕,因为会直接报错。(与调研plperl的用户权限无关,plperl是不允许执行不安全操作的,例如调研system接口)

这两个参数的解释 :
https://www.postgresql.org/docs/9.5/static/plperl-under-the-hood.html
plperl.on_plperl_init (string)
plperl.on_plperlu_init (string)

These parameters specify Perl code to be executed when a Perl interpreter is specialized for plperl or plperlu respectively. 

This will happen when a PL/Perl or PL/PerlU function is first executed in a database session, or when an additional interpreter has to be created because the other language is called or a PL/Perl function is called by a new SQL role. 

This follows any initialization done by plperl.on_init. 
The SPI functions are not available when this code is executed. 

The Perl code in plperl.on_plperl_init is executed after "locking down" the interpreter, and thus it can only perform trusted operations.  

If the code fails with an error it will abort the initialization and propagate out to the calling query, causing the current transaction or subtransaction to be aborted.   

Any actions already done within Perl won't be undone; however, that interpreter won't be used again. If the language is used again the initialization will be attempted again within a fresh Perl interpreter.

Only superusers can change these settings. 
Although these settings can be changed within a session, such changes will not affect Perl interpreters that have already been used to execute functions.

代码如下

src/pl/plperl/plperl.c

        DefineCustomStringVariable("plperl.on_plperl_init",
                                                           gettext_noop("Perl initialization code to execute once when plperl is first used."),
                                                           NULL,
                                                           &plperl_on_plperl_init,
                                                           NULL,
                                                           PGC_SUSET, 0,
                                                           NULL, NULL, NULL);

        DefineCustomStringVariable("plperl.on_plperlu_init",
                                                           gettext_noop("Perl initialization code to execute once when plperlu is first used."),
                                                           NULL,
                                                           &plperl_on_plperlu_init,
                                                           NULL,
                                                           PGC_SUSET, 0,
                                                           NULL, NULL, NULL);


src/backend/utils/misc/guc.c
                case PGC_SUSET:
                        if (context == PGC_USERSET || context == PGC_BACKEND)
                        {
                                ereport(elevel,
                                                (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
                                                 errmsg("permission denied to set parameter \"%s\"",
                                                                name)));
                                return 0;
                        }
                        break;

只有超级用户能设置这两个值,普通用户在会话中设置plperl.on_plperl_init时,触发设置则报错。

postgres=> set session plperl.on_plperl_init=''; 
SET
postgres=> SELECT * FROM test_munge();
WARNING:  42501: permission denied to set parameter "plperl.on_plperl_init"
LOCATION:  set_config_option, guc.c:5794

测试例子

postgresql.conf 参数

#plperl.on_plperlu_init = ' system("touch /home/digoal/t123") '
plperl.on_plperl_init = ' system("touch /home/digoal/t123") '
#plperl.on_init=' system("touch /home/digoal/tttt") '

测试

CREATE TABLE test (
    i int,
    v varchar
);

INSERT INTO test (i, v) VALUES (1, 'first line');
INSERT INTO test (i, v) VALUES (2, 'second line');
INSERT INTO test (i, v) VALUES (3, 'third line');
INSERT INTO test (i, v) VALUES (4, 'immortal');

CREATE OR REPLACE FUNCTION test_munge() RETURNS SETOF test AS 
$$

    my $rv = spi_exec_query('select i, v from test;');
    my $status = $rv->{status};
    my $nrows = $rv->{processed};
    foreach my $rn (0 .. $nrows - 1) {
        my $row = $rv->{rows}[$rn];
        $row->{i} += 200 if defined($row->{i});
        $row->{v} =~ tr/A-Za-z/a-zA-Z/ if (defined($row->{v}));
        return_next($row);
    }
    return undef;

$$
 LANGUAGE plperl;

SELECT * FROM test_munge();

使用 stat touch /home/digoal/t123 查看时间戳的变化
判断是否触发。

plperl和plperlu语言的区别

plperl是trust语言,在创建它的函数时,会监测安全性,例如过滤一些OS操作,等。普通用户和超级用户都可以创建plperl语言的函数。

plperlu则是untruste语言,允许任何操作,只有超级用户能创建plperlu的函数。

如果已经设置了plperl.on_plperl_init是一个不安全的值,则新建plperl函数会报错。

postgres=# show plperl.on_plperl_init;
        plperl.on_plperl_init        
-------------------------------------
  system("touch /home/digoal/t123") 
(1 row)

postgres=# CREATE OR REPLACE FUNCTION test_munge() RETURNS SETOF test AS 
$$

    my $rv = spi_exec_query('select i, v from test;');
    my $status = $rv->{status};
    my $nrows = $rv->{processed};
    foreach my $rn (0 .. $nrows - 1) {
        my $row = $rv->{rows}[$rn];
        $row->{i} += 200 if defined($row->{i});
        $row->{v} =~ tr/A-Za-z/a-zA-Z/ if (defined($row->{v}));
        return_next($row);
    }
    return undef;

$$
 LANGUAGE plperl;

ERROR:  38000: 'system' trapped by operation mask at line 2.
CONTEXT:  while executing plperl.on_plperl_init
compilation of PL/Perl function "test_munge"
LOCATION:  plperl_trusted_init, plperl.c:1016

调用时,如果触发了不安全的plperl.on_plperl_init,也会报错。

$ vi postgresql.conf
plperl.on_plperl_init = ' system("touch /home/digoal/t123") '
$ pg_ctl reload

postgres=# SELECT * FROM test_munge();
ERROR:  38000: 'system' trapped by operation mask at line 2.
CONTEXT:  while executing plperl.on_plperl_init
compilation of PL/Perl function "test_munge"
LOCATION:  plperl_trusted_init, plperl.c:1016

小结

  1. PostgreSQL将函数语言分为两类,一类是trust的另一类是untrust的。

trust的语言,不允许执行有破坏性的操作,例如系统命令,文件访问等。普通用户可以创建trust语言的函数。
untrust的语言,允许执行任何操作,只有superuser能创建untrust语言的函数。
如果只开放普通数据库用户出去,是没有安全风险的。

  1. PostgreSQL 为 plperl或plperlu语言设置了两种钩子,分别允许在加载libperl.so时被触发(在_PG_init(void)里面实现);

或者在加载perl解释器时被触发,其中加载解释器时又分为两种,plperl和perlu的设置。
用户利用钩子,可以实现一些特殊场景的应用。

  1. 数据库普通用户无法修改钩子参数
#plperl.on_plperlu_init = ' system("touch /home/digoal/t123") '
plperl.on_plperl_init = ' system("touch /home/digoal/t123") '
#plperl.on_init=' system("touch /home/digoal/tttt") '
  1. 即使设置了危险的plperl.on_plperl_init参数,因为这个参数的内容是在plperl函数风险评估后执行的,所以如果有风险也不允许执行,不存在安全风险。
plperl.on_plperl_init = ' system("touch /home/digoal/t123") '
postgres=# SELECT * FROM test_munge();
ERROR:  'system' trapped by operation mask at line 2.
CONTEXT:  while executing plperl.on_plperl_init
compilation of PL/Perl function "test_munge"

综上,PostgreSQL对语言的管理是非常安全的,只要不随意把超级用户放出去,不随意使用untrust语言创建不安全的函数。

相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
目录
相关文章
|
7月前
|
监控 关系型数据库 数据库
rds的安全性
rds的安全性
258 7
|
7月前
|
关系型数据库 MySQL 数据库
深入MySQL数据库进阶实战:性能优化、高可用性与安全性
深入MySQL数据库进阶实战:性能优化、高可用性与安全性
643 0
|
关系型数据库 物联网 PostgreSQL
沉浸式学习PostgreSQL|PolarDB 11: 物联网(IoT)、监控系统、应用日志、用户行为记录等场景 - 时序数据高吞吐存取分析
物联网场景, 通常有大量的传感器(例如水质监控、气象监测、新能源汽车上的大量传感器)不断探测最新数据并上报到数据库. 监控系统, 通常也会有采集程序不断的读取被监控指标(例如CPU、网络数据包转发、磁盘的IOPS和BW占用情况、内存的使用率等等), 同时将监控数据上报到数据库. 应用日志、用户行为日志, 也就有同样的特征, 不断产生并上报到数据库. 以上数据具有时序特征, 对数据库的关键能力要求如下: 数据高速写入 高速按时间区间读取和分析, 目的是发现异常, 分析规律. 尽量节省存储空间
769 1
|
3月前
|
Oracle NoSQL 关系型数据库
主流数据库对比:MySQL、PostgreSQL、Oracle和Redis的优缺点分析
主流数据库对比:MySQL、PostgreSQL、Oracle和Redis的优缺点分析
548 2
|
2月前
|
存储 关系型数据库 MySQL
四种数据库对比MySQL、PostgreSQL、ClickHouse、MongoDB——特点、性能、扩展性、安全性、适用场景
四种数据库对比 MySQL、PostgreSQL、ClickHouse、MongoDB——特点、性能、扩展性、安全性、适用场景
|
7月前
|
SQL 安全 关系型数据库
MySQL安全性:防止攻击和保护数据
MySQL安全性:防止攻击和保护数据
524 1
|
5月前
|
SQL 关系型数据库 MySQL
(八)MySQL锁机制:高并发场景下该如何保证数据读写的安全性?
锁!这个词汇在编程中出现的次数尤为频繁,几乎主流的编程语言都会具备完善的锁机制,在数据库中也并不例外,为什么呢?这里牵扯到一个关键词:高并发,由于现在的计算机领域几乎都是多核机器,因此再编写单线程的应用自然无法将机器性能发挥到最大,想要让程序的并发性越高,多线程技术自然就呼之欲出,多线程技术一方面能充分压榨CPU资源,另一方面也能提升程序的并发支持性。
455 3
|
关系型数据库 定位技术 分布式数据库
沉浸式学习PostgreSQL|PolarDB 18: 通过GIS轨迹相似伴随|时态分析|轨迹驻点识别等技术对拐卖、诱骗场景进行侦查
本文主要教大家怎么用好数据库, 而不是怎么运维管理数据库、怎么开发数据库内核.
1319 1
|
7月前
|
存储 关系型数据库 MySQL
TiDB与MySQL、PostgreSQL等数据库的比较分析
【2月更文挑战第25天】本文将对TiDB、MySQL和PostgreSQL等数据库进行详细的比较分析,探讨它们各自的优势和劣势。TiDB作为一款分布式关系型数据库,在扩展性、并发性能等方面表现突出;MySQL以其易用性和成熟性受到广泛应用;PostgreSQL则在数据完整性、扩展性等方面具有优势。通过对比这些数据库的特点和适用场景,帮助企业更好地选择适合自己业务需求的数据库系统。
1167 4
|
7月前
|
SQL 关系型数据库 MySQL
PostgreSQL【异常 01】java.io.IOException:Tried to send an out-of-range integer as a 2-byte value 分析+解决
PostgreSQL【异常 01】java.io.IOException:Tried to send an out-of-range integer as a 2-byte value 分析+解决
475 1

相关产品

  • 云原生数据库 PolarDB
  • 云数据库 RDS PostgreSQL 版