我正在开发一个典型的客户端-服务器网络应用程序。它使用类似于 GraphQL 的系统,客户端可以灵活地指定所需的数据,而无需为每种类型的数据提供自定义 API 端点。服务器正在运行节点,并使用具有典型
pg.Pool
的node-postgres。客户可以发送如下内容:
{select: '*', from: 'expenses', where: {'op': 'gt', 'lhs': 'expenses.amount', 'rhs': 20}}
这将被翻译为
SELECT * FROM expenses WHERE expenses.amount > $1
(给定 $1
= 20)。只要足够小心,该系统就可以免受注入攻击。
我还想合并行级安全策略。例如:
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)中,您可以类似地在开始时使用
SET ROLE ...
语句包装每个生成的查询,从而产生相同的问题。另一种方法是为具有该特定角色的每个查询创建一个新连接。我相信 postgres 永远不会允许该连接切换到另一个角色。但是为每个查询设置一个新连接会导致大量开销。
如何强制执行行级安全性而不影响每个查询的新连接的性能?
正如您所观察到的,但是设置占位符参数并使用
SET LOCAL ROLE
临时承担不同的角色可能会被能够执行任意 SQL 的攻击者破坏,就像在 SQL 注入攻击中一样。
我认为没有一种方法可以安全地避免 SQL 注入来完成您想要的任务。问题是一个基本问题:您在应用程序中处理身份验证,而不是在数据库中(您使用单个应用程序用户从连接池中受益),但您希望数据库通过行级安全性处理授权。这需要应用程序通过某种方式告诉数据库应用程序用户是什么。现在,应用程序可以告诉数据库任何信息的唯一方法是使用 SQL,而可以运行任意 SQL 语句的攻击者总是可以破坏这一点。
我认为您唯一的选择是强化您的应用程序以抵御 SQL 注入攻击。