AWS IAM User Self-Created Access Key Subsequently Used

edit
IMPORTANT: This documentation is no longer updated. Refer to Elastic's version policy and the latest documentation.

AWS IAM User Self-Created Access Key Subsequently Used

edit

Detects an AWS IAM user using an existing credential to create a new access key for itself and subsequently using the new key within one hour. This behavior can indicate an adversary converting compromised credentials into an additional long-term credential for persistence. Unlike a standalone self-service key creation alert, requiring subsequent use of the new key reduces noise from unused or abandoned credential-rotation operations.

Rule type: esql

Rule indices: None

Severity: medium

Risk score: 47

Runs every: 5m

Searches indices from: now-35m (Date Math format, see also Additional look-back time)

Maximum alerts per execution: 100

References:

Tags:

  • Domain: Cloud
  • Data Source: AWS
  • Data Source: Amazon Web Services
  • Data Source: AWS IAM
  • Use Case: Identity and Access Audit
  • Tactic: Persistence
  • Resources: Investigation Guide

Version: 1

Rule authors:

  • Elastic

Rule license: Elastic License v2

Investigation guide

edit

Triage and analysis

Investigating AWS IAM User Self-Created Access Key Subsequently Used

This rule detects an IAM user using an existing access key (Token A) to create a new access key for itself (Token B), followed by an AWS API request authenticated with Token B within one hour. The creation event proves that Token A was used to request the additional credential, while the subsequent CloudTrail event confirms that Token B became active.

This sequence can indicate persistence after an IAM user’s credentials are compromised. An adversary can use the compromised key to create another long-term credential, then continue accessing the account through the new key even after the original credential is identified and disabled.

Possible investigation steps

  • Identify the IAM user and AWS account using aws.cloudtrail.user_identity.arn and cloud.account.id.
  • Review Token A in Esql_priv.aws_cloudtrail_user_identity_access_key_id_creator_values. Determine whether it is an expected credential for the IAM user and whether it was previously observed from unusual infrastructure.
  • Confirm that the key in Esql_priv.aws_cloudtrail_response_elements_access_key_access_key_id_values matches the subsequently used key in Esql_priv.aws_cloudtrail_user_identity_access_key_id_created_key_use_values.
  • Compare Esql.source_ip_self_access_key_creation_values with Esql.source_ip_created_access_key_use_values. A rapid change in source IP, ASN, geography, or hosting provider increases suspicion.
  • Compare Esql.user_agent_original_self_access_key_creation_values with Esql.user_agent_original_created_access_key_use_values to identify changes between the creation and use clients.
  • Review Esql.event_action_created_access_key_use_values to determine what Token B accessed or modified after creation.
  • Examine nearby CloudTrail activity for IAM reconnaissance, MFA changes, login-profile changes, policy modifications, or attempts to create additional credentials.
  • Confirm whether the activity corresponds to an approved credential-rotation workflow and whether the user is permitted to create its own long-term access keys.

False positive analysis

  • Approved credential-rotation automation may create and immediately test or use a replacement access key.
  • Developer onboarding or recovery workflows may create a key and validate it with an initial AWS API request.
  • Validate the principal, source addresses, user agents, timing, and change records before excluding the activity. Prefer narrowly scoped exceptions for verified automation rather than excluding the IAM user broadly.

Response and remediation

  • If unauthorized, deactivate or delete Token B immediately.
  • Rotate or disable Token A and investigate how it was exposed or compromised.
  • Review every CloudTrail event authenticated with Token B and determine whether it accessed data, changed IAM permissions, created resources, or established additional persistence.
  • Review the IAM user’s policies and remove unnecessary iam:CreateAccessKey permissions.
  • Prefer temporary credentials and IAM Identity Center for human access instead of long-term IAM user access keys.
  • Preserve the relevant CloudTrail events, access key IDs, source addresses, and user-agent values for incident response.

Rule query

edit
FROM logs-aws.cloudtrail-*
| WHERE data_stream.dataset == "aws.cloudtrail"
    AND (
      (
        event.provider == "iam.amazonaws.com"
        AND event.action == "CreateAccessKey"
        AND event.outcome == "success"
        AND aws.cloudtrail.user_identity.type == "IAMUser"
      )
      OR (
        aws.cloudtrail.user_identity.type == "IAMUser"
        AND aws.cloudtrail.user_identity.access_key_id LIKE "AKIA*"
        AND NOT (
          event.provider == "iam.amazonaws.com"
          AND event.action == "CreateAccessKey"
        )
      )
    )
| GROK aws.cloudtrail.response_elements """.*accessKeyId=(?<Esql_priv.aws_cloudtrail_response_elements_access_key_access_key_id>AKIA[A-Z0-9]{16}).*"""
| EVAL
    Esql.aws_cloudtrail_self_access_key_creation_flag = CASE(
      event.provider == "iam.amazonaws.com"
        AND event.action == "CreateAccessKey"
        AND event.outcome == "success"
        AND aws.cloudtrail.user_identity.type == "IAMUser"
        AND (
          user.target.name IS NULL
          OR user.name == user.target.name
        )
        AND Esql_priv.aws_cloudtrail_response_elements_access_key_access_key_id IS NOT NULL,
      1,
      0
    ),
    Esql.aws_cloudtrail_created_access_key_use_flag = CASE(
      aws.cloudtrail.user_identity.type == "IAMUser"
        AND aws.cloudtrail.user_identity.access_key_id LIKE "AKIA*"
        AND NOT (
          event.provider == "iam.amazonaws.com"
          AND event.action == "CreateAccessKey"
        ),
      1,
      0
    )
| EVAL
    Esql_priv.aws_cloudtrail_user_identity_access_key_id_creator = CASE(
      Esql.aws_cloudtrail_self_access_key_creation_flag == 1,
      aws.cloudtrail.user_identity.access_key_id,
      null
    ),
    Esql_priv.aws_cloudtrail_access_key_id_correlation = CASE(
      Esql.aws_cloudtrail_self_access_key_creation_flag == 1,
      Esql_priv.aws_cloudtrail_response_elements_access_key_access_key_id,
      aws.cloudtrail.user_identity.access_key_id
    )
| WHERE Esql_priv.aws_cloudtrail_access_key_id_correlation IS NOT NULL
    AND aws.cloudtrail.user_identity.arn IS NOT NULL
| STATS
    Esql.aws_cloudtrail_self_access_key_creation_count =
      COUNT(*) WHERE Esql.aws_cloudtrail_self_access_key_creation_flag == 1,
    Esql.aws_cloudtrail_created_access_key_use_count =
      COUNT(*) WHERE Esql.aws_cloudtrail_created_access_key_use_flag == 1,
    Esql_priv.aws_cloudtrail_user_identity_access_key_id_creator_values =
      VALUES(Esql_priv.aws_cloudtrail_user_identity_access_key_id_creator)
      WHERE Esql.aws_cloudtrail_self_access_key_creation_flag == 1,
    Esql_priv.aws_cloudtrail_response_elements_access_key_access_key_id_values =
      VALUES(Esql_priv.aws_cloudtrail_response_elements_access_key_access_key_id)
      WHERE Esql.aws_cloudtrail_self_access_key_creation_flag == 1,
    Esql_priv.aws_cloudtrail_user_identity_access_key_id_created_key_use_values =
      VALUES(aws.cloudtrail.user_identity.access_key_id)
      WHERE Esql.aws_cloudtrail_created_access_key_use_flag == 1,
    Esql.timestamp_self_access_key_creation_min =
      MIN(@timestamp)
      WHERE Esql.aws_cloudtrail_self_access_key_creation_flag == 1,
    Esql.timestamp_created_access_key_use_min =
      MIN(@timestamp)
      WHERE Esql.aws_cloudtrail_created_access_key_use_flag == 1,
    Esql.source_ip_self_access_key_creation_values =
      VALUES(source.ip)
      WHERE Esql.aws_cloudtrail_self_access_key_creation_flag == 1,
    Esql.source_ip_created_access_key_use_values =
      VALUES(source.ip)
      WHERE Esql.aws_cloudtrail_created_access_key_use_flag == 1,
    Esql.user_agent_original_self_access_key_creation_values =
      VALUES(user_agent.original)
      WHERE Esql.aws_cloudtrail_self_access_key_creation_flag == 1,
    Esql.user_agent_original_created_access_key_use_values =
      VALUES(user_agent.original)
      WHERE Esql.aws_cloudtrail_created_access_key_use_flag == 1,
    Esql.event_action_created_access_key_use_values =
      VALUES(event.action)
      WHERE Esql.aws_cloudtrail_created_access_key_use_flag == 1
  BY cloud.account.id,
     aws.cloudtrail.user_identity.arn,
     Esql_priv.aws_cloudtrail_access_key_id_correlation
| WHERE Esql.aws_cloudtrail_self_access_key_creation_count > 0
    AND Esql.aws_cloudtrail_created_access_key_use_count > 0
    AND Esql.timestamp_created_access_key_use_min >
        Esql.timestamp_self_access_key_creation_min
    AND DATE_DIFF(
      "seconds",
      Esql.timestamp_self_access_key_creation_min,
      Esql.timestamp_created_access_key_use_min
    ) <= 3600
| KEEP
    cloud.account.id,
    aws.cloudtrail.user_identity.arn,
    Esql.*,
    Esql_priv.*

Framework: MITRE ATT&CKTM