Are Cloud Serverless Functions Exposing Your Data?

Jun 06, 2024
12 minutes
... views

More than 25% of all publicly accessible serverless functions have access to sensitive data, as seen in internal research. The question then becomes, Are cloud serverless functions exposing your data? — which is followed by How can we assess them?

Evaluating Public Access Across Cloud Providers

Many architectural design questions arise when it comes to the use of serverless functions in cloud environments. In this blog post, we'll examine the question of public access, focusing on the main offerings of the three leading cloud providers — AWS Lambda, Azure Functions and GCP Cloud Functions.

Already an expert? Just need a quick reference? Jump to the end of this post for a look at our cheat sheets.

Public access can be broken down into two segments:

  • Identity: Lack of authentication and authorization enforcement on the function
  • Network: Lack of network restriction and boundary enforcement on the function

Identity and network misconfigurations can impact the security posture of serverless functions and increase the risk of security incidents and data leaks.

Security Risks of Serverless as a Perimeter

Choosing the right serverless offering entails operational and security considerations. Serverless functions are often targeted by adversaries looking to reach a company’s data, and therefore can be considered a perimeter that requires appropriate security. Functions may hold a company’s intellectual property (IP) and may interact with or even alter, company data stored at other locations (e.g., S3). As such, a security gap that enables an adversary to read, write or execute functions could lead to compromised data.

Real-Life Security Dilemma

Let’s look at the following scenario. You have a public website where customers can download a report of their past activity. You use a function with an HTTP trigger to fetch or compile, as well as to serve the file to the client. Should the function be public? Does the site force authentication that we might want to trickle down?

Public Function Considerations

Is a function that you can reach via the public internet that requires a client certificate for interaction publicly available or does it simply lack network restrictions? What actions should we consider when it comes to public availability — invoke/execute, read, write, or something else?

For a function to be publicly accessible, it must:

  1. Be accessible from the public internet (i.e., publicly accessible network-wise)
  2. Require no additional form of authentication (i.e., unauthenticated, anonymous access)

Potential Risks of Using Public Functions

From the perspective of an adversary, what can be gained from public accessibility? Alternatively, what is the risk from the defender’s perspective?

We can divide our focus into three sections based on the action type available:

  • A publicly invocable function is contextual. It depends on what the function does — the more sensitive the original use case for the function, the higher the risk.
  • A publicly writable function offers a bit more room for imagination. Based on the function’s permissions, we could use it to do our bidding. This requires an additional step — waiting for its invocation.
  • A publicly readable function could enable us to expose potentially sensitive information pertaining to the function and its related data, sometimes even obtaining the function’s source code.

While the core aspects and considerations of all serverless offerings are similar, each cloud provider’s offering has a different set of configurations. Therefore, we must look at them independently to truly understand the options.

Security Considerations for AWS Lambda Functions

AWS’ main serverless offering is Lambda functions. As mentioned above, we will examine two angles of public access — network and identity. First, let’s map out the different actions and permissions that might interest us and divide them into the following categories:

  • Invoke: lambda:InvokeFunctionUrl, lambda:InvokeFunction
  • Write: lambda:DeleteFunctionUrlConfig, lambda:UpdateFunctionUrlConfig, lambda:UpdateFunctionCode, lambda:UpdateFunctionConfiguration, lambda:PutFunctionEventInvokeConfig, lambda:UpdateFunctionEventInvokeConfig
  • Read: lambda:GetFunction, lambda:GetFunctionConfiguration, lambda:GetFunctionUrlConfig

When made publicly available, these permissions can enable anyone to either read, write, or invoke (execute) the function. While the emphasis is usually placed on the public invocation of functions, reading the source code of a function or even changing it could be just as dangerous.

For example, an adversary who can change the source code of a function that they cannot invoke could replace the code with their own malicious code and wait for the next invocation, at which point the function would operate on their behalf.

Now that we have an idea of potentially dangerous actions, how do we determine whether they're publicly available?

Network in AWS

The first aspect of public access is the network. Can you reach the function via the public internet? By default, the answer is yes, however, once the function is configured with access to a virtual public cloud (VPC) within your environment, the default changes. In such an environment, the function’s network accessibility depends on the various networking configurations, such as access controls on the connected VPC or configured endpoints or gateways.

Identity in AWS

The second aspect of public access is identity. Does the function require any authentication material or enforce any level of authorization? Consider the actions we discussed earlier. When the resource-based policy enables any principal to carry out an action on the function, the action can be considered public from the identity perspective. While this is true for most actions, when looking at the invocation of Lambdas through function URLs (dedicated HTTP endpoints) we must consider an additional layer of authentication.

Example of a resource-based policy that allows an action publicly
Figure 1: Example of a resource-based policy that allows an action publicly[/caption][/caption]For function URLs, we can configure one of two authentication types (lambda:FunctionUrlAuthType) — NONE and AWS_IAM.

When configuring a function URL with NONE, a resource-based policy is created that enables the principal to carry out lambda:InvokeFunctionUrl action on the function. This allows anyone to invoke the function (via the HTTP endpoint) without additional credential material.

Resource-based policy created for a function URL configured with auth_type NONE
Figure 2: Resource-based policy created for a function URL configured with auth_type NONE

If we specify any principal ‘*’ (everyone) when configuring a function with AWS_IAM, anyone with a valid AWS token can invoke the function (via the HTTP endpoint). Since there are no restrictions regarding who can create an AWS account, we can also consider this public.

Resource-based policy created for a function URL configured with auth_type AWS_IAM and principal
Figure 3: Resource-based policy created for a function URL configured with auth_type AWS_IAM and principal

The following screenshots are examples of invocation requests to a Lambda configured with AWS_IAM auth_type with no network restrictions. Both requests are from the same AWS entity, which is external to the Lambda’s account. During the first request, the principal in the resource-based policy is configured as a role within the Lambda’s account.

Request fails on principal restrictions
Figure 4: Request fails on principal restrictions

During the second request, the principal in the resource-based policy is configured as '*' (everyone).

Request succeeds after changing the principal to ‘*’ (everyone)
Figure 5: Request succeeds after changing the principal to ‘*’ (everyone)

Security Considerations for Azure Functions

Azure Functions is Azure’s main serverless offering. We will look at two angles of public access — network and identity — as we did when analyzing AWS Lambdas. Due to the way Azure Functions code is stored, writing or reading it for each Function requires a key, and as such, it can't be considered public. For that reason, we'll focus on the invoke/execute action.

Network in Azure

When discussing the network availability of an Azure Function, there are multiple layers that we need to consider. Networking for Functions is done at the Function app level. First, there’s the native “Public network access” configuration. Second, we must take into consideration Vnet integration (network injection) and private network endpoints.

Private endpoints and public network access are incompatible configurations (i.e., they cannot coexist). As such, if private endpoints are configured for the Function app, they cannot, by definition, be accessible via the public internet.

While Vnet integration applies to all outbound traffic (i.e., initiated by the Function) by default, it can be limited to route only private traffic through the Vnet. At that point, accessibility is determined by the Vnet’s configurations, such as the applied network security groups (NSG). However, since the integration applies only to traffic initiated by the Function, it is slightly less relevant from a public invocation perspective.

Identity in Azure

We have multiple options for the identity side of Azure Functions invocation. At the Function app level, we can enforce authentication using Entra ID or a variety of OAuth and OIDC-based identity providers (IdPs). At the individual Function level, we can enforce key-based authentication or no authentication at all (anonymous).

A publicly invocable Function (from the identity aspect) scenario occurs only when the Function app either has no authentication configured, or the “Allow unauthenticated access” option is enabled — and the individual Function’s authorization level is set to “Anonymous.”

Setting the individual Function as anonymous is carried out during its creation.

Setting “ANONYMOUS” during function creation
Figure 6: Setting “ANONYMOUS” during function creation

We can also view the postcreation configuration in the code.

Code snippet that updates authorization level
Figure 7: Code snippet that updates authorization level

Here's an example of an authentication configuration for a Function app that enables unauthenticated access:

Reflected changes shown in azure portal
Figure 8: Reflected changes shown in azure portal

With the configurations shown above and given no network restrictions, anyone can invoke a Function publicly.

Anonymous call from web interface.
Figure 9: Anonymous call from web interface.

Security Considerations for GCP Cloud Functions

GCP Cloud Functions is GCP’s main serverless offering. Just like we analyzed the previous two offerings, here we will examine network and identity to define public access. We’ll look at the invocation in GCP Cloud Functions as we did with Azure Functions.

GCP offers two generations of Cloud Functions — Gen 1 and Gen 2. Gen 2 is built on top of Cloud Run and adds efficiency and support for further events. As such, it benefits from Cloud Run’s inherent security features, such as sandboxing and the Function’s containerization. In addition, each generation requires different permissions.

  • Gen 1
    • cloudfunctions.functions.invoke
  • Gen 2
    • run.executions.cancel
    • run.jobs.run
    • run.routes.invoke

Network in GCP

GCP enables three default configuration options for networking: allow all traffic, allow internal traffic only, allow internal traffic and traffic from cloud load balancing. Among the three, only allow all traffic permits connections from the public internet.

Similar to Azure, in GCP Cloud Functions we can connect our Function to a VPC for outbound connections, which is the point where the VPC security mechanisms, such as firewall rules, determine the permitted connections. The VPC connection, though, applies only to traffic initiated by the Function, making this configuration less relevant in the public invocation discussion.

Identity in GCP

We must address multiple considerations regarding the identity end of GCP Cloud Functions. The first is the native “Allow unauthenticated invocation” configuration, which permits public access from the identity aspect while the alternative requires authentication.

But it does not end here. If we look into the configurations, we’ll notice that we can add roles to different principals. GCP provides us with two special identifiers:

  • allAuthenticatedUsers, which represents any entity authenticated with a Google accountallUsers, which represents anyone.

Since anyone can create a personal Google account, we can treat the two identifiers as “Everyone.” By adding the permissions mentioned above (or more commonly, the Cloud Functions Invoker or Cloud Run Invoker predefined roles that contain the permissions) to either the allUsers or allAuthenticatedUsers principals, we enable public access from the identity aspect.

We can enable unauthenticated access to invoke the function during its creation.

Figure 10: GCP Function authentication setting

Alternatively, we can require authentication and add permissions to the allUsers group (in this case a Gen two instance).

“AllUsers” principal (anyone with internet access) role assignment
Figure 11: “AllUsers” principal (anyone with internet access) role assignment

Once we apply the configuration, we can see that GCP changes the authentication status to “Allow unauthenticated.”

Before:

Authentication setting before role assignment
Figure 12: Authentication setting before role assignment

After:

Authentication setting after role assignment
Figure 13: Authentication setting after role assignment

Given the above configuration and assuming no network restrictions, the Function is publicly invocable.

Callable function result
Figure 14: Callable function result

Summary

While serverless computing often provides a simpler, more efficient, more scalable, and less expensive solution to build applications, it is subject to many potential pitfalls, regardless of your vendor of choice. In this blog, we briefly covered some configurations of the main cloud providers’ key serverless offerings, and showed how easily they can become convoluted and lead to errors.

In the best case scenario, you miss one configuration while another keeps you safe (e.g., misconfigured network access but successfully configuring identity controls). In the worst case scenario, your function is publicly accessible and, depending on context, could act as a pivot for an adversary within your environment. As such, it is important to understand the services and each of their configurations, as well as to set up boundaries, good practices and security measures to prevent these misconfigurations from happening.

AWS Cheat Sheet: Is My Lambda Exposed?

AWS Cheat Sheet
Figure 15: AWS Cheat Sheet

Azure Cheat Sheet: Is My Function Exposed?

Figure 16: Azure Cheat Sheet

GCP Cheat Sheet: Is My Cloud Function Exposed?

GCP Cheat Sheet
Figure 17: GCP Cheat Sheet

Learn More

For a fuller understanding of how your data is exposed in the cloud, read our comprehensive State of Cloud Data Security 2023 report, which not only sheds light on critical aspects of cloud data security but also provides actionable steps to defend your valuable data.


Subscribe to Cloud Native Security Blogs!

Sign up to receive must-read articles, Playbooks of the Week, new feature announcements, and more.