跳到主要内容
  1. Posts/

Terraform Goat 初探:通过 ECS 实例元数据服务获取 RAM 角色临时凭据

通过 Terraform Goat 快速部署云环境靶场,实践通过元数据窃取凭证的场景。

无孔不入:云上常见攻击场景 中,我们提到了窃取凭据这一步的实现方法之一:通过实例元数据服务获取角色临时凭据。借助 Terraform Goat 这一工具,我们可以对这类场景进行动手实践,同时对 Terraform 语法进行初步了解。

Terraform Goat 部署 #

使用 docker-compose 部署 Terraform Goat 环境,相关参数比较简单,但由于需要和 shell 交互需要注意开启交互模式和 tty:

services:
  terraformgoat_aliyun:
    image: registry.cn-hongkong.aliyuncs.com/huoxian_pub/terraformgoat_aliyun:0.0.7
    container_name: terraformgoat_aliyun
    stdin_open: true
    tty: true

networks:
  default:
    external: true
    name: lab

随后运行 docker exec -it terraformgoat_aliyun /bin/bash 进入环境 shell,以阿里云为例进行 AK 配置:

$ aliyun configure

这里我们可以在 RAM 控制台创建 RAM 用户 terraform-goat,访问方式勾选“使用永久 AccessKey 访问”并获取 AccessKey ID 和 AccessKey Secret,填入 prompt 中即可。那么这个 RAM 用户需要哪些权限呢?我们可以看一下它创建资源时需要执行哪些操作。

Terraform 文件分析 #

进入相关目录 aliyun/ecs/ecs_ssrf,检查 main.tf 并进行逐步分析。

ECS 实例 #

resource "alicloud_instance" "instance" {
  security_groups            = alicloud_security_group.group.*.id
  instance_type              = data.alicloud_instance_types.types_ds.instance_types.0.id
  image_id                   = "ubuntu_18_04_64_20G_alibase_20190624.vhd"
  instance_name              = "huocorp_terraform_goat_instance"
  vswitch_id                 = alicloud_vswitch.vswitch.id
  system_disk_size           = 20
  internet_max_bandwidth_out = 100
  user_data                  = <<EOF
#!/bin/bash
sudo apt-get -y update
sudo apt-get -y install apache2
sudo apt-get -y install php
sudo apt-get -y install php-curl
sudo sed -i 's/Listen 80/Listen 8080/' /etc/apache2/ports.conf
sudo /etc/init.d/apache2 restart
cd /var/www/html
sudo apt-get -y install wget
sudo wget https://huocorp-oss.oss-cn-beijing.aliyuncs.com/terraform-goat-dependency-files/ssrf-lab.zip
sudo apt-get -y install unzip
sudo unzip ssrf-lab.zip
sudo mv ./ssrf-lab/static/flag69152201.txt /
EOF
  depends_on = [
    alicloud_security_group.group,
    alicloud_vswitch.vswitch,
  ]
}

可以看到:

  1. 首先设置安全组为 alicloud_security_group 资源类型下名称为 group 的资源,使用 .* 匹配所有资源实例。
  2. 实例规格来自 alicloud_instance_types 数据源类型下名称为 type_ds 的数据源,其中 instance_types 是该数据源返回的一个数据。这里提取了第一个数据实例的 id。在后面可以看到数据源定义,表示 1 核 1G 规格:
data "alicloud_instance_types" "types_ds" {
  cpu_core_count = 1
  memory_size    = 1
}
  1. VSwitch 为 alicloud_vswitch 资源类型下名称为 vswitch 的资源。
  2. user_data 属性表示 自定义数据,在创建 ECS 实例时可以在高级选项中设置,实例首次启动时会运行输入的脚本进行初始化:

图 1|自定义数据

这里可以看到初始化时启动了 ssrf-lab 服务,且可以发现 flag 的位置,但对于我们之后模拟的攻击者而言暂时是未知的。

  1. depends_on 属性表示该实例依赖 alicloud_security_group.groupalicloud_vswitch.vswitch 两项资源。

安全组 #

resource "alicloud_security_group" "group" {
  name   = "huocorp_terraform_goat_security_group"
  vpc_id = alicloud_vpc.vpc.id
  depends_on = [
    alicloud_vpc.vpc
  ]
}

resource "alicloud_security_group_rule" "allow_all_tcp" {
  type              = "ingress"
  ip_protocol       = "tcp"
  nic_type          = "intranet"
  policy            = "accept"
  port_range        = "8080/8080"
  priority          = 1
  security_group_id = alicloud_security_group.group.id
  cidr_ip           = "0.0.0.0/0"
  depends_on = [
    alicloud_security_group.group
  ]
}

可以看到:

  1. 安全组对应的 VPC 为后面定义的 alicloud_vpc.vpc,因此存在依赖关系。
  2. 在安全组规则 allow_all_tcp 中,设置了内网入方向规则,放行了任意 IPv4 至 TCP/8080 端口的访问,并通过 security_group_id 绑定至上面定义的安全组,因此存在依赖关系。

VPC #

resource "alicloud_vpc" "vpc" {
  vpc_name   = "huocorp_terraform_goat_vpc"
  cidr_block = "172.16.0.0/16"
}

resource "alicloud_vswitch" "vswitch" {
  vpc_id       = alicloud_vpc.vpc.id
  cidr_block   = "172.16.0.0/24"
  zone_id      = "cn-beijing-h"
  vswitch_name = "huocorp_terraform_goat_vswitch"
  depends_on = [
    alicloud_vpc.vpc
  ]
}

这里先定义了一个网段为 172.16.0.0/16 的 VPC,随后定义了其子网 172.16.0.0/24 分配给一个北京地域可用区 H 的 VSwitch,后者依赖前者。

RAM 角色与授权 #

首先定义 RAM 角色,在信任策略中为 ECS 服务授予 sts:AssumeRole 权限:

resource "alicloud_ram_role" "role" {
  name     = "huocorp-terraform-goat-role"
  force    = true
  document = <<EOF
  {
  "Statement": [
    {
      "Action": "sts:AssumeRole",
      "Effect": "Allow",
      "Principal": {
        "Service": [
          "ecs.aliyuncs.com"
        ]
      }
    }
  ],
  "Version": "1"
  }
  EOF
}

AssumeRole 操作允许一个实体(用户/角色/服务)临时扮演另一个角色,即获取另一个角色的权限。调用该 API 之后,实体可以获得一组临时安全凭证(AK,SK,STS Token)。适用于跨账户访问、权限委派、联合身份验证和服务间访问等场景。

这里的含义是,ECS 服务可以通过调用 AssumeRole 来临时获取 huocorp-terraform-goat-role 角色的权限。实际业务场景中,这样做通常是为了能让 ECS 不需要硬编码 AK 也能访问其他云资源,同时避免直接授予 ECS 服务过高的权限。

随后定义 RAM 授权策略,这里直接就是管理员权限:

resource "alicloud_ram_policy" "policy" {
  policy_name     = "AdministratorAccess"
  force           = true
  policy_document = <<EOF
  {
    "Statement": [
        {
            "Action": "*",
            "Effect": "Allow",
            "Resource": "*"
        }
    ],
    "Version": "1"
  }
  EOF 
}

将 RAM 角色绑定到之前创建的 ECS 实例:

resource "alicloud_ram_role_attachment" "attach" {
  role_name    = alicloud_ram_role.role.name
  instance_ids = alicloud_instance.instance.*.id
  depends_on = [
    alicloud_instance.instance
  ]
}

最后将上面创建的 RAM 授权策略绑定到 huocorp-terraform-goat-role 角色:

resource "alicloud_ram_role_policy_attachment" "attach" {
  policy_name = alicloud_ram_policy.policy.name
  policy_type = alicloud_ram_policy.policy.type
  role_name   = alicloud_ram_role.role.name
  depends_on = [
    alicloud_ram_policy.policy,
    alicloud_ram_role.role
  ]
}

总结 #

outputs.tf 中可以看到输出了 ssrf-lab 服务地址:

output "ssrf_lab_address_link" {
  value       = "http://${alicloud_instance.instance.public_ip}:8080/ssrf-lab"
  description = "ssrf lab address link."
}

从上面的所有操作可以总结,进行这些操作的 RAM 用户需要的权限包括(这里直接使用了系统授权策略,如需更细粒度权限控制可以编写自定义授权策略):

  • AliyunECSFullAccess
  • AliyunVPCFullAccess
  • AliyunRAMFullAccess

SSRF 环境启动 #

给我们的 RAM 用户 terraform-goat 授予上述权限后,就可以启动环境了:

$ terraform init
$ terraform apply

无可售卖实例问题 #

过程中可能遇到所选可用区无相应规格的可售卖实例的问题,terraform destroy、切换 VSwitch 的可用区(等同于 ECS 实例所处的可用区)并重新 terraform apply,本以为可以解决,但新的可用区又出现了 VSwitch 无可售卖实例的问题。为了一劳永逸地自动化解决该问题,查询 Alibaba Cloud Provider|Terraform Registry 发现可以设置一个 data 过滤出满足指定条件的可用区:

data "alicloud_zones" "zones_ds" {
  available_instance_type = data.alicloud_instance_types.types_ds.instance_types.0.id
  available_resource_creation = "VSwitch"
}

这里将我们 1C1G 对应的实例类型以及 VSwitch 产品传入,即可过滤出即存在对应规格可售卖实例、又存在可售卖 VSwitch 实例的可用区了。随后修改 VSwitch 的 zone_id

resource "alicloud_vswitch" "vswitch" {
  vpc_id       = alicloud_vpc.vpc.id
  cidr_block   = "172.16.0.0/24"
  zone_id      = data.alicloud_zones.zones_ds.zones.0.id
  vswitch_name = "huocorp_terraform_goat_vswitch"
  depends_on = [
    alicloud_vpc.vpc
  ]
}

重新 terraform apply 即可,随后会输出 ssrf-lab 的链接,稍等一段时间后访问即可成功访问服务。

图 2|ssrf-lab 服务

获取 RAM 角色临时凭据 #

通过 SSRF 访问元数据服务(阿里云元数据服务地址为 http://100.100.100.200/latest/meta-data):

图 3|访问元数据服务

除 SSRF 之外,如果我们成功获取了实例或实例上的容器的命令执行权限(例如通过容器中的 Web 应用漏洞入侵容器)也能达到同样效果。

只要 ECS 实例绑定了 RAM 角色(例如在 ACK 服务中会自动授权 Node 实例一个 kubernetesworkerrole 开头的 RAM 角色),在元数据服务中就可以看到存在 ram/ 目录(访问时注意带上 /)。

ECS 绑定 RAM 角色方法:

图 4|ECS 绑定 RAM 角色

ram/security-credentials 目录下可以看到 ECS 绑定的 RAM 角色,随后访问 ram/security-credentials/[对应的 RAM 角色名称],就可以获取到该角色的 STS Token:

图 5|获取 RAM 角色 STS Token

加固方式:可以设置实例元数据访问模式为仅加固模式,这样访问元数据服务也需要携带凭证(但有可能影响部分服务功能,例如 ACK)。然而,如果攻击者已经取得了命令执行权限而不只是 SSRF,那么同样可以根据 官方文档 中的方法获取 token 后再获取元数据。

接管云控制台 #

将获得的凭证三件套配置到 CLI 中,即可调用 API 创建可登录控制台且具备管理员权限的 RAM 账号:

$ aliyun configure --mode StsToken
$ aliyun ram CreateUser --UserName merc
$ aliyun ram CreateLoginProfile --UserName merc --Password ee027a5983ffb0c4
$ aliyun ram AttachPolicyToUser --PolicyType System --PolicyName AdministratorAccess --UserName merc

登录控制台时需要 UID 作为后缀,可以通过 aliyun ram GetAccountAlias 获取。

实际上,只要 ECS 绑定的 RAM 角色具备 AliyunRAMFullAccess 权限,我们就可以通过调用 API 修改 RAM 策略的方式将自己的 RAM 用户提权到管理员。但如果不具备修改 RAM 策略的权限,那么只能在该 RAM 角色当前具备的权限范围内进行操作了。因此合理控制云上实体(用户/角色/服务)的权限范围尤为重要。

获取 flag #

官方文档 中记录了所有实例元数据的含义,其中存在很多有价值的信息,例如获取 VPC/VSwitch 网段,可以用于后续内网扫描:

图 6|获取 VPC 网段

或是实例的自定义数据(不属于元数据,在上一层目录):

图 7|获取自定义数据

从这里可以发现自定义数据中的 flag 路径,利用 SSRF 即可读取:

图 8|获取 flag

参考资料 #

  1. Terraform Goat
  2. 从云服务器 SSRF 漏洞到接管你的阿里云控制台
  3. Alibaba Cloud Provider|Terraform Registry
  4. 使用自定义数据进行实例初始化
  5. 实例元数据
Mercury
作者
Mercury
Security Engineer