Skip to content
Open
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
[metadata]
Comment thread
bryans3c marked this conversation as resolved.
creation_date = "2026/06/18"
integration = ["aws"]
maturity = "production"
updated_date = "2026/06/18"

[rule]
author = ["Elastic"]
description = """
Identifies a single AWS Lambda execution-role credential (a temporary assumed-role access key) that is used both from
inside the Lambda execution environment and from outside it within the same window. When a function runs, its temporary
credentials are presented with the Lambda runtime user agent (containing "exec-env/AWS_Lambda") or with a request
invoked by "lambda.amazonaws.com". An adversary who exfiltrates those credentials, typically through a server-side
request forgery or remote code execution flaw in the function, can replay them from their own host, where the same
access key appears without the Lambda runtime markers and from an unrelated source. Observing the same temporary key
both inside and outside the runtime is a strong indicator of stolen Lambda credentials being abused.
"""
false_positives = [
"""
Some architectures proxy or forward Lambda activity in ways that can strip the runtime user agent, and certain AWS
service integrations may act on a function's behalf. Validate the access key, the principal in
`aws.cloudtrail.user_identity.arn`, the external source IPs, and the user agents observed outside the runtime before
treating the activity as malicious. Known proxies or service integrations can be tuned out after validation.
""",
]
from = "now-61m"
interval = "60m"
language = "esql"
license = "Elastic License v2"
name = "AWS Lambda Execution Role Credentials Used Outside Function"
note = """## Triage and analysis

### Investigating AWS Lambda Execution Role Credentials Used Outside Function

AWS Lambda functions receive temporary credentials for their execution role and use them from within the managed runtime. CloudTrail records those calls with a user agent containing `exec-env/AWS_Lambda` or with `aws.cloudtrail.user_identity.invoked_by` set to `lambda.amazonaws.com`. If the same temporary access key is also seen making calls without those runtime markers, the credentials were almost certainly extracted from the function (for example via SSRF or RCE against the function code) and replayed from an attacker-controlled location.

This rule aggregates CloudTrail by the temporary access key and flags keys that were used both inside and outside the Lambda runtime in the same window.

### Possible investigation steps

- Review `Esql.source_ips` and `Esql.user_agents` for the external (non-runtime) activity and determine whether the source IPs and clients are expected for the workload.
- Map `aws.cloudtrail.user_identity.arn` to the Lambda function and its execution role, and review the function's code, dependencies, and recent changes for an SSRF/RCE or credential-exposure path.
- Examine `Esql.actions` for the operations performed with the credential outside the runtime, especially data access, IAM, or STS calls that exceed the function's normal behavior.
- Determine the blast radius of the execution role's permissions and whether any sensitive actions succeeded.
- Pivot on the external source IPs across other identities and accounts for signs of broader credential abuse.

### False positive analysis

- Proxies, log-forwarding layers, or service integrations that act on a function's behalf may occasionally present a Lambda credential without the runtime user agent. Validate the external source and client, and tune out known benign patterns on `aws.cloudtrail.user_identity.arn` or the observed user agents.

### Response and remediation

- If the credential abuse is confirmed, terminate active sessions and rotate or revoke the execution role's permissions; the temporary key cannot be individually revoked, so remove or restrict the role's trust and permissions.
- Patch the function's credential-exposure path (for example the SSRF/RCE vulnerability) and redeploy from a known-good source.
- Review and reduce the execution role's permissions to least privilege, and assess any actions performed with the stolen credential for data exposure or further compromise.

### Additional information

- [Using AWS Lambda environment variables (runtime identifiers)](https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html)
"""
references = [
"https://docs.aws.amazon.com/lambda/latest/dg/lambda-intro-execution-role.html",
"https://docs.aws.amazon.com/lambda/latest/dg/logging-using-cloudtrail.html",
"https://hackingthe.cloud/aws/exploitation/lambda-steal-iam-credentials/",
]
risk_score = 73
rule_id = "0ae9b6c5-1deb-4ed4-9337-659dd701dfbc"
severity = "high"
tags = [
"Domain: Cloud",
"Data Source: AWS",
"Data Source: Amazon Web Services",
"Data Source: AWS Lambda",
"Use Case: Threat Detection",
"Tactic: Defense Evasion",
"Resources: Investigation Guide",
]
timestamp_override = "event.ingested"
type = "esql"

query = '''
from logs-aws.cloudtrail-*

| where
aws.cloudtrail.user_identity.type == "AssumedRole"
and aws.cloudtrail.user_identity.access_key_id IS NOT NULL

| eval is_lambda_call = case(
user_agent.original LIKE "*exec-env/AWS_Lambda*"
or aws.cloudtrail.user_identity.invoked_by == "lambda.amazonaws.com",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only time I see this invoked_by value is when aws.cloudtrail.user_identity.type == AWSService, how are you comparing these AssumedRole lambda calls to the original service Lambda calls?

1, 0)

| stats
Esql.lambda_runtime_call_sum = sum(is_lambda_call),
Esql.total_call_count = count(),
Esql.source_ip_values = values(source.ip),
Esql.user_agent_values = values(user_agent.original),
Esql.event_action_values = values(event.action)
by
aws.cloudtrail.user_identity.access_key_id,
aws.cloudtrail.user_identity.arn,
cloud.account.id

| eval Esql.external_call_count = Esql.total_call_count - Esql.lambda_runtime_call_sum

| where Esql.lambda_runtime_call_sum > 0 and Esql.external_call_count > 0

| keep
aws.cloudtrail.user_identity.access_key_id,
aws.cloudtrail.user_identity.arn,
cloud.account.id,
Esql.lambda_runtime_call_sum,
Esql.external_call_count,
Esql.source_ip_values,
Esql.user_agent_values,
Esql.event_action_values

| sort Esql.external_call_count desc
'''


[[rule.threat]]
framework = "MITRE ATT&CK"
[[rule.threat.technique]]
id = "T1078"
name = "Valid Accounts"
reference = "https://attack.mitre.org/techniques/T1078/"
[[rule.threat.technique.subtechnique]]
id = "T1078.004"
name = "Cloud Accounts"
reference = "https://attack.mitre.org/techniques/T1078/004/"



[rule.threat.tactic]
id = "TA0005"
name = "Defense Evasion"
reference = "https://attack.mitre.org/tactics/TA0005/"

[rule.investigation_fields]
field_names = [
"aws.cloudtrail.user_identity.access_key_id",
"aws.cloudtrail.user_identity.arn",
"cloud.account.id",
"Esql.lambda_runtime_call_sum",
"Esql.external_call_count",
"Esql.source_ip_values",
"Esql.user_agent_values",
"Esql.event_action_values",
]

Loading