Detection rules › Elastic

Cloud Instance Metadata Credential Path HTTP Request

Status
production
Severity
medium
Time window
9m
Author
Elastic
Source
github.com/elastic/detection-rules

Detects HTTP GET requests to the link-local instance metadata service (169.254.169.254) for cloud credential or token paths on AWS, GCP, or Azure. Adversaries and vulnerable workloads use scripts, shells, or application runtimes to read IAM role credentials or OAuth tokens from the metadata API. Requires the Network Packet Capture integration with HTTP decoding on ports 80 and 443 and process enrichment enabled so "process.*" fields are present.

Known false positives

  • Cloud agents (SSM, waagent, cloud-init, instance connect) and authorized scanners may reach the same paths during provisioning or health checks. Exclude known agent user agents, source hosts, or parent processes after baselining.

MITRE ATT&CK coverage

Rule body

[metadata]
creation_date = "2026/05/23"
integration = ["network_traffic"]
maturity = "production"
updated_date = "2026/05/23"

[rule]
author = ["Elastic"]
description = """
Detects HTTP GET requests to the link-local instance metadata service (169.254.169.254) for cloud credential or token
paths on AWS, GCP, or Azure. Adversaries and vulnerable workloads use scripts, shells, or application runtimes to read
IAM role credentials or OAuth tokens from the metadata API. Requires the Network Packet Capture integration with HTTP
decoding on ports 80 and 443 and process enrichment enabled so "process.*" fields are present.
"""
false_positives = [
    """
    Cloud agents (SSM, waagent, cloud-init, instance connect) and authorized scanners may reach the same paths during
    provisioning or health checks. Exclude known agent user agents, source hosts, or parent processes after baselining.
    """,
]
from = "now-9m"
index = ["logs-network_traffic.http*", "packetbeat-*"]
language = "eql"
license = "Elastic License v2"
name = "Cloud Instance Metadata Credential Path HTTP Request"
note = """## Triage and analysis

### Investigating Cloud Instance Metadata Credential Path HTTP Request

This rule matches outbound HTTP GETs to `169.254.169.254` where the URL path requests IAM credentials or cloud OAuth
tokens, filtered to common scripting runtimes, suspicious executable paths, or tool-like user agents.

### Investigation steps

- Confirm `url.path` (AWS `security-credentials`, GCP `oauth2/access_token`, Azure `metadata/identity/oauth2/token`).
- Review `process.name`, `process.executable`, and `user_agent.original` — scripted tools and temp-path binaries are higher risk.
- Check `host.name` or `host.hostname` and whether the workload should run on a cloud VM with an instance profile or managed identity.
- Correlate with cloud audit or sign-in logs for role assumption or token use shortly after the request.
- If credentials may have been exposed, rotate the instance role or managed identity and review API activity from that principal.

### False positives

- Platform agents and bootstrap scripts on new instances; allowlist by user agent or host group where validated.

### Response

- Restrict IMDS access (IMDSv2 hop limit, network policy) and remove unnecessary instance permissions.
- Investigate the host for follow-on credential use or lateral movement.

## Setup

Deploy the [Network Packet Capture](https://www.elastic.co/docs/reference/integrations/network_traffic) integration via Fleet on cloud workloads.

- Enable **Capture HTTP Traffic** and include ports **80** and **443**.
- Enable **Monitor Processes** so network events include the process that initiated the connection.
- Prefer ECS field remapping (`map_to_ecs`) on integration data streams.
"""
references = [
    "https://www.elastic.co/docs/reference/integrations/network_traffic",
    "https://hackingthe.cloud/aws/general-knowledge/intro_metadata_service/"
]
risk_score = 47
rule_id = "73dd1f2c-3c24-4e13-a64b-dfd510e9fd98"
severity = "medium"
tags = [
    "Domain: Cloud",
    "Domain: Network",
    "OS: Linux",
    "OS: Windows",
    "OS: macOS",
    "Use Case: Threat Detection",
    "Tactic: Credential Access",
    "Data Source: Network Packet Capture",
    "Resources: Investigation Guide",
]
timestamp_override = "event.ingested"
type = "eql"

query = '''
network where event.module == "network_traffic" and destination.ip == "169.254.169.254" and destination.port == 80 and
http.request.method == "GET" and url.path : (
  "/latest/meta-data/iam/security-credentials/*",
  "*computeMetadata/v1/instance/service-accounts/*/oauth2/access_token*",
  "*metadata/identity/oauth2/token*"
) and (
  ?process.name : (
    "curl", "wget", "python*", "node", "bun", "php*", "ruby", "perl", "bash", "dash", "sh", "tcsh", "tclsh", "wish",
    "csh", "zsh", "ksh", "fish", "mksh", "busybox",
    "bun.exe", "node.exe", "powershell.exe", "cmd.exe", "curl.exe", "wget.exe", "rundll32.exe", "w3wp.exe", "java*", 
    "go", "nc", "netcat", "nginx", "apache*", "httpd", "tomcat*", "catalina", "spring*", "dotnet", "gunicorn", "uwsgi", 
    ".*", "osascript"
  ) or ?process.executable : (
    "/tmp/*", "/var/tmp/*", "/dev/shm/*", "/home/*/*", "/var/run/*", "/run/*", "/boot/*", "/.*", "C:\\Users\\*", "?:\\ProgramData\\*"
  ) or user_agent.original : (
    "curl*", "wget*", "python*", "ruby*", "Go-http-client*", "node*", "axios*", "undici*", "java*", "php*", "Bun*",
    "Apache-HttpClient*", "okhttp*", "RestTemplate*", "*WindowsPowerShell*", "*roadtools*", "*fasthttp*", "*azurehound*", "*bloodhound*", "*aiohttp*"
  )
)
'''


[[rule.threat]]
framework = "MITRE ATT&CK"

[[rule.threat.technique]]
id = "T1552"
name = "Unsecured Credentials"
reference = "https://attack.mitre.org/techniques/T1552/"

[[rule.threat.technique.subtechnique]]
id = "T1552.005"
name = "Cloud Instance Metadata API"
reference = "https://attack.mitre.org/techniques/T1552/005/"

[rule.threat.tactic]
id = "TA0006"
name = "Credential Access"
reference = "https://attack.mitre.org/tactics/TA0006/"

Stages and Predicates

Stage 1: network

network where event.module == "network_traffic" and destination.ip == "169.254.169.254" and destination.port == 80 and
http.request.method == "GET" and url.path : (
  "/latest/meta-data/iam/security-credentials/*",
  "*computeMetadata/v1/instance/service-accounts/*/oauth2/access_token*",
  "*metadata/identity/oauth2/token*"
) and (
  ?process.name : (
    "curl", "wget", "python*", "node", "bun", "php*", "ruby", "perl", "bash", "dash", "sh", "tcsh", "tclsh", "wish",
    "csh", "zsh", "ksh", "fish", "mksh", "busybox",
    "bun.exe", "node.exe", "powershell.exe", "cmd.exe", "curl.exe", "wget.exe", "rundll32.exe", "w3wp.exe", "java*", 
    "go", "nc", "netcat", "nginx", "apache*", "httpd", "tomcat*", "catalina", "spring*", "dotnet", "gunicorn", "uwsgi", 
    ".*", "osascript"
  ) or ?process.executable : (
    "/tmp/*", "/var/tmp/*", "/dev/shm/*", "/home/*/*", "/var/run/*", "/run/*", "/boot/*", "/.*", "C:\\Users\\*", "?:\\ProgramData\\*"
  ) or user_agent.original : (
    "curl*", "wget*", "python*", "ruby*", "Go-http-client*", "node*", "axios*", "undici*", "java*", "php*", "Bun*",
    "Apache-HttpClient*", "okhttp*", "RestTemplate*", "*WindowsPowerShell*", "*roadtools*", "*fasthttp*", "*azurehound*", "*bloodhound*", "*aiohttp*"
  )
)

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
destination.ipeq
  • 169.254.169.254 corpus 3 (elastic 2, kusto 1)
field:"dest_ip" kind:eq value:"169.254.169.254"
destination.porteq
  • 80 transforms: number corpus 11 (sigma 6, elastic 3, kusto 2)
field:"DestinationPort" kind:eq value:"80"
event.moduleeq
  • network_traffic
field:"event.module" kind:eq value:"network_traffic"
http.request.methodeq
  • GET
field:"http.request.method" kind:eq value:"GET"
process.executablewildcard
  • /.*
  • /boot/* corpus 28 (elastic 28)
  • /dev/shm/* corpus 53 (elastic 51, sigma 2)
  • /home/*/* corpus 23 (elastic 23)
  • /run/* corpus 20 (elastic 20)
  • /tmp/* corpus 58 (elastic 56, sigma 2)
  • /var/run/* corpus 23 (elastic 23)
  • /var/tmp/* corpus 56 (elastic 55, sigma 1)
  • ?:\ProgramData\* corpus 17 (elastic 17)
  • C:\Users\* corpus 2 (sigma 1, elastic 1)
field:"Image" kind:wildcard
process.namewildcard
  • .* corpus 38 (elastic 38)
  • apache*
  • bash corpus 12 (elastic 12)
  • bun corpus 2 (elastic 2)
  • bun.exe
  • busybox corpus 12 (elastic 12)
  • catalina
  • cmd.exe corpus 121 (elastic 92, splunk 29)
  • csh corpus 7 (elastic 7)
  • curl corpus 29 (elastic 24, splunk 5)
  • curl.exe corpus 34 (elastic 31, splunk 3)
  • dash corpus 9 (elastic 9)
  • dotnet corpus 4 (elastic 4)
  • fish corpus 7 (elastic 7)
  • go
  • gunicorn
  • httpd
  • java* corpus 4 (elastic 4)
  • ksh corpus 8 (elastic 8)
  • mksh corpus 3 (elastic 3)
  • nc corpus 5 (elastic 5)
  • netcat corpus 5 (elastic 5)
  • nginx
  • node corpus 25 (elastic 25)
  • node.exe corpus 6 (elastic 6)
  • osascript corpus 46 (elastic 46)
  • perl corpus 8 (elastic 8)
  • php* corpus 28 (elastic 28)
  • powershell.exe corpus 184 (elastic 140, splunk 44)
  • python* corpus 71 (elastic 71)
  • ruby
  • rundll32.exe corpus 126 (elastic 100, splunk 26)
  • sh corpus 12 (elastic 11, splunk 1)
  • spring*
  • tclsh corpus 3 (elastic 3)
  • tcsh corpus 8 (elastic 8)
  • tomcat*
  • uwsgi
  • w3wp.exe corpus 3 (elastic 3)
  • wget corpus 19 (elastic 18, splunk 1)
  • wget.exe corpus 6 (elastic 6)
  • wish corpus 3 (elastic 3)
  • zsh corpus 12 (elastic 12)
field:"process_name" kind:wildcard
url.pathwildcard
  • *computeMetadata/v1/instance/service-accounts/*/oauth2/access_token*
  • *metadata/identity/oauth2/token*
  • /latest/meta-data/iam/security-credentials/*
field:"url.path" kind:wildcard
user_agent.originalwildcard
  • *WindowsPowerShell*
  • *aiohttp*
  • *azurehound*
  • *bloodhound*
  • *fasthttp*
  • *roadtools*
  • Apache-HttpClient*
  • Bun*
  • Go-http-client*
  • RestTemplate*
  • axios* corpus 3 (elastic 3)
  • curl* corpus 2 (elastic 2)
  • java* corpus 2 (elastic 2)
  • node* corpus 2 (elastic 2)
  • okhttp*
  • php* corpus 2 (elastic 2)
  • python* corpus 2 (elastic 2)
  • ruby*
  • undici*
  • wget* corpus 2 (elastic 2)
field:"aws::userAgent" kind:wildcard