提问人:Evert Heylen 提问时间:10/11/2023 更新时间:10/20/2023 访问量:118
在共享 postgres 池中强制实施行级安全性
Enforcing row-level security in a shared postgres pool
问:
我正在开发一个典型的客户端-服务器 Web 应用程序。它使用的系统有点像 GraphQL,客户端可以灵活地指定所需的数据,而无需为每种类型的数据自定义 API 端点。服务器正在运行 node,并且正在使用具有典型 pg 的 node-postgres。游泳池
。客户端可以发送如下内容:
{select: '*', from: 'expenses', where: {'op': 'gt', 'lhs': 'expenses.amount', 'rhs': 20}}
这将被翻译成(给定 = 20)。只要足够小心,就可以使该系统免受注入攻击。SELECT * FROM expenses WHERE expenses.amount > $1
$1
我还想合并行级安全策略。例如:
create policy only_see_own_expenses on expenses using (expenses.user_id = <USER ID>);
作为额外的安全屏障,我想确保即使注入攻击成功,客户端也无法“取消设置”其用户 ID。
我见过以下几种定义:<USER ID>
current_user
在这种情况下,应用程序的每个用户还需要一个 Postgres 用户/角色- 任意设置,例如在事务开始时与
current_setting('myapp.user_id')
SET LOCAL myapp.user_id = ...
方法(2)对我来说似乎是最灵活的。我只是将每个生成的 SQL 查询包装在一个 .问题在于攻击者可以注入另一个语句,并模拟另一个用户。BEGIN; SET LOCAL myapp.user_id = 123; {generated query}; END;
SET LOCAL
在方法 (1) 中,类似地,您可以在开始时用语句包装每个生成的查询,从而产生相同的问题。另一种方法是为每个具有该特定角色的查询创建一个新连接。我相信 postgres 永远不会允许该连接切换到另一个角色。但是,为每个查询设置一个新连接将导致大量开销。SET ROLE ...
如何在不影响每个查询新连接的性能的情况下强制实施行级安全性?
答:
正如你所观察到的,设置占位符参数并用于临时代入不同的角色可能会被攻击者破坏,他们可以执行任意 SQL,就像在 SQL 注入攻击中一样。SET LOCAL ROLE
我不认为有一种方法可以完成您想要的事情,而不需要SQL注入。这是一个根本问题:您在应用程序中处理身份验证,而不是在数据库中处理身份验证(在数据库中使用单个应用程序用户从连接池中受益),但您希望数据库通过行级安全性处理授权。这需要一种方法让应用程序告诉数据库应用程序用户是什么。现在,应用程序可以告诉数据库任何内容的唯一方法是使用 SQL,而能够运行任意 SQL 语句的攻击者总是可以破坏它。
我认为您唯一的选择是强化您的应用程序以抵御 SQL 注入攻击。
作为额外的安全屏障,我想确保即使注入攻击成功,客户端也无法“取消设置”其用户 ID。
您的第二个解决方案不仅完全可行,而且正是许多网站所做的。当然,任何人都可以在 cookie、URL、SQL 语句甚至自定义协议框架中编辑参数,但只要您不这样做user_id
- 公开有效标识符的列表
- 使标识符易于/可能被猜测/暴力破解
- 共享或重复使用它们
改变它毫无意义,因为你没有什么可以改变它。如果有人只是“取消设置”ID,它将中断他们的访问权限。如果他们设置错误,也是一样的。您唯一关心的是确保如果他们设置正确,他们就不可能从您那里获得另一个 ID。假设上述情况,将另一个令牌更改为意味着它是在系统外部获得的,超出了您的控制范围(给定、被盗、泄露 - 不是由您提供的),或者它只是同一个人的多个会话,并且他们无论如何都有该访问权限。
话虽如此,它绝不能让你免于锁定你的SQL以防止注入,它要求你在其他一切之上设置和管理这种基于令牌的身份验证,这本身可能是一个挑战。确保您知道规则和权限的工作原理,并且您不会通过例程或视图泄露任何内容。security definer
security_barrier
评论
security definer
security_invoker
EXPLAIN (ANALYZE)
explain analyze
评论