如果你和我一样,你会倾向于每天至少拆掉你的测试实验室并重新构建一次。虽然这绝对是一件非常有趣的事情,并且可以有效地利用您的时间,但是每次只要有一个火而忘记的脚本,它就会完全按照您的需要进行设置,这不是很神奇吗?
幸运的是,这是可以使用基础设施即代码实现的!在这篇文章中,我将介绍如何使用 Packer、Terraform 和 Ansible 立即从空的 ESXi 服务器转到启动并运行的 Windows 域。
如果你想在家一起玩,这篇文章中的所有代码都可以在 GitHub 上找到。
我假设您已经在家中设置了 ESXi 服务器,因为如果您还没有,您是哪种红队成员?带着您的云解决方案离开这里。
我们需要在 ESXi 中启用 SSH,您可以通过 Web 界面执行此操作 - 只需记住之后再次禁用它即可。
通过 SSH 连接到您的 ESXi 服务器并启用GuestIPHack
,老实说,我不知道这是做什么的,但我尝试不这样做,但它不起作用:
esxcli system settings advanced set -o /Net/GuestIPHack -i 1
**编辑:**它让我很烦,以至于我查了一下。它允许 Packer 通过使用 ARP 数据包检查从 ESXi 推断 VM 的 IP。
接下来,我们将需要为域建立一个新的网络。我们可以在启动并运行后对其进行配置,以将其与家庭网络隔离。
我们将创建一个虚拟交换机,然后创建一个端口组并将其分配给该交换机。
网络 > 虚拟交换机 > 添加标准虚拟交换机:
网络 > 端口组 > 添加端口组:
好的,现在我们的 ESXi 服务器已经准备就绪,我们需要从某个地方进行配置。为方便起见,我将使用 WSL——但任何基于 Linux 的主机也可以使用。
我们需要安装 OVFTool,它用于从命令行远程导入/导出/管理 VM。这是一个很棒的工具,但要下载它,您需要使用 VMware 网站**,并且**您需要创建一个帐户,因为他们实际上是有史以来最糟糕的人:
https://code.vmware.com/web/tool/4.4.0/ovf
下载 .bundle 文件,您可以将其作为脚本执行,它应该可以很好地安装。
我们还需要一些其他的东西,幸运的是,这些都可以在不离开我们舒适的终端的情况下安装,你知道,就像文明的软件包一样。我们需要:
我们需要将 Packer 存储库添加到我们的源代码中,但是无论您做什么都不要像我最初那样从默认的 Ubuntu 存储库安装版本。它就像我的灵魂一样古老而破碎。
curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo apt-key add -
sudo apt-add-repository "deb [arch=amd64] https://apt.releases.hashicorp.com $(lsb_release -cs) main"
sudo apt-get update && sudo apt-get install packerapt install ansible python3-winrm sshpass terraform
完毕!现在我们准备开始 VMing。
Packer 是一种用于自动构建 VM 映像的工具。它的工作方式是我们创建一个 JSON 配置文件,提供指向 ISO 的链接、机器的一些参数(例如磁盘大小、内存分配等),它会消失并为我们带来一些魔力。
在这种情况下,我们将需要一个 Windows 10 和 Windows Server 2016 VM 映像。然后,随着我们添加更多主机,我们可以根据需要多次部署该映像。
那里有很多 Packer 模板,其中一些是旧的,不能与最新版本的 Packer 一起使用,所以要小心,但如果你通读一些,你可以对其中的内容有所了解。这是一个很好的例子,还有更多。
我喜欢使用来自DetectionLab的 Packer 配置和脚本,因为我知道它们可以工作,这对于计算机来说是不寻常的。DetectionLab 最初是让我对 IaC 产生兴趣的原因,它是一个很棒的项目,您可以在此处阅读更多信息。看看这个。
首先,我们需要将 Packer 指向我们的 ESXi 服务器。查看 packer_files 目录,我们将更改variables.json
:
{
"esxi_host": "192.168.X.X",
"esxi_datastore": "datastore_name_here",
"esxi_username": "root_yolo",
"esxi_password": "password_here",
"esxi_network_with_dhcp_and_internet": "VM Network"
}
我们将要使用的两个主要 Packer 配置文件在这里:
重要提示: DetectionLab 版本中列出的 ISO 适用于 Windows 10 企业评估版。如果您从我的 GitHub 下载了存储库,则它适用于 Windows 10 Pro。这一点很重要,因为(据我所知)不可能将 Windows 10 的评估版升级到完全许可的版本,因为在宽限期到期后可以许可 Pro 映像。很高兴知道您是否想建立一个更永久的实验室。
您可以在这些文件中更改每个模板的基本规范,我喜欢给他们 8GB 的 RAM 来尝试加快速度,但是
对于下一阶段,它有助于使用tmux
(或屏幕,无论如何),我们可以将终端分成两半,以便我们可以并行运行每个实例:
tmux
Ctrl + B
Shift + 5
在第一个窗口中:packer build -var-file variables.json windows_10_esxi.json
在第二个窗口 ( Ctrl + B, Ctrl + Right
) 中:packer build -var-file variables.json windows_2016_esxi.json
我们走了!这需要一些时间,它将从 MS 网站下载每个 Windows 版本的 ISO,然后使用 OVFTool 将它们作为 VM 安装到 ESXi 服务器上。然后它将运行打包程序配置中列出的系统准备脚本,关闭 VM 并将其转换为映像:
这在我的服务器上花了 9 分钟,但在您的线路速度和服务器有多少果汁上可能需要更长的时间:Build 'vmware-iso' finished after 9 minutes 3 seconds.
当它运行时,您可以切换到 ESXi 控制台以查看现实生活中发生的奇迹:
如果单击单个 VM 名称,您可以查看控制台预览,以尝试了解它们在配置过程中所处的阶段,并确保它们在安装 Windows 或类似的东西时没有停滞,但是脚本来自在撰写本文时,我的回购绝对没有问题。
当您有两个处于关机状态的新虚拟机时,您就会知道它已完成,如下所示:
到此,VM 映像完成。如果您不知道如何操作,可以按 退出 tmux Ctrl + D
。
接下来我们将使用 Terraform。这将获取准备好的 VM 映像种子,将它们种植在虚拟花园中并浇水,直到它们长成漂亮的空白 Windows 机器。
让我们创建一些配置文件。在 terraform_files 目录中,编辑variables.tf
,在这里我们放:
variable "esxi_hostname" {
default = "<IP>"
}variable "esxi_hostport" {
default = "22"
}
variable "esxi_username" {
default = "root"
}
variable "esxi_password" {
default = "<password>"
}
variable "esxi_datastore" {
default = "<datastore>"
}
variable "vm_network" {
default = "VM Network"
}
variable "hostonly_network" {
default = "HostOnly"
}
现在,Terraform 并没有“正式”支持 ESXi,至少没有 vSphere。所以为了让它玩得好,我们需要使用一些魔法。这是一个像魅力一样工作的非官方提供程序,因此对那里的开发人员表示敬意。versions.tf
:
terraform {
required_version = ">= 1.0.0"
required_providers {
esxi = {
source = "josenk/esxi"
version = "1.9.0"
}
}
}
main.tf
是行动发生的地方,我们可以定义我们的基础设施。我已将其配置为创建一个 Windows 2016 服务器和两个 Windows 10 主机:
# Define our ESXi variables from variables.tf
provider "esxi" {
esxi_hostname = var.esxi_hostname
esxi_hostport = var.esxi_hostport
esxi_username = var.esxi_username
esxi_password = var.esxi_password
}# Domain Controller
resource "esxi_guest" "dc1" {
guest_name = "dc1"
disk_store = var.esxi_datastore
guestos = "windows9srv-64"
boot_disk_type = "thin"
memsize = "8192"
numvcpus = "2"
resource_pool_name = "/"
power = "on"
clone_from_vm = "WindowsServer2016"
# This is the network that bridges your host machine with the ESXi VM
network_interfaces {
virtual_network = var.vm_network
mac_address = "00:50:56:a1:b1:c1"
nic_type = "e1000"
}
# This is the local network that will be used for VM comms
network_interfaces {
virtual_network = var.hostonly_network
mac_address = "00:50:56:a1:b2:c1"
nic_type = "e1000"
}
guest_startup_timeout = 45
guest_shutdown_timeout = 30
}
# Workstation 1
resource "esxi_guest" "workstation1" {
guest_name = "workstation1"
disk_store = var.esxi_datastore
guestos = "windows9-64"
boot_disk_type = "thin"
memsize = "8192"
numvcpus = "2"
resource_pool_name = "/"
power = "on"
clone_from_vm = "Windows10"
# This is the network that bridges your host machine with the ESXi VM
network_interfaces {
virtual_network = var.vm_network
mac_address = "00:50:56:a2:b1:c3"
nic_type = "e1000"
}
# This is the local network that will be used for VM comms
network_interfaces {
virtual_network = var.hostonly_network
mac_address = "00:50:56:a2:b2:c3"
nic_type = "e1000"
}
guest_startup_timeout = 45
guest_shutdown_timeout = 30
}
# Workstation 2
resource "esxi_guest" "workstation2" {
guest_name = "workstation2"
disk_store = var.esxi_datastore
guestos = "windows9-64"
boot_disk_type = "thin"
memsize = "8192"
numvcpus = "2"
resource_pool_name = "/"
power = "on"
clone_from_vm = "Windows10"
# This is the network that bridges your host machine with the ESXi VM
network_interfaces {
virtual_network = var.vm_network
mac_address = "00:50:56:a2:b1:c4"
nic_type = "e1000"
}
# This is the local network that will be used for VM comms
network_interfaces {
virtual_network = var.hostonly_network
mac_address = "00:50:56:a2:b2:c4"
nic_type = "e1000"
}
guest_startup_timeout = 45
guest_shutdown_timeout = 30
}
现在我们已经定义了它,是时候构建它了。首先,初始化 terraform 以下载 中定义的 ESXi 提供程序versions.tf
,并运行计划以检查我们的配置main.tf
是否正确,并对我们的基础架构进行最终检查:
[[email protected] redlab ]$ terraform initInitializing the backend...
Initializing provider plugins...
- Finding josenk/esxi versions matching "1.9.0"...
- Installing josenk/esxi v1.9.0...
- Installed josenk/esxi v1.9.0 (self-signed, key ID A3C2BB2C490C3920)
Partner and community providers are signed by their developers.
If you'd like to know more about provider signing, you can read about it here:
https://www.terraform.io/docs/cli/plugins/signing.htmlTerraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.
Terraform has been successfully initialized!
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.
If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
[[email protected] redlab ]$ terraform plan
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# esxi_guest.dc1 will be created
+ resource "esxi_guest" "dc1" {
+ boot_disk_size = (known after apply)
+ boot_disk_type = "thin"
+ clone_from_vm = "WindowsServer2016"
+ disk_store = "datastore"
+ guest_name = "dc1"
+ guest_shutdown_timeout = 30
+ guest_startup_timeout = 45
+ guestos = "windows9srv-64"
+ id = (known after apply)
+ ip_address = (known after apply)
+ memsize = "8192"
+ notes = (known after apply)
+ numvcpus = "2"
+ ovf_properties_timer = (known after apply)
+ power = "on"
+ resource_pool_name = "/"
+ virthwver = (known after apply)
+ network_interfaces {
+ mac_address = "00:50:56:a1:b1:c1"
+ nic_type = "e1000"
+ virtual_network = "VM Network"
}
+ network_interfaces {
+ mac_address = "00:50:56:a1:b2:c1"
+ nic_type = "e1000"
+ virtual_network = "HostOnly"
}
}
# esxi_guest.workstation1 will be created
+ resource "esxi_guest" "workstation1" {
+ boot_disk_size = (known after apply)
+ boot_disk_type = "thin"
+ clone_from_vm = "Windows10"
+ disk_store = "datastore"
+ guest_name = "workstation1"
+ guest_shutdown_timeout = 30
+ guest_startup_timeout = 45
+ guestos = "windows9-64"
+ id = (known after apply)
+ ip_address = (known after apply)
+ memsize = "8192"
+ notes = (known after apply)
+ numvcpus = "2"
+ ovf_properties_timer = (known after apply)
+ power = "on"
+ resource_pool_name = "/"
+ virthwver = (known after apply)
+ network_interfaces {
+ mac_address = "00:50:56:a2:b1:c3"
+ nic_type = "e1000"
+ virtual_network = "VM Network"
}
+ network_interfaces {
+ mac_address = "00:50:56:a2:b2:c3"
+ nic_type = "e1000"
+ virtual_network = "HostOnly"
}
}
# esxi_guest.workstation2 will be created
+ resource "esxi_guest" "workstation2" {
+ boot_disk_size = (known after apply)
+ boot_disk_type = "thin"
+ clone_from_vm = "Windows10"
+ disk_store = "datastore"
+ guest_name = "workstation2"
+ guest_shutdown_timeout = 30
+ guest_startup_timeout = 45
+ guestos = "windows9-64"
+ id = (known after apply)
+ ip_address = (known after apply)
+ memsize = "8192"
+ notes = (known after apply)
+ numvcpus = "2"
+ ovf_properties_timer = (known after apply)
+ power = "on"
+ resource_pool_name = "/"
+ virthwver = (known after apply)
+ network_interfaces {
+ mac_address = "00:50:56:a2:b1:c4"
+ nic_type = "e1000"
+ virtual_network = "VM Network"
}
+ network_interfaces {
+ mac_address = "00:50:56:a2:b2:c4"
+ nic_type = "e1000"
+ virtual_network = "HostOnly"
}
}
Plan: 5 to add, 0 to change, 0 to destroy.
运行terraform apply
并在提示让事情移动时输入“是”。
同样,这将需要一段时间,因此请在您家中运行 ESXi 的任何设备上喝杯咖啡并烤一些棉花糖。
完成此操作后,我强烈建议为每个主机拍摄 VM 快照,以防万一。意外发生...
哇!差不多了,现在我们只需要使用 Ansible 配置主机。Ansible 是一种工具,它可以异步连接到主机,然后使用远程管理工具进行配置,在这种情况下,我们使用的是 WinRM,因为速度慢且不可预测是最好的。
为此,我们需要做一些事情。首先,我们必须手动做一些事情并创建一个库存文件(至少现在,我们可以稍后自动执行此步骤,但让它保持“简单”)。Ansible 使用此文件来查找网络上的主机。从 ESXi 控制台获取每个主机的 IP,并将它们放入以下文件中:
inventory.yml
:
---dc1:
hosts:
0.0.0.0:
workstation1:
hosts:
0.0.0.0:
workstation2:
hosts:
0.0.0.0:
接下来让我们用我们的密码、用户、域名等创建一个变量文件。创建一个名为的目录group_vars
并将一些值添加到all.yml
:
ansible_user: vagrant
ansible_password: vagrant
ansible_port: 5985
ansible_connection: winrm
ansible_winrm_transport: basic
ansible_winrm_server_cert_validation: ignoredomain_name: hacklab.local
domain_admin_user: overwatch
domain_admin_pass: DomainAdminPass1!
domain_user_user: alice
domain_user_pass: AlicePass1!
exchange_domain_user: '{{ domain_admin_user }}@{{ domain_name | upper }}'
exchange_domain_pass: '{{ domain_admin_pass }}'
显然,这些可以更改为您的要求。
Ansible 使用 YAML 文件在主机上运行指令,这些指令集称为playbooks。我们可以将不同的剧本分配给不同的主机,以按照我们认为合适的方式配置它们。例如,我们想要设置一个域控制器和两个 Windows 10 工作站,因此我们可以为这两个用例创建一个剧本。
制作以下目录结构/文件:
roles/dc1/tasks/main.yml
然后我们可以添加以下指令集来将主机配置为域控制器:
---
- name: Disable Windows Firewall
win_shell: "Set-NetFirewallProfile -Profile Domain,Public,Private -Enabled False"
- name: Hostname -> DC
win_hostname:
name: dc
register: res- name: Reboot
win_reboot:
when: res.reboot_required
- name: Disable password complexity
win_security_policy:
section: System Access
key: PasswordComplexity
value: 0
- name: Set Local Admin Password
win_user:
name: Administrator
password: '{{ domain_admin_pass }}'
state: present
groups_action: add
groups:
- Administrators
- Users
ignore_errors: yes
- name: Set HostOnly IP address
win_shell: "If (-not(Get-NetIPAddress | where {$_.IPAddress -eq '192.168.56.100'})) {$adapter = (get-netadapter | where {$_.MacAddress -eq '00-50-56-A1-B2-C1'}).Name; New-NetIPAddress –InterfaceAlias $adapter –AddressFamily IPv4 -IPAddress 192.168.56.100 –PrefixLength 24 -DefaultGateway 192.168.56.1 } Else { Write-Host 'IP Address Already Created.' }"
- name: Set DNS server
win_shell: "$adapter = (get-netadapter | where {$_.MacAddress -eq '00-50-56-A1-B2-C1'}).Name; Set-DnsClientServerAddress -InterfaceAlias $adapter -ServerAddresses 127.0.0.1,8.8.8.8"
- name: Check Variables are Configured
assert:
that:
- domain_name is defined
- domain_admin_user is defined
- domain_admin_pass is defined
- name: Install Active Directory Domain Services
win_feature: >
name=AD-Domain-Services
include_management_tools=yes
include_sub_features=yes
state=present
register: adds_installed
- name: Install RSAT AD Admin Center
win_feature:
name: RSAT-AD-AdminCenter
state: present
register: rsat_installed
- name: Rebooting
win_reboot:
reboot_timeout_sec: 60
- name: Create Domain
win_domain:
dns_domain_name: '{{ domain_name }}'
safe_mode_password: '{{ domain_admin_pass }}'
register: domain_setup
- name: Reboot After Domain Creation
win_reboot:
when: domain_setup.reboot_required
- name: Create Domain Admin Account
win_domain_user:
name: '{{ domain_admin_user }}'
upn: '{{ domain_admin_user }}@{{ domain_name }}'
description: '{{ domain_admin_user }} Domain Account'
password: '{{ domain_admin_pass }}'
password_never_expires: yes
groups:
- Domain Admins
- Enterprise Admins
- Schema Admins
state: present
register: pri_domain_setup_create_user_result
retries: 30
delay: 15
until: pri_domain_setup_create_user_result is successful
- name: Create Domain User Account
win_domain_user:
name: '{{ domain_user_user }}'
upn: '{{ domain_user_user }}@{{ domain_name }}'
description: '{{ domain_user_user }} Domain Account'
password: '{{ domain_user_pass }}'
password_never_expires: yes
groups:
- Domain Users
state: present
register: create_user_result
retries: 30
delay: 15
until: create_user_result is successful
- name: Verify Account was Created
win_whoami:
become: yes
become_method: runas
vars:
ansible_become_user: '{{ domain_admin_user }}@{{ domain_name }}'
ansible_become_pass: '{{ domain_admin_pass }}'
接下来roles/common/tasks/main.yml
我们可以配置一个通用的“通用”剧本,它应该在多个主机上运行(在我们的例子中,在工作站上):
---- name: Disable Windows Firewall
win_shell: "Set-NetFirewallProfile -Profile Domain,Public,Private -Enabled False"
- name: Install git
win_chocolatey:
name: git
state: present
- name: Check Variables are Set
assert:
that:
- domain_admin_user is defined
- domain_admin_pass is defined
- name: Join Host to Domain
win_domain_membership:
dns_domain_name: '{{ domain_name }}'
domain_admin_user: '{{ domain_admin_user }}@{{ domain_name }}'
domain_admin_password: '{{ domain_admin_pass }}'
state: domain
register: domain_join_result
- name: Reboot Host After Joining Domain
win_reboot:
when: domain_join_result.reboot_required
- name: Test Domain User Login
win_whoami:
become: yes
become_method: runas
vars:
ansible_become_user: '{{ domain_user_user }}@{{ domain_name}}'
ansible_become_password: '{{ domain_user_pass }}'
然后我们创建一个剧本来设置每个工作站的 IP 配置。这感觉是一种手动且费力的分配静态 IP 和设置主机名的方式,但我想不出更好的方法。更进一步,我们可以使用这些文件,以便他们可以使用不同的工具或其他任何工具配置每个主机。在roles/workstation1/tasks/main.yml
:
---- name: Hostname -> workstation1
win_hostname:
name: workstation1
register: res
- name: Reboot
win_reboot:
when: res.reboot_required
- name: Set HostOnly IP Address
win_shell: "If (-not(get-netipaddress | where {$_.IPAddress -eq '192.168.56.110'})) {$adapter = (get-netadapter | where {$_.MacAddress -eq '00-50-56-A2-B2-C3'}).Name; New-NetIPAddress –InterfaceAlias $adapter –AddressFamily IPv4 -IPAddress 192.168.56.110 –PrefixLength 24 -DefaultGateway 192.168.56.1 } Else { Write-Host 'IP Address Already Created.' }"
- name: Set HostOnly DNS Address
win_shell: "$adapter = (get-netadapter | where {$_.MacAddress -eq '00-50-56-A2-B2-C3'}).Name; Set-DnsClientServerAddress -InterfaceAlias $adapter -ServerAddresses 192.168.56.100,8.8.8.8"
工作站2 roles/workstation2/tasks/main.yml
::
---- name: Hostname -> workstation2
win_hostname:
name: workstation2
register: res
- name: Reboot
win_reboot:
when: res.reboot_required
- name: Set HostOnly IP Address
win_shell: "If (-not(get-netipaddress | where {$_.IPAddress -eq '192.168.56.111'})) {$adapter = (get-netadapter | where {$_.MacAddress -eq '00-50-56-A2-B2-C4'}).Name; New-NetIPAddress –InterfaceAlias $adapter –AddressFamily IPv4 -IPAddress 192.168.56.111 –PrefixLength 24 -DefaultGateway 192.168.56.1 } Else { Write-Host 'IP Address Already Created.' }"
- name: Set HostOnly DNS Address
win_shell: "$adapter = (get-netadapter | where {$_.MacAddress -eq '00-50-56-A2-B2-C4'}).Name; Set-DnsClientServerAddress -InterfaceAlias $adapter -ServerAddresses 192.168.56.100,8.8.8.8"
静态 IP 是根据通过 Terraform 设置的 MAC 地址分配的,这似乎是一种复杂的方法,但它确实有效。
最后,我们需要创建一个***主***剧本来将所有内容联系在一起。这将是我们使用 Ansible 执行的文件,并将运行我们分配给每个角色的所有各种任务。
在我们的顶级目录中,让我们创建playbook.yml
:
---- hosts: dc1
strategy: free
gather_facts: False
roles:
- dc1
tags: dc1
- hosts: workstation1
strategy: free
gather_facts: False
roles:
- workstation1
- common
tags: workstation1
- hosts: workstation2
strategy: free
gather_facts: False
roles:
- workstation2
- common
tags: workstation2
然后让我们用以下方法启动它:ansible-playbook -v playbook.yml -i inventory.yml
现在回过头来,等到您的域为您配置好。如果一切按计划进行,请拍一张照片,然后尽情玩耍!
如果实验室中出现任何问题,您可以删除虚拟机并从 Terraform 阶段重新开始,或者如果您对它们进行了快照,则只需将其回滚。
就是这样。在以后的文章中,我将介绍一些检测软件,这些软件将数据反馈给 SIEM 解决方案,例如 Splunk 或 ELK。同时,我建议检查:
https://hausec.com/2021/03/04/creating-a-red-blue-team-home-lab/
快乐黑客!