Lambda Code Updated by User


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.

Query · python

from panther_aws_helpers import aws_cloudtrail_success, aws_rule_context


def rule(event):
    if not (
        aws_cloudtrail_success(event)
        and event.get("eventSource") == "lambda.amazonaws.com"
        and event.get("eventName").startswith("UpdateFunctionCode")
    ):
        return False

    identity_type = event.deep_get("userIdentity", "type", default="")
    if identity_type in ("IAMUser", "FederatedUser"):
        return True
    if identity_type == "AssumedRole":
        role_name = event.deep_get(
            "userIdentity", "sessionContext", "sessionIssuer", "userName", default=""
        )
        return role_name.startswith("AWSReservedSSO_")
    return False


def title(event):
    lambda_name = event.deep_get(
        "responseElements", "functionName", default="LAMBDA_NAME_NOT_FOUND"
    )
    return (
        f"[AWS.CloudTrail] User [{event.udm('actor_user')}] "
        f"updated Lambda function code for [{lambda_name}]"
    )


def alert_context(event):
    context = aws_rule_context(event)
    context["identity_type"] = event.deep_get("userIdentity", "type")
    context["user_arn"] = event.deep_get("userIdentity", "arn")

    return context

Analyst notes

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

Raw source Lambda Code Updated by User · Panther Python
Esc
Published by panther-labs/panther-analysis ↗, licensed under Apache 2.0 ↗. Reproduced here unmodified.
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"
      }

# ------ paired body: aws_overwrite_lambda_code.py ------

from panther_aws_helpers import aws_cloudtrail_success, aws_rule_context


def rule(event):
    if not (
        aws_cloudtrail_success(event)
        and event.get("eventSource") == "lambda.amazonaws.com"
        and event.get("eventName").startswith("UpdateFunctionCode")
    ):
        return False

    identity_type = event.deep_get("userIdentity", "type", default="")
    if identity_type in ("IAMUser", "FederatedUser"):
        return True
    if identity_type == "AssumedRole":
        role_name = event.deep_get(
            "userIdentity", "sessionContext", "sessionIssuer", "userName", default=""
        )
        return role_name.startswith("AWSReservedSSO_")
    return False


def title(event):
    lambda_name = event.deep_get(
        "responseElements", "functionName", default="LAMBDA_NAME_NOT_FOUND"
    )
    return (
        f"[AWS.CloudTrail] User [{event.udm('actor_user')}] "
        f"updated Lambda function code for [{lambda_name}]"
    )


def alert_context(event):
    context = aws_rule_context(event)
    context["identity_type"] = event.deep_get("userIdentity", "type")
    context["user_arn"] = event.deep_get("userIdentity", "arn")

    return context

Detection rules belong to the projects that publish them and remain under their own licenses. This site indexes and links to them; it claims no rights in them.