Skip to content

Commit

Permalink
Update and rename ###mmistakes#2023-01-21-IAM-attacking-AWS.md to 202…
Browse files Browse the repository at this point in the history
…3-01-21-IAM-attacking-AWS.md
  • Loading branch information
kymb0 authored Jan 23, 2023
1 parent 32fc2ae commit 478f082
Showing 1 changed file with 30 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,14 @@ I learnt a ton about IAM and how to attack it and, more importantly, had an abso

## Discovering the Application

Once we succesfuly deploy our environment via terraform, we will have access to the application URL, and navigating here shows a blog website.
Once we successfully deploy our environment via terraform, we will have access to the application URL, and navigating here shows a blog website.
![blog_landing_page](/assets/images/AWS_1/Blog_landing_page.jpg)

We do not have any credentials at this stage, there is however, a sign up feature that allows us to create our own account to gain access to a dashboard where we can create new blog posts.
![register](/assets/images/AWS_1/Sign_up.jpg)
![new_post](/assets/images/AWS_1/newpost.jpg)

The first thing that jumps out at us is the file upload feature, and to be more precise, the fact that we can upload via a url. This indicates that either the server will be embeding a link to the image on the blog, or retrieving the data at the specified URL and storing it somewhere. Hopefully it is the later, as this will present a clear vector for SSRF.
The first thing that jumps out at us is the file upload feature, and to be more precise, the fact that we can upload via a url. This indicates that either the server will be embedding a link to the image on the blog, or retrieving the data at the specified URL and storing it somewhere. Hopefully it is the later, as this will present a clear vector for SSRF.
![file_upload_feature](/assets/images/AWS_1/fileupload.jpg)

So to test this, we feed it a page containing an image, and view the response in burp to better understand the application logic:
Expand All @@ -44,11 +44,11 @@ We visit the returned s3 link to confirm it contains the original linked image a

## Getting a Foothold

The most obvious step from here is to attempt a metadata v1 attack, where we trick the underlying EC2 instance to hit the AWS metadata endpoint to retrive privileged information.
The most obvious step from here is to attempt a metadata v1 attack, where we trick the underlying EC2 instance to hit the AWS metadata endpoint to retrieve privileged information.

![ssrf_v1_attempt](/assets/images/AWS_1/ssrf_v1_attempt.jpg)

This however does not work and returns a server error immediately. Further testing revealed that the application required a `200` response to be succesful. This indicates either that metadata v2 was in use which requires more sophisticated techniques, or, that the webapp may be running on lambda.
This however does not work and returns a server error immediately. Further testing revealed that the application required a `200` response to be successful. This indicates either that metadata v2 was in use which requires more sophisticated techniques, or, that the webapp may be running on lambda.

After trying a few different approaches, I discovered an LFI (local file inclusion) escalation which allowed for the retrieval of files within the ephemeral environment.
As a common way to feed credentials to lambda functions is via environment variables, we attempt to continue our chain of attacks by retrieving a copy of `/proc/self/environ` (although the environment that runs the Lambda functions is ephemeral, the credentials remain valid for a period of time)
Expand All @@ -59,11 +59,11 @@ We download the stored file on the S3 bucket and `cat` the contents, revealing t

![ssrf_loot](/assets/images/AWS_1/SSRF_LOOT.JPG)

We store these secrets in our envrinment variables for our terminal session and succesfully make a call to AWS:
We store these secrets in our environment variables for our terminal session and successfully make a call to AWS:

![stolen_key_auth_success](/assets/images/AWS_1/stolen_key_auth_success.jpg)

Unfortunately from here however, we discover the account does not have IAM list privileges after trying to list policies. I even tried to enumerate with an AWS exploit tool called [PACU](https://github.com/RhinoSecurityLabs/pacu) to see if I was missing anything, it was however a dead-end on the IAM front. When this happened I said "IAM dissapointed"
Unfortunately from here however, we discover the account does not have IAM list privileges after trying to list policies. I even tried to enumerate with an AWS exploit tool called [PACU](https://github.com/RhinoSecurityLabs/pacu) to see if I was missing anything, it was however a dead-end on the IAM front. When this happened I said "IAM disappointed"

![dissapointed](/assets/images/AWS_1/dissapointed.gif)
![list_policy_fail](/assets/images/AWS_1/list_policy_fail.jpg)
Expand All @@ -76,18 +76,18 @@ We know that our file is being stored on the production S3 bucket, so we run `aw
![s3_list-buckets](/assets/images/AWS_1/s3_enum_dev_bucket.jpg)
![s3_list_objects](/assets/images/AWS_1/s3_list_objects_loot.jpg)

Although we were unable to enumerate our own level of permissions, we can determine we have a high level fo access to the `S3` service as not only can we list thes objects, but we can access and download them as well.
Although we were unable to enumerate our own level of permissions, we can determine we have a high level of access to the `S3` service as not only can we list these objects, but we can access and download them as well.

![s3_download_objects](/assets/images/AWS_1/s3_download_loot.jpg)
![s3_stolen_config](/assets/images/AWS_1/ssh_config.jpg)

The stolen information in the exposed bucket allow us to SSH straight into an EC2 instance which did not have proper [rules for inbound traffic](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/authorizing-access-to-an-instance.html) setup.

![ssh_succesful](/assets/images/AWS_1/ssh_successful.jpg)
![ssh_successful](/assets/images/AWS_1/ssh_successful.jpg)

## Reviewing IAM

A qucik explaination of why we run `aws sts get-caller-identity` followed by `aws iam list-attached-role-policies --role-name AWS_GOAT_ROLE` after sonnecting via `ssh` is as follows: First, `sts get-caller-identity`, allows us to discover which credentials are being used to call `aws` operations, we can determine the specifics of what we are dealing with by matching the output against the `IAM` `ARN` syntaxes from the [AWS reference page](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_identifiers.html)
A quick explanation of why we run `aws sts get-caller-identity` followed by `aws iam list-attached-role-policies --role-name AWS_GOAT_ROLE` after connecting via `ssh` is as follows: First, `sts get-caller-identity`, allows us to discover which credentials are being used to call `aws` operations, we can determine the specifics of what we are dealing with by matching the output against the `IAM` `ARN` syntaxes from the [AWS reference page](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_identifiers.html)

SO after looking at this, we know that the _assumed role_ syntax is as below, and that the rolename must be `AWS_GOAT_ROLE`, as the _role-session-name_ is simply used [uniquely identify](https://docs.aws.amazon.com/cli/latest/reference/sts/assume-role.html#:~:text=Use%20the%20role%20session%20name,account%20that%20owns%20the%20role.) a session when the role is assumed.

Expand All @@ -99,7 +99,8 @@ We retrieve the policy document for the `dev-ec2-lambda-policies` policy which w

![dev-ec2-lambda-policies](/assets/images/AWS_1/dev-ec2-lambda-policies.jpg)

So reviewing the above output we can see that this policy has access to attach policies to other roles, this is granted by the `iam:AttachRolePolcy` action. The rourcse it can perform this action against is restriced to `blog_app_lambda_data` which just so happens to be the other role we have access to via the previously explored SSRF attack. This policy also has the `iam:CreatePolicy` action set, which presents avery interesting escalation vector - allowe me to explain: if we are looking at a policy right now to determine what this role can and cannot do, and through doing so we have discovered that the policy itself allows for the creation of NEW polcies, and that we can attach those policies to the `blog_app_lambda_data` role which we have access to, this means that we can grant a policy allowing for ALL actions against ALL resources, so basically Administrator access without actually attaching the AWS managed `AdministratorAccess` polciy, which, in a real setting would likely raise alarm bells if we were to add ourselves to it.
So reviewing the above output we can see that this policy has access to attach policies to other roles, this is granted by the `iam:AttachRolePolcy` action. The resource it can perform this action against is restricted to `blog_app_lambda_data` which just so happens to be the other role we have access to via the previously explored SSRF attack. This policy also has the `iam:CreatePolicy` action set, which presents a very interesting escalation vector - allow me to explain below:
If we are looking at a policy right now to determine what this role can and cannot do, and through doing so we have discovered that the policy itself allows for the creation of NEW polices, and that we can attach those policies to the `blog_app_lambda_data` role which we have access to, this means that we can grant a policy allowing for ALL actions against ALL resources, so basically Administrator access without actually attaching the AWS managed `AdministratorAccess` policy, which, in a real setting would likely raise alarm bells if we were to add ourselves to it.

## Abusing Lambda to escalate privileges

Expand All @@ -110,18 +111,32 @@ Let's take another look at the `blog_app_lambda_data` role now that we have an a

![dev-ec2-lambda-policies](/assets/images/AWS_1/lambda-data-role-policies.jpg)

The `blog_app_lambda_data` role has full lambda access, meaning we can create functions. If we look back at `dev-ec2-lambda-policies` for we see that `iam:PassRole` is present. If the `blog_app_lambda_data` role had the same level of permissions that the `AWS_GOAT_ROLE` role did through the `dev-ec2-lambda-policies` policy; we could effectively create a lambda function that creates a new policy and attaches it to the `blog_app_lambda_data` role. We could abuse the presence of `iam:PassRole` to execute with the `dev-ec2-lambda-policies` level of permissions.
The `blog_app_lambda_data` role has full lambda access, meaning we can create functions. If we look back at `dev-ec2-lambda-policies` we see that `iam:PassRole` is present. The `iam:PassRole` allows for the creation of functions which will execute under the context of another role.
With the above in mind, if the `blog_app_lambda_data` role had the same level of permissions that the `AWS_GOAT_ROLE` role did through the `dev-ec2-lambda-policies` policy, we could effectively create a lambda function that creates a new policy and attaches it to the `blog_app_lambda_data` role.
We could then abuse the presence of `iam:PassRole` to execute a new lambda function with the `dev-ec2-lambda-policies` level of permissions.
Luckily the `iam:AttachRolePolcy` action will allow us to simply attach the `dev-ec2-lambda-policies` policy to `blog_app_lambda_data`, we do this as below:

![blog_app_lambda_data_attachrole](/assets/images/AWS_1/blog_app_lambda_data_attachrole.jpg)
![blog_app_lambda_data_attachrole](/assets/images/AWS_1/blog_app_lambda_data_attachrole.jpg)

Now that we have the permissions of both policies attached to one role, let's create a malicous lambda function as below:
Now that we have the permissions of both policies attached to one role, let's create a malicious lambda function as below:

![lambda_priv_esc_function](/assets/images/AWS_1/lambda_priv_esc_function.jpg)

We zip the `.py` file up and create a lambda function before invoking it. Running `aws iam list-attached-role-policies --role-name blog_app_lambda_data` after doing this shows that we have succesfully managed to create a new policy and attach it, all from within a lambda function.
We zip the `.py` file up and create a lambda function before invoking it. Running `aws iam list-attached-role-policies --role-name blog_app_lambda_data` after doing this shows that we have successfully managed to create a new policy and attach it, all from within a lambda function.

**IMPORTANT NOTE** _Although for the purpose of this exercise I opted to perform these actions from a local terminal with the exported secrets, best practice would be to run commands from an EC2 instance wherever possible so that it doesn't look like actions are being performed from outside AWS. so, in other words, I should have created, invoked, and deleted the function from my ssh session via exporting the secrets as was done earlier in this post_

![lambda_priv_esc_success](/assets/images/AWS_1/lambda_priv_esc_success.jpg)

Don't forget to remove the function afterwards:
![cleanup](/assets/images/AWS_1/cleanup.jpg)

![cleanup](/assets/images/AWS_1/cleanup.jpg)

We can double check the actions we can perform and the resources we can perform them against via our new policy, and as we can see, it is quite permissive (an asterisk denotes any action/resource)

![full_perms](/assets/images/AWS_1/full_perms.jpg)

So after this reading this post, you should no longer have your head in the clouds with regards to `IAM` at the very least.

### STAY TUNED FOR THE NEXT EPISODE
![tv](/assets/images/AWS_1/adventure_time_tv.jpg)

0 comments on commit 478f082

Please sign in to comment.