aws-eks-node-imds-credential
Pods on EC2 managed nodes with IMDS hop-limit >= 2 can reach 169.254.169.254 and retrieve temporary credentials for the node group IAM role. Any code execution inside such a pod inherits the node role without any IRSA or Pod Identity annotation.
match
A conjunctive graph pattern. Variables (?x) bind node ids; every clause must hold.
where
node_type(?node) ==
VirtualMachine # EC2 node in EKS managed node group
node_type(?node_role) in [MachineIdentity, Role]
?node.metadataOptions.httpPutResponseHopLimit >= 2 # IMDS reachable by pods
pods are co-located on ?node (EKS managed node group, not Fargate)
emit
| source type | Compute |
|---|---|
| target type | Credential |
| source | ?node |
| target | ?node_role |
| conditions | network_reachability service_state |
| state logic | CONDITIONAL(network_reachability): ACTIVE only when the hop-limit is confirmed >= 2 (from ec2:DescribeLaunchTemplates metadataOptions.httpPutResponseHopLimit; default for EKS managed nodes is 2). POTENTIAL when hop-limit is uncollected (assume exploitable until confirmed hardened). BLOCKED when hop-limit = 1 (recommended hardening — pods cannot reach IMDS). NEVER emit for Fargate pods (no host EC2, no IMDS). |
Narrative
EC2 node {node.name} (EKS managed node group, IMDS hop-limit >= 2) runs as IAM role {node_role.name}. Any pod co-located on this node can reach the instance metadata endpoint (169.254.169.254) and retrieve temporary STS credentials for {node_role.name} without any IRSA or Pod Identity annotation - the node role's temporary credentials are ambient and available to all pod code on this node.
Raw rule rules/derived/aws/eks.yaml
id: aws-eks-node-imds-credential
emits: ExposesCredential
description: Pods on EC2 managed nodes with IMDS hop-limit >= 2 can reach 169.254.169.254 and retrieve
temporary credentials for the node group IAM role. Any code execution inside such a pod inherits the
node role without any IRSA or Pod Identity annotation.
match:
- - node: null
- ExecutesAs
- node_role: null
where:
- 'node_type(?node) == VirtualMachine # EC2 node in EKS managed node group'
- node_type(?node_role) in [MachineIdentity, Role]
- '?node.metadataOptions.httpPutResponseHopLimit >= 2 # IMDS reachable by pods'
- pods are co-located on ?node (EKS managed node group, not Fargate)
emit:
source_type: Compute
target_type: Credential
source: ?node
target: ?node_role
permissions: []
conditions:
- network_reachability
- service_state
state_logic: "CONDITIONAL(network_reachability): ACTIVE only when the hop-limit is confirmed >= 2 (from\
\ ec2:DescribeLaunchTemplates metadataOptions.httpPutResponseHopLimit; default for EKS managed nodes\
\ is 2). POTENTIAL when hop-limit is uncollected (assume exploitable until confirmed hardened). BLOCKED\
\ when hop-limit = 1 (recommended hardening \u2014 pods cannot reach IMDS). NEVER emit for Fargate\
\ pods (no host EC2, no IMDS)."
confidence: 0.8
derived_from:
- ?node ExecutesAs ?node_role
- ?node.metadataOptions.httpPutResponseHopLimit >= 2 (node IMDS reachable)
false_positive_note: "NEVER emit for Fargate pods \u2014 Fargate has no host EC2 instance and no IMDS\
\ exposure. Only for EC2 launch type (managed node groups). Downgrade to POTENTIAL when hop-limit\
\ is not collected (unknown); BLOCKED when hop-limit = 1. The node role's temporary STS credentials\
\ are retrievable by ANY pod on the node, not just pods with specific RBAC \u2014 it is an ambient\
\ credential available to all co-located pods. This is a fact edge (the node exposes the temporary\
\ credential to co-located pods); the path-finding value comes from attacker code running in any such\
\ pod."
narrative: "EC2 node {node.name} (EKS managed node group, IMDS hop-limit >= 2) runs as IAM role {node_role.name}.\
\ Any pod co-located on this node can reach the instance metadata endpoint (169.254.169.254) and retrieve\
\ temporary STS credentials for {node_role.name} without any IRSA or Pod Identity annotation \u2014\
\ the node role's temporary credentials are ambient and available to all pod code on this node."