├── .gitignore
├── Ansible
├── ansible.cfg
├── cloudlabs.yml
├── files
│ ├── DesktopWallpaper.png
│ └── bginfo.bgi
├── group_vars
│ ├── linux.tmpl
│ └── windows.tmpl
├── inventory.yml
└── roles
│ ├── common
│ └── tasks
│ │ └── main.yml
│ ├── dc
│ └── tasks
│ │ └── main.yml
│ ├── domain
│ └── tasks
│ │ └── main.yml
│ ├── win10
│ └── tasks
│ │ └── main.yml
│ └── winserv2019
│ ├── files
│ └── webserver
│ │ ├── default.aspx
│ │ ├── default.aspx.cs
│ │ └── web.config
│ └── tasks
│ └── main.yml
├── LICENSE
├── README.md
├── Terraform
├── 01-init.tf
├── 02-network.tf
├── 03-dc.tf
├── 04-winserv2019.tf
├── 05-windows10.tf
├── 07-hackbox.tf
├── 08-ansible.tf
├── files
│ ├── ConfigureRemotingForAnsible.ps1
│ └── FirstLogonCommands.xml
├── outputs.tf
├── terraform.tfvars.example
└── variables.tf
└── assets
└── labs.png
/.gitignore:
--------------------------------------------------------------------------------
1 | # Local .terraform directories
2 | **/.terraform/*
3 |
4 | # .tfstate files
5 | *.tfstate
6 | *.tfstate.*
7 |
8 | # Crash log files
9 | crash.log
10 |
11 | # Ignore any .tfvars files that are generated automatically for each Terraform run. Most
12 | # .tfvars files are managed as part of configuration and so should be included in
13 | # version control.
14 | #
15 | # example.tfvars
16 |
17 | # Ignore override files as they are usually used to override resources locally and so
18 | # are not checked in
19 | override.tf
20 | override.tf.json
21 | *_override.tf
22 | *_override.tf.json
23 |
24 | # Include override files you do wish to add to version control using negated pattern
25 | #
26 | # !example_override.tf
27 |
28 | # Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan
29 | # example: *tfplan*
30 |
--------------------------------------------------------------------------------
/Ansible/ansible.cfg:
--------------------------------------------------------------------------------
1 | [defaults]
2 | inventory = inventory.yml
3 | host_key_checking = False
--------------------------------------------------------------------------------
/Ansible/cloudlabs.yml:
--------------------------------------------------------------------------------
1 | ---
2 | # Configure the DC to prepare the domain
3 | - hosts: all
4 | strategy: free
5 | tasks:
6 | - include_role:
7 | name: domain
8 | when: "'dc' in group_names"
9 |
10 | # Run common tasks on all Windows machines in parallel
11 | - hosts: windows
12 | roles:
13 | - common
14 |
15 | # Domain-join clients and run post-domain configuration in parallel
16 | - hosts: windows
17 | strategy: free
18 | tasks:
19 | - include_role:
20 | name: winserv2019
21 | when: "'winserv2019' in group_names"
22 | - include_role:
23 | name: win10
24 | when: "'win10' in group_names"
25 | - include_role:
26 | name: dc
27 | when: "'dc' in group_names"
--------------------------------------------------------------------------------
/Ansible/files/DesktopWallpaper.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thehackerish/ADLab/1cb674565fbe1a08c4f4c2b8e8a8f2c0907b6a0b/Ansible/files/DesktopWallpaper.png
--------------------------------------------------------------------------------
/Ansible/files/bginfo.bgi:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thehackerish/ADLab/1cb674565fbe1a08c4f4c2b8e8a8f2c0907b6a0b/Ansible/files/bginfo.bgi
--------------------------------------------------------------------------------
/Ansible/group_vars/linux.tmpl:
--------------------------------------------------------------------------------
1 |
2 | ansible_user: ${username}
3 | ansible_password: ${password}
4 | ansible_become: true
5 | ansible_connection: ssh
6 | ansible_winrm_server_cert_validation: ignore
--------------------------------------------------------------------------------
/Ansible/group_vars/windows.tmpl:
--------------------------------------------------------------------------------
1 |
2 | ansible_user: ${username}
3 | ansible_password: ${password}
4 | ansible_connection: winrm
5 | ansible_winrm_server_cert_validation: ignore
6 | domain_name: ${domain_name}
7 | reverse_dns_zone: "10.13.37.0/24"
--------------------------------------------------------------------------------
/Ansible/inventory.yml:
--------------------------------------------------------------------------------
1 | ---
2 | windows:
3 | children:
4 | dc:
5 | hosts:
6 | 10.13.37.10:
7 | winserv2019:
8 | hosts:
9 | 10.13.37.100:
10 | win10:
11 | hosts:
12 | 10.13.37.150:
--------------------------------------------------------------------------------
/Ansible/roles/common/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - name: Copy desktop wallpaper
4 | win_copy:
5 | src: DesktopWallpaper.png
6 | dest: C:\Users\Public\Pictures\DesktopWallpaper.png
7 |
8 | - name: Copy bginfo configuration
9 | win_copy:
10 | src: bginfo.bgi
11 | dest: C:\Users\Public\bginfo.bgi
12 |
13 | # Note: Chocolatey packages should be minimized to avoid hitting the rate limit (only 1 outbound IP is used)
14 | - name: Install utilities
15 | ignore_errors: yes
16 | win_chocolatey:
17 | name:
18 | - GoogleChrome
19 | - bginfo
20 | state: present
21 | ignore_checksums: true
22 |
23 | - name: Configure bginfo as autorun
24 | win_regedit:
25 | path: 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run'
26 | name: bginfo
27 | data: 'C:\ProgramData\chocolatey\bin\bginfo.exe c:\users\public\bginfo.bgi /silent /timer:0 /nolicprompt'
28 |
29 | - name: Ensure 'Search' unpinned from the taskbar
30 | ansible.windows.win_regedit:
31 | path: HKCU:\Software\Microsoft\Windows\CurrentVersion\Search
32 | name: SearchboxTaskbarMode
33 | data: 0
34 | type: dword
35 |
36 | - name: Ensure 'News and Interests' unpinned from the taskbar
37 | ansible.windows.win_regedit:
38 | path: HKLM:\SOFTWARE\Policies\Microsoft\Windows\Windows Feeds
39 | name: EnableFeeds
40 | data: 0
41 | type: dword
42 | state: present
43 |
44 | - name: Ensure 'People' unpinned from the taskbar
45 | ansible.windows.win_regedit:
46 | path: HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced\People
47 | name: PeopleBand
48 | data: 0
49 | type: dword
50 |
51 | - name: Ensure 'Edge', 'Store' other built-in shortcuts unpinned from the taskbar
52 | ansible.windows.win_regedit:
53 | path: HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Taskband
54 | name: Favorites
55 | state: absent
56 |
57 | - name: Hide Task View, Chat and Cortana, always show file extensions
58 | ansible.windows.win_regedit:
59 | path: HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced
60 | name: "{{ item }}"
61 | data: 0
62 | type: dword
63 | loop:
64 | - ShowCortanaButton
65 | - ShowTaskViewButton
66 | - TaskbarDa
67 | - TaskbarMn
68 | - HideFileExt
--------------------------------------------------------------------------------
/Ansible/roles/dc/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | # Install ADCS
4 | - name: Install ADCS
5 | win_feature:
6 | name: AD-Certificate
7 | state: present
8 | include_sub_features: yes
9 | include_management_tools: yes
10 | register: win_feature
11 |
12 | - name: Reboot after ADCS installation
13 | win_reboot:
14 | when: win_feature.reboot_required
15 |
16 | # Populate AD and add vulnerabilities using BadBlood
17 | - name: Download BadBlood
18 | win_get_url:
19 | url: https://github.com/chvancooten/BadBlood/archive/refs/heads/master.zip
20 | dest: C:\Users\Public\Downloads\BadBlood-master.zip
21 |
22 | - name: Extract BadBlood archive
23 | win_unzip:
24 | src: C:\Users\Public\Downloads\BadBlood-master.zip
25 | dest: C:\Users\Public\Downloads
26 | creates: C:\Users\Public\Downloads\BadBlood-master
27 | delete_archive: yes
28 |
29 | - name: Run BadBlood to populate AD insecurely
30 | ansible.windows.win_powershell:
31 | script: C:\Users\Public\Downloads\BadBlood-master\Invoke-BadBlood.ps1
32 | parameters:
33 | NonInteractive: true
34 | SkipLapsInstall: true
35 | creates: C:\Windows\AD.populated
36 |
37 | - name: Touch file to track AD state
38 | win_file:
39 | path: C:\Windows\AD.populated
40 | state: touch
41 |
42 | - name: Clean up BadBlood files
43 | win_file:
44 | path: C:\Users\Public\Downloads\BadBlood-master
45 | state: absent
--------------------------------------------------------------------------------
/Ansible/roles/domain/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | # Install AD
4 | - name: Create new AD domain
5 | win_domain:
6 | dns_domain_name: "{{ domain_name }}"
7 | safe_mode_password: "{{ ansible_password }}"
8 | register: domain_install
9 |
10 | - name: Reboot after AD installation
11 | win_reboot:
12 | when: domain_install.reboot_required
--------------------------------------------------------------------------------
/Ansible/roles/win10/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | # Join domain
4 | - name: Configure DNS settings
5 | win_dns_client:
6 | adapter_names: Ethernet
7 | ipv4_addresses:
8 | - 10.13.37.10
9 | - 8.8.8.8
10 |
11 | - name: Join machine to domain
12 | win_domain_membership:
13 | dns_domain_name: "{{ domain_name }}"
14 | domain_admin_user: "{{ ansible_user }}@{{ domain_name }}"
15 | domain_admin_password: "{{ ansible_password }}"
16 | state: domain
17 | register: domain_state
18 |
19 | - name: Reboot after joining domain
20 | win_reboot:
21 | when: domain_state.reboot_required
--------------------------------------------------------------------------------
/Ansible/roles/winserv2019/files/webserver/default.aspx:
--------------------------------------------------------------------------------
1 | <%@ Page Language="C#" AutoEventWireup="true" CodeFile="default.aspx.cs" Inherits="CS" %>
2 |
3 |
4 |
5 |
6 |
7 |
14 |
15 |
16 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Ansible/roles/winserv2019/files/webserver/default.aspx.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Web;
5 | using System.Web.UI;
6 | using System.Web.UI.WebControls;
7 |
8 | using System.IO;
9 |
10 | public partial class CS : System.Web.UI.Page
11 | {
12 | protected void UploadFile(object sender, EventArgs e)
13 | {
14 | string folderPath = Server.MapPath("~/Files/");
15 |
16 | //Check whether Directory (Folder) exists.
17 | if (!Directory.Exists(folderPath))
18 | {
19 | //If Directory (Folder) does not exists. Create it.
20 | Directory.CreateDirectory(folderPath);
21 | }
22 |
23 | //Save the File to the Directory (Folder).
24 | FileUpload1.SaveAs(folderPath + Path.GetFileName(FileUpload1.FileName));
25 |
26 | //Display the success message.
27 | lblMessage.Text = Path.GetFileName(FileUpload1.FileName) + " has been uploaded.";
28 | }
29 | }
--------------------------------------------------------------------------------
/Ansible/roles/winserv2019/files/webserver/web.config:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/Ansible/roles/winserv2019/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | # Join domain
4 | - name: Configure DNS settings
5 | win_dns_client:
6 | adapter_names: Ethernet
7 | ipv4_addresses:
8 | - 10.13.37.10
9 | - 8.8.8.8
10 |
11 | - name: Join machine to domain
12 | win_domain_membership:
13 | dns_domain_name: "{{ domain_name }}"
14 | domain_admin_user: "{{ ansible_user }}@{{ domain_name }}"
15 | domain_admin_password: "{{ ansible_password }}"
16 | state: domain
17 | register: domain_state
18 |
19 | - name: Reboot after joining domain
20 | win_reboot:
21 | when: domain_state.reboot_required
22 |
23 | # Install vulnerable IIS web server
24 | - name: Install IIS with management tools
25 | win_feature:
26 | name: Web-Server
27 | state: present
28 | include_sub_features: yes
29 | include_management_tools: yes
30 | register: win_feature
31 |
32 | - name: Copy webserver files
33 | win_copy:
34 | src: webserver/
35 | dest: C:\inetpub\wwwroot
36 |
37 | - name: Remove default webserver files
38 | win_file:
39 | path: "{{ item }}"
40 | state: absent
41 | loop:
42 | - C:\inetpub\wwwroot\iisstart.htm
43 | - C:\inetpub\wwwroot\iisstart.png
44 |
45 | - name: Give IIS_IUSRS control of webserver files
46 | win_acl:
47 | path: C:\inetpub\wwwroot
48 | user: IIS_IUSRS
49 | rights: FullControl
50 | type: allow
51 | state: present
52 | inherit: ContainerInherit, ObjectInherit
53 | propagation: 'None'
54 |
55 | - name: Reboot if installing Web-Server feature requires it
56 | win_reboot:
57 | when: win_feature.reboot_required
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ADLab
2 | _By [@thehackerish](https://twitter.com/thehackerish)_
3 | Video tutorial on https://youtu.be/H76WtmP2Je4
4 |
5 | _Based heavily on the work of [@chvancooten](https://twitter.com/chvancooten), Ansible role for Elastic Security deployment by [@nodauf](https://twitter.com/nodauf)_
6 |
7 | Provisioning scripts for an Active Directory lab environment. Designed to be deployed to Azure.
8 |
9 | ## Setup
10 |
11 | The lab is provisioned automatically using Terraform and Ansible. First, Terraform deploys all the infrastructure and prepares the machines for provisioning. It then kicks off a role-based Ansible playbook from the Debian attacker machine to provision the Windows-based machines. The full process takes about 15 to 20 minutes to complete.
12 |
13 | > 💸 **Note:** The machine sizes are moderately small by default ('Standard_B1ms'). For better performence, use Standard_B2ms or Standard_B4ms. With the latter, the bill will be approx. €10 per day of active use, your mileage may vary. Change the appropriate 'size' settings in `terraform.tfvars` to change machine sizes.
14 |
15 | ### Deployment
16 |
17 | - Clone the repo to your Azure cloud shell. It conveniently has all you need (Terraform and an authenticated Azure provider). Alternatively, install and configure Terraform and the Azure provider yourself. Ansible is installed automatically as part of the provisioning process.
18 | - Copy `terraform.tfvars.example` to `terraform.tfvars` in the `Terraform` directory, and configure the variables appropriately.
19 | - In the same directory, run `terraform init`.
20 | - When you're ready to deploy, run `terraform apply` (or `terraform apply --auto-approve` to skip the approval check).
21 |
22 | Once deployment and provisioning have finished, the output variables (public IP / DNS name, administrative passwords, machine names, etc.) will be displayed. You are now ready to connect to the labs!
23 |
24 | ### Removal
25 |
26 | - When you're done with the labs, run `terraform destroy` to tear down the environment.
27 |
28 | ## Labs
29 |
30 | 
31 |
32 | The labs consist of a selection of machines:
33 |
34 | - Windows Server 2016 DC
35 | - Active Directory Certificate Services (ADCS) installed
36 | - Windows Server 2019
37 | - Internet Information Services (IIS) web server with simple vulnerable app
38 | - Windows 10 client
39 | - Debian attacker box
40 |
41 | One public IP is exposed for the whole lab. The IP ranges defined in the `ip-whitelist` are allowed to access the following ports on this IP address, which are bound to the following services using a load balancer:
42 |
43 | - Port 22 -> Attacker machine SSH
44 | - Port 80 -> Windows Server 2019 IIS web server with vulnerable page
45 | - Port 3389 -> Windows 10 Client RDP
46 |
47 | Another public IP is used for outbound Internet connectivity for all lab machines.
48 |
--------------------------------------------------------------------------------
/Terraform/01-init.tf:
--------------------------------------------------------------------------------
1 | # Terraform initialization
2 | terraform {
3 | required_providers {
4 | azurerm = {
5 | source = "hashicorp/azurerm"
6 | version = "~> 2.98.0"
7 | }
8 | }
9 |
10 | required_version = ">= 1.1.5"
11 | }
12 |
13 | provider "azurerm" {
14 | skip_provider_registration = true
15 | features {}
16 | }
17 |
18 | # Specify where the custom data file is for WinRM initialization
19 | locals {
20 | custom_data_content = base64encode(file("${path.module}/files/ConfigureRemotingForAnsible.ps1"))
21 | }
22 |
23 | # Generate random password for windows local admins
24 | resource "random_string" "windowspass" {
25 | length = 16
26 | special = false
27 | }
28 |
29 | # Generate random password for windows local admins
30 | resource "random_string" "linuxpass" {
31 | length = 16
32 | special = false
33 | }
34 |
35 |
36 | # Get a reference to the existing resource group
37 | data "azurerm_resource_group" "thehackerish-rg" {
38 | name = var.resource-group
39 | }
--------------------------------------------------------------------------------
/Terraform/02-network.tf:
--------------------------------------------------------------------------------
1 | # Get the current public IP for deployment whitelisting
2 | data "http" "public-ip" {
3 | url = "http://ipv4.icanhazip.com"
4 | }
5 |
6 | # Create a virtual network within the resource group
7 | resource "azurerm_virtual_network" "thehackerish-vnet" {
8 | name = "thehackerish-vnet"
9 | resource_group_name = data.azurerm_resource_group.thehackerish-rg.name
10 | location = data.azurerm_resource_group.thehackerish-rg.location
11 | address_space = ["10.0.0.0/8"]
12 | }
13 |
14 | # Create a subnet within the virtual network
15 | resource "azurerm_subnet" "thehackerish-subnet" {
16 | name = "thehackerish-subnet"
17 | resource_group_name = data.azurerm_resource_group.thehackerish-rg.name
18 | virtual_network_name = azurerm_virtual_network.thehackerish-vnet.name
19 | address_prefixes = ["10.13.37.0/24"]
20 | }
21 |
22 | # Create a network security group for the subnet
23 | resource "azurerm_network_security_group" "thehackerish-nsg" {
24 | name = "thehackerish-nsg"
25 | location = data.azurerm_resource_group.thehackerish-rg.location
26 | resource_group_name = data.azurerm_resource_group.thehackerish-rg.name
27 |
28 | security_rule {
29 | name = "SSH"
30 | priority = 1001
31 | direction = "Inbound"
32 | access = "Allow"
33 | protocol = "Tcp"
34 | source_port_range = "*"
35 | destination_port_range = "22"
36 | source_address_prefixes = "${distinct(concat(var.ip-whitelist, [chomp(data.http.public-ip.body)]))}"
37 | destination_address_prefix = "*"
38 | }
39 |
40 | security_rule {
41 | name = "HTTP"
42 | priority = 1002
43 | direction = "Inbound"
44 | access = "Allow"
45 | protocol = "Tcp"
46 | source_port_range = "*"
47 | destination_port_range = "80"
48 | source_address_prefixes = var.ip-whitelist
49 | destination_address_prefix = "*"
50 | }
51 |
52 | security_rule {
53 | name = "RDP"
54 | priority = 1003
55 | direction = "Inbound"
56 | access = "Allow"
57 | protocol = "Tcp"
58 | source_port_range = "*"
59 | destination_port_range = "3389"
60 | source_address_prefixes = var.ip-whitelist
61 | destination_address_prefix = "*"
62 | }
63 |
64 | security_rule {
65 | name = "Internal"
66 | priority = 1004
67 | direction = "Inbound"
68 | access = "Allow"
69 | protocol = "Tcp"
70 | source_port_range = "*"
71 | destination_port_range = "*"
72 | source_address_prefix = "10.13.37.0/24"
73 | destination_address_prefix = "*"
74 | }
75 | }
76 |
77 | resource "azurerm_subnet_network_security_group_association" "thehackerish-nsga" {
78 | subnet_id = azurerm_subnet.thehackerish-subnet.id
79 | network_security_group_id = azurerm_network_security_group.thehackerish-nsg.id
80 | }
81 |
82 | # Create a public IP address for the lab
83 | resource "azurerm_public_ip" "thehackerish-ip" {
84 | name = "thehackerish-ip"
85 | location = data.azurerm_resource_group.thehackerish-rg.location
86 | resource_group_name = data.azurerm_resource_group.thehackerish-rg.name
87 | allocation_method = "Static"
88 | domain_name_label = var.domain-name-label
89 | sku = "Standard"
90 | }
91 |
92 | # Create another public IP address for outbound traffic
93 | resource "azurerm_public_ip" "thehackerish-ip-outbound" {
94 | name = "thehackerish-ip-outbound"
95 | location = data.azurerm_resource_group.thehackerish-rg.location
96 | resource_group_name = data.azurerm_resource_group.thehackerish-rg.name
97 | allocation_method = "Static"
98 | sku = "Standard"
99 | }
100 |
101 | # Create a load balancer on the public IP
102 | resource "azurerm_lb" "thehackerish-lb" {
103 | name = "thehackerish-lb"
104 | location = data.azurerm_resource_group.thehackerish-rg.location
105 | resource_group_name = data.azurerm_resource_group.thehackerish-rg.name
106 | sku = "Standard"
107 |
108 | frontend_ip_configuration {
109 | name = "thehackerish-lb-ip-public"
110 | public_ip_address_id = azurerm_public_ip.thehackerish-ip.id
111 | }
112 | }
113 |
114 | resource "azurerm_lb_nat_rule" "thehackerish-lb-nat-http" {
115 | resource_group_name = data.azurerm_resource_group.thehackerish-rg.name
116 | loadbalancer_id = azurerm_lb.thehackerish-lb.id
117 | name = "HTTPAccess"
118 | protocol = "Tcp"
119 | frontend_port = 80
120 | backend_port = 80
121 | frontend_ip_configuration_name = "thehackerish-lb-ip-public"
122 | }
123 |
124 | resource "azurerm_lb_nat_rule" "thehackerish-lb-nat-ssh" {
125 | resource_group_name = data.azurerm_resource_group.thehackerish-rg.name
126 | loadbalancer_id = azurerm_lb.thehackerish-lb.id
127 | name = "SSHAccess"
128 | protocol = "Tcp"
129 | frontend_port = 22
130 | backend_port = 22
131 | frontend_ip_configuration_name = "thehackerish-lb-ip-public"
132 | }
133 |
134 | resource "azurerm_lb_nat_rule" "thehackerish-lb-nat-rdp" {
135 | resource_group_name = data.azurerm_resource_group.thehackerish-rg.name
136 | loadbalancer_id = azurerm_lb.thehackerish-lb.id
137 | name = "RDPAccess"
138 | protocol = "Tcp"
139 | frontend_port = 3389
140 | backend_port = 3389
141 | frontend_ip_configuration_name = "thehackerish-lb-ip-public"
142 | }
143 |
144 | # Create NAT gateway for outbound internet access
145 | resource "azurerm_nat_gateway" "thehackerish-nat-gateway" {
146 | name = "thehackerish-nat-gateway"
147 | location = data.azurerm_resource_group.thehackerish-rg.location
148 | resource_group_name = data.azurerm_resource_group.thehackerish-rg.name
149 | }
150 |
151 | resource "azurerm_nat_gateway_public_ip_association" "thehackerish-nat-gateway-ip" {
152 | nat_gateway_id = azurerm_nat_gateway.thehackerish-nat-gateway.id
153 | public_ip_address_id = azurerm_public_ip.thehackerish-ip-outbound.id
154 | }
155 |
156 | resource "azurerm_subnet_nat_gateway_association" "thehackerish-nat-gateway-subnet" {
157 | subnet_id = azurerm_subnet.thehackerish-subnet.id
158 | nat_gateway_id = azurerm_nat_gateway.thehackerish-nat-gateway.id
159 | }
160 |
--------------------------------------------------------------------------------
/Terraform/03-dc.tf:
--------------------------------------------------------------------------------
1 | # Network Interface
2 | resource "azurerm_network_interface" "thehackerish-vm-dc-nic" {
3 | name = "thehackerish-vm-dc-nic"
4 | location = data.azurerm_resource_group.thehackerish-rg.location
5 | resource_group_name = data.azurerm_resource_group.thehackerish-rg.name
6 |
7 | ip_configuration {
8 | name = "thehackerish-vm-dc-config"
9 | subnet_id = azurerm_subnet.thehackerish-subnet.id
10 | private_ip_address_allocation = "Static"
11 | private_ip_address = "10.13.37.10"
12 | }
13 | }
14 |
15 | # Virtual Machine
16 | resource "azurerm_windows_virtual_machine" "thehackerish-vm-dc" {
17 | name = "thehackerish-vm-dc"
18 | computer_name = var.dc-hostname
19 | size = var.dc-size
20 | provision_vm_agent = true
21 | enable_automatic_updates = true
22 | resource_group_name = data.azurerm_resource_group.thehackerish-rg.name
23 | location = data.azurerm_resource_group.thehackerish-rg.location
24 | timezone = var.timezone
25 | admin_username = var.windows-user
26 | admin_password = random_string.windowspass.result
27 | custom_data = local.custom_data_content
28 | network_interface_ids = [
29 | azurerm_network_interface.thehackerish-vm-dc-nic.id,
30 | ]
31 |
32 | os_disk {
33 | name = "thehackerish-vm-dc-osdisk"
34 | caching = "ReadWrite"
35 | storage_account_type = "StandardSSD_LRS"
36 | }
37 |
38 | source_image_reference {
39 | publisher = "MicrosoftWindowsServer"
40 | offer = "WindowsServer"
41 | sku = "2016-Datacenter"
42 | version = "latest"
43 | }
44 |
45 | additional_unattend_content {
46 | setting = "AutoLogon"
47 | content = "${random_string.windowspass.result}true1${var.windows-user}"
48 | }
49 |
50 | additional_unattend_content {
51 | setting = "FirstLogonCommands"
52 | content = "${file("${path.module}/files/FirstLogonCommands.xml")}"
53 | }
54 |
55 | tags = {
56 | DoNotAutoShutDown = "yes"
57 | }
58 | }
--------------------------------------------------------------------------------
/Terraform/04-winserv2019.tf:
--------------------------------------------------------------------------------
1 | # Network Interface
2 | resource "azurerm_network_interface" "thehackerish-vm-winserv2019-nic" {
3 | name = "thehackerish-vm-winserv2019-nic"
4 | location = data.azurerm_resource_group.thehackerish-rg.location
5 | resource_group_name = data.azurerm_resource_group.thehackerish-rg.name
6 |
7 | ip_configuration {
8 | name = "thehackerish-vm-winserv2019-config"
9 | subnet_id = azurerm_subnet.thehackerish-subnet.id
10 | private_ip_address_allocation = "Static"
11 | private_ip_address = "10.13.37.100"
12 | }
13 | }
14 |
15 | resource "azurerm_network_interface_nat_rule_association" "thehackerish-vm-winserv2019-nic-nat" {
16 | network_interface_id = azurerm_network_interface.thehackerish-vm-winserv2019-nic.id
17 | ip_configuration_name = "thehackerish-vm-winserv2019-config"
18 | nat_rule_id = azurerm_lb_nat_rule.thehackerish-lb-nat-http.id
19 | }
20 |
21 | # Virtual Machine
22 | resource "azurerm_windows_virtual_machine" "thehackerish-vm-winserv2019" {
23 | name = "thehackerish-vm-winserv2019"
24 | computer_name = var.winserv2019-hostname
25 | size = var.winserv2019-size
26 | provision_vm_agent = true
27 | enable_automatic_updates = true
28 | resource_group_name = data.azurerm_resource_group.thehackerish-rg.name
29 | location = data.azurerm_resource_group.thehackerish-rg.location
30 | timezone = var.timezone
31 | admin_username = var.windows-user
32 | admin_password = random_string.windowspass.result
33 | custom_data = local.custom_data_content
34 | network_interface_ids = [
35 | azurerm_network_interface.thehackerish-vm-winserv2019-nic.id,
36 | ]
37 |
38 | os_disk {
39 | name = "thehackerish-vm-winserv2019-osdisk"
40 | caching = "ReadWrite"
41 | storage_account_type = "StandardSSD_LRS"
42 | }
43 |
44 | source_image_reference {
45 | publisher = "MicrosoftWindowsServer"
46 | offer = "WindowsServer"
47 | sku = "2019-Datacenter"
48 | version = "latest"
49 | }
50 |
51 | additional_unattend_content {
52 | setting = "AutoLogon"
53 | content = "${random_string.windowspass.result}true1${var.windows-user}"
54 | }
55 |
56 | additional_unattend_content {
57 | setting = "FirstLogonCommands"
58 | content = "${file("${path.module}/files/FirstLogonCommands.xml")}"
59 | }
60 |
61 | tags = {
62 | DoNotAutoShutDown = "yes"
63 | }
64 | }
--------------------------------------------------------------------------------
/Terraform/05-windows10.tf:
--------------------------------------------------------------------------------
1 | # Network Interface
2 | resource "azurerm_network_interface" "thehackerish-vm-windows10-nic" {
3 | name = "thehackerish-vm-windows10-nic"
4 | location = data.azurerm_resource_group.thehackerish-rg.location
5 | resource_group_name = data.azurerm_resource_group.thehackerish-rg.name
6 |
7 | ip_configuration {
8 | name = "thehackerish-vm-windows10-config"
9 | subnet_id = azurerm_subnet.thehackerish-subnet.id
10 | private_ip_address_allocation = "Static"
11 | private_ip_address = "10.13.37.150"
12 | }
13 | }
14 |
15 | resource "azurerm_network_interface_nat_rule_association" "thehackerish-vm-windows10-nic-nat" {
16 | network_interface_id = azurerm_network_interface.thehackerish-vm-windows10-nic.id
17 | ip_configuration_name = "thehackerish-vm-windows10-config"
18 | nat_rule_id = azurerm_lb_nat_rule.thehackerish-lb-nat-rdp.id
19 | }
20 |
21 | # Virtual Machine
22 | resource "azurerm_windows_virtual_machine" "thehackerish-vm-windows10" {
23 | name = "thehackerish-vm-windows10"
24 | computer_name = var.win10-hostname
25 | size = var.win10-size
26 | provision_vm_agent = true
27 | enable_automatic_updates = true
28 | resource_group_name = data.azurerm_resource_group.thehackerish-rg.name
29 | location = data.azurerm_resource_group.thehackerish-rg.location
30 | timezone = var.timezone
31 | admin_username = var.windows-user
32 | admin_password = random_string.windowspass.result
33 | custom_data = local.custom_data_content
34 | network_interface_ids = [
35 | azurerm_network_interface.thehackerish-vm-windows10-nic.id,
36 | ]
37 |
38 | os_disk {
39 | name = "thehackerish-vm-windows10-osdisk"
40 | caching = "ReadWrite"
41 | storage_account_type = "StandardSSD_LRS"
42 | }
43 |
44 | source_image_reference {
45 | publisher = "MicrosoftWindowsDesktop"
46 | offer = "Windows-10"
47 | sku = "win10-21h2-ent-g2"
48 | version = "latest"
49 | }
50 |
51 | additional_unattend_content {
52 | setting = "AutoLogon"
53 | content = "${random_string.windowspass.result}true1${var.windows-user}"
54 | }
55 |
56 | additional_unattend_content {
57 | setting = "FirstLogonCommands"
58 | content = "${file("${path.module}/files/FirstLogonCommands.xml")}"
59 | }
60 |
61 | tags = {
62 | DoNotAutoShutDown = "yes"
63 | }
64 | }
--------------------------------------------------------------------------------
/Terraform/07-hackbox.tf:
--------------------------------------------------------------------------------
1 | # Network Interface
2 | resource "azurerm_network_interface" "thehackerish-vm-hackbox-nic" {
3 | name = "thehackerish-vm-hackbox-nic"
4 | location = data.azurerm_resource_group.thehackerish-rg.location
5 | resource_group_name = data.azurerm_resource_group.thehackerish-rg.name
6 |
7 | ip_configuration {
8 | name = "thehackerish-vm-hackbox-nic-config"
9 | subnet_id = azurerm_subnet.thehackerish-subnet.id
10 | private_ip_address_allocation = "Static"
11 | private_ip_address = "10.13.37.200"
12 | }
13 | }
14 |
15 | resource "azurerm_network_interface_nat_rule_association" "thehackerish-vm-hackbox-nic-nat" {
16 | network_interface_id = azurerm_network_interface.thehackerish-vm-hackbox-nic.id
17 | ip_configuration_name = "thehackerish-vm-hackbox-nic-config"
18 | nat_rule_id = azurerm_lb_nat_rule.thehackerish-lb-nat-ssh.id
19 | }
20 |
21 | # Virtual Machine
22 | resource "azurerm_linux_virtual_machine" "thehackerish-vm-hackbox" {
23 | name = "thehackerish-vm-hackbox"
24 | computer_name = var.hackbox-hostname
25 | resource_group_name = data.azurerm_resource_group.thehackerish-rg.name
26 | location = data.azurerm_resource_group.thehackerish-rg.location
27 | size = var.hackbox-size
28 | disable_password_authentication = false
29 | admin_username = var.linux-user
30 | admin_password = random_string.linuxpass.result
31 | network_interface_ids = [
32 | azurerm_network_interface.thehackerish-vm-hackbox-nic.id,
33 | ]
34 |
35 | os_disk {
36 | name = "thehackerish-vm-hackbox-osdisk"
37 | caching = "ReadWrite"
38 | storage_account_type = "StandardSSD_LRS"
39 | }
40 |
41 | source_image_reference {
42 | publisher = "Debian"
43 | offer = "debian-11"
44 | sku = "11-gen2"
45 | version = "latest"
46 | }
47 |
48 | tags = {
49 | DoNotAutoShutDown = "yes"
50 | }
51 | }
--------------------------------------------------------------------------------
/Terraform/08-ansible.tf:
--------------------------------------------------------------------------------
1 | # Prepare the Windows group variable template with the right username and password
2 | resource "local_file" "ansible-groupvars-windows-creation" {
3 |
4 | depends_on = [
5 | var.windows-user,
6 | var.domain-dns-name,
7 | random_string.windowspass
8 | ]
9 |
10 | content = templatefile("${path.module}/../Ansible/group_vars/windows.tmpl",
11 | {
12 | username = var.windows-user
13 | password = random_string.windowspass.result
14 | domain_name = var.domain-dns-name
15 | })
16 |
17 | filename = "../Ansible/group_vars/windows.yml"
18 | }
19 |
20 | # Prepare the Linux group variable template with the right username and password
21 | resource "local_file" "ansible-groupvars-linux-creation" {
22 |
23 | depends_on = [
24 | var.linux-user,
25 | random_string.linuxpass
26 | ]
27 |
28 | content = templatefile("${path.module}/../Ansible/group_vars/linux.tmpl",
29 | {
30 | username = var.linux-user
31 | password = random_string.linuxpass.result
32 | })
33 |
34 | filename = "../Ansible/group_vars/linux.yml"
35 | }
36 |
37 | # Provision the lab using Ansible from the hackbox machine
38 | resource "null_resource" "ansible-provisioning" {
39 |
40 | # All VMs have to be up before provisioning can be initiated
41 | depends_on = [
42 | azurerm_windows_virtual_machine.thehackerish-vm-dc,
43 | azurerm_windows_virtual_machine.thehackerish-vm-winserv2019,
44 | azurerm_windows_virtual_machine.thehackerish-vm-windows10,
45 | azurerm_linux_virtual_machine.thehackerish-vm-hackbox,
46 | ]
47 |
48 | triggers = {
49 | always_run = "${timestamp()}"
50 | }
51 |
52 | connection {
53 | type = "ssh"
54 | host = azurerm_public_ip.thehackerish-ip.ip_address
55 | user = var.linux-user
56 | password = random_string.linuxpass.result
57 | }
58 |
59 | # Copy Ansible folder to hackbox machine for provisioning
60 | provisioner "file" {
61 | source = "../Ansible"
62 | destination = "/dev/shm"
63 | }
64 |
65 | # Kick off ansible
66 | provisioner "remote-exec" {
67 | inline = [
68 | "sudo apt -qq update >/dev/null && sudo apt -qq install -y git ansible sshpass > /dev/null",
69 | "ansible-galaxy collection install ansible.windows community.general > /dev/null",
70 | "cd /dev/shm/Ansible",
71 | "ansible-playbook -v cloudlabs.yml"
72 | ]
73 | }
74 | }
--------------------------------------------------------------------------------
/Terraform/files/ConfigureRemotingForAnsible.ps1:
--------------------------------------------------------------------------------
1 | #Requires -Version 3.0
2 |
3 | # Configure a Windows host for remote management with Ansible
4 | # -----------------------------------------------------------
5 | #
6 | # This script checks the current WinRM (PS Remoting) configuration and makes
7 | # the necessary changes to allow Ansible to connect, authenticate and
8 | # execute PowerShell commands.
9 | #
10 | # All events are logged to the Windows EventLog, useful for unattended runs.
11 | #
12 | # Use option -Verbose in order to see the verbose output messages.
13 | #
14 | # Use option -CertValidityDays to specify how long this certificate is valid
15 | # starting from today. So you would specify -CertValidityDays 3650 to get
16 | # a 10-year valid certificate.
17 | #
18 | # Use option -ForceNewSSLCert if the system has been SysPreped and a new
19 | # SSL Certificate must be forced on the WinRM Listener when re-running this
20 | # script. This is necessary when a new SID and CN name is created.
21 | #
22 | # Use option -EnableCredSSP to enable CredSSP as an authentication option.
23 | #
24 | # Use option -DisableBasicAuth to disable basic authentication.
25 | #
26 | # Use option -SkipNetworkProfileCheck to skip the network profile check.
27 | # Without specifying this the script will only run if the device's interfaces
28 | # are in DOMAIN or PRIVATE zones. Provide this switch if you want to enable
29 | # WinRM on a device with an interface in PUBLIC zone.
30 | #
31 | # Use option -SubjectName to specify the CN name of the certificate. This
32 | # defaults to the system's hostname and generally should not be specified.
33 |
34 | # Written by Trond Hindenes
35 | # Updated by Chris Church
36 | # Updated by Michael Crilly
37 | # Updated by Anton Ouzounov
38 | # Updated by Nicolas Simond
39 | # Updated by Dag Wieërs
40 | # Updated by Jordan Borean
41 | # Updated by Erwan Quélin
42 | # Updated by David Norman
43 | #
44 | # Version 1.0 - 2014-07-06
45 | # Version 1.1 - 2014-11-11
46 | # Version 1.2 - 2015-05-15
47 | # Version 1.3 - 2016-04-04
48 | # Version 1.4 - 2017-01-05
49 | # Version 1.5 - 2017-02-09
50 | # Version 1.6 - 2017-04-18
51 | # Version 1.7 - 2017-11-23
52 | # Version 1.8 - 2018-02-23
53 | # Version 1.9 - 2018-09-21
54 |
55 | # Support -Verbose option
56 | [CmdletBinding()]
57 |
58 | Param (
59 | [string]$SubjectName = $env:COMPUTERNAME,
60 | [int]$CertValidityDays = 1095,
61 | [switch]$SkipNetworkProfileCheck,
62 | $CreateSelfSignedCert = $true,
63 | [switch]$ForceNewSSLCert,
64 | [switch]$GlobalHttpFirewallAccess,
65 | [switch]$DisableBasicAuth = $false,
66 | [switch]$EnableCredSSP
67 | )
68 |
69 | Function Write-ProgressLog {
70 | $Message = $args[0]
71 | Write-EventLog -LogName Application -Source $EventSource -EntryType Information -EventId 1 -Message $Message
72 | }
73 |
74 | Function Write-VerboseLog {
75 | $Message = $args[0]
76 | Write-Verbose $Message
77 | Write-ProgressLog $Message
78 | }
79 |
80 | Function Write-HostLog {
81 | $Message = $args[0]
82 | Write-Output $Message
83 | Write-ProgressLog $Message
84 | }
85 |
86 | Function New-LegacySelfSignedCert {
87 | Param (
88 | [string]$SubjectName,
89 | [int]$ValidDays = 1095
90 | )
91 |
92 | $hostnonFQDN = $env:computerName
93 | $hostFQDN = [System.Net.Dns]::GetHostByName(($env:computerName)).Hostname
94 | $SignatureAlgorithm = "SHA256"
95 |
96 | $name = New-Object -COM "X509Enrollment.CX500DistinguishedName.1"
97 | $name.Encode("CN=$SubjectName", 0)
98 |
99 | $key = New-Object -COM "X509Enrollment.CX509PrivateKey.1"
100 | $key.ProviderName = "Microsoft Enhanced RSA and AES Cryptographic Provider"
101 | $key.KeySpec = 1
102 | $key.Length = 4096
103 | $key.SecurityDescriptor = "D:PAI(A;;0xd01f01ff;;;SY)(A;;0xd01f01ff;;;BA)(A;;0x80120089;;;NS)"
104 | $key.MachineContext = 1
105 | $key.Create()
106 |
107 | $serverauthoid = New-Object -COM "X509Enrollment.CObjectId.1"
108 | $serverauthoid.InitializeFromValue("1.3.6.1.5.5.7.3.1")
109 | $ekuoids = New-Object -COM "X509Enrollment.CObjectIds.1"
110 | $ekuoids.Add($serverauthoid)
111 | $ekuext = New-Object -COM "X509Enrollment.CX509ExtensionEnhancedKeyUsage.1"
112 | $ekuext.InitializeEncode($ekuoids)
113 |
114 | $cert = New-Object -COM "X509Enrollment.CX509CertificateRequestCertificate.1"
115 | $cert.InitializeFromPrivateKey(2, $key, "")
116 | $cert.Subject = $name
117 | $cert.Issuer = $cert.Subject
118 | $cert.NotBefore = (Get-Date).AddDays(-1)
119 | $cert.NotAfter = $cert.NotBefore.AddDays($ValidDays)
120 |
121 | $SigOID = New-Object -ComObject X509Enrollment.CObjectId
122 | $SigOID.InitializeFromValue(([Security.Cryptography.Oid]$SignatureAlgorithm).Value)
123 |
124 | [string[]] $AlternativeName += $hostnonFQDN
125 | $AlternativeName += $hostFQDN
126 | $IAlternativeNames = New-Object -ComObject X509Enrollment.CAlternativeNames
127 |
128 | foreach ($AN in $AlternativeName) {
129 | $AltName = New-Object -ComObject X509Enrollment.CAlternativeName
130 | $AltName.InitializeFromString(0x3, $AN)
131 | $IAlternativeNames.Add($AltName)
132 | }
133 |
134 | $SubjectAlternativeName = New-Object -ComObject X509Enrollment.CX509ExtensionAlternativeNames
135 | $SubjectAlternativeName.InitializeEncode($IAlternativeNames)
136 |
137 | [String[]]$KeyUsage = ("DigitalSignature", "KeyEncipherment")
138 | $KeyUsageObj = New-Object -ComObject X509Enrollment.CX509ExtensionKeyUsage
139 | $KeyUsageObj.InitializeEncode([int][Security.Cryptography.X509Certificates.X509KeyUsageFlags]($KeyUsage))
140 | $KeyUsageObj.Critical = $true
141 |
142 | $cert.X509Extensions.Add($KeyUsageObj)
143 | $cert.X509Extensions.Add($ekuext)
144 | $cert.SignatureInformation.HashAlgorithm = $SigOID
145 | $CERT.X509Extensions.Add($SubjectAlternativeName)
146 | $cert.Encode()
147 |
148 | $enrollment = New-Object -COM "X509Enrollment.CX509Enrollment.1"
149 | $enrollment.InitializeFromRequest($cert)
150 | $certdata = $enrollment.CreateRequest(0)
151 | $enrollment.InstallResponse(2, $certdata, 0, "")
152 |
153 | # extract/return the thumbprint from the generated cert
154 | $parsed_cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
155 | $parsed_cert.Import([System.Text.Encoding]::UTF8.GetBytes($certdata))
156 |
157 | return $parsed_cert.Thumbprint
158 | }
159 |
160 | Function Enable-GlobalHttpFirewallAccess {
161 | Write-Verbose "Forcing global HTTP firewall access"
162 | # this is a fairly naive implementation; could be more sophisticated about rule matching/collapsing
163 | $fw = New-Object -ComObject HNetCfg.FWPolicy2
164 |
165 | # try to find/enable the default rule first
166 | $add_rule = $false
167 | $matching_rules = $fw.Rules | Where-Object { $_.Name -eq "Windows Remote Management (HTTP-In)" }
168 | $rule = $null
169 | If ($matching_rules) {
170 | If ($matching_rules -isnot [Array]) {
171 | Write-Verbose "Editing existing single HTTP firewall rule"
172 | $rule = $matching_rules
173 | }
174 | Else {
175 | # try to find one with the All or Public profile first
176 | Write-Verbose "Found multiple existing HTTP firewall rules..."
177 | $rule = $matching_rules | ForEach-Object { $_.Profiles -band 4 }[0]
178 |
179 | If (-not $rule -or $rule -is [Array]) {
180 | Write-Verbose "Editing an arbitrary single HTTP firewall rule (multiple existed)"
181 | # oh well, just pick the first one
182 | $rule = $matching_rules[0]
183 | }
184 | }
185 | }
186 |
187 | If (-not $rule) {
188 | Write-Verbose "Creating a new HTTP firewall rule"
189 | $rule = New-Object -ComObject HNetCfg.FWRule
190 | $rule.Name = "Windows Remote Management (HTTP-In)"
191 | $rule.Description = "Inbound rule for Windows Remote Management via WS-Management. [TCP 5985]"
192 | $add_rule = $true
193 | }
194 |
195 | $rule.Profiles = 0x7FFFFFFF
196 | $rule.Protocol = 6
197 | $rule.LocalPorts = 5985
198 | $rule.RemotePorts = "*"
199 | $rule.LocalAddresses = "*"
200 | $rule.RemoteAddresses = "*"
201 | $rule.Enabled = $true
202 | $rule.Direction = 1
203 | $rule.Action = 1
204 | $rule.Grouping = "Windows Remote Management"
205 |
206 | If ($add_rule) {
207 | $fw.Rules.Add($rule)
208 | }
209 |
210 | Write-Verbose "HTTP firewall rule $($rule.Name) updated"
211 | }
212 |
213 | # Setup error handling.
214 | Trap {
215 | $_
216 | Exit 1
217 | }
218 | $ErrorActionPreference = "Stop"
219 |
220 | # Get the ID and security principal of the current user account
221 | $myWindowsID = [System.Security.Principal.WindowsIdentity]::GetCurrent()
222 | $myWindowsPrincipal = new-object System.Security.Principal.WindowsPrincipal($myWindowsID)
223 |
224 | # Get the security principal for the Administrator role
225 | $adminRole = [System.Security.Principal.WindowsBuiltInRole]::Administrator
226 |
227 | # Check to see if we are currently running "as Administrator"
228 | if (-Not $myWindowsPrincipal.IsInRole($adminRole)) {
229 | Write-Output "ERROR: You need elevated Administrator privileges in order to run this script."
230 | Write-Output " Start Windows PowerShell by using the Run as Administrator option."
231 | Exit 2
232 | }
233 |
234 | $EventSource = $MyInvocation.MyCommand.Name
235 | If (-Not $EventSource) {
236 | $EventSource = "Powershell CLI"
237 | }
238 |
239 | If ([System.Diagnostics.EventLog]::Exists('Application') -eq $False -or [System.Diagnostics.EventLog]::SourceExists($EventSource) -eq $False) {
240 | New-EventLog -LogName Application -Source $EventSource
241 | }
242 |
243 | # Detect PowerShell version.
244 | If ($PSVersionTable.PSVersion.Major -lt 3) {
245 | Write-ProgressLog "PowerShell version 3 or higher is required."
246 | Throw "PowerShell version 3 or higher is required."
247 | }
248 |
249 | # Find and start the WinRM service.
250 | Write-Verbose "Verifying WinRM service."
251 | If (!(Get-Service "WinRM")) {
252 | Write-ProgressLog "Unable to find the WinRM service."
253 | Throw "Unable to find the WinRM service."
254 | }
255 | ElseIf ((Get-Service "WinRM").Status -ne "Running") {
256 | Write-Verbose "Setting WinRM service to start automatically on boot."
257 | Set-Service -Name "WinRM" -StartupType Automatic
258 | Write-ProgressLog "Set WinRM service to start automatically on boot."
259 | Write-Verbose "Starting WinRM service."
260 | Start-Service -Name "WinRM" -ErrorAction Stop
261 | Write-ProgressLog "Started WinRM service."
262 |
263 | }
264 |
265 | # WinRM should be running; check that we have a PS session config.
266 | If (!(Get-PSSessionConfiguration -Verbose:$false) -or (!(Get-ChildItem WSMan:\localhost\Listener))) {
267 | If ($SkipNetworkProfileCheck) {
268 | Write-Verbose "Enabling PS Remoting without checking Network profile."
269 | Enable-PSRemoting -SkipNetworkProfileCheck -Force -ErrorAction Stop
270 | Write-ProgressLog "Enabled PS Remoting without checking Network profile."
271 | }
272 | Else {
273 | Write-Verbose "Enabling PS Remoting."
274 | Enable-PSRemoting -Force -ErrorAction Stop
275 | Write-ProgressLog "Enabled PS Remoting."
276 | }
277 | }
278 | Else {
279 | Write-Verbose "PS Remoting is already enabled."
280 | }
281 |
282 | # Ensure LocalAccountTokenFilterPolicy is set to 1
283 | # https://github.com/ansible/ansible/issues/42978
284 | $token_path = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System"
285 | $token_prop_name = "LocalAccountTokenFilterPolicy"
286 | $token_key = Get-Item -Path $token_path
287 | $token_value = $token_key.GetValue($token_prop_name, $null)
288 | if ($token_value -ne 1) {
289 | Write-Verbose "Setting LocalAccountTOkenFilterPolicy to 1"
290 | if ($null -ne $token_value) {
291 | Remove-ItemProperty -Path $token_path -Name $token_prop_name
292 | }
293 | New-ItemProperty -Path $token_path -Name $token_prop_name -Value 1 -PropertyType DWORD > $null
294 | }
295 |
296 | # Make sure there is a SSL listener.
297 | $listeners = Get-ChildItem WSMan:\localhost\Listener
298 | If (!($listeners | Where-Object { $_.Keys -like "TRANSPORT=HTTPS" })) {
299 | # We cannot use New-SelfSignedCertificate on 2012R2 and earlier
300 | $thumbprint = New-LegacySelfSignedCert -SubjectName $SubjectName -ValidDays $CertValidityDays
301 | Write-HostLog "Self-signed SSL certificate generated; thumbprint: $thumbprint"
302 |
303 | # Create the hashtables of settings to be used.
304 | $valueset = @{
305 | Hostname = $SubjectName
306 | CertificateThumbprint = $thumbprint
307 | }
308 |
309 | $selectorset = @{
310 | Transport = "HTTPS"
311 | Address = "*"
312 | }
313 |
314 | Write-Verbose "Enabling SSL listener."
315 | New-WSManInstance -ResourceURI 'winrm/config/Listener' -SelectorSet $selectorset -ValueSet $valueset
316 | Write-ProgressLog "Enabled SSL listener."
317 | }
318 | Else {
319 | Write-Verbose "SSL listener is already active."
320 |
321 | # Force a new SSL cert on Listener if the $ForceNewSSLCert
322 | If ($ForceNewSSLCert) {
323 |
324 | # We cannot use New-SelfSignedCertificate on 2012R2 and earlier
325 | $thumbprint = New-LegacySelfSignedCert -SubjectName $SubjectName -ValidDays $CertValidityDays
326 | Write-HostLog "Self-signed SSL certificate generated; thumbprint: $thumbprint"
327 |
328 | $valueset = @{
329 | CertificateThumbprint = $thumbprint
330 | Hostname = $SubjectName
331 | }
332 |
333 | # Delete the listener for SSL
334 | $selectorset = @{
335 | Address = "*"
336 | Transport = "HTTPS"
337 | }
338 | Remove-WSManInstance -ResourceURI 'winrm/config/Listener' -SelectorSet $selectorset
339 |
340 | # Add new Listener with new SSL cert
341 | New-WSManInstance -ResourceURI 'winrm/config/Listener' -SelectorSet $selectorset -ValueSet $valueset
342 | }
343 | }
344 |
345 | # Check for basic authentication.
346 | $basicAuthSetting = Get-ChildItem WSMan:\localhost\Service\Auth | Where-Object { $_.Name -eq "Basic" }
347 |
348 | If ($DisableBasicAuth) {
349 | If (($basicAuthSetting.Value) -eq $true) {
350 | Write-Verbose "Disabling basic auth support."
351 | Set-Item -Path "WSMan:\localhost\Service\Auth\Basic" -Value $false
352 | Write-ProgressLog "Disabled basic auth support."
353 | }
354 | Else {
355 | Write-Verbose "Basic auth is already disabled."
356 | }
357 | }
358 | Else {
359 | If (($basicAuthSetting.Value) -eq $false) {
360 | Write-Verbose "Enabling basic auth support."
361 | Set-Item -Path "WSMan:\localhost\Service\Auth\Basic" -Value $true
362 | Write-ProgressLog "Enabled basic auth support."
363 | }
364 | Else {
365 | Write-Verbose "Basic auth is already enabled."
366 | }
367 | }
368 |
369 | # If EnableCredSSP if set to true
370 | If ($EnableCredSSP) {
371 | # Check for CredSSP authentication
372 | $credsspAuthSetting = Get-ChildItem WSMan:\localhost\Service\Auth | Where-Object { $_.Name -eq "CredSSP" }
373 | If (($credsspAuthSetting.Value) -eq $false) {
374 | Write-Verbose "Enabling CredSSP auth support."
375 | Enable-WSManCredSSP -role server -Force
376 | Write-ProgressLog "Enabled CredSSP auth support."
377 | }
378 | }
379 |
380 | If ($GlobalHttpFirewallAccess) {
381 | Enable-GlobalHttpFirewallAccess
382 | }
383 |
384 | # Configure firewall to allow WinRM HTTPS connections.
385 | $fwtest1 = netsh advfirewall firewall show rule name="Allow WinRM HTTPS"
386 | $fwtest2 = netsh advfirewall firewall show rule name="Allow WinRM HTTPS" profile=any
387 | If ($fwtest1.count -lt 5) {
388 | Write-Verbose "Adding firewall rule to allow WinRM HTTPS."
389 | netsh advfirewall firewall add rule profile=any name="Allow WinRM HTTPS" dir=in localport=5986 protocol=TCP action=allow
390 | Write-ProgressLog "Added firewall rule to allow WinRM HTTPS."
391 | }
392 | ElseIf (($fwtest1.count -ge 5) -and ($fwtest2.count -lt 5)) {
393 | Write-Verbose "Updating firewall rule to allow WinRM HTTPS for any profile."
394 | netsh advfirewall firewall set rule name="Allow WinRM HTTPS" new profile=any
395 | Write-ProgressLog "Updated firewall rule to allow WinRM HTTPS for any profile."
396 | }
397 | Else {
398 | Write-Verbose "Firewall rule already exists to allow WinRM HTTPS."
399 | }
400 |
401 | # Test a remoting connection to localhost, which should work.
402 | $httpResult = Invoke-Command -ComputerName "localhost" -ScriptBlock { $using:env:COMPUTERNAME } -ErrorVariable httpError -ErrorAction SilentlyContinue
403 | $httpsOptions = New-PSSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck
404 |
405 | $httpsResult = New-PSSession -UseSSL -ComputerName "localhost" -SessionOption $httpsOptions -ErrorVariable httpsError -ErrorAction SilentlyContinue
406 |
407 | If ($httpResult -and $httpsResult) {
408 | Write-Verbose "HTTP: Enabled | HTTPS: Enabled"
409 | }
410 | ElseIf ($httpsResult -and !$httpResult) {
411 | Write-Verbose "HTTP: Disabled | HTTPS: Enabled"
412 | }
413 | ElseIf ($httpResult -and !$httpsResult) {
414 | Write-Verbose "HTTP: Enabled | HTTPS: Disabled"
415 | }
416 | Else {
417 | Write-ProgressLog "Unable to establish an HTTP or HTTPS remoting session."
418 | Throw "Unable to establish an HTTP or HTTPS remoting session."
419 | }
420 | Write-VerboseLog "PS Remoting has been successfully configured for Ansible."
--------------------------------------------------------------------------------
/Terraform/files/FirstLogonCommands.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 | cmd /c "mkdir C:\terraform"
9 | Create the Terraform working directory
10 | 11
11 |
12 |
13 | cmd /c "copy C:\AzureData\CustomData.bin C:\terraform\ConfigureRemotingForAnsible.ps1"
14 | Move the CustomData file to the working directory
15 | 12
16 |
17 |
18 | powershell.exe -sta -ExecutionPolicy Unrestricted -file C:\terraform\ConfigureRemotingForAnsible.ps1
19 | Move the CustomData file to the working directory
20 | 13
21 |
22 |
--------------------------------------------------------------------------------
/Terraform/outputs.tf:
--------------------------------------------------------------------------------
1 | output "public-ip" {
2 | value = azurerm_public_ip.thehackerish-ip.ip_address
3 | description = "The public IP address used to connect to the lab."
4 | }
5 |
6 | output "public-ip-dns" {
7 | value = azurerm_public_ip.thehackerish-ip.fqdn
8 | description = "The public DNS name used to connect to the lab."
9 | }
10 |
11 | output "windows-domain" {
12 | value = var.domain-dns-name
13 | description = "The the Active Directory domain name."
14 | }
15 |
16 | output "windows-user" {
17 | value = var.windows-user
18 | description = "The username used to connect to the Windows machine."
19 | }
20 |
21 | output "windows-password" {
22 | value = random_string.windowspass.result
23 | description = "The password used for Windows local admin accounts."
24 | }
25 |
26 | output "hackbox-hostname" {
27 | value = var.hackbox-hostname
28 | description = "The hostname of the attacker VM."
29 | }
30 |
31 | output "linux-user" {
32 | value = var.linux-user
33 | description = "The SSH username used to connect to Linux machines."
34 | }
35 |
36 | output "linux-password" {
37 | value = random_string.linuxpass.result
38 | description = "The password used for Linux admin accounts."
39 | }
40 |
41 | output "dc-hostname" {
42 | value = var.dc-hostname
43 | description = "The hostname of the Domain Controller."
44 | }
45 |
46 | output "winserv2019-hostname" {
47 | value = var.winserv2019-hostname
48 | description = "The hostname of the Windows Server 2019 VM."
49 | }
50 |
51 | output "win10-hostname"{
52 | value = var.win10-hostname
53 | description = "The hostname of the Windows 10 VM."
54 | }
55 |
56 | output "ip-whitelist" {
57 | value = join(", ", var.ip-whitelist)
58 | description = "The IP address(es) that are allowed to connect to the various lab interfaces."
59 | }
60 |
61 | output "Finished"{
62 | value = "Stay curious, keep learning, and go find some bugs!"
63 | }
--------------------------------------------------------------------------------
/Terraform/terraform.tfvars.example:
--------------------------------------------------------------------------------
1 | # Terraform variables
2 | # See 'variables.tf' for definitions
3 |
4 | # Required
5 | resource-group = "resourcegroupname"
6 | ip-whitelist = ["1.2.3.4/32", "8.8.8.0/24"]
7 |
8 | # Optional (defaults are shown)
9 | timezone = "Central Europe Standard Time"
10 | domain-name-label = "thehackerish"
11 | domain-dns-name = "th.local"
12 | windows-user = "cooten"
13 | linux-user = "cooten"
14 | hackbox-hostname = "hackbox"
15 | dc-hostname = "dc"
16 | winserv2019-hostname = "winserv2019"
17 | win10-hostname = "win10"
18 | win10-size = "Standard_B1ms"
19 | winserv2019-size = "Standard_B1ms"
20 | dc-size = "Standard_B1ms"
21 | hackbox-size = "Standard_B1ms"
22 |
--------------------------------------------------------------------------------
/Terraform/variables.tf:
--------------------------------------------------------------------------------
1 | variable "resource-group" {
2 | type = string
3 | description = "The name of the sandbox resource group."
4 | }
5 |
6 | variable "timezone" {
7 | type = string
8 | description = "The timezone of the lab VMs."
9 | default = "Central Europe Standard Time"
10 | }
11 |
12 | variable "ip-whitelist" {
13 | description = "A list of CIDRs that will be allowed to access the exposed services."
14 | type = list(string)
15 | }
16 |
17 | variable "domain-name-label" {
18 | description = "The DNS name of the Azure public IP."
19 | type = string
20 | default = "cloudlabs"
21 | }
22 |
23 | variable "domain-dns-name" {
24 | description = "The DNS name of the Active Directory domain."
25 | type = string
26 | default = "cloud.labs"
27 | }
28 |
29 | variable "hackbox-hostname" {
30 | type = string
31 | description = "The hostname of the attacker VM."
32 | default = "hackbox"
33 | }
34 |
35 | variable "hackbox-size" {
36 | type = string
37 | description = "The machine size of the attacker VM."
38 | default = "Standard_B4ms"
39 | }
40 |
41 | variable "dc-hostname" {
42 | type = string
43 | description = "The hostname of the Windows Server 2016 DC VM."
44 | default = "dc"
45 | }
46 |
47 | variable "dc-size" {
48 | type = string
49 | description = "The machine size of the Windows Server 2016 DC VM."
50 | default = "Standard_B4ms"
51 | }
52 |
53 | variable "winserv2019-hostname" {
54 | type = string
55 | description = "The hostname of the Windows Server 2019 VM."
56 | default = "winserv2019"
57 | }
58 |
59 | variable "winserv2019-size" {
60 | type = string
61 | description = "The machine size of the Windows Server 2019 VM."
62 | default = "Standard_B4ms"
63 | }
64 |
65 | variable "win10-hostname" {
66 | type = string
67 | description = "The hostname of the Windows 10 VM."
68 | default = "win10"
69 | }
70 |
71 | variable "win10-size" {
72 | type = string
73 | description = "The machine size of the Windows 10 VM."
74 | default = "Standard_B4ms"
75 | }
76 |
77 | variable "windows-user" {
78 | type = string
79 | description = "The local administrative username for Windows machines. Password will be generated."
80 | default = "labadmin"
81 | }
82 |
83 | variable "linux-user" {
84 | type = string
85 | description = "The username used to access Linux machines via SSH."
86 | default = "labadmin"
87 | }
--------------------------------------------------------------------------------
/assets/labs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thehackerish/ADLab/1cb674565fbe1a08c4f4c2b8e8a8f2c0907b6a0b/assets/labs.png
--------------------------------------------------------------------------------