如何测试你的红队基础设施(上)
2020-02-27 10:40:15 Author: www.4hou.com(查看原文) 阅读量:269 收藏

随着红队建设的发展,我们需要建立可靠的环境。 为了与我们所处的猫捉老鼠的游戏保持一致,我们必须具备维护健壮的基础设施的能力,这些基础设施一旦被蓝队发现就可以重新创建。更重要的是,我们需要确保环境在部署时没有任何问题。

今天我开始了这个系列文章的第一篇,我们将会采用一些我们经常负责攻击的 DevOps 团队流行的做法,并希望利用这些技术来帮助我们构建稳定的和已测试过的基础架构。 诚然,这些年来,DevOps 已经发展成了一个流行词,但是就像“RedTeam”这个词被过度营销冲淡了一样,在它的背后有一些我们可以从中受益的核心原则。

在这篇文章中,我将首先快速回顾一下 RedTeam 基础设施是如何在代码中定义的,这些代码通常存在于某个 Git repo 中。 然而,更重要的是,我们将继续探讨如何随着环境的演变和复杂性的增加而对其进行测试,最后演练如何在混合测试中引入 CI 管道来帮助自动化测试。

管理我们的基础设施

长期以来,典型的RedTeam基础设施部署的构建一直采用“重定向”的概念。最基本的C2部署是这样的:

image.png

到目前为止,我们都知道在我们的 C2 框架选择之前使用重定向器的优势,使我们能够通过你的团队服务器现场控制客户敏感数据,屏蔽流量的真实来源,同时允许快速重新创建这些一次性重定向器,而不需要拆除后端服务器。

那么,这一切通常是如何管理的呢? 多年来,我看到过许多不同的环境部署方式。 最常见的设置方式是手动部署每个组件。 对于 AWS 用户来说,这意味着通过管理控制台运行 EC2实例,通过 SSH 接入,apt-get 接入 NGINX,配置 SSH 进行反向隧道等等。 当然,一旦 BlueTeam 找到了重定向器,实例就会被删除,新实例会被分拆,等等.....。

现在很明显,这样的做法很无聊而且容易出错,因此自动化 RedTeam 基础设施部署的想法越来越受欢迎。 同样,我也看到了这样做的不同方式,从在脚本中包装 aws 或 az shell 命令,到通过配置文件使用 Terraform 管理环境。

对于那些手动部署环境的人来说,你肯定知道在压力下引入错误配置是多么容易。 即使是自动部署,在错误的位置更改配置文件,或者错误配置的脚本暴露了搭建的环境,在最好的情况下,你可能会发现 BlueTeam 很早就发现了你,更糟糕的情况是,你可能会让自己暴露更多(我正看着你的 Cobalt Strike 指纹;)。

现在,根据一个建议的设计来测试你的基础设施的挑战已经不是什么新鲜事了,而且在非信息安全的领域中一直在讨论这个问题。 对于那些深夜狂看 YouTube 视频的人来说,我们知道一些公司在这方面做得有多好。在这些视频中,Netflix 的工程师们愉快地讨论拆除部分测试弹性基础设施,或者自豪地展示微服务架构是如何给他们每个人提供生产访问,但讽刺的是,作为在客户的网络中创建基础设施并负责维护路径的安全专业人士,我很少看到有人讨论你这类话题。

但在我们开始测试我们的基础设施之前,我们实际上需要一些基础设施来进行测试。 那么,让我们从调整一些重定向器开始。

Terraform

首先,我们将把重点放在实际创建基础设施的过程上,这些基础设施将容纳我们的资源。 你们中的许多人应该都熟悉 Terraform,这是一个来自 Hashicorp 的流行工具,已经被一些 RedTeam 的人提及多次,例如 RastaMouse,他是第一个在2017年的博客文章中强调其管理攻击性基础设施的能力的人之一。

对于那些不熟悉这个工具的人来说,Terraform 允许你在云服务上创建、更新和破坏基础设施,比如 AWS、 Azure 和 DigitalOcean。 基础设施是在 HCL 脚本中描述的,一旦设计完成,我们就把它留给 Terraform 及其提供者来“实现它(点击查看演示视频)”。

为了这篇文章的目的,我们将创建一个非常简单的环境,由2个 EC2实例充当重定向器。 一个 Terraform 脚本就可以做到这一点,这个脚本通常看起来是这样的:

provider "aws“{
  region = "eu-west-2"
}
variable "INSTANCE_NAMES“{
  default = {
    "0“= "Redirector-ShortHaul"
    "1“= "Redirector-LongHaul"
  }
}
variable "PUBLIC_KEY“{
  default = "./keys/terraformkey.pub"
}
variable "MANAGEMENT_IP“{
  default = "1.2.3.4/32"
}
resource "aws_key_pair“"terraformkey“{
  key_name   = "${terraform.workspace}-terraform-key"
  public_key = file("${var.PUBLIC_KEY}")
}
resource "aws_security_group“"redirector-sg“{
  name = "redirector-sg"
  # Allow HTTP inbound
  ingress {
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
    from_port   = 80
    to_port     = 80
  }
  # Allow HTTPS inbound
  ingress {
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
    from_port   = 443
    to_port     = 443
  }
  # Allow management from our management IP
  ingress {
    protocol    = "tcp"
    cidr_blocks = ["${var.MANAGEMENT_IP}"]
    from_port   = 22
    to_port     = 22
  }
  # Allow global outbound
  egress {
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
    from_port   = 0
    to_port     = 0
  }
}
data "aws_ami“"ubuntu“{
  most_recent = true
  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-bionic-18.04-amd64-server-*"]
  }
  filter {
    name   = "virtualization-type"
    values = ["hvm"]
  }
  owners = ["099720109477"]
}
resource "aws_instance“"redirector“{
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t2.micro"
  count         = length(var.INSTANCE_NAMES)
  key_name      = aws_key_pair.terraformkey.key_name
  vpc_security_group_ids = [
    "${aws_security_group.redirector-sg.id}",
  ]
  tags = {
    Name    = "${var.INSTANCE_NAMES[count.index]}"
    JobName = "${terraform.workspace}"
  }
}
output "redirector_ips“{
    description = "Public IP addresses of created redirectors"
    value = aws_instance.redirector.*.public_ip
}

为了确保我们能够运行基础设施的多个实例,并且不与其他参与资源发生冲突,我们通常通过以下方式创建一个新的 Terraform 工作空间:

terraform workspace new InsecureBank

image.png

但是在我们部署之前,让我们通过执行 terraform plan 来测试一下一切是否正常:

image.png

一旦我们检查了所有的东西,我们就可以转移到 terraform apply 命令:

image.png

当 Terraform 完成后,我们可以查询 AWS 并验证我们的服务器是否已经创建:

aws ec2 describe-instances --query 'Reservations[].Instances[].[Tags[?Key==`Name`].Value,InstanceType,PublicIpAddress]' --output json

image.png

我们的重定向器现在已经上线,下一步我们就需要做些准备。 在这里不要试图使用 SSH,没有必要手动配置任何东西。 相反,让我们来看看如何使这一步骤自动化。

Ansible

尽管有很多不同的工具,比如 Chef 和 Puppet,但是 Ansible 仍然是我最喜欢的配置工具之一,而且它的社区支持非常棒。

我们知道,我们有2台在线服务器,我们现在需要做一些准备。 并且我们知道这两台服务器的功能是相同的,这意味着我们需要编写一个良好的 Ansible 剧本以便于能够应用到每台服务器上。

让我们从创建我们的工作空间开始。 下面的是我发现对这类项目比较有用的布局

.
├── ansible.cfg
├── playbooks
│   └── 10_example.yml
├── roles
│   ├── requirements.yml
│   └── ubuntu-nginx
│       ├── ...
└── site.yml

这种布局的思想是提供位置来存储我们的剧本文件和角色文件,同时包括一个 Ansible 配置文件,它将提供存储我们的规则的位置。 虽然这个布局对我很有用,但如果你有首选的工作空间,请继续使用你自己的布局。

我尝试坚持创建 Ansible 角色来封装类似领域的配置,例如,通过这样的项目,我们可以为下面这些领域创建单独的角色:

· 加固基础设施

· 安装 nginx 以暴露 HTTP (s) C2

· 配置 SSH 以便从团队服务器进行访问

· 添加任何日志功能

显然,你可以随心所欲地定制你的重定向器,但是在本例中,我们将重点放在 web 服务器角色上。 我们的角色很简单,包括以下任务:

· 确保添加了所需的用户和组

· 安装 NGINX

· 通过配置文件配置 NGINX 进行反向代理

综上所述,我们的 Ansible 角色的 YAML 可能看起来像下面这样:

---
- name: Ensure group "nginx“exists
  group:
    name: nginx
    state: present
- name: Add the user nginx
  user:
    name: nginx
    shell: /bin/nologin
    groups: nginx
- name: Install NGINX
  apt:
    name: nginx
    state: present
    update_cache: yes
- name: Add NGINX configuration from template
  template:
    src: default.conf.j2
    dest: /etc/nginx/conf.d/default.conf
    owner: root
    group: root
    mode: 0644
  notify: restart nginx
在这里,我们采取了一些简单的步骤来添加我们的 NGINX 用户,安装 NGINX,并添加一个由模板制作的配置文件。 一旦我们有了自己的角色,我们可以使用一个剧本来应用它,例如:
---
 - hosts: redirector*
   become: yes
   gather_facts: no
   pre_tasks:
        - raw: test -e /usr/bin/python || (apt -y update && apt install -y python-minimal)
        - setup: # aka gather_facts
   roles:
        - redirector-nginx

随着我们的角色和剧本的构建,将我们的 Ansible 角色应用于我们的重定向的过程通过 terraform-inventory 工具变得简单明了,它允许我们解析一个 Terraform 状态文件并动态地构建一个 Ansible 目录。 例如,在我们上面的 Ansible 项目中,我们可以使用下面的方法来应用我们的角色:

TF_STATE=../terraform ansible-playbook --inventory-file=/usr/local/bin/terraform-inventory -u ubuntu --private-key ../terraform/keys/terraformkey site.yml

image.png

现在考虑一下这个问题... 有多少次你克隆了你的基础设施 repo,试图把事情做得更好,结果却发现有人更改过代码,事情不再像预期的那样运作了? 或者你已经添加了一些在当时看起来似乎是个好主意的功能,但是在准备环境时,你意识到事情看起来并不对劲?

当然,这些问题都有解决方案,毕竟,在开发领域,单元测试和集成测试代码是标准的。 现在,我们的基础设施在 Git 中描述,我们也可以应用类似的实践。 对于这个特殊的项目,我们将在两个方面进行测试。 第一个是我们的 Ansible 角色,它实际上负责准备我们的重定向器。 第二个是我们的 AWS 基础设施的部署,这些设施支持我们的重定向器,以及实际的 AWS 重定向器部署后的状态。

使用 Molecule 测试 Ansible 角色

开发 Ansible 角色可能是一件痛苦的事情,主要是因为需要时间去应用一个角色,检查错误,清理你的镜像,向路西法祈祷,然后重复这些工作。 幸运的是,有一些工具可以帮助我们测试 Ansible 角色,这些工具也可以让我们从提高的开发速度中获益。 为此,我们将使用一个叫做 Molecule 的框架。 这个框架已经存在了一段时间,基本上它允许你单独部署 Ansible 角色(例如,在一个 Docker 容器中) ,并且在角色应用之后使用 TestInfra 测试容器的状态。

首先,我们需要使用 pip 来安装 Molecule:

# Create our venv
python -m venv env
source ./env/bin/activate

# Install molecule
pip install --user molecule

一旦安装完成,我们就可以为我们在 Ansible 角色建立一些测试工作。 Molecule  使用“场景”的概念来描述一组测试用例。 让我们创建我们的缺省场景来测试我们的 NGINX 角色,命令如下:

cd rolename; molecule init scenario -r rolename

这会创建一个新的名为 molecule 的目录,目录中会添加你的 Ansible 角色。 如果打开目录,我们会看到这样的东西:

.
└── default
    ├── Dockerfile.j2
    ├── INSTALL.rst
    ├── molecule.yml
    ├── playbook.yml
    └── tests
        ├── test_default.py
        └── test_default.pyc

对于这个场景,我们的测试将添加到 test_default.py 这个 Python 脚本中,并将使用 TestInfra 框架。 但是在开始之前,我们应该花一些时间通过 molecule.yml 文件配置我们的场景。 由于我通常喜欢使用基于 Ubuntu 的服务器,我想确保我的测试在一个类似的环境中运行。 为此,我们只需将平台部分更新为:

platforms:
  - name: instance
    image: ubuntu:18.04

接下来让我们创建一些简单的测试来展示这个框架的威力吧。 我们知道这个角色将会部署 NGINX,所以作为一个很好的起点,我们可以测试一下,以确保 NGINX 在我们的角色运行之后确实安装进行了成功,如下所示:

def test_nginx_installed(host):
    nginx = host.package("nginx")
    assert nginx.is_installed

我们还要确保 NGINX 在我们的角色被应用时正在运行:

def test_nginx_running(host):
    nginx = host.service("nginx")
    assert nginx.is_running

那么我们使用反向代理信息的自定义配置又如何呢? 好吧,让我们添加一些检查,以确保当角色被应用时,我们的配置确实是存在的:

def test_nginx_configuration(host):
    passwd = host.file("/etc/nginx/conf.d/default.conf")
    assert passwd.contains("proxy_pass")
    assert passwd.user == "nginx"
    assert passwd.group == "nginx"
    assert passwd.mode == 0o644

在我们的 Molecule 测试用例中,让我们开始一个快速测试,以确保一切正常运行:

molecule test

在我们的例子中,我们可能会看到这样的情况:

image.png

这意味着测试失败,因为我们的 NGINX 配置文件属于 root 用户,我们的测试正在寻找 NGINX 的用户,所以让我们更新测试来修复这一问题:

def test_nginx_configuration(host):
    passwd = host.file("/etc/nginx/conf.d/default.conf")
    assert passwd.contains("proxy_pass")
    assert passwd.user == "root"
    assert passwd.group == "root"
    assert passwd.mode == 0o644

希望当我们重新运行我们的测试时,我们会看到这样的结果:

image.png

当然,你需要扩展你的测试用例以包含所需的粒度级别,但是正如你所看到的,一旦执行,我们就知道我们的角色将完全按照我们想要的方式执行。

本文翻译自:https://blog.xpnsec.com/testing-redteam-infra/如若转载,请注明原文地址:


文章来源: https://www.4hou.com/posts/WjGE
如有侵权请联系:admin#unsafe.sh