LoFP LoFP / highly distributed environments (e.g., globally deployed automation or edge nodes) may cause a single iam user to appear from multiple ips. review the geolocation, network context, and user agent patterns to rule out benign use.

Techniques

Sample rules

AWS Access Token Used from Multiple Addresses

Description

This rule identifies potentially suspicious activity by detecting instances where a single IAM user’s temporary session token is accessed from multiple IP addresses within a short time frame. Such behavior may suggest that an adversary has compromised temporary credentials and is utilizing them from various locations. To enhance detection accuracy and minimize false positives, the rule incorporates criteria that evaluate unique IP addresses, user agents, cities, and networks. These additional checks help distinguish between legitimate distributed access patterns and potential credential misuse. Detected activities are classified into different types based on the combination of unique indicators, with each classification assigned a fidelity score reflecting the likelihood of malicious behavior. High fidelity scores are given to patterns most indicative of threats, such as multiple unique IPs, networks, cities, and user agents. Medium and low fidelity scores correspond to less severe patterns, enabling security teams to effectively prioritize alerts.

Detection logic

FROM logs-aws.cloudtrail* metadata _id, _version, _index
| WHERE @timestamp > NOW() - 30 minutes
    // filter on CloudTrail logs for STS temporary session tokens used by IAM users

  AND event.dataset == "aws.cloudtrail"
  AND aws.cloudtrail.user_identity.arn IS NOT NULL
  AND aws.cloudtrail.user_identity.type == "IAMUser"
  AND source.ip IS NOT NULL
    
    // exclude known benign IaC tools and Amazon Network
  AND NOT (user_agent.original LIKE "%Terraform%" OR user_agent.original LIKE "%Ansible%" OR user_agent.original LIKE "%Pulumni%")
  AND `source.as.organization.name` != "AMAZON-AES"
    
    // exclude noisy service APIs less indicative of malicous behavior
  AND event.provider NOT IN ("health.amazonaws.com", "monitoring.amazonaws.com", "notifications.amazonaws.com", "ce.amazonaws.com", "cost-optimization-hub.amazonaws.com", "servicecatalog-appregistry.amazonaws.com", "securityhub.amazonaws.com")

| EVAL
  // create a time window for aggregation
    time_window = DATE_TRUNC(30 minutes, @timestamp),
  // capture necessary fields for detection and investigation  
    user_id = aws.cloudtrail.user_identity.arn,
    access_key_id = aws.cloudtrail.user_identity.access_key_id,
    ip = source.ip,
    user_agent = user_agent.original,
    ip_string = TO_STRING(source.ip),  // Convert IP to string
    ip_user_agent_pair = CONCAT(ip_string, " - ", user_agent.original),  // Combine IP and user agent
    ip_city_pair = CONCAT(ip_string, " - ", source.geo.city_name), // Combine IP and city
    city = source.geo.city_name,
    event_time = @timestamp,
    network_arn = `source.as.organization.name` 

| STATS
    event_actions = VALUES(event.action),
    event_providers = VALUES(event.provider),
    access_key_id = VALUES(access_key_id),
    user_id = VALUES(user_id), 
    ip_list = VALUES(ip),  // Collect list of IPs
    user_agent_list = VALUES(user_agent),  // Collect list of user agents
    ip_user_agent_pairs = VALUES(ip_user_agent_pair),  // Collect list of IP - user agent pairs
    cities_list = VALUES(city), // Collect list of cities
    ip_city_pairs = VALUES(ip_city_pair), // Collect list of IP - city pairs
    networks_list = VALUES(network_arn), // Collect list of networks
    unique_ips = COUNT_DISTINCT(ip),
    unique_user_agents = COUNT_DISTINCT(user_agent),
    unique_cities = COUNT_DISTINCT(city),
    unique_networks = COUNT_DISTINCT(network_arn),
    first_seen = MIN(event_time),
    last_seen = MAX(event_time),
    total_events = COUNT()
  BY time_window, access_key_id 

| EVAL
 //   activity type based on combinations of detection criteria 
    activity_type = CASE(
        unique_ips >= 2 AND unique_networks >= 2 AND unique_cities >= 2 AND unique_user_agents >= 2, "multiple_ip_network_city_user_agent", // high severity
        unique_ips >= 2 AND unique_networks >= 2 AND unique_cities >= 2, "multiple_ip_network_city", // high severity
        unique_ips >= 2 AND unique_cities >= 2, "multiple_ip_and_city", // medium severity
        unique_ips >= 2 AND unique_networks >= 2, "multiple_ip_and_network", // medium severity
        unique_ips >= 2 AND unique_user_agents >= 2, "multiple_ip_and_user_agent", // low severity
        "normal_activity"
    ),
 // likelihood of malicious activity based on activity type
    fidelity_score = CASE(
        activity_type == "multiple_ip_network_city_user_agent", "high",
        activity_type == "multiple_ip_network_city", "high",
        activity_type == "multiple_ip_and_city", "medium",
        activity_type == "multiple_ip_and_network", "medium",
        activity_type == "multiple_ip_and_user_agent", "low"
    )

| KEEP
    time_window, activity_type, fidelity_score, total_events, first_seen, last_seen,
    user_id, access_key_id, event_actions, event_providers, ip_list, user_agent_list, ip_user_agent_pairs, cities_list, ip_city_pairs, networks_list, unique_ips, unique_user_agents, unique_cities, unique_networks

| WHERE activity_type != "normal_activity"