cloud security

Detecting AWS GuardDuty Findings Automation

Automate AWS GuardDuty threat detection findings processing using EventBridge and Lambda to enable real-time incident response, automatic quarantine of compromised resources, and security notification workflows.

automationawseventbridgeguarddutyincident-responselambdasiemthreat-detection
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

Overview

Amazon GuardDuty is a threat detection service that continuously monitors AWS accounts for malicious activity and unauthorized behavior. By integrating GuardDuty with Amazon EventBridge and AWS Lambda, security teams achieve automated, real-time responses to threats, reducing mean time to response (MTTR) from hours to seconds. GuardDuty analyzes VPC Flow Logs, CloudTrail management and data events, DNS logs, EKS audit logs, and S3 data events.

When to Use

  • When investigating security incidents that require detecting aws guardduty findings automation
  • When building detection rules or threat hunting queries for this domain
  • When SOC analysts need structured procedures for this analysis type
  • When validating security monitoring coverage for related attack techniques

Prerequisites

  • AWS account with GuardDuty enabled
  • IAM roles for Lambda execution
  • EventBridge configured for GuardDuty events
  • SNS topic for security notifications
  • Security Hub integration (recommended)

Enable GuardDuty

# Enable GuardDuty
aws guardduty create-detector --enable --finding-publishing-frequency FIFTEEN_MINUTES
 
# Enable additional data sources
aws guardduty update-detector \
  --detector-id DETECTOR_ID \
  --data-sources '{
    "S3Logs": {"Enable": true},
    "Kubernetes": {"AuditLogs": {"Enable": true}},
    "MalwareProtection": {"ScanEc2InstanceWithFindings": {"EbsVolumes": true}},
    "RuntimeMonitoring": {"Enable": true}
  }'

EventBridge Rule Configuration

Rule for high-severity findings

{
  "source": ["aws.guardduty"],
  "detail-type": ["GuardDuty Finding"],
  "detail": {
    "severity": [{"numeric": [">=", 7.0]}]
  }
}

Create EventBridge rule via CLI

aws events put-rule \
  --name "guardduty-high-severity" \
  --event-pattern '{
    "source": ["aws.guardduty"],
    "detail-type": ["GuardDuty Finding"],
    "detail": {
      "severity": [{"numeric": [">=", 7.0]}]
    }
  }'
 
aws events put-targets \
  --rule "guardduty-high-severity" \
  --targets "Id"="lambda-handler","Arn"="arn:aws:lambda:us-east-1:123456789012:function:guardduty-response"

Lambda Automated Response Functions

EC2 Instance Isolation

import boto3
import json
import os
 
ec2 = boto3.client('ec2')
sns = boto3.client('sns')
 
QUARANTINE_SG = os.environ.get('QUARANTINE_SECURITY_GROUP')
SNS_TOPIC = os.environ.get('SNS_TOPIC_ARN')
 
def lambda_handler(event, context):
    finding = event['detail']
    finding_type = finding['type']
    severity = finding['severity']
    account_id = finding['accountId']
    region = finding['region']
 
    # Extract resource information
    resource = finding.get('resource', {})
    resource_type = resource.get('resourceType', '')
 
    if resource_type == 'Instance':
        instance_id = resource['instanceDetails']['instanceId']
        instance_tags = {t['key']: t['value']
                        for t in resource['instanceDetails'].get('tags', [])}
 
        # Skip if already quarantined
        if instance_tags.get('SecurityStatus') == 'Quarantined':
            return {'statusCode': 200, 'body': 'Already quarantined'}
 
        # Get current security groups for forensics
        instance = ec2.describe_instances(InstanceIds=[instance_id])
        current_sgs = [sg['GroupId'] for sg in
                       instance['Reservations'][0]['Instances'][0]['SecurityGroups']]
 
        # Tag instance with finding info and original SGs
        ec2.create_tags(
            Resources=[instance_id],
            Tags=[
                {'Key': 'SecurityStatus', 'Value': 'Quarantined'},
                {'Key': 'GuardDutyFinding', 'Value': finding_type},
                {'Key': 'OriginalSecurityGroups', 'Value': ','.join(current_sgs)},
                {'Key': 'QuarantineTime', 'Value': finding['updatedAt']}
            ]
        )
 
        # Move to quarantine security group (blocks all traffic)
        if QUARANTINE_SG:
            ec2.modify_instance_attribute(
                InstanceId=instance_id,
                Groups=[QUARANTINE_SG]
            )
 
        # Create EBS snapshots for forensics
        volumes = ec2.describe_volumes(
            Filters=[{'Name': 'attachment.instance-id', 'Values': [instance_id]}]
        )
        for vol in volumes['Volumes']:
            ec2.create_snapshot(
                VolumeId=vol['VolumeId'],
                Description=f'GuardDuty forensic snapshot - {finding_type}',
                TagSpecifications=[{
                    'ResourceType': 'snapshot',
                    'Tags': [
                        {'Key': 'Purpose', 'Value': 'ForensicCapture'},
                        {'Key': 'SourceInstance', 'Value': instance_id},
                        {'Key': 'FindingType', 'Value': finding_type}
                    ]
                }]
            )
 
        # Notify security team
        sns.publish(
            TopicArn=SNS_TOPIC,
            Subject=f'[GuardDuty] {finding_type} - Instance {instance_id} Quarantined',
            Message=json.dumps({
                'action': 'instance_quarantined',
                'instance_id': instance_id,
                'finding_type': finding_type,
                'severity': severity,
                'account': account_id,
                'region': region,
                'original_security_groups': current_sgs,
                'description': finding.get('description', '')
            }, indent=2)
        )
 
        return {
            'statusCode': 200,
            'body': f'Instance {instance_id} quarantined and snapshots created'
        }
 
    return {'statusCode': 200, 'body': 'Non-EC2 finding processed'}

IAM Credential Compromise Response

import boto3
import json
import os
 
iam = boto3.client('iam')
sns = boto3.client('sns')
 
SNS_TOPIC = os.environ.get('SNS_TOPIC_ARN')
 
def lambda_handler(event, context):
    finding = event['detail']
    finding_type = finding['type']
 
    if 'IAMUser' not in finding_type and 'UnauthorizedAccess' not in finding_type:
        return {'statusCode': 200, 'body': 'Not an IAM finding'}
 
    resource = finding.get('resource', {})
    access_key_details = resource.get('accessKeyDetails', {})
    user_name = access_key_details.get('userName', '')
    access_key_id = access_key_details.get('accessKeyId', '')
 
    if not user_name:
        return {'statusCode': 200, 'body': 'No user identified'}
 
    actions_taken = []
 
    # Deactivate the compromised access key
    if access_key_id and access_key_id != 'GeneratedFindingAccessKeyId':
        try:
            iam.update_access_key(
                UserName=user_name,
                AccessKeyId=access_key_id,
                Status='Inactive'
            )
            actions_taken.append(f'Deactivated access key {access_key_id}')
        except Exception as e:
            actions_taken.append(f'Failed to deactivate key: {str(e)}')
 
    # Attach deny-all policy to user
    deny_policy = {
        "Version": "2012-10-17",
        "Statement": [{
            "Effect": "Deny",
            "Action": "*",
            "Resource": "*"
        }]
    }
 
    try:
        iam.put_user_policy(
            UserName=user_name,
            PolicyName='GuardDuty-DenyAll-Quarantine',
            PolicyDocument=json.dumps(deny_policy)
        )
        actions_taken.append(f'Applied deny-all policy to {user_name}')
    except Exception as e:
        actions_taken.append(f'Failed to apply deny policy: {str(e)}')
 
    # Notify
    sns.publish(
        TopicArn=SNS_TOPIC,
        Subject=f'[GuardDuty] IAM Compromise - {user_name}',
        Message=json.dumps({
            'finding_type': finding_type,
            'user': user_name,
            'access_key': access_key_id,
            'actions_taken': actions_taken,
            'severity': finding['severity']
        }, indent=2)
    )
 
    return {'statusCode': 200, 'body': json.dumps(actions_taken)}

Terraform Deployment

resource "aws_guardduty_detector" "main" {
  enable = true
  finding_publishing_frequency = "FIFTEEN_MINUTES"
 
  datasources {
    s3_logs { enable = true }
    kubernetes { audit_logs { enable = true } }
    malware_protection {
      scan_ec2_instance_with_findings {
        ebs_volumes { enable = true }
      }
    }
  }
}
 
resource "aws_cloudwatch_event_rule" "guardduty_high" {
  name        = "guardduty-high-severity"
  description = "GuardDuty high severity findings"
 
  event_pattern = jsonencode({
    source      = ["aws.guardduty"]
    detail-type = ["GuardDuty Finding"]
    detail = {
      severity = [{ numeric = [">=", 7.0] }]
    }
  })
}
 
resource "aws_cloudwatch_event_target" "lambda" {
  rule = aws_cloudwatch_event_rule.guardduty_high.name
  arn  = aws_lambda_function.guardduty_response.arn
}

Finding Categories

Category Severity Range Examples
Backdoor 5.0 - 8.0 Backdoor:EC2/C&CActivity
CryptoCurrency 5.0 - 8.0 CryptoCurrency:EC2/BitcoinTool
Trojan 5.0 - 8.0 Trojan:EC2/BlackholeTraffic
UnauthorizedAccess 5.0 - 8.0 UnauthorizedAccess:IAMUser/ConsoleLogin
Recon 2.0 - 5.0 Recon:EC2/PortProbeUnprotected
Persistence 5.0 - 8.0 Persistence:IAMUser/AnomalousBehavior

Multi-Account Setup

# Designate GuardDuty administrator
aws guardduty enable-organization-admin-account \
  --admin-account-id 111111111111
 
# Auto-enable for new accounts
aws guardduty update-organization-configuration \
  --detector-id DETECTOR_ID \
  --auto-enable

References

Source materials

References and resources

Everything below is rendered for inspection. Script files are read-only and never run.

References 3

api-reference.md2.4 KB

AWS GuardDuty Findings Automation — API Reference

Libraries

Library Install Purpose
boto3 pip install boto3 AWS SDK for GuardDuty API

Key boto3 GuardDuty Methods

Method Description
list_detectors() List GuardDuty detector IDs
get_detector(DetectorId=) Get detector configuration
list_findings(DetectorId=, FindingCriteria=) Query finding IDs
get_findings(DetectorId=, FindingIds=[]) Get finding details
get_findings_statistics(DetectorId=) Finding counts by severity
archive_findings(DetectorId=, FindingIds=[]) Archive processed findings
list_members(DetectorId=) List member accounts (multi-account)
create_sample_findings(DetectorId=) Generate sample findings for testing

Finding Severity Levels

Range Level Description
7.0-8.9 High Compromised resource, active threat
4.0-6.9 Medium Suspicious activity, potential threat
1.0-3.9 Low Attempted suspicious activity

GuardDuty Finding Types

Type Prefix Category
Recon: Reconnaissance activity
UnauthorizedAccess: Unauthorized access attempt
CryptoCurrency: Crypto mining activity
Trojan: Malware communication
Stealth: Logging/monitoring evasion
Policy: Policy violation
Persistence: Persistence mechanism

GuardDuty Protection Features

Feature Description
S3_DATA_EVENTS S3 data plane monitoring
EKS_AUDIT_LOGS EKS control plane monitoring
EBS_MALWARE_PROTECTION EBS volume malware scanning
RDS_LOGIN_EVENTS RDS login activity monitoring
LAMBDA_NETWORK_LOGS Lambda function network monitoring
RUNTIME_MONITORING EC2/ECS/EKS runtime threat detection

FindingCriteria Filter

criteria = {
    "Criterion": {
        "severity": {"Gte": 7.0},
        "service.archived": {"Eq": ["false"]},
        "type": {"Eq": ["UnauthorizedAccess:EC2/SSHBruteForce"]},
    }
}

External References

standards.md0.7 KB

Standards - AWS GuardDuty Findings Automation

MITRE ATT&CK Mapping

  • TA0001 Initial Access: UnauthorizedAccess findings
  • TA0003 Persistence: Persistence:IAMUser findings
  • TA0005 Defense Evasion: Stealth findings
  • TA0006 Credential Access: CredentialAccess findings
  • TA0010 Exfiltration: Exfiltration findings
  • TA0040 Impact: CryptoCurrency/Trojan findings

NIST 800-53

  • IR-4: Incident Handling
  • IR-5: Incident Monitoring
  • SI-4: System Monitoring
  • AU-6: Audit Record Review

AWS Security Best Practices

  • Enable GuardDuty in all regions
  • Configure multi-account with Organizations
  • Publish findings every 15 minutes
  • Integrate with Security Hub
workflows.md0.7 KB

Workflows - AWS GuardDuty Findings Automation

Automated Response Workflow

1. GuardDuty detects threat → Generates finding
2. EventBridge receives finding event
3. EventBridge routes to Lambda based on severity/type
4. Lambda executes automated response:
   - EC2: Quarantine instance, snapshot volumes
   - IAM: Deactivate keys, apply deny policy
   - S3: Block public access, enable versioning
5. SNS notifies security team
6. Finding synced to Security Hub
7. Analyst reviews and confirms actions

Triage Workflow

1. HIGH (7-8.9): Immediate auto-response + page on-call
2. MEDIUM (4-6.9): Auto-notify + queue for review
3. LOW (1-3.9): Log and batch review weekly

Scripts 2

agent.py6.9 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""AWS GuardDuty findings automation and detection agent."""

import json
import sys
import argparse
from datetime import datetime

try:
    import boto3
except ImportError:
    print("Install: pip install boto3")
    sys.exit(1)


def get_guardduty_detector(session):
    """Get the active GuardDuty detector ID."""
    gd = session.client("guardduty")
    detectors = gd.list_detectors()["DetectorIds"]
    if not detectors:
        return None
    return detectors[0]


def list_findings(session, detector_id, severity_min=4.0, max_results=50):
    """List GuardDuty findings filtered by severity."""
    gd = session.client("guardduty")
    criteria = {
        "Criterion": {
            "severity": {"Gte": severity_min},
            "service.archived": {"Eq": ["false"]},
        }
    }
    finding_ids = gd.list_findings(
        DetectorId=detector_id,
        FindingCriteria=criteria,
        SortCriteria={"AttributeName": "severity", "OrderBy": "DESC"},
        MaxResults=max_results,
    )["FindingIds"]

    if not finding_ids:
        return []

    details = gd.get_findings(
        DetectorId=detector_id, FindingIds=finding_ids
    )["Findings"]

    results = []
    for f in details:
        results.append({
            "id": f.get("Id", ""),
            "type": f.get("Type", ""),
            "severity": f.get("Severity", 0),
            "title": f.get("Title", ""),
            "description": f.get("Description", "")[:200],
            "resource_type": f.get("Resource", {}).get("ResourceType", ""),
            "region": f.get("Region", ""),
            "first_seen": f.get("Service", {}).get("EventFirstSeen", ""),
            "last_seen": f.get("Service", {}).get("EventLastSeen", ""),
            "count": f.get("Service", {}).get("Count", 0),
            "action_type": f.get("Service", {}).get("Action", {}).get("ActionType", ""),
        })
    return results


def get_finding_statistics(session, detector_id):
    """Get finding count grouped by type and severity."""
    gd = session.client("guardduty")
    stats = gd.get_findings_statistics(
        DetectorId=detector_id,
        FindingStatisticTypes=["COUNT_BY_SEVERITY"],
    )
    return stats.get("FindingStatistics", {})


def check_detector_configuration(session, detector_id):
    """Audit GuardDuty detector configuration for coverage gaps."""
    gd = session.client("guardduty")
    findings = []

    detector = gd.get_detector(DetectorId=detector_id)
    status = detector.get("Status", "")
    if status != "ENABLED":
        findings.append({
            "check": "Detector status",
            "status": status,
            "severity": "CRITICAL",
            "issue": "GuardDuty detector is not enabled",
        })

    features = detector.get("Features", [])
    expected_features = {
        "S3_DATA_EVENTS", "EKS_AUDIT_LOGS", "EBS_MALWARE_PROTECTION",
        "RDS_LOGIN_EVENTS", "LAMBDA_NETWORK_LOGS", "RUNTIME_MONITORING",
    }
    enabled_features = set()
    for feat in features:
        if feat.get("Status") == "ENABLED":
            enabled_features.add(feat.get("Name", ""))

    missing = expected_features - enabled_features
    for m in missing:
        findings.append({
            "check": f"Feature: {m}",
            "status": "DISABLED",
            "severity": "HIGH",
            "issue": f"GuardDuty feature {m} not enabled — reduced detection coverage",
        })

    return findings


def check_member_accounts(session, detector_id):
    """Check GuardDuty member account enrollment in multi-account setup."""
    gd = session.client("guardduty")
    members = gd.list_members(
        DetectorId=detector_id, OnlyAssociated="true"
    ).get("Members", [])

    results = []
    for m in members:
        status = m.get("RelationshipStatus", "")
        results.append({
            "account_id": m.get("AccountId", ""),
            "email": m.get("Email", ""),
            "status": status,
            "detector_id": m.get("DetectorId", ""),
        })
        if status != "Enabled":
            results[-1]["finding"] = f"Member account not fully enabled: {status}"
            results[-1]["severity"] = "HIGH"
    return results


def auto_archive_low_severity(session, detector_id, threshold=2.0):
    """Auto-archive findings below severity threshold."""
    gd = session.client("guardduty")
    criteria = {
        "Criterion": {
            "severity": {"Lt": threshold},
            "service.archived": {"Eq": ["false"]},
        }
    }
    finding_ids = gd.list_findings(
        DetectorId=detector_id,
        FindingCriteria=criteria,
        MaxResults=100,
    )["FindingIds"]

    if finding_ids:
        gd.archive_findings(DetectorId=detector_id, FindingIds=finding_ids)
    return {"archived_count": len(finding_ids)}


def run_audit(args):
    """Execute GuardDuty findings automation audit."""
    print(f"\n{'='*60}")
    print(f"  AWS GUARDDUTY FINDINGS AUTOMATION")
    print(f"  Generated: {datetime.utcnow().isoformat()} UTC")
    print(f"{'='*60}\n")

    session = boto3.Session(profile_name=args.profile, region_name=args.region)
    report = {}

    detector_id = get_guardduty_detector(session)
    if not detector_id:
        print("ERROR: No GuardDuty detector found")
        return {"error": "No detector found"}

    report["detector_id"] = detector_id
    print(f"Detector ID: {detector_id}\n")

    config_findings = check_detector_configuration(session, detector_id)
    report["configuration_findings"] = config_findings
    print(f"--- CONFIGURATION AUDIT ({len(config_findings)} findings) ---")
    for f in config_findings:
        print(f"  [{f['severity']}] {f['check']}: {f['issue']}")

    findings = list_findings(session, detector_id, args.min_severity)
    report["active_findings"] = len(findings)
    print(f"\n--- ACTIVE FINDINGS ({len(findings)}, severity >= {args.min_severity}) ---")
    for f in findings[:15]:
        print(f"  [{f['severity']:.1f}] {f['type']}")
        print(f"         {f['title'][:80]}")

    members = check_member_accounts(session, detector_id)
    report["member_accounts"] = members
    print(f"\n--- MEMBER ACCOUNTS ({len(members)}) ---")
    for m in members:
        status_icon = "OK" if m["status"] == "Enabled" else "WARN"
        print(f"  [{status_icon}] {m['account_id']}: {m['status']}")

    return report


def main():
    parser = argparse.ArgumentParser(description="GuardDuty Findings Automation Agent")
    parser.add_argument("--profile", default=None, help="AWS profile name")
    parser.add_argument("--region", default="us-east-1", help="AWS region")
    parser.add_argument("--min-severity", type=float, default=4.0,
                        help="Minimum severity threshold (default: 4.0)")
    parser.add_argument("--output", help="Save report to JSON file")
    args = parser.parse_args()

    report = run_audit(args)
    if args.output:
        with open(args.output, "w") as f:
            json.dump(report, f, indent=2, default=str)
        print(f"\n[+] Report saved to {args.output}")


if __name__ == "__main__":
    main()
process.py4.0 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""
AWS GuardDuty Findings Management Script

Lists, analyzes, and exports GuardDuty findings for security operations.
"""

import boto3
import json
import sys
from datetime import datetime
from collections import Counter


def get_detector_id(session):
    """Get the GuardDuty detector ID for the current region."""
    client = session.client('guardduty')
    response = client.list_detectors()
    detectors = response.get('DetectorIds', [])
    if not detectors:
        print("[!] No GuardDuty detector found. Enable GuardDuty first.")
        return None
    return detectors[0]


def list_findings(session, severity_min=0, max_results=50):
    """List GuardDuty findings filtered by severity."""
    client = session.client('guardduty')
    detector_id = get_detector_id(session)
    if not detector_id:
        return []

    criteria = {}
    if severity_min > 0:
        criteria['severity'] = {'Gte': severity_min}

    response = client.list_findings(
        DetectorId=detector_id,
        FindingCriteria={'Criterion': criteria} if criteria else {},
        SortCriteria={'AttributeName': 'severity', 'OrderBy': 'DESC'},
        MaxResults=max_results
    )

    finding_ids = response.get('FindingIds', [])
    if not finding_ids:
        print("[+] No findings found")
        return []

    details = client.get_findings(
        DetectorId=detector_id,
        FindingIds=finding_ids
    )

    findings = details.get('Findings', [])
    print(f"[+] Retrieved {len(findings)} findings\n")

    for f in findings:
        sev = f['Severity']
        sev_label = 'HIGH' if sev >= 7 else 'MEDIUM' if sev >= 4 else 'LOW'
        print(f"  [{sev_label} {sev}] {f['Type']}")
        print(f"    Account: {f['AccountId']} | Region: {f['Region']}")
        print(f"    Description: {f.get('Description', 'N/A')[:100]}")
        print()

    return findings


def get_findings_statistics(session):
    """Get statistical summary of GuardDuty findings."""
    client = session.client('guardduty')
    detector_id = get_detector_id(session)
    if not detector_id:
        return

    response = client.get_findings_statistics(
        DetectorId=detector_id,
        FindingStatisticTypes=['COUNT_BY_SEVERITY']
    )

    stats = response.get('FindingStatistics', {})
    severity_counts = stats.get('CountBySeverity', {})

    print("[+] GuardDuty Findings Statistics:")
    for severity, count in sorted(severity_counts.items(), reverse=True):
        print(f"  Severity {severity}: {count} findings")


def analyze_findings(findings):
    """Analyze findings for patterns and top threats."""
    if not findings:
        return

    type_counter = Counter(f['Type'] for f in findings)
    severity_counter = Counter(
        'HIGH' if f['Severity'] >= 7 else 'MEDIUM' if f['Severity'] >= 4 else 'LOW'
        for f in findings
    )

    print("\n[+] Finding Type Distribution:")
    for finding_type, count in type_counter.most_common(10):
        print(f"  {count:3d} - {finding_type}")

    print("\n[+] Severity Distribution:")
    for level in ['HIGH', 'MEDIUM', 'LOW']:
        print(f"  {level}: {severity_counter.get(level, 0)}")


if __name__ == "__main__":
    import argparse
    parser = argparse.ArgumentParser(description="GuardDuty Findings Manager")
    parser.add_argument("--list", action="store_true", help="List findings")
    parser.add_argument("--stats", action="store_true", help="Show statistics")
    parser.add_argument("--severity", type=float, default=0, help="Minimum severity filter")
    parser.add_argument("--max-results", type=int, default=50)
    parser.add_argument("--region", default="us-east-1")
    parser.add_argument("--profile", type=str)
    args = parser.parse_args()

    kwargs = {"region_name": args.region}
    if args.profile:
        kwargs["profile_name"] = args.profile
    session = boto3.Session(**kwargs)

    if args.list:
        findings = list_findings(session, args.severity, args.max_results)
        analyze_findings(findings)
    if args.stats:
        get_findings_statistics(session)

Assets 1

template.mdtext/markdown · 0.8 KB
Keep exploring