-
Notifications
You must be signed in to change notification settings - Fork 674
[New Rule] AWS Lambda Execution Role Credentials Used Outside Lambda #6292
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
f66f00e
e3de389
6852a96
91fb5dc
2630413
296726d
b512e4a
9eb79bc
08b7e09
56d9efb
b2c6bc1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,151 @@ | ||
| [metadata] | ||
| 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 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. tested on Zocalo and it looks too common, you will need to baseline this behavior before assigning high severity |
||
| 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 user_agent.original LIKE "*awslambda-worker/*" | ||
| or aws.cloudtrail.user_identity.invoked_by == "lambda.amazonaws.com", | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The only time I see this invoked_by value is when
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch Isai, you're right. I tested the rule with a detonation script and confirmed your observation in the alert data: invoked_by is null for all the AssumedRole events, so that side of the OR never fires. The user-agent check is carrying the whole detection. Digging into the alert payload also surfaced a related FP I want to flag: the user_agent_values on my detonation alert included awslambda-worker/1.0 rusoto/0.48.0 rust/1.94.0 linux events in the external bucket. That's the Lambda worker service creating CloudWatch log groups using the function's execution role on cold-start. With the rule as written, those land in external_call_count because they don't match exec-env/AWS_Lambda and invoked_by is null, which means a normal Lambda first-invocation could trigger this rule on its own with no attacker involvement. Proposed fix:
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes to both suggestions here |
||
| 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) | ||
|
Comment on lines
+94
to
+98
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd also consider adding some @timestamp fields to support triage later |
||
| 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", | ||
| ] | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.