AWS EC2 Stop, Start, and User Data Modification Correlation

Identifies a short sequence of EC2 management APIs against the same instance that is consistent with modifying instance user data and forcing it to run on the next boot: ModifyInstanceAttribute with user data, followed by stop and start. Adversaries may update userData and cycle instance state so malicious scripts execute as root on Linux or as the system context on Windows. This rule correlates successful StopInstances, StartInstances, and ModifyInstanceAttribute events that reference userData within a five-minute window, grouped by instance, user.name, account, source IP, and user agent. A hit requires exactly three distinct API names in that bucket.

Elastic rule (View on GitHub)

  1[metadata]
  2creation_date = "2026/04/03"
  3integration = ["aws"]
  4maturity = "production"
  5updated_date = "2026/04/03"
  6
  7[rule]
  8author = ["Elastic"]
  9description = """
 10Identifies a short sequence of EC2 management APIs against the same instance that is consistent with modifying instance
 11user data and forcing it to run on the next boot: `ModifyInstanceAttribute` with user data, followed by stop and start.
 12Adversaries may update `userData` and cycle instance state so malicious scripts execute as root on Linux or as the
 13system context on Windows. This rule correlates successful `StopInstances`, `StartInstances`, and
 14`ModifyInstanceAttribute` events that reference `userData` within a five-minute window, grouped by instance,
 15`user.name`, account, source IP, and user agent. A hit requires exactly three distinct API names in that bucket.
 16"""
 17false_positives = [
 18    """
 19    Legitimate automation or administrators may change user data and restart instances during maintenance, image
 20    baking, or configuration fixes. Review the caller identity, change tickets, and whether `user_agent.original` and
 21    `source.ip` match known tooling and networks (the rule groups on both together with `user.name`).
 22    """,
 23]
 24from = "now-20m"
 25interval = "5m"
 26language = "esql"
 27license = "Elastic License v2"
 28name = "AWS EC2 Stop, Start, and User Data Modification Correlation"
 29note = """## Triage and analysis
 30
 31### Investigating AWS EC2 Stop, Start, and User Data Modification Correlation
 32
 33This detection aggregates successful EC2 `StopInstances`, `StartInstances`, and `ModifyInstanceAttribute` (with
 34`userData` in request parameters) over **five-minute** windows. Rows are keyed by **instance ID** (`Esql.instance_id`
 35from the grok on `aws.cloudtrail.request_parameters`), **`user.name`**, **`cloud.account.id`**, **`user_agent.original`**,
 36and **`source.ip`**. The rule fires only when **`Esql.event_action_unique_count` is 3**, meaning all three API names
 37appear in the same bucket—consistent with changing user data and cycling the instance to run it.
 38
 39The aggregated result does **not** include raw `request_parameters`; use the alert’s instance, account, user, IP, user
 40agent, and time bucket to query CloudTrail for the underlying events and payloads.
 41
 42#### Possible investigation steps
 43
 44- **Interpret the alert columns**: Review `Esql.event_action_values` to confirm the three actions are present (typically
 45  `ModifyInstanceAttribute`, `StopInstances`, `StartInstances`). Use `Esql.event_action_unique_count` to verify the
 46  rule logic (expect `3`).
 47- **Confirm the instance**: Use `Esql.instance_id` plus `cloud.account.id` in CMDB or AWS Resource Groups. Ensure the
 48  grok-derived ID matches the instance you expect (multi-instance API calls can affect extraction).
 49- **Identify the caller**: Tie `user.name` to an IAM user or role session name as shown in CloudTrail; for assumed roles,
 50  pivot in raw logs on `aws.cloudtrail.user_identity.arn` and session context in the same time window.
 51- **Validate client and origin**: Compare `user_agent.original` and `source.ip` to known admin workstations, bastions,
 52  or CI/CD egress. The rule intentionally groups by these fields so unrelated sessions do not merge into one bucket.
 53- **Recover user data context**: In CloudTrail (or the integration’s `aws.cloudtrail.request_parameters` on raw events),
 54  inspect the `ModifyInstanceAttribute` record for `userData` and whether values are base64 or placeholders.
 55- **Hunt for follow-on activity**: After the window, look for IAM changes, role assumption, or data access from the
 56  instance or the same principal.
 57
 58### False positive analysis
 59
 60- **Infrastructure as code**: Terraform, Ansible, and Pulumi user agents are excluded, but other automation may still
 61  match. Validate pipeline identity, change tickets, and whether stop/start is part of approved maintenance.
 62- **Break-glass or support workflows**: Some teams modify user data and restart instances during recovery; confirm with
 63  the workload owner.
 64- **Shared `user.name` or NAT**: If many callers share one identity or IP, bucketing may still separate sessions when IP
 65  or user agent differs; conversely, identical UA/IP across benign bulk operations can resemble this pattern—confirm
 66  intent.
 67
 68### Response and remediation
 69
 70- If unauthorized, isolate the instance, revoke or restrict the principal’s EC2 permissions, and rotate any credentials
 71  that may have been exposed in user data.
 72- Prefer Secrets Manager or Parameter Store over long-lived secrets in user data.
 73
 74### Additional information
 75
 76- [AWS EC2 User Data](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html)
 77- [ModifyInstanceAttribute](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_ModifyInstanceAttribute.html)
 78- [Local EC2 privilege escalation through user data](https://hackingthe.cloud/aws/exploitation/local_ec2_priv_esc_through_user_data)
 79"""
 80references = [
 81    "https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_ModifyInstanceAttribute.html",
 82    "https://hackingthe.cloud/aws/exploitation/local_ec2_priv_esc_through_user_data",
 83]
 84risk_score = 73
 85rule_id = "7e5c0e5a-95a5-404e-a5b0-278d35dc3325"
 86severity = "high"
 87tags = [
 88    "Domain: Cloud",
 89    "Data Source: AWS",
 90    "Data Source: Amazon Web Services",
 91    "Data Source: AWS EC2",
 92    "Data Source: AWS CloudTrail",
 93    "Use Case: Threat Detection",
 94    "Tactic: Execution",
 95    "Resources: Investigation Guide",
 96]
 97timestamp_override = "event.ingested"
 98type = "esql"
 99
100query = '''
101FROM logs-aws.cloudtrail-* 
102| WHERE event.provider == "ec2.amazonaws.com" 
103    and event.outcome == "success"
104    and aws.cloudtrail.user_identity.type != "AWSService"
105    and not (
106      user_agent.original like "*Terraform*"
107      or user_agent.original like "*Ansible*"
108      or user_agent.original like "*Pulumi*"
109    ) and not source.address in ("cloudformation.amazonaws.com", "servicecatalog.amazonaws.com")
110    and
111  (
112   event.action in ("StopInstances", "StartInstances") or 
113   (event.action == "ModifyInstanceAttribute" and aws.cloudtrail.request_parameters like "*userData=*")
114   )
115| grok aws.cloudtrail.request_parameters """instanceId=(?<Esql.instance_id>[^,}\]]+)"""
116| STATS Esql.event_action_unique_count = COUNT_DISTINCT(event.action), 
117        Esql.event_action_values = VALUES(event.action) by Esql.instance_id, user.name, cloud.account.id, Esql.time_bucket = DATE_TRUNC(5 minute, @timestamp) , user_agent.original, source.ip, source.as.organization.name, source.geo.country_name
118| where Esql.event_action_unique_count == 3
119| Keep Esql.*, user.name, cloud.account.id, user_agent.original, source.ip, source.as.organization.name, source.geo.country_name
120'''
121
122
123[[rule.threat]]
124framework = "MITRE ATT&CK"
125
126[[rule.threat.technique]]
127id = "T1059"
128name = "Command and Scripting Interpreter"
129reference = "https://attack.mitre.org/techniques/T1059/"
130
131[[rule.threat.technique.subtechnique]]
132id = "T1059.009"
133name = "Cloud API"
134reference = "https://attack.mitre.org/techniques/T1059/009/"
135
136[rule.threat.tactic]
137id = "TA0002"
138name = "Execution"
139reference = "https://attack.mitre.org/tactics/TA0002/"
140
141[[rule.threat]]
142framework = "MITRE ATT&CK"
143
144[[rule.threat.technique]]
145id = "T1578"
146name = "Modify Cloud Compute Infrastructure"
147reference = "https://attack.mitre.org/techniques/T1578/"
148
149[rule.threat.tactic]
150id = "TA0005"
151name = "Defense Evasion"
152reference = "https://attack.mitre.org/tactics/TA0005/"
153
154[rule.investigation_fields]
155field_names = [
156  "Esql.event_action_unique_count",
157  "Esql.event_action_values",
158  "Esql.instance_id",
159  "user.name",
160  "cloud.account.id",
161  "user_agent.original",
162  "source.ip",
163  "Esql.time_bucket",
164]

Triage and analysis

Investigating AWS EC2 Stop, Start, and User Data Modification Correlation

This detection aggregates successful EC2 StopInstances, StartInstances, and ModifyInstanceAttribute (with userData in request parameters) over five-minute windows. Rows are keyed by instance ID (Esql.instance_id from the grok on aws.cloudtrail.request_parameters), user.name, cloud.account.id, user_agent.original, and source.ip. The rule fires only when Esql.event_action_unique_count is 3, meaning all three API names appear in the same bucket—consistent with changing user data and cycling the instance to run it.

The aggregated result does not include raw request_parameters; use the alert’s instance, account, user, IP, user agent, and time bucket to query CloudTrail for the underlying events and payloads.

Possible investigation steps

  • Interpret the alert columns: Review Esql.event_action_values to confirm the three actions are present (typically ModifyInstanceAttribute, StopInstances, StartInstances). Use Esql.event_action_unique_count to verify the rule logic (expect 3).
  • Confirm the instance: Use Esql.instance_id plus cloud.account.id in CMDB or AWS Resource Groups. Ensure the grok-derived ID matches the instance you expect (multi-instance API calls can affect extraction).
  • Identify the caller: Tie user.name to an IAM user or role session name as shown in CloudTrail; for assumed roles, pivot in raw logs on aws.cloudtrail.user_identity.arn and session context in the same time window.
  • Validate client and origin: Compare user_agent.original and source.ip to known admin workstations, bastions, or CI/CD egress. The rule intentionally groups by these fields so unrelated sessions do not merge into one bucket.
  • Recover user data context: In CloudTrail (or the integration’s aws.cloudtrail.request_parameters on raw events), inspect the ModifyInstanceAttribute record for userData and whether values are base64 or placeholders.
  • Hunt for follow-on activity: After the window, look for IAM changes, role assumption, or data access from the instance or the same principal.

False positive analysis

  • Infrastructure as code: Terraform, Ansible, and Pulumi user agents are excluded, but other automation may still match. Validate pipeline identity, change tickets, and whether stop/start is part of approved maintenance.
  • Break-glass or support workflows: Some teams modify user data and restart instances during recovery; confirm with the workload owner.
  • Shared user.name or NAT: If many callers share one identity or IP, bucketing may still separate sessions when IP or user agent differs; conversely, identical UA/IP across benign bulk operations can resemble this pattern—confirm intent.

Response and remediation

  • If unauthorized, isolate the instance, revoke or restrict the principal’s EC2 permissions, and rotate any credentials that may have been exposed in user data.
  • Prefer Secrets Manager or Parameter Store over long-lived secrets in user data.

Additional information

References

Related rules

to-top