To access material, start machines and answer questions login.
According to , is "a serverless, event-driven compute service that lets you run code for virtually any type of application or backend service without provisioning or managing servers."
While that's a mouthful of marketing jargon, what does that mean?
"Serverless" is the concept that you, as the end-user, do not need to build or deploy any infrastructure to run a function. manages all that for you. claims you can "Simply write and upload code as a .zip file." In reality, each is a function that runs in its own with multiple isolation layers between customers. But all of that is abstracted away from the customer by .
supports a number of languages (opens in new tab), called runtime environments. The most popular are NodeJS, Python, Java, and Go.
"Event-driven (opens in new tab)" is what happens after you upload your code. All functions are triggered by an event. That event can be a manual invocation of the function or tied to other activities in the account, such as a console login or a user writing an object to an bucket. This event-driven nature allows you to create complex applications based on multiple services.
Because functions are event-driven, they have a few limitations. The most significant limitation is that a function can run for no longer than 15 minutes before it times out.
is one of the next frontiers of cloud-native applications, so it's important to know how they work, how to create them securely, and how they can be attacked. Additionally, many cloud security tools leverage to manage security events, so as a cloud security practitioner, you will need to be able to build with .
Learning Objectives
- Understand the benefits and limitations of service and functions
- Learn about the various components and settings for a function
- Understand the multiple ways a developer or operator can misconfigure a function to make it less secure.
Select the Cloud details button at the top of the room:

Where needed generate the environment required for the room. The "Generate Environment" button will appear if the room contains an environment that needs to be generated.

For any issues with the environment, select the "Reset Environment" button. Review this article for more information.
To view the credentials required for the environment, select the credentials tab. You can use these credentials to access the environment in various ways. More information can be found here:

instantiates a new execution environment (opens in new tab) when a function is first invoked. This instantiation is called a cold-start and is significant for applications where response time is critical. If multiple requests come in at once, will scale up the number of execution environments. Only one invocation will run in an execution environment at any one time; however, after the invocation is completed, the execution environment will be reused for subsequent requests (for a non-deterministic period, but typically between 45 minutes to an hour).
After the execution environment is instantiated, the function handler is invoked. This is where the service calls a specific function or method in the code. The inputs of the invocations are called the "event". Also passed to the handler is the "context," which contains details such as the function name, logging information, the function timeout, and time remaining before the timeout occurs.
Here is a basic python function that simply prints out the environment variables inside the execution environment:import boto3
from botocore.exceptions import ClientError
import json
import os
import logging
# This code executes when the execution environment is created and sets up how the python logger will work
logger = logging.getLogger()
logger.setLevel(getattr(logging, os.getenv('LOG_LEVEL', default='INFO')))
logging.getLogger('botocore').setLevel(logging.WARNING)
logging.getLogger('boto3').setLevel(logging.WARNING)
# This code executes when the function is invoked
def lambda_handler(event, context):
logger.debug("Received event: " + json.dumps(event, sort_keys=True))
for key in os.environ.keys():
logger.info(f"{key}={os.environ[key]}") os.environ[key]
return(event)
The code outside the lambda_handler() is executed when creates the execution environment. In this case it's setting up the default python logger module. Each time the function is invoked, the function named lambda_handler() is called with the event provided by the caller and the context defined by the service. This function then logs the event and each of the environment variables.
When a function execution completes, the object returned is passed back to the caller.
functions can be invoked synchronously or asynchronously. In a synchronous invocation, the caller waits for the function to finish executing before returning. With an asynchronous invocation, the caller sends the event but doesn't wait for the execution to complete. It's incumbent on the application designer to handle error conditions and the resulting output for these "fire and forget" invocations.
You can develop functions in the Console (opens in new tab), or via infrastructure as code like CloudFormation (opens in new tab) or Terraform (opens in new tab). When you're developing in the console, you can create test events for your functions.

You can have multiple test events and give them unique names. You can even share the test events with other developers in the same account.
Now save the value of "invoked_function_arn". You will need that in a future task.
functions have many moving parts, and it's good to know what most of them are when defending, attacking, or just building with . You can find most of these settings and components in the Console (opens in new tab), and we've created a sample function for you to play with.
Code
code is typically stored as a zip file in , but for smaller functions, you can often pass the zip file as part of the call. Every code package must have a file name specified by the handler, and in that file, a function or method specified by the handler. The exact specifications for the handler are run-time dependent.
Test events
You can configure Test Events for writing and troubleshooting code in the Console.
Memory and Timeout
You can specify the amount of memory allocated to a function: 128MB to 10GB. The service allocates capacity to the execution environment based on the memory size. Additionally, you can specify the timeout of a function, up to 900 seconds.
billing uses two dimensions. The number of executions and the gigabyte-seconds of execution. A function that uses 3GB of and runs for 3 seconds bills at 9GB-Seconds. A 512MB Function that runs for 4 seconds will bill at 2GB-Seconds.
Runtimes
Every function has a selected runtime (opens in new tab). NodeJS and Python are the most popular runtimes, but Java, Ruby, .Net, and Go are also available. Customers can even create custom runtimes (opens in new tab). You can find the complete list of runtimes at 's website (opens in new tab).
Role
The Execution Role (opens in new tab) is like an Instance profile. It's a set of predefined permissions the function has permission to act on resources in the account.
In 's least-privilege fashion, you must explicitly define the ability to write logs to logs in the execution role.
Environment Variables
allows configuring environment variables as part of the runtime. This allows developers to use the same code zip file across multiple environments (dev, test, prod). The same lambda:GetFunction API call that gets the code will also grant you a copy of the environment variables and values.
Because environment variables could contain sensitive information, AWS encrypts them using Key Management Service (KMS).
Invocation Policy
In addition to the execution role, a function can have a resource-based policy that defines who or what can call the lambda:InvokeFunction call on the function. When we discussed resource policies in the module, you invoked a function in the account 019181489476. That was possible because the TryHackMe-time function granted InvokeFunction to all the accounts in the TryHackMe organization.
It is possible to have a public function that anyone could execute if they knew the account id, region, and function name (the unique components of the ARN).
Logging
The service will log any data written by the function to STDOUT (output) or STDERR (error messages) to Logs (opens in new tab). Logs is 's monitoring and observability service and consists of a service that can accept metrics and logs. also allows you to create Alarms and Dashboards about the metrics and logs.
Each function will write its logs to a log group called ///<functionname>. You cannot change the name of the log group, nor can you configure multiple functions to write to the same log group.
By default, functions are assigned a public IP address from a dedicated pool of -owned IP addresses. However, it is possible to have a function invoked inside your with a private IP (RFC1918) address. This is typically required if the function must communicate with internal resources like an RDS database or a resource in an on-prem data center.
When a function is created inside a , a Hyperplane ENI (opens in new tab) is created in each specified subnet that all execution environments share. Like all ENIs, these ENIs have security groups attached to them. While execution environments usually don't listen for incoming connections, you can use the egress (outbound) rules of the Security Group to determine what resources the function can access.
Concurrency and Throttling
Since functions are event-driven, an extremely large number of events can potentially trigger a large number of executions. In order to capacity plan and avoid runaway bills, implements concurrency limits (opens in new tab) on function executions. An account will support 1000 simultaneous executions in each region by default. If your application needs to scale beyond 1000 concurrent connections, you can request a limit increase with Support. When the concurrency limit is reached, executions are throttled. Events are not lost but instead queued up for later execution.
developers can also set a throttling limit on a per-function basis to not exceed the scaling capacity of resources the function relies on.
EFS
Every execution environment has 500MB of scratch space in the /tmp directory. That is the only part of the execution environment filesystem that is writable by the function code. When the execution environment is terminated, the contents of /tmp are lost. For most use-cases, that is fine. functions are intended to be stateless.
A recent addition to is the ability to mount an Elastic FileSystem into the function. Amazon Elastic (opens in new tab) (EFS) is 's managed Network (NFS) service. It allows Instances, Containers, and functions to share a common filesystem.
Layers
Often, a developer needs to share a common set of code or libraries across a number of functions. Rather than the same set of code and libraries in multiple zip files across potentially hundreds or thousands of functions, has the concept of a Layer. The layer is expanded and is available to the runtime when the execution environment is created. layers have resource policies so that they can be shared across accounts. Files in the layer will appear in the filesystem under the /opt directory.
Function URLs
recently introduced function URLs. These are dedicated HTTPS endpoints to your function, allowing it to be invoked over the internet. The URLs are in the form of:
https://<url-id>.-url.<region>.on. where the url-id is a unique identifier. states:
generates the <url-id> portion of the endpoint based on a number of factors, including your account ID. Because this process is deterministic, it may be possible for anyone to retrieve your account ID from the <url-id>.
Function URLs can be invoked with or without Authentication. If Authentication is required, you must sign the request (opens in new tab) as part of the request. If no Authentication is required, any simple CURL command from anywhere will invoke the function.
Using the AWS CLI, investigate this Lambda function in another account.
aws lambda get-function --function-name arn:aws:lambda:us-east-1:019181489476:function:sample-lambdaWhat is the RunTime of the function?
What are the first two sentences of the error message you received when the get-function command tried to read the environment variables?
Using curl, download the zip file referenced by the Code Location. Open the file, and enter the contents of the "question3.txt" file.
The handler function is missing from the zip file. What should the filename be?
There are several ways can be misconfigured or attacked. The primary risk is via insecure code. Most functions execute based on an event that is user generated. If the code doesn't validate user-supplied inputs properly, a function could be compromised.
An additional risk is when the code writes sensitive data to STDOUT. This data would appear in the logs for the function and be visible to anyone with read-access to the account.
Another way that functions can be misconfigured is through overly permissive execution roles—granting a function more permissions than required increases the blast radius of the damage that an attacker can do with any code vulnerability discovered.
Role Permissions are available via the environment variables AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_SESSION_TOKEN. If the code allowed an external attacker to access these three environment variables, they would be able to use the powers of the function to engage in potential privilege escalation or data exfiltration.
While user-defined environment variables are KMS encrypted, they're typically easy to access from within the function or via the or Console. If environment variables are used to pass secrets to the function, those secrets are easy to exfiltrate from the account.
How the function is invoked can be a security misconfiguration if the resource policy is misconfigured. While resource policies are usually used with the lambda:invokeFunction IAM Action, if you used lambda:* instead, the principal would have the ability to upload new code. If the resource policy allowed all AWS users (i.e., Principal: *), then you would have a major security risk.
It's important to realize how concurrency works as an attacker and defender. As an attacker, you can use concurrency limits to engage in Denial of Service attacks and potentially throttle functions used to respond to security events. As a defender or author of a function responding to the security events, understanding throttling and implementing some reserved concurrency can mitigate the potential damage from a denial of service event.
Modifying a layer is a great way to inject malicious code into a large number of functions.
Finally, when you call the lambda:GetFunction , part of the response is a pre-signed URL to download the Code zip file from the service's bucket. This is a useful discovery tactic, because developers often hardcode things in their applications where they shouldn't.
In the next room, you'll get a chance to use many of these techniques to exfiltrate sensitive data using misconfigured functions.
In your account, there is a function named "TryHackMeLambdaRoom-envars-function" Invoke this function (via the console). What is the value of SECRET_CONNECTION_STRING?
What file is deployed by the Lambda Layer attached to the "TryHackMeLambdaRoom-envars-function"?
Modify the "TryHackMeLambdaRoom-envars-function" to echo the contents of the flag.txt
Ready to learn Cyber Security?
TryHackMe provides free online cyber security training to secure jobs & upskill through a fun, interactive learning environment.
Already have an account? Log in
