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);
由于计划选择在比较昂贵的 NOT LIKE
之前执行比较便宜的 tricky
函数,因此 phone_data
表中的每个人和电话号码都将被打印为 NOTICE
。即使防止用户定义新函数,内置函数也可以用于类似的攻击。(例如,大多数转换函数在其产生的错误消息中包含其输入值。)
类似的注意事项适用于更新规则。在上一节的示例中,示例数据库中表的拥有者可以向其他人授予 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
查看查询计划,或测量针对视图的查询运行时间。恶意攻击者可能能够推断出一些有关不可见数据的信息,甚至获得有关数据分布或最常见值的一些信息(因为这些内容可能会影响计划的运行时间;或者,因为它们也会反映在优化器统计信息中,选择计划)。如果此类“隐蔽信道”攻击令人担忧,那么最好根本不对数据授予任何访问权限。