Offensive Security Research

Remotely Accessible Ludus Ranges

Ludus has been terrific for quickly setting up comprehensive environments for testing security tools, C2 frameworks and any custom developed tool. However, quite often I wasn’t able to use the environment because I wasn’t at home currently or working on a different machine.

Therefore, I looked into making my Ludus ranges available remotely. I strongly dislike opening up any ports on my home network, and I am not always able to have outbound VPN traffic. Reading up on what other homelabbers have done, I opted to expose Ludus using CloudFlare tunnels. This means you have a trust relationship with CloudFlare.

It turned out there already was a tutorial on using CloudFlare with Ludus available on Youtube. However, I prefer written guides, and it was exposing Guacamole to the entire internet. As such, I decided to make a written guide and add mTLS on top as an additional layer of security.

Security Lab Design

To avoid having code scattered around on various different devices and VMs (aka the current state of affairs), I also include a development setup inside my lab. In my setup, I have two different physical machines:

  1. The first machine is dedicated to Ludus and will be running various ranges for testing
  2. The second machine will host the development infrastructure (Windows Dev VM, a Git instance & Git runners) and a Guacamole host (guac) that makes the lab available remotely.

As the lab environment is hosting malware and contains insecure configurations, I have it firewalled off from the rest of my network. Graphically, you can represent the lab as follows:

This blog will only cover the setting up of the guac host to make the lab remotely available. For setting up Ludus itself, I highly recommend following the getting started guide on the Ludus docs.

Before you start, ensure you have the following:

  • A public domain name that uses CloudFlare for DNS (DNS change may take up to 4 hours!)
  • A Ludus host with a range / multiple ranges you want to access remotely

Let’s get started

Before we get started download the Debian 12 ISO and create a fresh VM / bare-metal machine running Debian 12. I’ll refer to this machine as guac host. During the installation I only installed the SSH server software, a desktop environment is not needed.

After installing the base image, connect via ssh. Then run the following commands to install additional software, setup unattended upgrades and needed configuration:

 1su -
 2# Setup sudo
 3apt --assume-yes install curl sudo git vim cron
 4usermod -aG sudo [YOUR_USERNAME]
 5
 6# Setup unattended upgrades
 7apt --assume-yes install unattended-upgrades
 8sed -i 's/\/\/Unattended-Upgrade::InstallOnShutdown "false"/Unattended-Upgrade::InstallOnShutdown "false"/' /etc/apt/apt.conf.d/50unattended-upgrades
 9sed -i 's/\/\/Unattended-Upgrade::Mail ""/Unattended-Upgrade::Mail "root"/' /etc/apt/apt.conf.d/50unattended-upgrades
10sed -i 's/\/\/Unattended-Upgrade::Remove-Unused-Dependencies "false"/Unattended-Upgrade::Remove-Unused-Dependencies "true"/' /etc/apt/apt.conf.d/50unattended-upgrades
11sed -i 's/\/\/Unattended-Upgrade::Automatic-Reboot "false"/Unattended-Upgrade::Automatic-Reboot "true"/' /etc/apt/apt.conf.d/50unattended-upgrades
12sed -i 's/\/\/Unattended-Upgrade::Automatic-Reboot-Time "02:00"/Unattended-Upgrade::Automatic-Reboot-Time "04:00"/' /etc/apt/apt.conf.d/50unattended-upgrades
13sed -i 's/Unattended-Upgrade::Origins-Pattern {/Unattended-Upgrade::Origins-Pattern {\n\t"origin=cloudflared,codename=any,label=cloudflared";/' /etc/apt/apt.conf.d/50unattended-upgrades
14
15printf "APT::Periodic::Update-Package-Lists \"1\";\nAPT::Periodic::Download-Upgradeable-Packages \"1\";\nAPT::Periodic::AutocleanInterval \"7\";\nAPT::Periodic::Unattended-Upgrade \"1\";\n" > /etc/apt/apt.conf.d/20auto-upgrades
16
17exit
18newgrp sudo

Installing Guacamole

To install Guacamole we will use the Easy Guacamole Installer Repo. First clone the repo on your guac host and then open the 1-setup.sh file. In order for the installation to happen silently we’ll modify the variables in the script:

 1#######################################################################################################################
 2# Silent setup options - true/false or specific values below will skip prompt at install. EDIT TO SUIT ################
 3#######################################################################################################################
 4SERVER_NAME="guac"  
 5LOCAL_DOMAIN="guac.local" 
 6INSTALL_MYSQL="true" 
 7SECURE_MYSQL="true"
 8MYSQL_HOST="localhost"
 9MYSQL_PORT=3306                 
10GUAC_DB="guacamole_db"
11GUAC_USER="guacamole_user"   
12MYSQL_ROOT_PWD="[REDACTED]" 
13GUAC_PWD="[REDACTED]"
14GUACD_ACCOUNT="guacd"
15DB_TZ=$(cat /etc/timezone)   
16INSTALL_TOTP="false"
17INSTALL_DUO="false"
18INSTALL_LDAP="false"
19INSTALL_QCONNECT="false" 
20INSTALL_HISTREC="false"
21HISTREC_PATH="/var/lib/guacamole/recordings" 
22GUAC_URL_REDIR="true"
23INSTALL_NGINX="false"
24PROXY_SITE="$DEFAULT_FQDN"
25SELF_SIGN="false"
26RSA_KEYLENGTH="2048"
27CERT_COUNTRY="AU"  
28CERT_STATE="Victoria" 
29CERT_LOCATION="Melbourne" 
30CERT_ORG="Itiligent" 
31CERT_OU="I.T." 
32CERT_DAYS="3650"  
33LETS_ENCRYPT="false"  
34LE_DNS_NAME=""  
35LE_EMAIL="root@localhost"    
36BACKUP_EMAIL="root@localhost"  
37BACKUP_RETENTION="7"  
38RDP_SHARE_HOST=""    
39RDP_SHARE_LABEL="RDP Share"   
40RDP_PRINTER_LABEL="RDP Printer"  
41CRON_DENY_FILE="/etc/cron.deny" 

You can now start the installation by running the first script (do not run with sudo, it will prompt for it):

1bash ./1-setup.sh

To lower the chance of getting hacked while doing the setup, connect to Guacamole via its LAN IP address (eg. http://192.168.1.16:8080/guacamole) and harden it slightly. You should be able to log in with the default creds guacadmin:guacadmin. After logging in, go to settings in the top right corner menu.

Under users create a new user with a strong password. Scroll down to the permissions section and give this user all permissions and hit save.

Now logout and login with the new user. Go back to the user settings and select the guacadmin user, and under Account Restrictions set the Login disabled flag. For good measure, you may also want to remove all permissions in the permissions and connections sections:

Installing Cloudflare Tunnel

Let’s use CloudFlare tunnels to make the guac host available from anywhere. To start, login to your cloudflare account and look for tunnels (use the search feature). Then create a new tunnel, and as the type select cloudflared and give it a memorable name (eg. ludus).

Next, it will prompt you to install a connector. Select the instructions for Debian; they should be similar to the commands below:

1# Add cloudflare gpg key
2sudo mkdir -p --mode=0755 /usr/share/keyrings
3curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg | sudo tee /usr/share/keyrings/cloudflare-main.gpg >/dev/null
4
5# Add this repo to your apt repositories
6echo 'deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared any main' | sudo tee /etc/apt/sources.list.d/cloudflared.list
7
8# install cloudflared
9sudo apt-get update && sudo apt-get install cloudflared

After installing cloudflared we must instruct it to connect to the cloudflare backend. Next to the installation commands, you should also find a command similar to the following that can be used to connect your tunnel to the cloudflare backend:

1sudo cloudflared service install [REDACTED_TOKEN]

To expose guacamole you must add a public hostname to your tunnel. Choose a suitable subdomain (eg. guac) and then set the service type to HTTP and insert the internal IP address of your guac host followed by a colon and the port 8080 (eg. 192.168.1.16:8080). Cloudflare will only make your host available over HTTPS, so no need to worry about that!

Configuring mTLS

Now, to properly protect the lab we’ll use CloudFlare’s WAF to enforce mTLS authentication. CloudFlare provides a convenient PKI that we can use. On your dashboard, select your website then in the left-hand menu select SSL/TLS > Client Certificates. Next, click on the create certificate button to generate a certificate and key.

Store the key in a file named cf-[username].key.pem and the certificate in a file named cf-[username].cert.pem. You’ll need these later to import the certificate in your browser. Before you continue, click on the edit link to add the public host (eg. guac.[YOUR_DOMAIN].com) you created before to the list of mTLS enabled domains:

Now, we must configure the WAF to only allow mTLS authenticated inbound connections to your public host. Go to your website dashboard and then select Security > WAF in the left-hand menu. Then go to custom rules and create two rules.

The first rule is to match any mTLS authenticated connection and set its action to skip:

The second rule is to block any other connection:

Edit: If you also want to check for revoked certificates, then you must update the first rule to the following (cannot be done through the query editor):

(cf.tls_client_auth.cert_verified and (not cf.tls_client_auth.cert_revoked) and http.host eq "guac.[YOUR_DOMAIN].com")

Now you must create your mTLS certificate from the pem files you downloaded earlier. Windows and many browsers can handle the .pfx format, so lets convert to that format (most programs will require the certificate to be password protected, so set a password when prompted):

1openssl pkcs12 -export -out [username].pfx -inkey cf-[username].key.pem -in cf-[username].cert.pem

The last step is to import the certificate in your browser. You can typically find this under settings > security, but use Google to find exact steps for your particular browser. In my case, I found my browser did not handle mTLS well in combination with QUIC / HTTP3, so I had to disable that as well (consult CloudFlare Docs for per browser instructions).

You should now be able to access your Guacamole instance via its public url (eg. https://guac.yourdomain.com).

Setting up WireGuard

Now that you can reach your guac host via CloudFlare, the next step is to allow your guac host to reach the Ludus range.

I have multiple ranges covering different scenario’s. Therefore, I created a dedicated GUAC Ludus user and gave it access to all of my ranges. Run the following commands on your Ludus host:

1ludus --url https://127.0.0.1:8081 user add --name "GUAC" --userid "GUAC" 
2ludus --url https://127.0.0.1:8080 range access grant --target [RANGE_USER_1] --source GUAC
3ludus --url https://127.0.0.1:8080 range access grant --target [RANGE_USER_2] --source GUAC
4...

Note: If you also want to access the lab from an external development box, then you should create a second user for that.

Next, you will need to generate a WireGuard configuration for your GUAC user:

1ludus user wireguard --user GUAC --url https://127.0.0.1:8081 | base64 -w0

Finally, install WireGuard on your guac host. After installation, you can import the config and enable the connection (I am using a systemd service to ensure the system connects automatically):

1sudo apt --assume-yes install wireguard
2echo '[BASE64_ENCODED_CONFIG]' | base64 -d | sudo tee /etc/wireguard/ludus.conf
3sudo systemctl enable wg-quick@ludus --now

Now try to access one of the hosts in your range to verify it is working as intended!

Configuring Guacamole

With all the networking taken care of, we can log into Guacamole using its public URL (eg. https://guac.yourdomain.com/guacamole). Once logged in, go to settings in the top right menu and select the Connections tab.

I suggest you create a new group for every Ludus range you have, and then for every host you want to access, you can create a new connection (rdp, ssh, or vnc).

Sharing Files

Guacamole can virtualize a SMB share to facilitate filesharing from and to the Ludus VMs. First login with the root account on your guac host and execute the following commands:

1mkdir -p /var/lib/guacamole/drives
2chown root:guacd /var/lib/guacamole/drives
3chmod 775 /var/lib/guacamole/drives

Next, enable filesharing on a connection basis using the connection settings. To create a unique shared drive per user set the Drive path to /var/lib/guacamole/drives/${GUAC_USERNAME} in the connection settings. With this setup any uploaded file is automatically shared with all VMs the user has access to. To reveal the file upload menu press ctrl + alt + shift.

References