Skip to main content

ECS Task Definitions Should Not Have Privileged Containers

Overview

This check verifies that your Amazon ECS task definitions do not have containers configured to run in privileged mode. When a container runs with privileged: true, it gains almost unrestricted access to the host system, which breaks the security isolation that containers are designed to provide.

Risk

Running containers in privileged mode is a serious security concern. Privileged containers can:

  • Escape container isolation and access the underlying host system
  • Read and modify host devices including storage volumes
  • Extract secrets and credentials from the host or other containers
  • Compromise other workloads running on the same host
  • Modify system configurations leading to persistent backdoors

If an attacker compromises a privileged container, they effectively have root access to the host, turning a container breach into a full host compromise.

Remediation Steps

Prerequisites

You need permission to create and manage ECS task definitions (ecs:RegisterTaskDefinition, ecs:DescribeTaskDefinition).

Required IAM permissions
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ecs:RegisterTaskDefinition",
"ecs:DescribeTaskDefinition",
"ecs:ListTaskDefinitions",
"ecs:DeregisterTaskDefinition"
],
"Resource": "*"
}
]
}

AWS Console Method

  1. Open the Amazon ECS console
  2. Click Task definitions in the left navigation
  3. Select the task definition family flagged by Prowler
  4. Click on the latest revision number
  5. Click Create new revision (you cannot edit existing revisions)
  6. In the Container definitions section, click on the container name
  7. Scroll to Environment section
  8. Ensure Privileged is unchecked (disabled)
  9. Click Update
  10. Scroll down and click Create to register the new revision
  11. Update any services using this task definition to use the new revision

Important: After creating a new task definition revision, you must update your ECS services to use the new revision. Services will continue using the old (vulnerable) revision until updated.

AWS CLI (optional)

Step 1: Identify the privileged container

First, examine the current task definition to find which containers have privileged mode enabled:

aws ecs describe-task-definition \
--task-definition <task-family> \
--region us-east-1 \
--query 'taskDefinition.containerDefinitions[*].{Name:name,Privileged:privileged}'

Step 2: Export and modify the task definition

Export the current task definition to a file:

aws ecs describe-task-definition \
--task-definition <task-family> \
--region us-east-1 \
--query 'taskDefinition' > task-def.json

Edit task-def.json to set "privileged": false for each container (or remove the privileged field entirely, as false is the default).

Remove fields that cannot be included when registering a new revision:

# Remove metadata fields that aren't allowed in registration
jq 'del(.taskDefinitionArn, .revision, .status, .requiresAttributes, .compatibilities, .registeredAt, .registeredBy)' task-def.json > task-def-clean.json

Step 3: Register the new task definition revision

aws ecs register-task-definition \
--cli-input-json file://task-def-clean.json \
--region us-east-1

Step 4: Update services to use the new revision

aws ecs update-service \
--cluster <your-cluster-name> \
--service <your-service-name> \
--task-definition <task-family> \
--region us-east-1

This will trigger a rolling deployment with the new, non-privileged task definition.

CloudFormation (optional)

Ensure Privileged: false is set in your container definitions. Also consider adding other security hardening options:

AWSTemplateFormatVersion: '2010-09-09'
Description: ECS Task Definition without privileged containers

Parameters:
TaskFamily:
Type: String
Description: Family name for the task definition
ContainerImage:
Type: String
Description: Docker image for the container
ExecutionRoleArn:
Type: String
Description: ARN of the ECS task execution role

Resources:
TaskDefinition:
Type: AWS::ECS::TaskDefinition
Properties:
Family: !Ref TaskFamily
NetworkMode: awsvpc
RequiresCompatibilities:
- FARGATE
Cpu: '256'
Memory: '512'
ExecutionRoleArn: !Ref ExecutionRoleArn
ContainerDefinitions:
- Name: app
Image: !Ref ContainerImage
Essential: true
Privileged: false
ReadonlyRootFilesystem: true
User: "1000:1000"
PortMappings:
- ContainerPort: 8080
Protocol: tcp
LogConfiguration:
LogDriver: awslogs
Options:
awslogs-group: !Sub "/ecs/${TaskFamily}"
awslogs-region: !Ref AWS::Region
awslogs-stream-prefix: app

Outputs:
TaskDefinitionArn:
Description: ARN of the task definition
Value: !Ref TaskDefinition

Deploy the stack:

aws cloudformation deploy \
--template-file template.yaml \
--stack-name my-ecs-task \
--parameter-overrides \
TaskFamily=my-app \
ContainerImage=nginx:latest \
ExecutionRoleArn=arn:aws:iam::123456789012:role/ecsTaskExecutionRole \
--region us-east-1
Terraform (optional)

Set privileged = false in your container definitions JSON. Include additional security hardening:

variable "task_family" {
description = "Family name for the task definition"
type = string
}

variable "container_image" {
description = "Docker image for the container"
type = string
}

variable "execution_role_arn" {
description = "ARN of the ECS task execution role"
type = string
}

resource "aws_ecs_task_definition" "this" {
family = var.task_family
network_mode = "awsvpc"
requires_compatibilities = ["FARGATE"]
cpu = "256"
memory = "512"
execution_role_arn = var.execution_role_arn

container_definitions = jsonencode([
{
name = "app"
image = var.container_image
essential = true

# Security settings - disable privileged mode
privileged = false
readonlyRootFilesystem = true
user = "1000:1000"

portMappings = [
{
containerPort = 8080
protocol = "tcp"
}
]

logConfiguration = {
logDriver = "awslogs"
options = {
awslogs-group = "/ecs/${var.task_family}"
awslogs-region = "us-east-1"
awslogs-stream-prefix = "app"
}
}
}
])
}

output "task_definition_arn" {
description = "ARN of the task definition"
value = aws_ecs_task_definition.this.arn
}

Apply the configuration:

terraform apply

Verification

After creating the new task definition revision, verify privileged mode is disabled:

  1. Go to ECS > Task definitions in the AWS Console
  2. Click on your task definition family
  3. Select the latest revision
  4. Under each container, confirm Privileged shows as No or is not listed
CLI verification

Check that no containers have privileged mode enabled:

aws ecs describe-task-definition \
--task-definition <task-family> \
--region us-east-1 \
--query 'taskDefinition.containerDefinitions[*].{Name:name,Privileged:privileged}'

Expected output shows false or null for all containers:

[
{
"Name": "app",
"Privileged": false
}
]

To verify across all task definitions in your account:

for TASK_DEF in $(aws ecs list-task-definitions --status ACTIVE --region us-east-1 --query 'taskDefinitionArns[]' --output text); do
PRIVILEGED=$(aws ecs describe-task-definition \
--task-definition "$TASK_DEF" \
--region us-east-1 \
--query 'taskDefinition.containerDefinitions[?privileged==`true`].name' \
--output text)
if [ -n "$PRIVILEGED" ]; then
echo "FAIL: $TASK_DEF has privileged containers: $PRIVILEGED"
fi
done

Additional Resources

Notes

  • Fargate compatibility: AWS Fargate does not support privileged containers at all. If your task definition is flagged, it is likely running on EC2 launch type. Consider migrating to Fargate for enhanced security isolation.

  • Legitimate use cases: Very few workloads genuinely require privileged mode. Common alternatives include:

    • Use specific Linux capabilities instead of full privileges (linuxParameters.capabilities)
    • Mount only the specific devices needed (devices in container definition)
    • Use sidecar containers for system-level tasks
  • Defense in depth: Beyond disabling privileged mode, consider these additional hardening measures:

    • Set readonlyRootFilesystem: true to prevent filesystem modifications
    • Run containers as non-root users (user: "1000:1000")
    • Drop unnecessary Linux capabilities
    • Enable logging for container activity
  • Service updates: Creating a new task definition revision does not automatically update running services. You must explicitly update each service to use the new revision, which triggers a rolling deployment.