Introduction To Serverless Security: Part 2 - Input Validation

Infosec writer Miguel A. Calles illustrates the importance of input validation in serverless environments where there are multiple input sources.

Introduction To Serverless Security: Part 2 - Input Validation

The term "serverless" implies having no server and might encourage the enthusiastic to think, "We can be less strict on security. The typical secure coding practices do not apply." The reality is no matter where you code is hosted, secure coding practices still apply.

The Need for Input Validation

This article focuses on input validation which helps mitigate the top web application security vulnerability: injection attacks. The Open Web Application Security Project (OWASP) has deemed injection at the highest security risk in both traditional web applications and serverless applications. In my case study, "Wreaking Havoc via an API," I provide a real life example how input validation would have prevented me from heavily disrupting a vendor's database. The extra effort required to check any inputs are what you expect can protect you against injection attacks, denial of service attacks, and data defects.

Serverless Environments Have Various Input Sources

Serverless environments not only relieve you of managing how to host your code, they also provide you multiple ways to get data inputs for your code. (You may want to read the previous article, "Introduction To Serverless Security: Part 1 - Dependencies," to get a quick overview on serverless environments.) This article will focus on inputs sources from Amazon Web Services (AWS) and use Node.js in the sample code.

AWS enables you to use multiple inputs sources to call your Lambda function. These inputs come as events to your function. The inputs can come from databases, queues, HTTP events via an API gateway, file storage, and more. This is a list of the AWS services that provide inputs to Lambda functions:

  • Amazon Kinesis
  • Amazon DynamoDB
  • Amazon Simple Queue Service
  • Elastic Load Balancing (Application Load Balancer)
  • Amazon Cognito
  • Amazon Lex
  • Amazon Alexa
  • Amazon API Gateway
  • Amazon CloudFront (Lambda@Edge)
  • Amazon Kinesis Data Firehose
  • Amazon Simple Storage Service
  • Amazon Simple Notification Service
  • Amazon Simple Email Service
  • AWS CloudFormation
  • Amazon CloudWatch Logs
  • Amazon CloudWatch Events
  • AWS CodeCommit
  • AWS Config

This list comes from the AWS Developer Guide.

Fortunately, you only need to worry about the event sources for which your application was developed.

Example: Deleting a File From a Static Web Page

Assume you developed a static web site using the Amazon Simple Storage Service (S3) service. This means your S3 bucket, i.e. your storage folder, is public to the world. You use a Lambda function to delete web pages to minimize storage costs and to reduce clutter on your site. You configured your Lambda to accept events from the API Gateway to delete files you specify in the payload. You also create another Lambda that accepts API Gateway and S3 events. This second lambda updates your web pages to remove references to the file the first lambda deleted.

Deleting the Main Web Page

The first function might be vulnerable if it blindly deletes any S3 object, i.e. file, you specify in the payload. Assume your lambda function receives this API Gateway event:

{
    "body": "{\"Bucket\": \"MYBUCKET\", \"Key\": \"index.html\"}"
}

The other event details are omitted for simplicity. Visit the AWS Developer Guide for an example of a full event.

Your Lambda convenient parses the body and calls S3.

const s3 = new AWS.S3({apiVersion: '2006-03-01'});
module.exports.handler = (event, context, callback) => {
    // parse the event
    const params = JSON.parse(event.body);
    // delete the object
    return s3.deleteObject(params)
        .promise()
        .then(callback)
        .catch((err) => callback('Something went wrong.'));
}

Sample code for the purpose of illustration.

You may have noticed there is no input validation. The web site is a public bucket meaning it is accessible from https://MYBUCKET.s3-website-us-east-1.amazonaws.com/index.html if it is in the "us-east-1" AWS region. Everyone knows the bucket name is "MYBUCKET" and the object key is "index.html" for the main page. Let's assume you conveniently made your lambda function accessible from https://mybucket.com/deleteObject, it would be easy for someone to guess that address, send a test payload above, and see the result. (You could secure your API Gateway, which I plan to discuss in a future article.) Now your main web page is deleted.

Your second lambda is configured to get the S3 delete event. It gets an event like the one below.

{
  "Records": [
    {
      "eventVersion": "2.0",
      "eventSource": "aws:s3",
      "awsRegion": "us-east-1",
      "eventTime": "1970-01-01T00:00:00.000Z",
      "eventName": "ObjectRemoved:Delete",
      "userIdentity": {
        "principalId": "EXAMPLE"
      },
      "s3": {
        "s3SchemaVersion": "1.0",
        "configurationId": "testConfigRule",
        "bucket": {
          "name": "example-bucket",
          "ownerIdentity": {
            "principalId": "EXAMPLE"
          },
          "arn": "arn:aws:s3:::example-bucket"
        },
        "object": {
          "key": "test/key",
          "sequencer": "0A1B2C3D4E5F678901"
        }
      }
    }
  ]
}

Visit the AWS Developer Guide for a full example of the event.

Your Lambda code would then go through all the files in your bucket, read the files, remove any hyperlinks to the deleted file, and updates the file in the bucket.

const s3 = new AWS.S3({apiVersion: '2006-03-01'});
const helper = require('./helper');
module.exports.handler = (event, context, callback) => {
    // parse the event
    const bucketName = event.body.s3.bucekt.name;
    const objectKey = event.body.s3.object.key;
    // delete the object
    return helper.updateLinks()
        .then(callback)
        .catch((err) => callback('Something went wrong.'));
}

Sample code for the purpose of illustration. The specific details are left to the imagination of the reader.

Potential Input Valuation Solutions

How could you use input valuation to protect against such an attack?

For the first function, you could:

  • Ensure the event body is not exactly the same as the parameters needed by s3.deleteObject;
  • You could extract only the input data you need from the event body, rather than accepting them all;
  • You could check that object key, i.e., file name, is not index.html or any important file;
  • The function could check to delete object keys within a certain folder and/or created after a certain age.

For the second function, you could:

  • Check the event is valid by confirming:
    * The userIdentity is an expected value;
    * The eventTime is within a reasonable time frame;
  • You could limit the function to only work on the "MYBUCKET" bucket;
  • Limit the function to a set of files as you would have for the first function.

The goal is to assume none of the event data is trustworthy.

Conclusion

Input validation continues to be an important part of software development, even in cloud computing. Having a function be managed by a provider does not reduce or eliminate the responsibility of its security.

Before You Go

A Note from the Author

Join my mailing list to get updates on my writings, my short stories, my upcoming books, and cybersecurity news. Visit https://miguelacallesmba.com/subscribe to join.

Stay secure, Miguel

View my linkedIn profile

The awesome GIF used in this article is called Invalid Email and it was created by Hoang Nguyen.