I recently published an article on Dark-Reading, titled “Securing Serverless: Attacking an AWS Account via a Lambda Function”. The article is the first part in a 2-part series related to an experiment performed by Caleb Sima, in which he wanted to learn about the risks involved with serverless applications, and about AWS Lambda security. You can read Caleb’s “defender” part here .
As part of the LambdaShell experiment, I experienced at first-hand just how powerful the AWS Lambda security model can be, if done properly. As a matter of fact, being on the offensive side, I was both impressed and frustrated. Here’s a quote from the Dark-Reading article, which I’d like to further dig into in this post:
“…This is where I want to pause and take a moment to emphasize the security benefits of using a well-designed microservice architecture, in conjunction with a granular security permissions model such as the one offered by AWS Lambda. Think about it for a moment: I was facing an application that is vulnerable to RCE (remote code execution). In traditional applications (e.g. Web applications), an RCE is the holy grail for hackers. Usually, this means an attacker can run arbitrary code, access sensitive data, and in general abuse the system as he/she see fits.
Having said that, there I was, facing a system that is vulnerable to RCE, yet I was sitting frustrated in front of my screen and keyboard, with nothing much to do. The fact that other potentially more juicy parts of Caleb’s serverless application were not known or accessible to me, together with the fact that this vulnerable Lambda function only had limited permissions over CloudWatch logs put me in a position where I’m confined. It’s crazy.”
serverless Design for failure
When developing serverless applications, there are several principles and design choices you can make that will make your system more resilient to attacks:
Most serverless applications implement a microservices architecture, which means that the application is built using a collection of loosely coupled services. In a microservices architecture, services are fine-grained, which improves modularity. Other than the obvious reasons for choosing a modular design, there are also security benefits in the form of easier compartmentalization, that helps to reduce the blast radius in case a breach occurs.
Single Responsibility Principle
there are several design patterns for serverless applications. You can use serverless functions as a means to implement microservices, or in some cases, opt for creating a monolithic function (see more here: https://serverless.com/blog/serverless-architecture-code-patterns/ ). Yan Cui published an article on this topic, titled: “AWS Lambda – should you have few monolithic functions or many single-purposed functions?” (https://hackernoon.com/aws-lambda-should-you-have-few-monolithic-functions-or-many-single-purposed-functions-8c3872d4338f ). The principle of Single Responsibility states that every module should have responsibility over a single part of the functionality provided by the software, and that responsibility should be entirely encapsulated by the module. All its services should be narrowly aligned with that responsibility. This is obviously closely tied to microservices design. From a serverless security perspective, having many small functions, each with its own purpose in life, and its own responsibility, means that when a single function is breached, the overall system’s security posture is not affected.
Principle of Least Privilege
In information security the principle of least privilege requires that in a particular abstraction layer of a computing environment, every module (in our case, every serverless function) must be able to access only the information and resources that are necessary for its legitimate purpose. The AWS IAM model is one of the most granular and powerful permission models you can find. When you create IAM policies (on AWS, and in general), you should always follow the principle of least privilege. The role you assign to a function will dictate the permissions the function will have while it executes. Under certain conditions, this permissions model, might be the thing that will save your sensitive data. Read more about how to generate least-privileged IAM roles for your Lambda functions in this blog post .
Putting it all Together
When you design a serverless application, harness the microservices model and the principle of single responsibility, not only for breaking down large software into more manageable logical components, but also because you are essentially compartmentalizing different modules and capabilities from one another. Such a design, coupled with a well configured and strict IAM permissions model, will go a long way in reducing the blast radius when one of the components (functions) is vulnerable and abused.
In essence, this is very similar to how bulkheads are used in a ship – you are creating watertight compartments that can contain water in the case of a hull breach.
Going back to the LambdaShell experiment – I can’t remember a single event from my 20+ years of application security experience, where I achieved remote code execution on a system (legally of course), but couldn’t do anything useful with it.
I truly believe that from an application security standpoint, serverless brings with it a huge promise and tremendous potential for developing applications that are much more resilient to attacks, in ways that are far superior to what we’ve seen in traditional architectures (including containerized applications). The only thing you need to do in order to reap these benefits, is to follow serverless security best practices, and design your systems for failure.
For those interested in more information on AWS security best practices, we highly recommend reading the following AWS Lambda Security Quick-Start Guide .
*** This is a Security Bloggers Network syndicated blog from PureSec Blog (Launch) authored by Ory Segal, PureSec CTO. Read the original post at: https://www.puresec.io/blog/aws-security-best-practices-aws-lambda-security-design-for-failure