Detection rules › Panther
Lambda Code Updated by User
Detects when Lambda function code is updated by an IAM user, federated user, or AWS SSO user. This may indicate compromised credentials, a developer bypassing CI/CD guardrails, or insider threat activity.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Initial Access | T1078 Valid Accounts |
| Persistence | T1078 Valid Accounts |
| Privilege Escalation | T1078 Valid Accounts |
| Stealth | T1078 Valid Accounts |
Rules detecting the same action
Other rules on this platform that filter on the same API call or operation.
Rule body yaml
AnalysisType: rule
Filename: aws_overwrite_lambda_code.py
RuleID: "AWS.Lambda.UpdateFunctionCode"
DisplayName: "Lambda Code Updated by User"
Enabled: true
CreateAlert: true
LogTypes:
- AWS.CloudTrail
Reports:
MITRE ATT&CK:
- TA0007:T1078
Severity: High
Status: Experimental
Description: >
Detects when Lambda function code is updated by an IAM user, federated user, or AWS SSO user.
This may indicate compromised credentials, a developer bypassing CI/CD guardrails,
or insider threat activity.
Runbook: |
Verify the user identity and whether this is authorized.
Review the code changes made to the Lambda function.
If unauthorized:
- Disable the user's access key or revoke the SSO session
- Revert Lambda function code to previous version
- Investigate credential compromise
Reference: https://stratus-red-team.cloud/attack-techniques/AWS/aws.persistence.lambda-overwrite-code/
Tests:
- Name: IAM User Updates Lambda Code
ExpectedResult: true
Log:
{
"eventVersion": "1.05",
"userIdentity": {
"type": "IAMUser",
"principalId": "AIDAI123456789EXAMPLE",
"arn": "arn:aws:iam::123456789012:user/john.developer",
"accountId": "123456789012",
"accessKeyId": "AKIAI123456789EXAMPLE",
"userName": "john.developer"
},
"eventTime": "2019-01-01T00:00:00Z",
"eventSource": "lambda.amazonaws.com",
"eventName": "UpdateFunctionCode20150331v2",
"awsRegion": "us-west-2",
"sourceIPAddress": "203.0.113.50",
"userAgent": "aws-cli/2.13.5 Python/3.11.4",
"requestParameters": {
"functionName": "production-api"
},
"responseElements": {
"functionName": "production-api"
},
"requestID": "1",
"eventID": "1",
"readOnly": false,
"eventType": "AwsApiCall",
"recipientAccountId": "123456789012",
"p_log_type": "AWS.CloudTrail"
}
- Name: AssumedRole Updates Lambda Code (Normal CI/CD)
ExpectedResult: false
Log:
{
"eventVersion": "1.05",
"userIdentity": {
"type": "AssumedRole",
"principalId": "tester",
"arn": "arn:aws:sts::123456789012:assumed-role/tester",
"accountId": "123456789012",
"accessKeyId": "1",
"sessionContext": {
"sessionIssuer": {
"type": "Role",
"principalId": "1111",
"arn": "arn:aws:iam::123456789012:role/tester",
"accountId": "123456789012",
"userName": "Tester"
},
"webIdFederationData": {},
"attributes": {
"mfaAuthenticated": "true",
"creationDate": "2019-01-01T00:00:00Z"
}
}
},
"eventTime": "2019-01-01T00:00:00Z",
"eventSource": "lambda.amazonaws.com",
"eventName": "UpdateFunctionCode20150331",
"awsRegion": "us-west-2",
"sourceIPAddress": "111.111.111.111",
"userAgent": "console.amazonaws.com",
"requestParameters": {
"functionName": "my-lambda-function"
},
"responseElements": {
"functionName": "my-lambda-function"
},
"requestID": "1",
"eventID": "1",
"readOnly": false,
"eventType": "AwsApiCall",
"recipientAccountId": "123456789012",
"p_log_type": "AWS.CloudTrail"
}
- Name: SSO AssumedRole Updates Lambda Code
ExpectedResult: true
Log:
{
"eventVersion": "1.05",
"userIdentity": {
"type": "AssumedRole",
"principalId": "AROA123456789EXAMPLE:bob.ross",
"arn": "arn:aws:sts::123456789012:assumed-role/AWSReservedSSO_DevAdmin_abc123/bob.ross",
"accountId": "123456789012",
"accessKeyId": "ASIA123456789EXAMPLE",
"sessionContext": {
"sessionIssuer": {
"type": "Role",
"principalId": "AROA123456789EXAMPLE",
"arn": "arn:aws:iam::123456789012:role/aws-reserved/sso.amazonaws.com/AWSReservedSSO_DevAdmin_abc123",
"accountId": "123456789012",
"userName": "AWSReservedSSO_DevAdmin_abc123"
},
"webIdFederationData": {},
"attributes": {
"mfaAuthenticated": "false",
"creationDate": "2019-01-01T00:00:00Z"
}
}
},
"eventTime": "2019-01-01T00:00:00Z",
"eventSource": "lambda.amazonaws.com",
"eventName": "UpdateFunctionCode20150331v2",
"awsRegion": "us-west-2",
"sourceIPAddress": "203.0.113.50",
"userAgent": "aws-cli/2.13.5 Python/3.11.4",
"requestParameters": {
"functionName": "production-api"
},
"responseElements": {
"functionName": "production-api"
},
"requestID": "1",
"eventID": "1",
"readOnly": false,
"eventType": "AwsApiCall",
"recipientAccountId": "123456789012",
"p_log_type": "AWS.CloudTrail"
}
- Name: FederatedUser Updates Lambda Code
ExpectedResult: true
Log:
{
"eventVersion": "1.05",
"userIdentity": {
"type": "FederatedUser",
"principalId": "123456789012:federated-user",
"arn": "arn:aws:sts::123456789012:federated-user/federated-user",
"accountId": "123456789012",
"accessKeyId": "ASIA123456789EXAMPLE"
},
"eventTime": "2019-01-01T00:00:00Z",
"eventSource": "lambda.amazonaws.com",
"eventName": "UpdateFunctionCode20150331v2",
"awsRegion": "us-west-2",
"sourceIPAddress": "203.0.113.50",
"userAgent": "aws-cli/2.13.5 Python/3.11.4",
"requestParameters": {
"functionName": "production-api"
},
"responseElements": {
"functionName": "production-api"
},
"requestID": "1",
"eventID": "1",
"readOnly": false,
"eventType": "AwsApiCall",
"recipientAccountId": "123456789012",
"p_log_type": "AWS.CloudTrail"
}
- Name: AssumedRole Updates Lambda Code v2 (Normal CI/CD)
ExpectedResult: false
Log:
{
"eventVersion": "1.05",
"userIdentity": {
"type": "AssumedRole",
"principalId": "tester",
"arn": "arn:aws:sts::123456789012:assumed-role/tester",
"accountId": "123456789012",
"accessKeyId": "1",
"sessionContext": {
"sessionIssuer": {
"type": "Role",
"principalId": "1111",
"arn": "arn:aws:iam::123456789012:role/tester",
"accountId": "123456789012",
"userName": "Tester"
},
"webIdFederationData": {},
"attributes": {
"mfaAuthenticated": "true",
"creationDate": "2019-01-01T00:00:00Z"
}
}
},
"eventTime": "2019-01-01T00:00:00Z",
"eventSource": "lambda.amazonaws.com",
"eventName": "UpdateFunctionCode20150331v2",
"awsRegion": "us-west-2",
"sourceIPAddress": "111.111.111.111",
"userAgent": "console.amazonaws.com",
"requestParameters": {
"functionName": "my-lambda-function"
},
"responseElements": {
"functionName": "my-lambda-function"
},
"requestID": "1",
"eventID": "1",
"readOnly": false,
"eventType": "AwsApiCall",
"recipientAccountId": "123456789012",
"p_log_type": "AWS.CloudTrail"
}
- Name: Delete Function Event
ExpectedResult: false
Log:
{
"eventVersion": "1.05",
"userIdentity": {
"type": "AssumedRole",
"principalId": "tester",
"arn": "arn:aws:sts::123456789012:assumed-role/tester",
"accountId": "123456789012",
"accessKeyId": "1",
"sessionContext": {
"sessionIssuer": {
"type": "Role",
"principalId": "1111",
"arn": "arn:aws:iam::123456789012:role/tester",
"accountId": "123456789012",
"userName": "Tester"
},
"webIdFederationData": {},
"attributes": {
"mfaAuthenticated": "true",
"creationDate": "2019-01-01T00:00:00Z"
}
}
},
"eventTime": "2019-01-01T00:00:00Z",
"eventSource": "lambda.amazonaws.com",
"eventName": "DeleteFunction",
"awsRegion": "us-west-2",
"sourceIPAddress": "111.111.111.111",
"userAgent": "console.amazonaws.com",
"requestParameters": {
"functionName": "my-lambda-function"
},
"responseElements": {
"functionName": "my-lambda-function"
},
"requestID": "1",
"eventID": "1",
"readOnly": false,
"eventType": "AwsApiCall",
"recipientAccountId": "123456789012",
"p_log_type": "AWS.CloudTrail"
}
Detection logic
Condition
not (errorCode is_not_null or errorMessage is_not_null)
eventSource eq "lambda.amazonaws.com"
eventName starts_with "UpdateFunctionCode"
userIdentity.type in ["IAMUser", "FederatedUser"] or (userIdentity.type eq "AssumedRole" and userIdentity.sessionContext.sessionIssuer.userName starts_with "AWSReservedSSO_")
Exclusions
Top-level NOT(...) conjuncts: predicates this rule actively suppresses.
| Field | Kind | Excluded values |
|---|---|---|
errorCode | is_not_null | |
errorMessage | is_not_null |
Indicators
Each row is a field, operator, and value that the rule matches. The corpus column counts how many other rules in the catalog look for the same combination: high numbers point to widely-used, community-vetted indicators. Blank or 1 shows that the indicator is specific to this rule.
| Field | Kind | Values |
|---|---|---|
eventName | starts_with |
|
eventSource | eq |
|
userIdentity.sessionContext.sessionIssuer.userName | starts_with |
|
userIdentity.type | eq |
|
userIdentity.type | in |
|
Output fields
Fields the rule emits when it matches. Chronicle authors list these in the outcome block; they appear on the detection and $risk_score drives alerting. Sentinel / Defender XDR rules build them up through project / summarize / extend stages. Sentinel maps these into alert fields via entityMappings and customDetails; Defender XDR custom detections surface them as alert fields directly.
| Field | Source |
|---|---|
eventName | |
eventSource | |
awsRegion | |
recipientAccountId | |
sourceIPAddress | |
userAgent | |
userIdentity | |
actor_user | |
functionName | responseElements.functionName |