41.5. 规则和权限#
由于PostgreSQL规则系统会重写查询,因此会访问到原始查询中未使用的其他表/视图。当使用更新规则时,这可能包括对表的写访问。
重写规则没有单独的所有者。关系(表或视图)的所有者会自动成为为其定义的重写规则的所有者。PostgreSQL规则系统会更改默认访问控制系统的行为。除了与安全调用者视图关联的SELECT
规则(请参阅CREATE VIEW
)之外,所有因规则而使用的关系都会根据规则所有者的权限进行检查,而不是调用规则的用户。这意味着,除了安全调用者视图之外,用户只需要对其查询中明确命名的表/视图拥有所需的权限。
例如:用户有一份电话号码列表,其中一些是私人号码,其他号码对办公室助理有用。用户可以构建以下内容
CREATE TABLE phone_data (person text, phone text, private boolean);
CREATE VIEW phone_number AS
SELECT person, CASE WHEN NOT private THEN phone END AS phone
FROM phone_data;
GRANT SELECT ON phone_number TO assistant;
除了该用户(和数据库超级用户)之外,没有人可以访问phone_data
表。但由于GRANT
,助理可以在phone_number
视图上运行SELECT
。规则系统会将phone_number
中的SELECT
重写为phone_data
中的SELECT
。由于用户是phone_number
的所有者,因此也是该规则的所有者,因此现在会根据用户的权限检查对phone_data
的读取访问,并且允许该查询。还会执行对phone_number
的访问检查,但这是针对调用用户执行的,因此除了用户和助理之外,没有人可以使用它。
权限会逐个规则进行检查。因此,助理现在是唯一可以看到公共电话号码的人。但助理可以设置另一个视图,并向公众授予对该视图的访问权限。然后,任何人都可以通过助理的视图看到phone_number
数据。助理无法做的是创建直接访问phone_data
的视图。(实际上助理可以创建,但它不起作用,因为在权限检查期间会拒绝每次访问。)并且一旦用户注意到助理打开了他们的phone_number
视图,用户就可以撤销助理的访问权限。任何对助理视图的访问都会立即失败。
有人可能会认为这种逐个规则的检查存在安全漏洞,但实际上并非如此。但如果它不是这样工作,助理可以设置一个与phone_number
具有相同列的表,并每天将数据复制到那里。然后,这是助理自己的数据,助理可以向他们想要的人授予访问权限。GRANT
命令表示“我相信你”。如果你信任的人执行了上述操作,那么是时候考虑一下,然后使用REVOKE
。
请注意,虽然视图可以使用上述技术来隐藏某些列的内容,但除非已设置security_barrier
标志,否则无法可靠地隐藏未显示行中的数据。例如,以下视图不安全
CREATE VIEW phone_number AS
SELECT person, phone FROM phone_data WHERE phone NOT LIKE '412%';
此视图可能看起来是安全的,因为规则系统会将任何从phone_number
中进行的SELECT
重写为从phone_data
中进行的SELECT
,并添加仅需要phone
不以 412 开头的项的限定条件。但如果用户可以创建自己的函数,则很容易说服规划器在NOT LIKE
表达式之前执行用户定义的函数。例如
CREATE FUNCTION tricky(text, text) RETURNS bool AS $$
BEGIN
RAISE NOTICE '% => %', $1, $2;
RETURN true;
END;
$$ LANGUAGE plpgsql COST 0.0000000000000000000001;
SELECT * FROM phone_number WHERE tricky(person, phone);
phone_data
表中的每个人和电话号码都将作为NOTICE
打印,因为规划器会选择在更昂贵的NOT LIKE
之前执行廉价的tricky
函数。即使阻止用户定义新函数,内置函数也可以用于类似的攻击。(例如,大多数转换函数在其产生的错误消息中包含其输入值。)
类似的考虑事项适用于更新规则。在上一部分的示例中,示例数据库中表的拥有者可以将SELECT
、INSERT
、UPDATE
和DELETE
权限授予shoelace
视图中的其他人,但只能授予shoelace_log
中的SELECT
。写入日志项的规则操作仍将成功执行,并且该其他用户可以看到日志项。但他们无法创建虚假项,也无法操作或删除现有项。在这种情况下,不可能通过说服规划器更改操作顺序来颠覆规则,因为唯一引用shoelace_log
的规则是不合格的INSERT
。在更复杂的情况下可能并非如此。
当视图需要提供行级安全性时,应将security_barrier
属性应用到视图。这可以防止恶意选择的函数和运算符在视图完成工作之前将值从行中传递出去。例如,如果上面显示的视图像这样创建,它将是安全的
CREATE VIEW phone_number WITH (security_barrier) AS
SELECT person, phone FROM phone_data WHERE phone NOT LIKE '412%';
使用security_barrier
创建的视图的性能可能远低于不使用此选项创建的视图。通常,没有办法避免这种情况:如果可能损害安全性,则必须拒绝最快的可能计划。因此,此选项默认情况下未启用。
查询计划器在处理没有副作用的函数时具有更大的灵活性。此类函数称为LEAKPROOF
,其中包括许多简单、常用的运算符,例如许多相等运算符。查询计划器可以安全地允许在查询执行过程中的任何点评估此类函数,因为对用户不可见的行调用它们不会泄露有关不可见行的任何信息。此外,不带参数或未从安全屏障视图传递任何参数的函数不必标记为LEAKPROOF
即可下推,因为它们永远不会从视图接收数据。相比之下,可能根据作为参数接收的值引发错误的函数(例如在溢出或除以零时引发错误的函数)不是防泄漏的,如果在安全视图的行过滤器之前应用,可能会提供有关不可见行的重要信息。
重要的是要了解,即使使用security_barrier
选项创建的视图也仅在有限的意义上是安全的,即不可见元组的内容不会传递给可能不安全的函数。用户很可能还有其他方法来推断不可见数据;例如,他们可以使用EXPLAIN
查看查询计划,或测量针对视图的查询的运行时间。恶意攻击者可能能够推断出不可见数据的数量,甚至获取有关数据分布或最常见值的一些信息(因为这些事情可能会影响计划的运行时间;甚至,因为它们也反映在优化器统计信息中,所以计划的选择)。如果这些类型的“秘密通道”攻击令人担忧,那么最好根本不授予对数据的任何访问权限。