This article is part of #ServerlessSeptember. You'll find other helpful articles, detailed tutorials, and videos in this all-things-Serverless content collection. New articles are published every day — that's right, every day — from community members and cloud advocates in the month of September.
Find out more about how Microsoft Azure enables your Serverless functions at https://docs.microsoft.com/azure/azure-functions/.
What are we going to learn?
You’ve been hearing about Serverless literally all month long. So, there’s really no need to go further in details about how the advantages and disadvantages (are there?) of moving into serverless architecture. This post will be purely about security.
TL;DR
Do you deploy azure functions? (I'm here, aren't I?). Do you want to secure your functions? (still here...). Great! make sure you:
Specifically, I will discuss security best practices for serverless development. Mostly that means application security with a sprinkle of configuration. Because that what serverless is all about, no?! Because we shift most of the responsibilities to the cloud provider and what’s left for us to protect, is our code.
A dream come true, some might say. We no longer continuously check that our server has the latest OS security patches installed. That’s now Microsoft’s problem (If you are unfamiliar with the Shared Responsibilities for Cloud Computing, I strongly suggest you review it).
Security exists, but differently
Hold your horses. That is definitely one of the many security advantages of moving to serverless development. But don't get too comfortable... we still need security, it is just a little different. Even without provisioning or managing servers, Azure functions still execute code. If this code is poorly written, the function could still be vulnerable to application-level attacks.
An attacker could run malicious code in your account which could lead to sensitive data leakage, performing unauthorized actions in the cloud and in some extreme cases, it could even lead to a complete application takeover. If your Azure function is vulnerable and has the permissions to do so, the attacker could interact with other cloud resources and could end up owning them.
I hope that by now you agree with me that we need to address Serverless security. So, let’s get a little more technical and discuss some security best practices that will help you keep your Azure functions protected.
1. Perform input validation
Injection flaws attacks are one of the most common risks in applications and they are part of most secure coding best practice guides. Injection attacks try to exploit code which passes untrusted inputs directly to an interpreter before being executed or evaluated.
Since serverless functions can be triggered from different events sources like Cloud storage (Blob), NoSQL database (CosmosDB), Event Hubs, Queue, Graph events and more, injections are not strictly limited to inputs coming directly from the API calls and functions can consume input from each type of the possible event sources.
What should you do?
- In general, never trust input or make any assumptions about its validity.
- Always use safe APIs that sanitize or validate the input. When possible, use APIs which bind or parameterize variables (e.g. using prepared statements for SQL queries).
using System.Net;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;
using System.Text.RegularExpressions;
public static async Task<IActionResult> Run(HttpRequest req, ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
string name = req.Query["name"];
string validate_name_pattern = @"^[a-zA-Z-.' ]{2,64}$";
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
dynamic data = JsonConvert.DeserializeObject(requestBody);
name = name ?? data?.name;
bool isNameValid = Regex.IsMatch(name, validate_name_pattern);
log.LogInformation("Input validation result for " + name + ": " + isNameValid);
return name != null && isNameValid
? (ActionResult)new OkObjectResult($"Hello, {name}")
: new BadRequestObjectResult("Invalid name");
}
2. Follow Least Privilege principle
Bottom line: A serverless function should have only the privileges essential to performing its intended logic, following the "Principle of least privilege".
Since serverless functions are usually designed as micro-services, it is very common to have dozens, hundreds or even thousands of functions as part of the application. Managing function permissions and roles is a difficult and tedious task.
In many cases, developers are forced to use a single permission model or security role for all functions, which grants them access to other system components which are not actually required by the function. Exploiting an over-privileged function could lead into a security catastrophe in your organization's cloud.
Designating groups or individual roles responsible for specific functions in Azure helps avoid confusion that can lead to human and automation errors that create security risks.
What should you do?
- Review each function before deployment to identify excessive permissions
- Carefully examine functions to apply “least privilege” permissions, giving each function exactly, and only what is required for the function to successfully execute its task
- It is recommended to use a tool to automate the permission configuration process, like we do at Protego
- Use RBAC to assign permissions to users, groups, and applications at a certain scope. The scope of a role assignment can be a subscription, a resource group, or a single resource and avoid using wildcards (
*
) whenever possible
- Use Shared Access Signature (SAS) tokens to get limited access to other resources and services
3. Monitor 3rd-party dependencies
A serverless function's code is usually small. However, to be able to execute the desired tasks, functions make use of many dependencies and 3rd-party libraries.
Vulnerability introduced by the supply chain is one of the most common risks these days and attackers will target code that makes use of vulnerable libraries as an entry point to the application.
Additionally, in what we refer to as ‘Poisoning the Well,’ attackers aim to gain more long-term persistence in the application by means of an upstream attack. After poisoning the well, they patiently wait as the new version makes its way into cloud applications.
If you use .NET, Microsoft will announce about NuGet packages with known vulnerabilities, just like this one. But, this is valid for all runtimes. Whether you are using Python (pip), Java (Maven), Node (npm) or any other package management.
What should you do?
- Continuously monitor dependencies and their versions throughout the system using OWASP Dependency Track or any other system
- Obtain components only from official sources over secure links.
- Prefer signed packages to reduce the chance of including a modified, malicious component
- Continuously monitor packages with vulnerability databases like MITRE CVE and NVD
- It is recommended to scan dependencies for known vulnerabilities using tools such as OWASP Dependency Check or a commercial solution.
- For dotnet, Use dotnet-retire. A tool to check dependencies for versions with known vulnerabilities
- Check NuGet package vulnerabilities with OWASP SafeNuGet
- Use runtime-dependent security databases such as pyup for Python and npm Security Advisories For Node
- Use Audit.NET which integrates with VS to identify known vulnerabilities in .Net NuGet dependencies
4. Secure your cloud storage
Misconfigured cloud storage authentication/authorization is a widespread weakness affecting applications. There are numerous incidents of insecure cloud storage configurations have exposed sensitive, confidential information to unauthorized users. Occasionally, this data can even become public after being indexed by search engines.
Since serverless functions are usually stateless, many applications have an architecture that rely on cloud storage infrastructure to store and persist data between executions.
If the cloud storage does not enforce proper access controls, user might be able to upload files directly into it, leading into consuming high quotas and triggering internal functionalities.
What should you do?
- Identify and classify sensitive data
- Minimize storage of sensitive data to only what is absolutely necessary
- For sensitive data storage, add multi-factor authentication, and data encryption (in transit and at rest)
- Review Azure Storage security guide
- Grant limited access to Azure Storage resources using shared access signatures (SAS)
5. Secure your function secrets
There is always a need to store and maintain secrets in our application. Secrets could be API Keys, credentials to other resources (e.g. database), Crypto-keys (Encryption/Decryption) and sensitive configuration settings.
Storing such secrets in a plain text configuration file could end up being uploaded to and leaked through shared repositories (e.g. Github). There multiple tools that hackers use to try and identify such leaked keys. There are many storied
While environment variables are a useful way to persist data across serverless function executions, in some cases, such environment variables can leak and reach the wrong hands.
It is critical to store such secrets in a secure, encrypted storage environment.
What should you do?
- Use Microsoft CredScan tool to identify credential leaks
- Whenever possible, manage encryption keys in a centralized encryption key management infrastructure or service like Azure Key-vault
- You can read Working with Azure Key Vault in Azure Functions by Jan de Vries, a Microsoft MVP
- Read How to handle secrets with Azure Functions by David O'Brien, MVP for Microsoft Azure
- When using configuration file with sensitive information over Git, make sure to add them to the
.gitignore
file
6. Enforce Authentication and Authorization
Serverless architecture require that we orchestrate all our functions and services to form the overall system logic. While some functions expose public APIs, others serve as a pipe between processes and there are multiple ways to trigger them, including internal triggers events.
The stateless nature of serverless architecture requires a careful access control configuration for each of the resources, which could be onerous.
Imagine a serverless application which exposes a set of public APIs, all of which enforce proper authentication. At the other end of the system, the application reads files from a blob storage service where file contents are consumed as input to specific serverless functions. If proper authentication is not applied to the cloud storage service, the system is exposing an unauthenticated rogue entry point—an element not considered during system design.
What should you do?
- Review and apply Authentication and authorization in Azure App Service
- Follow Azure Identity Management and access control security best practices
- External-facing resources should require authentication and access control. Review Azure API Management Safeguarding by Stuart Leeks, Principal Software Development Engineer at Microsoft
- For service authentication between internal resources and services, use known secure methods, such as Federated Identity (e.g. SAML, OAuth2, Security Tokens)
What's next?
I could go on and on about this topic. But I believe that concentrating on a few, high impact, practical security enforcement could do a great deal in making your Azure functions safe.
For those of you who do want to learn more, I can suggest:
- Follow to the open-source OWASP Serverless Top 10 risk project
- Come to my talks or watch them virtually (when available)
- Follow me on Twitter to learn more about new security research, demos, challenges, talks, ideas and references.
- If you want to see how the villains thinks. This is my #ServerlessHacking talk from the last @DerbyCon
Top comments (0)