I’ve written in the past about how the stateless and ephemeral nature of serverless functions shifts the way attackers approach these systems. Some of the key ramifications of this shift are:
- The move to repetitive stateless attacks, where attackers leverage one or more application weaknesses to do some small volume of the overall attack, and repeat this process until the attack is complete.
(If you missed it, you can read my rant about this in my previous post, “Your Apps Have Gone Serverless. Has Your Security?“
- The importance of protecting against upstream supply chain injection attacks, where attackers take advantage of your functions dependency on third party code, and find one of these modules that doesn’t properly protect itself. The attacker then injects their malicious code into this module, and waits for the attack to trickle down into your functions.
If this doesn’t sound scary to you, read David Gilbertson’s excellent work of fiction (you hope) here.
(This is of course just as relevant in non-serverless applications, but because it’s much harder to achieve persistence in ephemeral compute, attackers will look at this vector more seriously in the serverless realm.)
A False Sense of App Security
But let’s revisit the assumptions. If there is one lesson you should learn in security, it’s that the greatest security flaws stem from faulty assumptions. It’s precisely because you believe all the marketing about your new all-wheel-drive jeep that you may take a turn too quickly and put yourself in danger. If you were driving a Hyundai Accent you’d probably be a lot more careful.
Much of our security thinking might be based on the assumption that attackers can’t live for more than a few minutes in our system. The fact that cloud providers are intentionally vague about how and when they recycle serverless runtimes makes it easy to miss the fact that these functions can live for hours, or maybe even days.
That being the case, we actually do need to consider the possibility that an attacker could find a way to stay in our system and do extended damage.
Cold and Warm
Anyone who has spent time with FaaS systems can often be found murmuring in his sleep “No, not a Cold Start!!”. Let’s quickly cover a very simplified view of how the cloud providers handle events that trigger our functions.
As you can see, both you and the cloud provider want to stay away from the “cold start” penalty (both in terms of cost and in terms of latency) and so over the past few years, providers are keeping containers warm for longer and longer to try and make sure that your requests can be handled as quickly as possible.
From a performance perspective, that’s a positive thing. From a security perspective, on the other hand, that can be a step backwards. That’s because the ephemeral nature of serverless functions meant that even if an attacker found a way to infiltrate your running function, they couldn’t rely on being around for too long. With the function runtime containers surviving for longer and longer, we need to be concerned about these “persistent” attacks again.
Yeah? Show Me…
Ok, let’s look at a quick, if slightly naive example. Say you have an AWS Lambda function that is tied to an API Gateway entry, that allows users to update their payment information.
Now, you’ve read the serverless security blogs. You get it. You configured the permissions on this function perfectly. All it can do is insert a record into the database. Even if an attacker can exploit your eval instruction on line 8, how much damage could they do? After all, they can only insert extra rows in the table, but that’s not the end of the world.
Except that this container could stick around for a while. Imagine an attacker that sends the following data to your function (I’ve added some line breaks in the JSON to make it more readable; JSON doesn’t really support multiline strings…):
Instead of just containing some payment info, this attack payload actually injects some code into your function. From now on, each time an innocent user passes her credit card information to your application, our attacker is going to get a copy. If the attacker is smart, he will try to infect as many containers as possible. He can do this, for example, by sending this malicious attack several hundred times in parallel, infecting many containers at once. If he’s even smarter, he will endeavor to keep your containers warm, and prevent losing his foothold in the application.
The challenge for this attack is that there is a one-time spike of one or more attack payloads, followed by nothing malevolent. And yet your attacker can receive a stream of payment details stolen from your customers for hours or even days.
So much for stateless and ephemeral…
OK, Enough Fear Mongering, What Do I Do to Protect from Serverless Attacks?
Ok, sorry. We don’t mean it badly, us security people. We just naturally focus on the challenges, not the benefits. None of this makes serverless bad. In fact, at most, it means serverless is just like non-serverless in this respect.
There are several things we should do, though, to help minimize our risk.
- Don’t assume
- Educate your developers to write secure code, and practice secure development.
- Protect your application with state-of-the-art serverless security tools that will help prevent the attacks, and detect anything that slips through
- Control your ephemerality: some security tools, including our own Protego solution, can force containers to recycle efficiently if they live too long, minimizing this attack surface
Serverless architectures are a leap forward in software engineering, and present both challenges and opportunities when securing your applications. It’s critical to keep your eyes open when considering all of the security risks, and make sure you’re using all the right tools and processes to protect your users and their data.