在共享 postgres 池中强制实施行级安全性

Enforcing row-level security in a shared postgres pool

提问人:Evert Heylen 提问时间:10/11/2023 更新时间:10/20/2023 访问量:118

问:

我正在开发一个典型的客户端-服务器 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>

  1. current_user在这种情况下,应用程序的每个用户还需要一个 Postgres 用户/角色
  2. 任意设置,例如在事务开始时与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 注入 节点 postgres 行级安全性

评论

1赞 Bergi 10/11/2023
"我相信 postgres 永远不会允许这种连接切换到另一个角色。- 确实如此。您需要区分身份验证角色(打开连接时使用的)、会话用户标识符(超级用户可以模拟)和当前用户标识符(可设置为授予会话用户的任何角色)。因此,除非您为每个用户创建单独的登录角色,否则 SQL 注入可能会切换到任意角色。

答:

2赞 Laurenz Albe 10/17/2023 #1

正如你所观察到的,设置占位符参数并用于临时代入不同的角色可能会被攻击者破坏,他们可以执行任意 SQL,就像在 SQL 注入攻击中一样。SET LOCAL ROLE

我不认为有一种方法可以完成您想要的事情,而不需要SQL注入。这是一个根本问题:您在应用程序中处理身份验证,而不是在数据库中处理身份验证(在数据库中使用单个应用程序用户从连接池中受益),但您希望数据库通过行级安全性处理授权。这需要一种方法让应用程序告诉数据库应用程序用户是什么。现在,应用程序可以告诉数据库任何内容的唯一方法是使用 SQL,而能够运行任意 SQL 语句的攻击者总是可以破坏它。

我认为您唯一的选择是强化您的应用程序以抵御 SQL 注入攻击。

2赞 Zegarek 10/18/2023 #2

作为额外的安全屏障,我想确保即使注入攻击成功,客户端也无法“取消设置”其用户 ID。

您的第二个解决方案不仅完全可行,而且正是许多网站所做的。当然,任何人都可以在 cookie、URL、SQL 语句甚至自定义协议框架中编辑参数,但只要您不这样做user_id

  1. 公开有效标识符的列表
  2. 使标识符易于/可能被猜测/暴力破解
  3. 共享或重复使用它们

改变它毫无意义,因为你没有什么可以改变它。如果有人只是“取消设置”ID,它将中断他们的访问权限。如果他们设置错误,也是一样的。您唯一关心的是确保如果他们设置正确,他们就不可能从您那里获得另一个 ID。假设上述情况,将另一个令牌更改为意味着它是在系统外部获得的,超出了您的控制范围(给定、被盗、泄露 - 不是由您提供的),或者它只是同一个人的多个会话,并且他们无论如何都有该访问权限。

话虽如此,它绝不能让你免于锁定你的SQL以防止注入,它要求你在其他一切之上设置和管理这种基于令牌的身份验证,这本身可能是一个挑战。确保您知道规则和权限的工作原理,并且您不会通过例程或视图泄露任何内容。security definersecurity_barrier

评论

0赞 Bergi 10/19/2023
...或视图(即关闭,这是默认设置!security definersecurity_invoker
0赞 Laurenz Albe 10/20/2023
是我误解了,还是你提倡“默默无闻的安全”?
0赞 Zegarek 10/20/2023
@LaurenzAlbe我不是,但我对你是如何得到这种印象的感兴趣
0赞 Laurenz Albe 10/20/2023
“只要你不让标识符容易/可能被猜测/暴力更改它就没有意义,因为你没有什么可以改变它。要找出由于 RLS 而无法读取的表中是否存在某个 UUID,难道不能只查询一下,看看是否有一行被过滤器删除了吗?EXPLAIN (ANALYZE)
1赞 Zegarek 10/20/2023
无论如何,你可以。但是为什么?如果你有一个有效的令牌,你不需要告诉你 - 如果你试图在暴力尝试中测试令牌,这只是一种较慢的方法。您甚至可以整天加载大量 uuid 猜测,直到 1gig,但值得提醒的是,即使是常规的 uuid 也是多么稀疏,在这种情况下,它不再是突破性的尝试,而是拒绝服务。它与任何其他密码猜谜游戏没有太大区别。explain analyze