The article originally appeared in The New Stack.

By its very nature, serverless can help make workloads more secure. With the advent of serverless as an option to deploy small components which can perform a specific function and be chained together to form full functioning services, security-minded developers have gained a useful new tool. Serverless can be used to abstract yet another layer from the infrastructure, enabling DevOps teams to stop worrying about keeping the compiler and runtime for their language up-to-date and patched against all known security vulnerabilities. That’s great from a security stance.

You could sum up the security advantages of serverless this way: What containers did for ensuring the operating system and hardware layer could be maintained and secured separately, serverless offers the same at the application and web server layer.

And yet, serverless is no silver bullet from a security perspective. Serverless environments still need to be designed and managed with security in mind at every moment.

Toward that end, this article discusses typical security risks associated with serverless computing, and tips for addressing them.

4 Security Risks of Serverless Applications:

Serverless still needs input validation

As with any application built and deployed, serverless applications process and transfer data. That data can arrive as part of the event that kicks off processing, or can be retrieved from a datasource as part of the application’s functionality.

Proper input validation does more than make sure area codes are properly formatted. Input validation stops some of the most common types of attacks that applications face. In the case of serverless applications, the most common type of injection attack is SQL Injection (SQLi).

SQL Injection attacks involve inserting code into a request that is designed to either return too much data, or destroy data.

A practical example of input validation is if your function expects a phone number to be passed in as part of the payload; then, it will return all orders for that phone number.

Good Case:

// Input JSON
// { “phonenumber”: “5065551212” }

// Relevant Code will just return what the phone number matches
sql = "SELECT * FROM orders WHERE phone = " + connection.escape(phonenumber);
connection.query(sql, function (error, results, fields) {
callback( null, results );

Returning way too much data:

// Input JSON now has the SQL character for match all results
// { “phonenumber”: “%” }

// Relevant Code is exactly the same but will return all orders
sql = "SELECT * FROM orders WHERE phone = " + connection.escape(phonenumber);
connection.query(sql, function (error, results, fields) {
callback( null, results );

With a simple Regex for input validation:

// Input JSON now has the SQL character for match all results
// { “phonenumber”: “%” }

// Relevant Code with an additional library that validates phone numbers
var PhoneNumber = require( 'awesome-phonenumber' );
var pn = new PhoneNumber( phonenumber, “US” );
// The number isn’t valid so it will execute what is in the else condition
if (pn.isValid()) {
    sql = "SELECT * FROM orders WHERE phone = " + connection.escape(phonenumber);
    connection.query(sql, function (error, results, fields) {
} else {
    results = "ERROR - invalid phone number”
}
callback( null, results );

More types of injection attacks are detailed in an article posted by Acunetix.

Secrets still need to be secret

When leveraging application development components like API gateways and cloud services like SQS on AWS, there is a move towards keys and certificates in the authentication and authorization spaces. As these aren’t traditional usernames and passwords, they can often be overlooked and checked into source repository systems. By leveraging vaults inside the different cloud platforms that are presented to the individual functions as environment variables, you can avoid having this problem.

If you are curious whether you have checked in any secrets into your code, there are tools like Truffle Hog that can help search your history on GitHub.

Setting an environment variable in Lambda is an option in the Environment Variables section. Azure and GCP Functions have similar configuration.

For example, in Python, you would read the environment variable by loading the OS package, then use it like every other variable.

import os

secret_api_token = os.environ['SECRET_API_TOKEN']

In JavaScript, it is using the process.env object.
const secret_api_token = process.env.SECRET_API_TOKEN

Too much access (and too little access control)

There are two distinct risks associated with access control in a serverless environment. The first centers around the access that clients have into the serverless app and how much access the serverless app has into the systems it interacts with.

Taking into consideration clients having too much access—as a serverless application is a single function, it is tempting to only put the minimum criteria in place to have it perform as needed. In reality, the function needs the ability to distinguish between clients that call it and only filter data so it is only allowing interaction with data that is relevant to that requestor. Whether it is automatically appending a WHERE clause onto an SQL query or including the requestor information on messages sent downstream, data leaking can lead to large public relations problems down the road if someone were to figure out they could ask for more data than is returned by default.

The second access-control risk occurs when developers take the easy way out and grant the specific application being worked on access to more than required of other functions and services it needs to work. This practice is common in development tiers as the goal is to just make it work, but in today’s model with continuous integration and continuous delivery (CI/CD), it is far too easy to have excess security propagate up to production systems. A simple example would be that in development the application is granted full access to the data store, but it only really needs the ability to select and insert. Having the extra access to the data store to update or delete may seem harmless until the day the wrong script is run against the wrong environment and you’ve lost precious customer data (like orders). Going forward, the best practice is to only grant the required access starting at the lowest tiers in development, and have separate users for each serverless function to make traceability and data integrity much easier to guarantee.

Third-party libraries age poorly

As with development on any platform, using third-party libraries is an extremely common and useful way to speed up development and delivery of business functionality. Even the input validation example above uses a library to check phone numbers.

Once you have deployed your new serverless application and you move on to the next piece of work, the application you just deployed starts to age. A large part of this aging is that third-party libraries are still being updated for new functionality and potential security vulnerabilities. If you aren’t constantly updating your applications to ensure the latest release of every library, you can very easily find yourself in a position like Capital One in 2016 where a third-party library that was out of date allowed the exposure of millions of customers’ private data.

The easiest way to mitigate this stale application problem without having to constantly update all your applications is to scan your code and build artifact repositories to identify at-risk libraries. By relying on a scanning tool you can make better use of your time and resources and be able to target which applications and libraries require an update vs. what would just be nice to update.

Conclusion

Serverless applications are great in that they remove the requirement to maintain and update the operating system and runtime layers of deployed applications. Unfortunately, however, serverless is not a silver bullet for solving problems with in-house code and third-party libraries. Serverless applications by design are small and single-purpose which is great from a design and deployment point of view, but a holistic approach needs to be taken to ensure that the vast number of serverless functions deployed are kept up-to-date from a security standpoint.

Related Serverless Security Posts:

  • Introduction to Serverless Security
  • How Serverless Changes the Security Paradigm
  • Serverless Comparison: Lambda vs. Azure vs. GCP vs. OpenWhisk
  • ← Back to All Posts Next Post →