If you have followed along with Part One, you should have Linux and Windows virtual machine templates, and terraform scripts to create the network topology.
To configure the hosts and add vulnerabilities, we will be using Ansible. The vulnerabilities were going to be adding in are designed to be quick and easy to exploit.
Working with Dynamic Inventories
Since some of our hosts have DHCP assigned IP addresses, we will need to write an inventory script to query the Proxmox API and determine the allocated IP addresses. The following Python code serves this purpose.
#!/usr/bin/env python3
import json
import requests
import sys
import urllib3
PROXMOX_HOST = 'https://192.168.1.201:8006'
USERNAME = 'root@pam'
PASSWORD = 'Password1'
VERIFY_SSL = False
NODE = 'pve'
if not VERIFY_SSL:
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
def get_auth_ticket():
url = f"{PROXMOX_HOST}/api2/json/access/ticket"
data = {'username': USERNAME, 'password': PASSWORD}
response = requests.post(url, data=data, verify=VERIFY_SSL)
response.raise_for_status()
result = response.json()['data']
return result['ticket'], result['CSRFPreventionToken']
def get_all_vmids(ticket):
headers = {'Cookie': f"PVEAuthCookie={ticket}"}
url = f"{PROXMOX_HOST}/api2/json/nodes/{NODE}/qemu"
response = requests.get(url, headers=headers, verify=VERIFY_SSL)
response.raise_for_status()
vms = response.json()['data']
return [vm['vmid'] for vm in vms]
def get_vm_ip(ticket, vmid):
headers = {'Cookie': f"PVEAuthCookie={ticket}"}
url = f"{PROXMOX_HOST}/api2/json/nodes/{NODE}/qemu/{vmid}/agent/network-get-interfaces"
try:
response = requests.get(url, headers=headers, verify=VERIFY_SSL)
if response.status_code != 200:
return None
data = response.json().get('data', {})
interfaces = data.get('result', [])
for interface in interfaces:
for addr in interface.get('ip-addresses', []):
ip = addr.get('ip-address')
if ip and not ip.startswith('127.') and not ip.startswith('169.254.') and ':' not in ip:
return ip
except Exception as e:
print(f"Error reading VM {vmid}: {e}", file=sys.stderr)
return None
return None
def get_vm_os_type(ticket, vmid):
headers = {'Cookie': f"PVEAuthCookie={ticket}"}
url = f"{PROXMOX_HOST}/api2/json/nodes/{NODE}/qemu/{vmid}/agent/get-osinfo"
try:
response = requests.get(url, headers=headers, verify=VERIFY_SSL)
if response.status_code != 200:
return None
data = response.json().get('data', {})
os_info = data.get('result', {})
os_name = os_info.get('name', '').lower()
if 'windows' in os_name:
return 'windows'
return 'linux'
except Exception as e:
print(f"Error reading OS info for VM {vmid}: {e}", file=sys.stderr)
return None
def build_inventory():
ticket, _ = get_auth_ticket()
headers = {'Cookie': f"PVEAuthCookie={ticket}"}
url = f"{PROXMOX_HOST}/api2/json/nodes/{NODE}/qemu"
response = requests.get(url, headers=headers, verify=VERIFY_SSL)
response.raise_for_status()
vms = response.json()['data']
hosts = []
hostvars = {}
for vm in vms:
vmid = vm['vmid']
vm_name = vm.get('name', f'vm-{vmid}')
ip = get_vm_ip(ticket, vmid)
os_type = get_vm_os_type(ticket, vmid)
if ip:
hosts.append(vm_name)
# Default to Linux settings
hostvars[vm_name] = {
'ansible_host': ip,
'ansible_user': 'bordergate',
'ansible_password': 'Password1',
'ansible_connection': 'ssh'
}
if os_type == 'windows':
hostvars[vm_name].update({
'ansible_user': 'Administrator',
'ansible_connection': 'winrm',
'ansible_winrm_transport': 'ntlm',
'ansible_winrm_port': 5985,
'ansible_winrm_server_cert_validation': 'ignore',
'ansible_password': 'Password1'
})
inventory = {
'all': {
'hosts': hosts,
'vars': {}
},
'_meta': {
'hostvars': hostvars
}
}
return inventory
def main():
if len(sys.argv) == 2 and sys.argv[1] == '--list':
inventory = build_inventory()
print(json.dumps(inventory, indent=2))
elif len(sys.argv) == 2 and sys.argv[1] == '--host':
print(json.dumps({}))
else:
print("Usage: inventory.py --list|--host <hostname>")
sys.exit(1)
if __name__ == '__main__':
main()
Log into the director system (which has access to all configured networks), and test the dynamic inventory management is working.
bordergate@N0-DIRECTOR:~$ sudo apt install ansible sshpass
bordergate@N0-DIRECTOR:~$ export ANSIBLE_HOST_KEY_CHECKING=False
bordergate@N0-DIRECTOR:~$ ansible -i ./inventory.py N2-DEMETER -m win_ping
N2-DEMETER | SUCCESS => {
"changed": false,
"ping": "pong"
}
bordergate@N0-DIRECTOR:~$ ansible -i ./inventory.py N1-ZEUS -m ping
N1-ZEUS | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"ping": "pong"
}
Adding Vulnerabilities
N1-ZEUS
This system has the following vulnerabilities.
- SNMP v1 configuration that shows the network 2 range
- IP routing is enabled, so the system will forward traffic to network 2
- The system has a web server listening that’s only accessible from network 2. This contains credentials to access the system using SSH
Create an Ansible configuration (CONFIGS/N1-ZEUS.yaml)
- hosts: N1-ZEUS
user: root
become: true
tasks:
- name: Set configuration file path
set_fact:
config_file_path: '../FILES/N1-ZEUS/'
- name: install nginx
apt: pkg=nginx state=present
- name: install snmpd
apt: pkg=snmpd state=present
- name: Enable IP forwarding
ansible.builtin.shell: echo 1 > /proc/sys/net/ipv4/ip_forward
- name: Clear previous IPtables rules
ansible.builtin.shell: iptables -F
- name: Enable NAT
ansible.builtin.shell: iptables -t nat -A POSTROUTING -o eth1 -j MASQUERADE
- name: Drop ICMP on input chain
ansible.builtin.shell: iptables -I INPUT -p icmp --icmp-type 8 -j DROP
- name: Drop ICMP on forward chain
ansible.builtin.shell: iptables -I FORWARD -p icmp --icmp-type 8 -j DROP
- name: Drop SMB
ansible.builtin.shell: iptables -I FORWARD -p tcp --dport 445 -j DROP
- name: Drop SSH
ansible.builtin.shell: iptables -I FORWARD -p tcp --dport 22 -j DROP
- name: Remove all 'listen 80' or 'listen 80 default_server' directives
replace:
path: /etc/nginx/sites-available/default
regexp: '^\s*listen\s+80.*;'
replace: ''
- name: Update NGINX to listen only on 172.16.24.7
lineinfile:
path: /etc/nginx/sites-available/default
regexp: '^(\s*)listen\s+.*;'
line: ' listen 172.16.24.7:80;'
state: present
backrefs: yes
- name: Copy SNMP configuration file
copy:
dest: /etc/snmp/snmpd.conf
content: |
sysLocation Sitting on the Dock of the Bay
sysContact Me <me@example.org>
sysServices 72
agentaddress udp:0.0.0.0:161
view systemonly included .1.3.6.1.2.1.1
view systemonly included .1.3.6.1.2.1.25.1
rocommunity public
rocommunity6 public default -V systemonly
rouser authPrivUser authpriv -V systemonly
includeDir /etc/snmp/snmpd.conf.d
- name: Copy sysctl configuration to enable IP forwarding
copy:
dest: /etc/sysctl.conf
content: |
net.ipv4.ip_forward=1
- name: Restart SNMP daemon
service:
name: snmpd
state: started
- name: Restart Nginx
service:
name: nginx
state: reloaded
- name: Ensure zeus user exists
user:
name: zeus
shell: /bin/bash
state: present
create_home: yes
password: "{{ 'ZeusJupiter' | password_hash('sha512') }}"
groups: sudo
append: yes
- name: Deploy robots.txt file
ansible.builtin.copy:
dest: /var/www/html/robots.txt
content: |
zeus:ZeusJupiter
owner: root
group: root
mode: '0644'
force: yes
- name: Create FLAG
copy:
dest: /root/FLAG.txt
content: |
FLAG{5a1684d7d326b054f5e4e6c6e2cd4a9507049b96}
N1-HERA
This system has the following vulnerabilities.
- An open NFS share leaks credentials, which allows access over SSH
- A mis-configured cron job to elevate to root
- The system is duel homed, which provides access to network 2
- hosts: N1-HERA
user: root
become: true
tasks:
- name: Set configuration file path
set_fact:
config_file_path: '../FILES/N1-HERA/'
- name: install nfs-kernel-server
apt: pkg=nfs-kernel-server state=present
- name: Copy NFS information leak
copy:
dest: /etc/exports
content: |
/srv *(ro,sync,subtree_check)
- name: Create user 'hera'
user:
name: hera
password: "{{ 'hercules' | password_hash('sha512') }}"
shell: /bin/bash
home: /home/hera
state: present
create_home: yes
- name: Copy NFS information leak
copy:
dest: /srv/creds.txt
content: |
hera:hercules
- name: Restart NFS daemon
service:
name: nfs-kernel-server.service
state: restarted
- name: Create vulnerable script
copy:
dest: /usr/local/bin/backup.sh
content: |
#!/bin/bash
echo "Backup started..."
owner: root
group: root
mode: '0777'
- name: Make sure script is executable
file:
path: /usr/local/bin/backup.sh
mode: '0777'
- name: Add cron job running as root
cron:
name: "Root cron for backup.sh"
user: root
job: "/usr/local/bin/backup.sh"
minute: "*/1"
- name: Create FLAG
copy:
dest: /root/FLAG.txt
content: |
FLAG{00c5d6c5df2e4a4494e1b2631eae9fbb52607d76}
N1-AEOLUS
This system runs a TFTP server that contains credentials which can be used to SSH into it. In addition, it’s running a DNS server for the pantheon.local domain, which supports zone transfers.
- hosts: N1-AEOLUS
user: root
become: true
tasks:
- name: Set configuration file path
set_fact:
config_file_path: '../FILES/N1-AEOLUS'
# DNS Server Setup
- name: Install BIND9 DNS server
apt:
name: bind9
state: present
update_cache: yes
- name: Configure named.conf.options
copy:
dest: /etc/bind/named.conf.options
content: |
options {
directory "/var/cache/bind";
allow-transfer { any; };
recursion yes;
allow-recursion { any; };
listen-on { any; };
listen-on-v6 { any; };
dnssec-validation no;
};
- name: Configure zone in named.conf.local
copy:
dest: /etc/bind/named.conf.local
content: |
zone "pantheon.local" {
type master;
file "/etc/bind/zones/db.pantheon.local";
allow-transfer { any; };
};
- name: Create zone directory
file:
path: /etc/bind/zones
state: directory
owner: bind
group: bind
mode: '0755'
- name: Create zone file for pantheon.local
copy:
dest: /etc/bind/zones/db.pantheon.local
content: |
;
; BIND data file for pantheon.local
;
$TTL 604800
@ IN SOA ns1.pantheon.local. admin.pantheon.local. (
2 ; Serial
604800 ; Refresh
86400 ; Retry
2419200 ; Expire
604800 ) ; Negative Cache TTL
;
@ IN NS ns1.pantheon.com.
ns1 IN A 172.16.24.99
demeter IN A 172.16.24.70
hermes IN A 172.16.24.55
apollo IN A 172.16.24.75
hades IN A 172.16.24.24
- name: Restart BIND9
service:
name: bind9
state: restarted
enabled: yes
- name: Install atftpd
apt:
name: atftpd
state: present
update_cache: yes
- name: Create TFTP root directory
file:
path: /srv/tftp
state: directory
owner: nobody
group: nogroup
mode: '0755'
- name: Configure atftpd options
copy:
dest: /etc/default/atftpd
content: |
USE_INETD=false
OPTIONS="--daemon --port 69 --retry-timeout 1 --verbose=5 /srv/tftp"
- name: Enable and restart atftpd
service:
name: atftpd
state: restarted
enabled: yes
- name: Create user 'aeolus'
user:
name: aeolus
password: "{{ 'dinlas' | password_hash('sha512') }}"
state: present
shell: /bin/bash
groups: sudo
- name: Copy credentials to TFTP directory
copy:
dest: /srv/tftp/startup-config
content: |
aeolus:dinlas
mode: '0644'
- name: Create FLAG
copy:
dest: /root/FLAG.txt
content: |
FLAG{07ca234f133dcf10cd5812c3faef980f954505d2}
N1-ARES
ARES contains the following vulnerabilities.
- The Guest account is enabled, and allows RDP access.
- A Windows service with weak privileges allows for privilege escalation
- The system is duel homed, allowing access to network 2
- hosts: N1-ARES
tasks:
- name: Set configuration file path
set_fact:
config_file_path: '../FILES/N1-ARES/'
- name: Disable Windows update
win_service:
name: Windows update
start_mode: disabled
state: stopped
- name: Create user 'ares'
win_user:
name: ares
password: 'P@ssw0rd123!'
state: present
groups:
- Users
password_never_expires: yes
user_cannot_change_password: no
- name: Disable Windows firewall
win_firewall:
state: disabled
profiles:
- Domain
- Private
- Public
tags: disable_firewall
- name: Enable guest account
win_command: net user Guest /active:yes
- name: Add Guest to Remote Desktop Users group
win_group_membership:
name: "Remote Desktop Users"
members:
- Guest
state: present
- name: Enable insecure guest logons
win_regedit:
path: HKLM:\Software\Policies\Microsoft\Windows\Lanmanworkstation
name: "AllowInsecureGuestAuth"
data: "1"
type: dword
- name: Add anonymous to everyone group
win_regedit:
path: HKLM:\SYSTEM\CurrentControlSet\Control\Lsa
name: "everyoneincludesanonymous"
data: "1"
type: dword
- name: Disable null session restrictions
win_regedit:
path: HKLM:\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters
name: "restrictnullsessaccess"
data: "0"
type: dword
- name: Enable RDP in the registry
win_regedit:
path: HKLM:\System\CurrentControlSet\Control\Terminal Server
name: fDenyTSConnections
data: 0
type: dword
- name: Ensure RDP service is running and set to auto-start
win_service:
name: TermService
start_mode: auto
state: started
- name: Create service directory
win_file:
path: C:\vulnsvc
state: directory
- name: Download service executable
ansible.windows.win_get_url:
url: http://192.168.24.250/service.exe
dest: C:\vulnsvc\service.exe
- name: Set permissions so 'Users' can overwrite the .exe file
win_acl:
path: C:\vulnsvc\service.exe
user: Users
rights: FullControl
type: allow
state: present
inherit: ContainerInherit,ObjectInherit
- name: Create and start the vulnerable Windows service
win_service:
name: VulnService
display_name: Vulnerable Service
description: Insecure service for privesc testing
path: 'cmd.exe /c C:\vulnsvc\service.exe'
start_mode: auto
state: started
- name: Copy flag file
copy:
dest: C:\Users\Administrator\Desktop\FLAG.txt
content: |
FLAG{2ff3e0b8bb5cdac3a2ea7baca6b48be0ed919de2}
For the vulnerable Windows service, I’m using the following code.
#include <windows.h>
#define SERVICE_NAME "MySampleService"
SERVICE_STATUS_HANDLE g_StatusHandle;
HANDLE g_StopEvent;
void SetStatus(DWORD state) {
SERVICE_STATUS status = {
.dwServiceType = SERVICE_WIN32_OWN_PROCESS,
.dwCurrentState = state,
.dwControlsAccepted = (state == SERVICE_RUNNING) ? SERVICE_ACCEPT_STOP : 0,
.dwWin32ExitCode = 0
};
SetServiceStatus(g_StatusHandle, &status);
}
VOID WINAPI ServiceCtrlHandler(DWORD ctrl) {
if (ctrl == SERVICE_CONTROL_STOP) {
SetStatus(SERVICE_STOP_PENDING);
SetEvent(g_StopEvent);
}
}
VOID WINAPI ServiceMain(DWORD argc, LPTSTR *argv) {
g_StatusHandle = RegisterServiceCtrlHandler(SERVICE_NAME, ServiceCtrlHandler);
if (!g_StatusHandle) return;
SetStatus(SERVICE_START_PENDING);
g_StopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (!g_StopEvent) {
SetStatus(SERVICE_STOPPED);
return;
}
SetStatus(SERVICE_RUNNING);
WaitForSingleObject(g_StopEvent, INFINITE);
CloseHandle(g_StopEvent);
SetStatus(SERVICE_STOPPED);
}
int main() {
SERVICE_TABLE_ENTRY table[] = {
{ SERVICE_NAME, ServiceMain },
{ NULL, NULL }
};
return StartServiceCtrlDispatcher(table) ? 0 : GetLastError();
}
Compile the service using MinGW.
x86_64-w64-mingw32-gcc service.c -o myservice.exe -ladvapi32
N2-APOLLO
APOLLO is a Windows system with VNC configured to auto-login as the administrator.
- hosts: N2-APOLLO
tasks:
- name: Set configuration file path
set_fact:
config_file_path: '../FILES/N2-APOLLO/'
- name: Add a static route back to Network One
ansible.windows.win_command: route -p add 192.168.24.0 mask 255.255.255.0 172.16.24.7
- name: Ensure the "Winlogon" registry path exists
ansible.windows.win_regedit:
path: HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon
name: AutoAdminLogon
type: string
data: "1"
state: present
ignore_errors: yes
- name: Ensure DefaultUserName registry key exists for auto-login
ansible.windows.win_regedit:
path: HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon
name: DefaultUserName
type: string
data: Administrator
state: present
ignore_errors: yes
- name: Ensure DefaultPassword registry key exists for auto-login
ansible.windows.win_regedit:
path: HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon
name: DefaultPassword
type: string
data: "Password1"
state: present
ignore_errors: yes
- name: Ensure ForceAutoLogon registry key exists
ansible.windows.win_regedit:
path: HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon
name: ForceAutoLogon
type: string
data: "1"
state: present
ignore_errors: yes
- name: Ensure DefaultDomainName registry key exists for auto-login
ansible.windows.win_regedit:
path: HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon
name: DefaultDomainName
type: string
data: "WORKGROUP"
state: present
ignore_errors: yes
- name: Disable lock screen
ansible.windows.win_regedit:
path: HKLM:\SOFTWARE\Policies\Microsoft\Windows\Personalization
name: NoLockScreen
type: dword
data: 1
- name: Disable password on wake/resume
ansible.windows.win_regedit:
path: HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI
name: DisableAcrylicBackgroundOnLogon
type: dword
data: 1
- name: Disable Windows update
win_service:
name: Windows update
start_mode: disabled
state: stopped
- name: Download TightVNC MSI
ansible.windows.win_get_url:
url: http://172.16.24.250/tightvnc-2.8.81-gpl-setup-64bit.msi
dest: C:\Windows\Temp\tightvnc-setup.msi
- name: Install TightVNC
ansible.windows.win_package:
path: C:\Windows\Temp\tightvnc-setup.msi
arguments: /quiet
state: present
- name: Allow TightVNC connections with no password
ansible.windows.win_regedit:
path: HKLM:\SOFTWARE\TightVNC\Server
name: UseVncAuthentication
data: 0
type: dword
- name: Ensure TightVNC allows multiple connections
ansible.windows.win_regedit:
path: HKLM:\SOFTWARE\TightVNC\Server
name: AlwaysShared
data: 1
type: dword
- name: TightVNC
win_service:
name: TightVNC Server
start_mode: auto
state: started
- name: Copy flag file
copy:
dest: C:\Users\Administrator\Desktop\FLAG.txt
content: |
FLAG{46e77971a498e89b5ac767dbfe0edadf06ddb4d3}
- name: Reboot the system
win_reboot:
reboot_timeout: 600
N2-DEMETER
A Windows host running Tomcat that can be exploited to gain administrative access to the host.
- name: N2-DEMETER - Tomcat Install
hosts: N2-DEMETER
gather_facts: yes
vars:
java_installer_url: http://172.16.24.250/OpenJDK8U-jdk_x64_windows_hotspot_8u402b06.msi
#https://github.com/adoptium/temurin8-binaries/releases/download/jdk8u402-b06/OpenJDK8U-jdk_x64_windows_hotspot_8u402b06.msi
java_installer_path: C:\Temp\OpenJDK8.msi
java_install_dir: 'C:\Program Files\Eclipse Adoptium\jdk-8.0.402.6-hotspot'
tomcat_version: 8.0.24
tomcat_zip_url: http://172.16.24.250/apache-tomcat-8.0.24-windows-x64.zip
#https://archive.apache.org/dist/tomcat/tomcat-8/v8.0.24/bin/apache-tomcat-8.0.24-windows-x64.zip
install_dir: 'C:\Tomcat'
unzip_dir: 'C:\Tomcat\apache-tomcat-8.0.24'
tasks:
- name: Set configuration file path
set_fact:
config_file_path: '../FILES/N2-DEMETER/'
- name: Add a static route back to Network One
ansible.windows.win_command: route add -p 192.168.24.0 mask 255.255.255.0 172.16.24.7
- name: Disable Windows Defender Real-Time Protection via registry
win_regedit:
path: HKLM:\SOFTWARE\Policies\Microsoft\Windows Defender\Real-Time Protection
name: DisableRealtimeMonitoring
data: 1
type: dword
state: present
- name: Exclude C:\ from Windows Defender scanning
win_shell: "Add-MpPreference -ExclusionPath C:"
- name: Ensure C:\Temp exists
ansible.windows.win_file:
path: C:\Temp
state: directory
- name: Disable Windows update
win_service:
name: Windows update
start_mode: disabled
state: stopped
- name: Download Java JDK 8 installer
ansible.windows.win_get_url:
url: "{{ java_installer_url }}"
dest: "{{ java_installer_path }}"
- name: Install Java JDK 8 silently
ansible.windows.win_package:
path: "{{ java_installer_path }}"
arguments: INSTALL_SILENT=Enable
product_id: ''
state: present
- name: Set JAVA_HOME system environment variable
ansible.windows.win_environment:
name: JAVA_HOME
value: "{{ java_install_dir }}"
level: machine
state: present
- name: Ensure Tomcat install directory exists
ansible.windows.win_file:
path: "{{ install_dir }}"
state: directory
- name: Download Tomcat ZIP
ansible.windows.win_get_url:
url: "{{ tomcat_zip_url }}"
dest: "{{ install_dir }}\\tomcat.zip"
- name: Unzip Tomcat
win_unzip:
src: "{{ install_dir }}\\tomcat.zip"
dest: "{{ install_dir }}"
remote_src: yes
ignore_errors: yes
- name: Configure tomcat-users.xml
win_copy:
content: |
<tomcat-users xmlns="http://tomcat.apache.org/xml"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://tomcat.apache.org/xml tomcat-users.xsd"
version="1.0">
<user username="tomcat" password="tomcat" roles="manager-gui,manager-script"/>
</tomcat-users>
dest: "{{ unzip_dir }}\\conf\\tomcat-users.xml"
- name: Install Tomcat as a Windows Service
ansible.windows.win_shell: |
set "JAVA_HOME={{ java_install_dir }}" && set "CATALINA_HOME={{ unzip_dir }}" && "{{ unzip_dir }}\\bin\\service.bat" install
args:
executable: cmd
register: tomcat_service_install
- name: Show Tomcat service install output
debug:
var: tomcat_service_install.stdout_lines
- name: Start Tomcat service
ansible.windows.win_service:
name: Tomcat8
state: started
start_mode: auto
- name: Copy flag file
copy:
dest: C:\Users\Administrator\Desktop\FLAG.txt
content: |
FLAG{a3d32a92a8b7ea9dce971974e954786ed7a684ba}
N2-HADES
This is a Linux system running MySQL. The root user can login to MySQL remotely, to extract the password for the ‘alice’ user account, which is an administrator.
- hosts: N2-HADES
user: root
become: true
tasks:
- name: Set configuration file path
set_fact:
config_file_path: '../FILES/N2-HADES/'
- name: Add new default gateway
ansible.builtin.command: ip route add default via 172.16.24.1 dev eth0
ignore_errors: yes
- name: Update apt package cache
ansible.builtin.apt:
update_cache: yes
async: 60
poll: 10
- name: install python3 SQL
apt: pkg=python3-pymysql state=present
- name: install MariaDB
apt: pkg=mariadb-server state=present
- name: Copy MariaDB configuration
copy:
dest: /etc/mysql/mariadb.conf.d/50-server.cnf
content: |
[server]
[mysqld]
pid-file = /run/mysqld/mysqld.pid
basedir = /usr
bind-address = 0.0.0.0
expire_logs_days = 10
character-set-server = utf8mb4
collation-server = utf8mb4_general_ci
[embedded]
[mariadb]
[mariadb-10.6]
- name: Restart MariaDB
service:
name: mariadb
state: restarted
- name: Set root password and switch auth plugin to mysql_native_password
ansible.builtin.shell: |
mysql -u root <<EOF
SET PASSWORD FOR 'root'@'localhost' = PASSWORD('MySuperSecureRootPW!');
FLUSH PRIVILEGES;
EOF
args:
executable: /bin/bash
- name: Create /root/.my.cnf with empty password for MySQL root
ansible.builtin.copy:
dest: /root/.my.cnf
content: |
[client]
user=root
password=MySuperSecureRootPW!
owner: root
group: root
mode: '0600'
- name: Create database 'users'
community.mysql.mysql_db:
name: users
state: present
- name: Create user 'alice' with password
mysql_user:
name: alice
password: "DownTheRabbitHole..."
host: '%'
state: present
- name: Create user 'alice'
user:
name: alice
password: "{{ 'DownTheRabbitHole...' | password_hash('sha512') }}"
state: present
shell: /bin/bash
groups: sudo
- name: Grant privileges to 'alice' on 'users' database
mysql_user:
name: alice
host: '%'
priv: "users.*:SELECT,INSERT,UPDATE,DELETE,CREATE,INDEX,DROP,ALTER,CREATE TEMPORARY TABLES,LOCK TABLES"
state: present
- name: Allow remote access to MySQL root without password
mysql_user:
name: root
password: ""
host: '%'
state: present
priv: "*.*:ALL"
- name: Create users table
mysql_query:
query: |
CREATE TABLE IF NOT EXISTS users (
userid INT(11) NOT NULL AUTO_INCREMENT,
username VARCHAR(150) NOT NULL,
password VARCHAR(150) NOT NULL,
PRIMARY KEY (userid)
);
login_db: users
- name: Insert 'alice' into 'users' table
mysql_query:
query: |
INSERT INTO users (username, password)
VALUES ('alice', 'DownTheRabbitHole...')
login_db: users
- name: Insert 'charlie' into 'users' table
mysql_query:
query: |
INSERT INTO users (username, password)
VALUES ('charlie', 'TiCiaMyEaTeN')
login_db: users
- name: Insert 'bob' into 'users' table
mysql_query:
query: |
INSERT INTO users (username, password)
VALUES ('bob', 'cHianTAlAiNO')
login_db: users
- name: Copy FLAG
copy:
dest: /root/FLAG.txt
content: |
FLAG{59e653e13ca373f0bc05b0b449fbd6427c9e0f53}
N2-HERMES
Another Linux host, this time hosting credentials on an FTP server.
- hosts: N2-HERMES
user: root
become: true
tasks:
- name: Set configuration file path
set_fact:
config_file_path: '../FILES/N2-HERMES'
- name: Add new default gateway
ansible.builtin.command: ip route add default via 172.16.24.1 dev eth0
ignore_errors: yes
- name: install vsftpd
apt: pkg=vsftpd state=present
- name: install samba
apt: pkg=samba state=present
- name: install postfix
apt: pkg=postfix state=present
- name: Copy VSFTP configuration
copy:
dest: /etc/vsftpd.conf
content: |
listen=NO
listen_ipv6=YES
anonymous_enable=YES
local_enable=YES
dirmessage_enable=YES
use_localtime=YES
xferlog_enable=YES
connect_from_port_20=YES
secure_chroot_dir=/var/run/vsftpd/empty
pam_service_name=vsftpd
rsa_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem
rsa_private_key_file=/etc/ssl/private/ssl-cert-snakeoil.key
ssl_enable=NO
- name: Create user 'bob'
user:
name: bob
password: "{{ 'Secret123' | password_hash('sha512') }}"
state: present
shell: /bin/bash
groups: sudo
- name: Copy credentials
copy:
dest: /srv/ftp/creds.txt
content: |
bob:Secret123
- name: Restart vsftpd daemon
service:
name: vsftpd
state: restarted
- name: Restart samba daemon
service:
name: smbd
state: restarted
- name: Restart postfix daemon
service:
name: postfix
state: restarted
N3-PROMETHEUS
This is a Linux host that leaks a MD5 hashed password using the finger daemon. For privilege escalation, we’re using a custom SetUID binary that can read arbitrary files.
#include <stdio.h>
#include <iostream>
#include <fstream>
// g++ read_config.c -o read_config
int main(int argc, char* argv[])
{
if (argc == 1)
{
printf("Usage ./read_config <configuration_filename>\n");
}
if (argc == 2)
{
printf("Using configuration file: %s\n",argv[1]);
std::string myText;
std::ifstream MyReadFile(argv[1]);
while (getline (MyReadFile, myText)) {
std::cout << myText;
}
MyReadFile.close();
}
return 0;
}
- name: Configure N3-PROMETHEUS
hosts: N3-PROMETHEUS
become: true
tasks:
- name: Add new default gateway
ansible.builtin.command: ip route add default via 10.0.24.1 dev eth0
ignore_errors: yes
- name: Ensure required packages are installed
apt:
name:
- finger
- fingerd
- openbsd-inetd
state: present
update_cache: yes
- name: Enable finger service in /etc/inetd.conf
lineinfile:
path: /etc/inetd.conf
regexp: '^finger\s+stream'
line: 'finger stream tcp nowait nobody /usr/sbin/tcpd /usr/sbin/in.fingerd'
create: yes
state: present
- name: Ensure inetd is enabled and restarted
systemd:
name: openbsd-inetd
enabled: true
state: restarted
- name: Ensure user exists
user:
name: prometheus
shell: /bin/bash
state: present
create_home: yes
password: "{{ 'Password1' | password_hash('sha512') }}"
- name: Ensure finger can read the plan file
ansible.builtin.shell: |
chmod a+x /home/prometheus/
- name: Create a .plan file for the user
copy:
dest: /home/prometheus/.plan
content: |
MD5:2ac9cb7dc02b3c0083eb70898e549b63
owner: prometheus
group: prometheus
mode: '0644'
- name: Check if tmux session for prometheus exists
ansible.builtin.shell: "tmux has-session -t prometheus_session 2>/dev/null"
register: tmux_session_exists
failed_when: false
ignore_errors: true
become: true
- name: Ensure prometheus has a tmux session
ansible.builtin.shell: |
su - prometheus -c "tmux new-session -d -s prometheus_session 'whoami; sleep 36000'"
when: tmux_session_exists.rc != 0
become: true
ignore_errors: true
- name: Attach tmux session for user
ansible.builtin.shell: "su - prometheus -c 'tmux attach -t prometheus_session'"
when: tmux_session_exists.rc == 0
become: true
- name: Ensure tmux session starts on reboot
cron:
name: 'Start tmux session for prometheus'
user: prometheus
special_time: 'reboot'
job: 'tmux new-session -d -s prometheus_session "whoami; sleep 36000"'
- name: Download read_config to /sbin/
get_url:
url: "http://10.0.24.250/read_config"
dest: "/sbin/read_config"
mode: '4755' # Set SUID root
- name: Create /root/FLAG.txt
copy:
dest: /root/FLAG.txt
content: "FLAG{9de37a38ab55a917a70cbf4adf4ce2f45c147c08}"
Host Configuration
Once all the Ansible configuration files have been created, we can use a bash script (configure_lab.sh) to configure all the hosts at once.
export ANSIBLE_HOST_KEY_CHECKING=False
#Network 1
ansible-playbook -i ./inventory.py CONFIGS/N1-ZEUS.yaml
ansible-playbook -i ./inventory.py CONFIGS/N1-HERA.yaml
ansible-playbook -i ./inventory.py CONFIGS/N1-ARES.yaml
ansible-playbook -i ./inventory.py CONFIGS/N1-AEOLUS.yaml
#Network 2
ansible-playbook -i ./inventory.py CONFIGS/N2-APOLLO.yaml
ansible-playbook -i ./inventory.py CONFIGS/N2-DEMETER.yaml
ansible-playbook -i ./inventory.py CONFIGS/N2-HADES.yaml
ansible-playbook -i ./inventory.py CONFIGS/N2-HERMES.yaml
#Network 3
ansible-playbook -i ./inventory.py CONFIGS/N3-PROMETHEUS.yaml
In Conclusion
At this stage, we have Ansible scripts to add vulnerabilities to the target hosts. Next, we need to work on improving the amount of randomisation in the CTF.