Serverless Security
Security for AWS Lambda Serverless Applications
Serverless computing is another beautiful cloud-based advancement for developers. But, like all applications, proper security is required to maximize the benefits. Learn more in this article.
Serverless computing is another beautiful cloud-based advancement for developers. But, like all applications, the proper security is required to maximize the benefits. What good is the newest, shiniest sports car if the gas tank is empty and you can’t drive it?
This article will demonstrate how to use Application Security to protect your serverless application from various OWASP Serverless Top 10 threats. For the purpose of this demo, you will need a Trend Micro Cloud One™ account. You can start your free trial here.
Serverless Application Used for Attack Demo
For our attack demo, we will be using the AWS Lambda serverless application known as ServerlessGoat. This application is deliberately insecure and maintained by OWASP. It serves as a Microsoft World .doc file to plain text converter service, meaning it receives a URL to a .doc file as input and will return the text inside the document back to the API caller. Here is the architecture for the application on Amazon Web Services (AWS):
Please note that this application is vulnerable to several kinds of attacks. Please do not deploy it in any AWS production account.
Let’s start with setting up Application Security with ServerlessGoat:
Application Security Integration with ServerlessGoat
1. Deploy the application from AWS repository. In your Lambda application, list all the Lambda functions that you want to protect.
2. Now, let's manage these Lambda Functions. Click on the Lambda function to visit the code pane and add the following code based on the Lambda runtime used. In this case, it’s json. Please refer to this link for other languages.
var trend_app_protect = require('trend_app_protect');
var _handler = async (event) => {
// Your application code here
return {
statusCode: 200,
body: 'Hello from Lambda',
};
};
// Export wrapped handler
exports.handler = trend_app_protect.api.aws_lambda.protectHandler(_handler);
You can use AWS custom runtimes to avoid changes to any code in the Lambda function
3. Add the Application Security layer ARN to your application by clicking on Add layer and choosing following option:
For ARNs refer to this link.
4. Go to Configuration tab and add the following environment variable:
TREND_AP_KEY: < key from Application Security Dashboard after creating a group >
TREND_AP_SECRET: < secret from Application Security Dashboard after creating a group >
TREND_AP_READY_TIMEOUT: 30
TREND_AP_TRANSACTION_FINISH_TIMEOUT: 10
TREND_AP_MIN_REPORT_SIZE: 1
TREND_AP_INITIAL_DELAY_MS: 1
TREND_AP_MAX_DELAY_MS: 100
TREND_AP_HTTP_TIMEOUT: 5
TREND_AP_PREFORK_MODE: False
TREND_AP_CACHE_DIR: /tmp/trend_cache
TREND_AP_LOG_FILE: STDERR
Apart from the key and secret, other environment variables do not need to be incorporated for custom runtime.
5. In General Configuration, increase Timeout to at least 120 seconds.
You can also make these changes in your deployment package for serverless application and deploy it, so you can manage it once launched.
6. Deploy your application and go to the Application Security console. Send a simple HTTP request or access the website from your browser for the hosted serverless application to activate the agent.
7. Now, you should see triggers on the Application Security console. The status should turn from grey to green.
8. When you trigger any module from the Application Security console, you will get the status Attacks Ongoing and the color changes to red.
Proof of Concept Demo
Now that Application Security is up and running on the DV, we’ll take a look at what types of attacks it will find. For the purpose of this demo, Application Security is kept in detect mode. You can also opt to keep it in block mode, which will block all the attacks.
1. Information Gathering
a. The API endpoint has very predictable URL: https://{string}.execute-api.{region}.amazonaws.com/{stage}/
b. Sending a simple GET request to the API endpoint gives us valuable information and validates that the application has an Amazon API Gateway.
If the application is exposed through Amazon API Gateway, the HTTP response headers might contain header names such as: x-amz-apigw-id, x-amzn-requestid, x-amzn-trace-id
c. In the above response, we can also see the stack trace, which shows that the application is located in the /var/task directory. This is where Lambda stores and executes your Lambda function. We also see the string _handler, which is a very common way to serverless functions (for example, the function name is handler, and it is defined inside index.js).
OWASP Serverless Top 10 Vulnerability: Improper exception handling and verbose error messages (SAS-10)
Detections: No
2. Exposing and Reverse Engineering the Lambda Function
Next, we will try to gain access to the source code of the Lambda function in order to reverse engineer it and discover additional weaknesses. For this demo, we will try to check whether the function is vulnerable to an operating system (OS) command injection.
a. First, we will blindly probe for the OS command injection using a common time-based probing method like invoking the sleep shell command.
There was a slight delay, but we have to be sure.
b. Increase the sleep time so it’s more than the default runtimes of most serverless applications and check if there is an error (which is what we want).
Now, after a long delay, we can see the error.
c. This validates that our OS command injection is working on the 'documenturl' parameter. Now, we can dig deeper using OS command injection. Insert the payload "https://foobar; cat /var/task/index.js #" in the parameter.
Press submit. If you see the output, you should see the lambda code.
d. There's a lot that can be learned from the source code:
- The application uses the Amazon DynamoDB (NoSQL database).
- The application uses a Node.js package called node-uuid
- The application stores sensitive user information (IP address and the document URL) inside the DynamoDB table. The name is defined in the TABLE_NAME environment variable.
- The root cause behind the OS command injection is using untrusted user input in the child_process.execSync() call.
- The output of API invocations is stored inside an Amazon Simple Storage Service (S3) bucket. The name is stored inside an environment variable: BUCKET_NAME.
OWASP Serverless Top 10 Vulnerability: Function Event-Data Injection (SAS-1)
Detections: Yes
Module: Remote Code Execution
3. Digging for Gold Inside Environment Variables
a. Put the following payload in the input: "https://foobar; env #"
If you see the output, there’s some very juicy information here. By grabbing the 3 AWS Identitiy and Access Management (IAM) tokens AWS_SESSION_TOKEN, AWS_SECRET_ACCESS_KEY and AWS_ACCESS_KEY_ID, we can now get the function's temporary execution role using AWS Command Line Interface (CLI).
b. Gain the function role access using AWS CLI:
export AWS_SECRET_ACCESS_KEY = "..."
export AWS_ACCESS_KEY_ID = "..."
export AWS_SESSION_TOKEN = "..."
Next, you can verify that you are indeed using the function's role, locally, by running: aws sts get-caller-identity.
This should return the following:
{
"UserId": "xxxxxxxxx",
"Account": "xxxxxxxxxx",
"Arn": "arn:aws:sts::xxxxxxxxxxxx:assumed-role/aws-serverless-repository-serv-FunctionConvertRole-xxxxxxxx/aws-serverless-repository-serverle-FunctionConvert-xxxxxxxxxx"
}
It's clear that we are now running under the assumed role of the function.
OWASP Serverless Top 10 Vulnerability: Insecure Application Secrets Storage (SAS-7)
Detections: Yes
Module: Remote Code Execution
4. Exploiting Over-Privileged IAM Roles
a. We can infer from the Lambda code that the developer is inserting the client's IP address and the document URL value into the DynamoDB table, by using the put() method of AWS.DynamoDB.DocumentClient. In a secure system, the permissions granted to the function should be least-privileged and minimal, for example, only dynamodb:PutItem.
However, when the developer chose the CRUD DynamoDB policy provided by AWS Serverless Application Model (SAM), they granted the function with the following permissions:
- dynamodb:GetItem
- dynamodb:DeleteItem
- dynamodb:PutItem
- dynamodb:Scan
- dynamodb:Query
- dynamodb:UpdateItem
- dynamodb:BatchWriteItem
- dynamodb:BatchGetItem
- dynamodb:DescribeTable
These permissions will now allow us to exploit the OS command injection weakness to exfiltrate data from the DynamoDB table, by abusing the dynamodb:Scan permission.
b. Use the following payload in the URL field, and see what happens:
https://; node -e 'const AWS = require("aws-sdk"); (async () => {console.log(await new AWS.DynamoDB.DocumentClient().scan({TableName: process.env.TABLE_NAME}).promise());})();'
As you can see from the below output, we accessed the entire contents of the table:
OWASP Serverless Top 10 Vulnerability: Over-Privileged Function Permissions and Roles (SAS-4)
Detections: Yes
Module: Remote Code Execution
Conclusion
As seen in the demo, Application Security is effective at detecting advanced threats and vulnerabilities that could cause harm to your serverless application. This allows you to build and deploy with confidence and satisfies the SecOps teams that security is integrated into your processes. It also encourages communication and collaboration between the teams so you can strengthen the DevOps culture.
Try it for yourself with a free 30-day trial today. You can also watch other serverless and container demos to learn more.