介绍
2024 年 6 月 2 日,Snowflake 与 Crowdstrike 和 Mandiant 发布了一份联合声明,回应了“正在进行的调查,涉及针对一些 Snowflake 客户帐户的针对性威胁活动”的报告。一位 SpecterOps 客户就其组织对此活动的响应与我联系,并提到与 Snowflake 相关的基于安全的信息似乎很少。在最初的声明中,Snowflake 为可能受到影响(或希望避免受到影响)的组织建议采取以下步骤:
-
对所有账户实施 Multi-Factor Authentication;
-
设置网络策略规则以仅允许授权用户或仅允许来自受信任位置(VPN、云工作负载 NAT 等)的流量;和
-
受影响的组织应重置和轮换 Snowflake 凭证。
虽然这些建议是很好的第一步,但我想知道,一旦我们更好地掌握了 Snowflake 的访问控制模型(及其相关的攻击路径)并更好地了解攻击者在受感染帐户上的活动细节,我们是否可以做任何其他事情。在这篇文章中,我将介绍高级 Snowflake 访问控制模型,分析 Mandiant 发布的事件报告,并提供有关绘制 Snowflake 部署的“访问模型”的说明。
这些建议解决了组织如何解决对其 Snowflake 实例的初始访问问题。但是,我对 Snowflake 环境中的 “post-exploitation” 感到好奇。在快速的 Google 搜索之后,我意识到关于 Snowflake 的威胁研究很少。我的下一个想法是查看 Snowflake 的访问控制模型,以更好地了解访问环境。我希望,如果我能理解用户是如何被授予对 Snowflake 账户中资源的访问权限的,我就可以开始了解攻击者在通过身份验证后可能会做什么。我还认为我们可以分析现有的攻击路径,以提出建议,以减少 Crowdstrike 和 Mandiant 报告的那种漏洞的爆炸半径。
虽然我们尚未将 Snowflake 集成到 BloodHound 社区版 (BHCE) 或企业版 (BHE) 中,但我们认为采用以图形为中心的方法来分析您的部署是有价值的,因为它可以帮助您了解类似于本文简介中描述的活动的影响。
Snowflake 访问控制模型
我的第一步是搜索有关 Snowflake 访问控制模型的任何文档。我很高兴找到一个页面,它提供了一个相对全面且易于理解的模型描述。他们将他们的模型描述为自由访问控制和基于角色的访问控制的混合体,其中“每个对象都有一个所有者,该所有者可以反过来授予对该对象的访问权限”,其中“将权限分配给角色,而角色又会分配给用户”。这些关系如下图所示:
请注意,角色 1 “拥有” 对象 1 和 2。然后,请注意,从对象 1 到角色 2 授予了两个不同的特权,并将角色 2 授予了用户 1 和 2。另请注意,可以将角色授予其他角色,这意味着存在类似于 Active Directory 中的组的嵌套层次结构。我发现有帮助的一件事是翻转其中一些 “边缘” 的关系。在此图中,他们指向授权,但访问方向相反。假设您是用户 1,并且您被授予了角色 2,该角色对对象 1 具有两个权限。因此,通过传递性,您对对象 1 有两个权限。
我们对如何授予对象的权限有一个大致的了解,但 Snowflake 实现了哪些类型的对象呢?它们提供了一个图形来显示这些对象之间的关系,它们将其描述为 “分层”。
请注意,在层次结构的顶部,有一个组织。每个组织可以有一个或多个账户。例如,我为进行这项研究而创建的试用只有一个 Account,但联系我的客户有 ~10。账户通常被认为是一切的纽带。将帐户视为 Active Directory 域的等效项会很有帮助。帐户中有用户、角色(组)、数据库、仓库(虚拟计算资源)和许多其他对象,例如 Security Integrations。在数据库上下文中是一个 Schema,在 Schema 上下文中是 Tables、Views、Stages(用于加载/卸载数据的临时存储)等。
当我开始了解每个对象的含义以及每个对象提供的权限类型时,我开始构建一个模型来显示它们可能的关系。在此过程中,我发现从层次结构的顶部 (帐户) 开始,然后逐步将实体类型集成到模型中,这很有帮助。这很有用,因为对实体的访问通常取决于对其父实体的访问。例如,如果用户还有权访问架构的父数据库,则用户只能与架构交互。这使我们能够抽象出细节,并对较低级别的访问做出有根据的推断。下面,我将描述我在模型中考虑的主要对象。
Account (考虑 Domain)
该帐户等效于域。所有对象都存在于帐户的上下文中。当您登录 Snowflake 时,您将以特定账户中的用户身份登录。大多数管理权限是对帐户进行操作的权限,例如 、 等。CREATE USER
MANAGE GRANTS
CREATE ROLE
CREATE DATABASE
EXECUTE TASK
用户(正是您认为的)
用户是您在 Snowflake 生态系统中的身份。当您登录系统时,您以特定用户身份登录,并且您可以根据授予的角色和角色授予的权限访问资源。
角色 (想想 组)
角色是为其分配权限的主要对象。可以向用户授予角色的 “USAGE”,类似于添加为组成员。还可以将角色授予其他角色,从而创建一个嵌套结构,以便于对权限进行精细控制。有 ~ 五个默认管理员帐户。第一个是 ,它是 Domain Admin 的 Snowflake 等效项。其余四个是 、 、 和 。ACCOUNTADMIN
ORGADMIN
SYSADMIN
SECURITYADMIN
USERADMIN
仓库
Warehouse 是“计算机资源的集群...例如 CPU、内存和临时存储“,用于在 Snowflake 会话中执行与数据库相关的操作。从表中检索行、更新表中的行以及从表中加载/卸载数据等操作都需要 Warehouse。
数据库
数据库被定义为 “架构的逻辑分组”。它是我们希望攻击者瞄准的信息容器。虽然数据库对象本身不包含任何数据,但用户必须有权访问数据库才能访问其从属对象(架构、表等)。
权限(想想访问权限)
权限定义谁可以对哪些资源执行哪个操作。在我们的上下文中,权限主要分配给角色。Snowflake 支持许多权限,其中一些权限适用于全局或帐户上下文(例如,),而另一些权限则特定于对象类型(例如,在 Database 上)。用户通过递归授予他们的 Role 来累积权限。CREATE USER
CREATE SCHEMA
访问图
对 Snowflake 的访问控制模型有了基本的了解,我们就可以创建一个图形模型,通过权限描述实体之间的关系。例如,我们知道可以向用户授予角色的权限。这相当于 Active Directory 用户是一个组。此外,我们发现一个角色可以被授予另一个角色,类似于 AD 中的组嵌套。最终,我们可以为 Snowflake 的 “access graph” 生成这个相对完整的初始模型。USAGE
MemberOf
USAGE
此模型可以帮助我们更好地了解事件期间可能发生的情况。它还可以帮助我们更好地了解 Snowflake 部署的访问情况,这可以帮助我们在攻击者获得访问权限时减少爆炸半径。
关于事件
随着更多细节的浮出水面,很明显,该活动针对的是客户凭证,而不是 Snowflake 的生产环境。后来,在 6 月 10 日,Mandiant 发布了一份更详细的报告,描述了在调查期间发现的威胁组织的一些活动。
Mandiant 描述了一种典型场景,其中威胁行为者入侵了公司雇用来构建、管理或管理其 Snowflake 部署的承包商的计算机。在许多情况下,这些承包商已经拥有管理权限,因此其证书的任何泄露都可能导致不利影响。现有的管理权限表明,威胁行为者在此活动期间无需通过攻击路径提升权限或泄露备用身份。
Mandiant 描述了观察到攻击者实施的活动类型。他们似乎对列举数据库表以查找有趣的信息以进行泄露感兴趣。一个重要的观察结果是,根据报告的活动,被盗用的用户似乎对 Snowflake 帐户具有管理员或管理员相邻权限。
在本节中,我们将讨论这些命令中的每一个,它们的作用以及我们如何在图形的上下文中理解它们。
正如 Mandiant 所描述的,第一个命令是 Discovery 命令,旨在列出所有可用的表。根据文档,用户至少需要对包含该表的 Schema 对象具有特权才能直接执行此命令。生产 Snowflake 部署通常具有许多数据库,每个数据库都有许多架构,因此对表的访问可能仅限于大多数非管理员。不过,我们可以在图表中验证这一点!USAGE
接下来,我们看到他们运行了命令。这表示他们必须从上一个命令中找到了他们感兴趣的一个或多个表。此命令的工作方式类似于 SQL 查询,并返回表中的行。在这种情况下,他们将转储整个表。权限文档指出,用户必须具有指定表 () 的权限才能执行此命令。此外,用户必须具有父数据库 () 和架构 () 的权限。SELECT
SELECT
<Target Table>
USAGE
<Target Database>
<Target Schema>
与表一样,阶段存在于架构上下文中;因此,必要的权限 , 存在于模式级别(又名 )上。用户还需要数据库 () 的权限。因此,用户可以为一个架构创建阶段,但不能为另一个架构创建阶段。通常,这是可以授予有限一组个人的特权,尤其是在涉及敏感数据库/架构时。CREATE STAGE
<Redacted Schema>
USAGE
<Redacted Database>
最后,攻击者调用命令,这是一种从 Snowflake 数据库中提取数据的方法。显然,Mandiant 编辑了路径,但一个可能的示例是使用临时阶段将数据复制到 Amazon S3 存储桶。在这种情况下,攻击者使用需要权限的变体。当然,攻击者在前面的命令中创建了 stage 资源,因此他们很可能拥有 stage,从而授予他们对对象的完全控制权。COPY INTO
COPY INTO <location>
WRITE
OWNERSHIP
构建您自己的图表
此时,你们中的一些人可能有兴趣查看您的 Snowflake Access Graph。本节将介绍如何收集必要的 Snowflake 数据、启动 Neo4j 并构建图形。它还提供了一些与 Snowflake 的建议相关的示例 Cypher 查询。
收集数据
第一步是从 Snowflake 收集与图形相关的数据。很酷的是,这实际上是一个相对简单的过程。我发现 Snowflake 的默认 Web 客户端 Snowsight 在收集此信息方面做得很好。登录后,您可以通过单击主页顶部的 Query data (查询数据) 按钮导航到 Snowsight。
到达那里后,您将有机会执行命令。本节将介绍用于收集构建图表所需数据的命令。我的解析脚本是为遵循特定命名约定的 CSV 文件构建的。命令返回结果后,单击下载按钮(向下箭头)并选择“Download as .csv”选项。
该模型支持 Account、Applications、Databases、Roles、Users 和 Warehouses。这意味着我们将不得不查询这些实体,这些实体将用作我们图表中的节点。这将下载具有与您的帐户相关的名称的文件。我的解析脚本期望某些命令的输出以特定方式命名。预期名称将在下面的相应部分中共享。
我发现我可以以非特权用户的身份查询 Applications、Databases、Roles 和 Users。但是,这对于需要 的 帐户 和需要特定于实例的访问权限(例如 )的 Warehouse 来说是不同的。ORGADMIN
ACCOUNTADMIN
应用
-
命令:
SHOW APPLICATIONS;
-
文件名:
application.csv
数据库
-
命令:
SHOW DATABASES;
-
文件名:
database.csv
集成
-
命令:
SHOW INTEGRATIONS;
-
文件名:
integration.csv
角色
-
命令:
SHOW ROLES;
-
文件名:
role.csv
用户
-
命令:
SHOW USERS;
-
文件名:
user.csv
仓库
-
命令:
SHOW WAREHOUSES;
-
文件名:
warehouse.csv
注意: 如上所述,用户只能枚举他们已被授予权限的仓库。授予非用户对所有仓库的可见性的一种方法是授予权限。
ACCOUNTADMIN
MANAGE WAREHOUSES
帐户
此时,我们几乎拥有了所需的所有实体数据。我们有一个最后一个查询,该查询将允许我们收集有关 Snowflake 帐户的详细信息。此查询只能由角色完成。假设您的用户已被授予 ,请转到浏览器的右上角,然后单击您当前的角色。这将产生一个下拉列表,其中显示有效授予用户的所有角色。在这里,您将选择 ,允许您在角色的上下文中运行命令。ORGADMIN
ORGADMIN
ORGADMIN
ORGADMIN
完成后,运行以下命令以列出账户详细信息。
-
命令:
SHOW ACCOUNTS;
-
文件名:
account.csv
拨款
最后,我们必须收集有关特权授予的信息。这些保留在 default 数据库的架构中。默认情况下,这些视图仅对角色可用。不过,未被授予该角色的用户可以通过 database 角色获得必要的读取访问权限。以下命令执行此操作(如果运行方式为 ):ACCOUNT_USAGE
SNOWFLAKE
ACCOUNTADMIN
USAGE
ACCOUNTADMIN
SECURITY_VIEWER
ACCOUNTADMIN
GRANT DATABASE ROLE snowflake.SECURITY_VIEWER TO <Role>
获得必要的权限后,您可以查询相关视图并将其导出为 CSV 文件。第一个视图是 ,它维护已向哪些用户授予哪些角色的列表。您可以使用以下命令枚举此列表。然后将其保存到 CSV 文件并重命名 。grants_to_users
grants_to_users.csv
SELECT * FROM snowflake.account_usage.grants_to_users;
最后一个视图是 ,它维护着授予角色的所有权限的列表。此 glue 最终允许用户与不同的 Snowflake 实体进行交互。可以使用以下命令枚举此视图。结果应保存为名为 .grants_to_roles
grants_to_roles.csv
SELECT * FROM snowflake.account_usage.grants_to_roles WHERE GRANTED_ON IN ('ACCOUNT', 'APPLICATION', 'DATABASE', 'INTEGRATION', 'ROLE', 'USER', 'WAREHOUSE');
设置 Neo4j
此时,我们有一个 Cypher 语句,可以使用它来生成 Snowflake 图,但在此之前,我们需要一个 Neo4j 实例。据我所知,最简单的方法是使用 BloodHound Community Edition docker-compose 部署选项。
注意: 虽然我们不会在本演示中专门使用 BHCE,但总体 docker-compose 设置包括一个配置为支持此示例的 Neo4j 实例。
为此,您必须首先在您的计算机上安装 Docker。完成后,下载我从 BHCE GitHub 存储库派生的此示例 docker-compose yaml 文件。接下来,在文本编辑器中打开并编辑第 51 行,以指向主机上您编写 Snowflake 数据文件的文件夹(例如,)。这将在您的主机和容器之间创建一个绑定挂载。现在,您可以通过执行以下命令来启动容器:docker-compose.yaml
/Users/jared/snowflake:/var/lib/neo4j/import/
grants_to_roles.csv
docker-compose -f /path/to/docker-composer.yaml up -d
这将导致 Docker 下载并运行相关的 Docker 容器。对于这个 Snowflake 图,我们将直接与 Neo4j 交互,因为这个模型还没有集成到 BloodHound 中。您可以通过浏览到 127.0.0.1:7474 并使用默认凭证 (neo4j:bloodhoundcommunityedition) 登录来访问 Neo4j Web 界面。
数据摄取
一旦您对 Neo4j 进行了身份验证,就可以进行数据摄取了。我最初编写了一个 PowerShell 脚本,该脚本将解析 CSV 文件并手动创建 Cypher 查询以创建相应的节点和边缘,但 SadProcessor 向我展示了一种更好的提取方法。他建议使用该条款。根据 Neo4j 的说法,“用于将 CSV 文件中的数据导入 Neo4j 数据库。这大大简化了 Snowflake 数据的摄取过程,并且比我的初始 PowerShell 脚本效率高得多。本节介绍我用于导入 Snowflake 数据的 Cypher 查询。在开始之前,了解每个命令必须单独运行至关重要。此外,这些命令假定您已按照建议命名文件。因此,您在 Docker Volume 中指定的文件夹的文件列表(例如 )应如下所示:LOAD CSV
LOAD CSV
/Users/jared/snowflake
-rwx------@ 1 cobbler staff 677 Jun 12 20:17 account.csv
-rwx------@ 1 cobbler staff 227 Jun 12 20:17 application.csv
-rwx------@ 1 cobbler staff 409 Jun 12 20:17 database.csv
-rwx------@ 1 cobbler staff 8362 Jun 12 20:17 grants_to_roles.csv
-rwx------@ 1 cobbler staff 344 Jun 12 20:17 grants_to_users.csv
-rwx------@ 1 cobbler staff 114 Jun 12 20:17 integration.csv
-rwx------@ 1 cobbler staff 895 Jun 12 20:17 role.csv
-rwx------@ 1 cobbler staff 12350 Jun 12 20:17 table.csv
-rwx------@ 1 cobbler staff 917 Jun 12 20:17 user.csv
-rwx------@ 1 cobbler staff 436 Jun 12 20:17 warehouse.csv
注意: 如果您没有 Snowflake 环境,但仍想查看图表,则可以在每个查询中替换 来使用我的示例数据集。
file:///
https://gist.githubusercontent.com/jaredcatkinson/c5e560f7d3d0003d6e446da534a89e79/raw/c9288f20e606d236e3775b11ac60a29875b72dbc/
引入帐户
LOAD CSV WITH HEADERS FROM 'file:///account.csv' AS line
CREATE (:Account {name: line.account_locator, created_on: line.created_on, organization_name: line.organization_name, account_name: line.account_name, snowflake_region: line.snowflake_region, account_url: line.account_url, account_locator: line.account_locator, account_locator_url: line.account_locator_url})
摄取应用程序
LOAD CSV WITH HEADERS FROM 'file:///application.csv' AS line
CREATE (:Application {name: line.name, created_on: line.created_on, source_type: line.source_type, source: line.source})
摄取数据库
LOAD CSV WITH HEADERS FROM 'file:///database.csv' AS line
CREATE (:Database {name: line.name, created_on: line.created_on, retention_time: line.retention_time, kind: line.kind})
摄取集成
LOAD CSV WITH HEADERS FROM 'file:///integration.csv' AS line
CREATE (:Integration {name: line.name, created_on: line.created_on, type: line.type, category: line.category, enabled: line.enabled})
引入角色
LOAD CSV WITH HEADERS FROM 'file:///role.csv' AS line
CREATE (:Role {name: line.name, created_on: line.created_on, assigned_to_users: line.assigned_to_users, granted_to_roles: line.granted_to_roles})
摄取用户
LOAD CSV WITH HEADERS FROM 'file:///user.csv' AS line
CREATE (:User {name: line.name, created_on: line.created_on, login_name: line.login_name, first_name: line.first_name, last_name: line.last_name, email: line.email, disabled: line.disabled, ext_authn_duo: line.ext_authn_duo, last_success_login: line.last_success_login, has_password: line.has_password, has_rsa_public_key: line.has_rsa_public_key})
摄取仓库
LOAD CSV WITH HEADERS FROM 'file:///warehouse.csv' AS line
CREATE (:Warehouse {name: line.name, created_on: line.created_on, state: line.state, size: line.size})
向用户引入授权
LOAD CSV WITH HEADERS FROM 'file:///grants_to_users.csv' AS usergrant
CALL {
WITH usergrant
MATCH (u:User) WHERE u.name = usergrant.GRANTEE_NAME
MATCH (r:Role) WHERE r.name = usergrant.ROLE
MERGE (u)-[:USAGE]->(r)
}
提取对角色的授权
:auto LOAD CSV WITH HEADERS FROM 'file:///grants_to_roles.csv' AS grant
CALL {
WITH grant
MATCH (src) WHERE grant.GRANTED_TO = toUpper(labels(src)[0]) AND src.name = grant.GRANTEE_NAME
MATCH (dst) WHERE grant.GRANTED_ON = toUpper(labels(dst)[0]) AND dst.name = grant.NAME
FOREACH (_ IN CASE WHEN grant.PRIVILEGE = 'USAGE' THEN [1] ELSE [] END | MERGE (src)-[:USAGE]->(dst))
FOREACH (_ IN CASE WHEN grant.PRIVILEGE = 'OWNERSHIP' THEN [1] ELSE [] END | MERGE (src)-[:OWNERSHIP]->(dst))
FOREACH (_ IN CASE WHEN grant.PRIVILEGE = 'APPLYBUDGET' THEN [1] ELSE [] END | MERGE (src)-[:APPLYBUDGET]->(dst))
FOREACH (_ IN CASE WHEN grant.PRIVILEGE = 'AUDIT' THEN [1] ELSE [] END | MERGE (src)-[:AUDIT]->(dst))
FOREACH (_ IN CASE WHEN grant.PRIVILEGE = 'MODIFY' THEN [1] ELSE [] END | MERGE (src)-[:MODIFY]->(dst))
FOREACH (_ IN CASE WHEN grant.PRIVILEGE = 'MONITOR' THEN [1] ELSE [] END | MERGE (src)-[:MONITOR]->(dst))
FOREACH (_ IN CASE WHEN grant.PRIVILEGE = 'OPERATE' THEN [1] ELSE [] END | MERGE (src)-[:OPERATE]->(dst))
FOREACH (_ IN CASE WHEN grant.PRIVILEGE = 'APPLY AGGREGATION POLICY' THEN [1] ELSE [] END | MERGE (src)-[:APPLY_AGGREGATION_POLICY]->(dst))
FOREACH (_ IN CASE WHEN grant.PRIVILEGE = 'APPLY AUTHENTICATION POLICY' THEN [1] ELSE [] END | MERGE (src)-[:APPLY_AUTHENTICATION_POLICY]->(dst))
FOREACH (_ IN CASE WHEN grant.PRIVILEGE = 'APPLY MASKING POLICY' THEN [1] ELSE [] END | MERGE (src)-[:APPLY_MASKING_POLICY]->(dst))
FOREACH (_ IN CASE WHEN grant.PRIVILEGE = 'APPLY PACKAGES POLICY' THEN [1] ELSE [] END | MERGE (src)-[:APPLY_PACKAGES_POLICY]->(dst))
FOREACH (_ IN CASE WHEN grant.PRIVILEGE = 'APPLY PASSWORD POLICY' THEN [1] ELSE [] END | MERGE (src)-[:APPLY_PASSWORD_POLICY]->(dst))
FOREACH (_ IN CASE WHEN grant.PRIVILEGE = 'APPLY PROTECTION POLICY' THEN [1] ELSE [] END | MERGE (src)-[:APPLY_PROTECTION_POLICY]->(dst))
FOREACH (_ IN CASE WHEN grant.PRIVILEGE = 'APPLY ROW ACCESS POLICY' THEN [1] ELSE [] END | MERGE (src)-[:APPLY_ROW_ACCESS_POLICY]->(dst))
FOREACH (_ IN CASE WHEN grant.PRIVILEGE = 'APPLY SESSION POLICY' THEN [1] ELSE [] END | MERGE (src)-[:APPLY_SESSION_POLICY]->(dst))
FOREACH (_ IN CASE WHEN grant.PRIVILEGE = 'ATTACH POLICY' THEN [1] ELSE [] END | MERGE (src)-[:ATTACH_POLICY]->(dst))
FOREACH (_ IN CASE WHEN grant.PRIVILEGE = 'BIND SERVICE ENDPOINT' THEN [1] ELSE [] END | MERGE (src)-[:BIND_SERVICE_ENDPOINT]->(dst))
FOREACH (_ IN CASE WHEN grant.PRIVILEGE = 'CANCEL QUERY' THEN [1] ELSE [] END | MERGE (src)-[:CANCEL_QUERY]->(dst))
FOREACH (_ IN CASE WHEN grant.PRIVILEGE = 'CREATE ACCOUNT' THEN [1] ELSE [] END | MERGE (src)-[:CREATE_ACCOUNT]->(dst))
FOREACH (_ IN CASE WHEN grant.PRIVILEGE = 'CREATE API INTEGRATION' THEN [1] ELSE [] END | MERGE (src)-[:CREATE_API_INTEGRATION]->(dst))
FOREACH (_ IN CASE WHEN grant.PRIVILEGE = 'CREATE APPLICATION' THEN [1] ELSE [] END | MERGE (src)-[:CREATE_APPLICATION]->(dst))
FOREACH (_ IN CASE WHEN grant.PRIVILEGE = 'CREATE APPLICATION PACKAGE' THEN [1] ELSE [] END | MERGE (src)-[:CREATE_APPLICATION_PACKAGE]->(dst))
FOREACH (_ IN CASE WHEN grant.PRIVILEGE = 'CREATE COMPUTE POOL' THEN [1] ELSE [] END | MERGE (src)-[:CREATE_COMPUTE_POOL]->(dst))
FOREACH (_ IN CASE WHEN grant.PRIVILEGE = 'CREATE CREDENTIAL' THEN [1] ELSE [] END | MERGE (src)-[:CREATE_CREDENTIAL]->(dst))
FOREACH (_ IN CASE WHEN grant.PRIVILEGE = 'CREATE DATA EXCHANGE LISTING' THEN [1] ELSE [] END | MERGE (src)-[:CREATE_DATA_EXCHANGE_LISTING]->(dst))
FOREACH (_ IN CASE WHEN grant.PRIVILEGE = 'CREATE DATABASE' THEN [1] ELSE [] END | MERGE (src)-[:CREATE_DATABASE]->(dst))
FOREACH (_ IN CASE WHEN grant.PRIVILEGE = 'CREATE DATABASE ROLE' THEN [1] ELSE [] END | MERGE (src)-[:CREATE_DATABASE_ROLE]->(dst))
FOREACH (_ IN CASE WHEN grant.PRIVILEGE = 'CREATE EXTERNAL VOLUME' THEN [1] ELSE [] END | MERGE (src)-[:CREATE_EXTERNAL_VOLUME]->(dst))
FOREACH (_ IN CASE WHEN grant.PRIVILEGE = 'CREATE INTEGRATION' THEN [1] ELSE [] END | MERGE (src)-[:CREATE_INTEGRATION]->(dst))
FOREACH (_ IN CASE WHEN grant.PRIVILEGE = 'CREATE NETWORK POLICY' THEN [1] ELSE [] END | MERGE (src)-[:CREATE_NETWORK_POLICY]->(dst))
FOREACH (_ IN CASE WHEN grant.PRIVILEGE = 'CREATE REPLICATION GROUP' THEN [1] ELSE [] END | MERGE (src)-[:CREATE_REPLICATION_GROUP]->(dst))
FOREACH (_ IN CASE WHEN grant.PRIVILEGE = 'CREATE ROLE' THEN [1] ELSE [] END | MERGE (src)-[:CREATE_ROLE]->(dst))
FOREACH (_ IN CASE WHEN grant.PRIVILEGE = 'CREATE SCHEMA' THEN [1] ELSE [] END | MERGE (src)-[:CREATE_SCHEMA]->(dst))
FOREACH (_ IN CASE WHEN grant.PRIVILEGE = 'CREATE SHARE' THEN [1] ELSE [] END | MERGE (src)-[:CREATE_SHARE]->(dst))
FOREACH (_ IN CASE WHEN grant.PRIVILEGE = 'CREATE USER' THEN [1] ELSE [] END | MERGE (src)-[:CREATE_USER]->(dst))
FOREACH (_ IN CASE WHEN grant.PRIVILEGE = 'CREATE WAREHOUSE' THEN [1] ELSE [] END | MERGE (src)-[:CREATE_WAREHOUSE]->(dst))
FOREACH (_ IN CASE WHEN grant.PRIVILEGE = 'EXECUTE DATA METRIC FUNCTION' THEN [1] ELSE [] END | MERGE (src)-[:EXECUTE_DATA_METRIC_FUNCTION]->(dst))
FOREACH (_ IN CASE WHEN grant.PRIVILEGE = 'EXECUTE MANAGED ALERT' THEN [1] ELSE [] END | MERGE (src)-[:EXECUTE_MANAGED_ALERT]->(dst))
FOREACH (_ IN CASE WHEN grant.PRIVILEGE = 'EXECUTE MANAGED TASK' THEN [1] ELSE [] END | MERGE (src)-[:EXECUTE_MANAGED_TASK]->(dst))
FOREACH (_ IN CASE WHEN grant.PRIVILEGE = 'EXECUTE TASK' THEN [1] ELSE [] END | MERGE (src)-[:EXECUTE_TASK]->(dst))
FOREACH (_ IN CASE WHEN grant.PRIVILEGE = 'IMPORT SHARE' THEN [1] ELSE [] END | MERGE (src)-[:IMPORT_SHARE]->(dst))
FOREACH (_ IN CASE WHEN grant.PRIVILEGE = 'MANAGE GRANTS' THEN [1] ELSE [] END | MERGE (src)-[:MANAGE_GRANTS]->(dst))
FOREACH (_ IN CASE WHEN grant.PRIVILEGE = 'MANAGE WAREHOUSES' THEN [1] ELSE [] END | MERGE (src)-[:MANAGE_WAREHOUSES]->(dst))
FOREACH (_ IN CASE WHEN grant.PRIVILEGE = 'MANAGEMENT SHARING' THEN [1] ELSE [] END | MERGE (src)-[:MANAGEMENT_SHARING]->(dst))
FOREACH (_ IN CASE WHEN grant.PRIVILEGE = 'MONITOR EXECUTION' THEN [1] ELSE [] END | MERGE (src)-[:MONITOR_EXECUTION]->(dst))
FOREACH (_ IN CASE WHEN grant.PRIVILEGE = 'OVERRIDE SHARE RESTRICTIONS' THEN [1] ELSE [] END | MERGE (src)-[:OVERRIDE_SHARE_RESTRICTIONS]->(dst))
FOREACH (_ IN CASE WHEN grant.PRIVILEGE = 'PURCHASE DATA EXCHANGE LISTING' THEN [1] ELSE [] END | MERGE (src)-[:PURCHASE_DATA_EXCHANGE_LISTING]->(dst))
FOREACH (_ IN CASE WHEN grant.PRIVILEGE = 'REFERENCE USAGE' THEN [1] ELSE [] END | MERGE (src)-[:REFERENCE_USAGE]->(dst))
FOREACH (_ IN CASE WHEN grant.PRIVILEGE = 'USE ANY ROLE' THEN [1] ELSE [] END | MERGE (src)-[:USE_ANY_ROLE]->(dst))
} IN TRANSACTIONS
执行完这些命令后,您可以通过运行查询来验证数据是否在图表中。下面的查询返回具有 Snowflake 账户路径的任何实体。
MATCH p=()-[*1..]->(a:Account)
RETURN p
这是查找管理员用户的常用方法。虽然 Snowflake 有一些默认的管理员角色,例如 、 、 、 和 ,但可以向自定义角色授予管理权限。ACCOUNTADMIN
ORGADMIN
SECURITYADMIN
SYSADMIN
USERADMIN
查询
拥有图表真是太棒了!但是,价值在于您可以提出的问题。我只玩了几天这个 Snowflake 图。尽管如此,我还是创建了一些查询,希望能帮助您收集有关 Mandiant 报告中报告的活动以及您对 Snowflake 建议的遵守情况的背景信息。
没有 MFA 的管理员
为了减少您对此广告活动和其他类似广告活动的曝光,Snowflake 的主要建议是在所有账户上启用 MFA。虽然在所有账户上实现 100% 覆盖率可能需要一些时间,但他们还建议对已被授予角色的用户启用 MFA。根据我对报告的阅读,攻击者可能泄露了管理员用户的凭据,因此首先从这些高权限帐户开始似乎是合理的。ACCOUNTADMIN
有两种方法可以确定哪些用户具有管理员权限。第一种是假设 admins 将被授予默认 admins 角色之一,如下所示:
MATCH p=((n:User WHERE n.ext_authn_duo = "false")-[:USAGE*1..]->(r:Role WHERE r.name CONTAINS "ADMIN"))
RETURN p
在这里,我们看到 7 个用户被授予了名称中包含字符串 “” 的角色。虽然这是一个好的开始,但字符串 “” 并不一定意味着该角色具有管理权限,没有字符串 “” 并不意味着该角色没有管理权限。相反,我建议根据管理员的有效权限搜索管理员。USAGE
ADMIN
ADMIN
第二个查询认为可以向自定义角色授予管理员权限。例如,如下所示的特权“授予授予或撤销对任何对象的权限的能力,就像调用角色是对象的所有者一样。这意味着,如果用户具有此权限,则他们可以授予自己或任何人对所需任何对象的访问权限。MANAGE_GRANTS
MATCH p=((n:User WHERE n.ext_authn_duo = "false")-[:USAGE*1..]->(r:Role)-[:MANAGE_GRANTS]->(a:Account))
RETURN p
在这里,我们看到 5 个未注册 MFA 的用户拥有 Snowflake 账户。两个用户被授予该角色,其他三个用户被授予自定义角色。和 custom 角色都被授予该角色,该角色是在账户上授予的。MANAGE_GRANTS
USAGE
ACCOUNTADMINS
USAGE
ACCOUNTADMINS
USAGE
SECURITYADMINS
MANAGE_GRANTS
用熟悉的术语重述,两个用户是该组的成员,该组嵌套在组内,该组位于 Domain Head 上。ACCOUNTADMINS
SECURITYADMINS
SetDACL
用户对数据库的访问
根据 Mandiant 的说法,攻击者的大部分行动都集中在数据库表中包含的数据上。虽然我的图形目前不支持架构或表实体,但需要指出的是,文档指出“对表进行操作还需要父数据库和架构的权限”。这意味着我们可以使用该图来了解哪些用户可以访问哪个数据库,然后推断他们可能有权访问数据库中的架构和表。USAGE
MATCH p=((u:User)-[:USAGE*1..]->(r:Role)-[:OWNERSHIP]->(d:Database WHERE d.name = "<DATABASE NAME GOES HERE>"))
RETURN p
在这里,和 用户通过角色拥有数据库。Jared
SNOWFLAKE
OWNERSHIP
SNOWFLAKE_SAMPLE_DATA
ACCOUNTADMIN
此查询显示有权访问指定数据库的所有用户。如果要检查对所有数据库的访问,可以运行以下查询:
MATCH p=((u:User)-[:USAGE*1..]->(r:Role)-[]->(d:Database))
RETURN p
过时的用户帐户
另一个简单的示例是识别从未使用过(已登录)的用户。修剪未使用的用户可能会减少整体攻击面。
MATCH (n:User WHERE n.last_success_login = "")
RETURN n
结论
我希望您找到了这个概述,并会发现这个图表功能很有用。我期待着您对图表的反馈!如果你写了一个有用的查询,请分享它,我会把它放在帖子里。此外,如果你想扩展这个图表,请告诉我,我会尽我所能帮助你。
在我离开之前,我想对 Snowflake 在这次活动之后的建议发表评论。如前所述,Snowflake 的主要建议是在所有账户上启用 MFA。值得一提的是,在他们的辩护中,Snowflake 一直(至少从此事件之前开始)建议对任何被授予该角色的用户(相当于 Domain Admin)启用 MFA。ACCOUNTADMIN
话虽如此,基于 Web 的平台的性质意味着,如果攻击者使用 Snowflake 会话破坏系统,他们可能会窃取会话令牌并重复使用它,即使用户启用了 MFA。在 Twitter 上以 @BakedSec 为名的奥斯汀·贝克 (Austin Baker) 指出了这一点。
这表明,我们必须关注的不仅仅是如何阻止攻击者获得访问权限。我们必须了解我们信息系统中的访问环境。问问自己,“您能否回答哪些用户可以在您的 Snowflake 部署中使用数据库?有了这张图,这个问题很容易回答,但如果没有这个问题,我们发现大多数组织都无法准确回答这些问题。当涉及嵌套组(在本例中为角色)时,预期访问和有效访问之间很容易出现分歧。随着时间的推移,情况只会变得更糟。我认为它是熵。DATASCIENCE
我们必须对云帐户使用与本地管理类似的方法。您不使用 Domain Administrator 帐户浏览 Web。否,您有两个账户,一个用于管理,另一个用于日常使用。您甚至可能有一个专门用于管理任务的系统。这些相同的想法应该适用于像 Snowflake 这样的云解决方案。您是否正在分析表中的数据?太好了,请使用您的 Database Reader 帐户。现在您需要授予 Luke 一个角色,以便他可以访问仓库?好的,跳上您的 Privileged Access Workstation 并使用您的帐户。同样的 0 级概念也适用于此上下文。我期待听到您的反馈!SECURITYADMIN
更新:来自 Push Security 的 Luke Jennings 在 SaaS 攻击矩阵中添加了一项称为会话 Cookie 盗窃的新技术。该技术展示了攻击者(特别是如果他们可以访问 SaaS 用户的工作站)可以窃取相关浏览器 cookie 以绕过 MFA 的一种方法。这并不意味着组织不应该努力在其用户(尤其是管理员账户)上启用 MFA,但它确实证明了在 SaaS 应用程序的访问控制模型中减少攻击路径的重要性。一种理解方式是,MFA 旨在使攻击者更难进入,但一旦他们进入,它就完全是关于攻击路径的。我在这篇文章中演示的图形方法是处理这些攻击路径以减少入侵的爆炸半径的第一步。
linux恶意软件开发对抗与进程安全管理视频教程
其它课程
linux文件系统存储与文件过滤安全开发视频教程(2024最新)
linux高级usb安全开发与源码分析视频教程
linux程序设计与安全开发
-
windows恶意软件开发与对抗视频教程
-
windows网络安全防火墙与虚拟网卡(更新完成)
-
windows文件过滤(更新完成)
-
USB过滤(更新完成)
-
游戏安全(更新中)
-
ios逆向
-
windbg
-
还有很多免费教程(限学员)
-
更多详细内容添加作者微信
原文始发于微信公众号(安全狗的自我修养):绘制 Snowflake 的 Access Landscape
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论