Skip to content

08. Guacamole

BaddKharma edited this page May 9, 2026 · 17 revisions

🚪 Guacamole

The lab's web-based remote access portal. Apache Guacamole runs in Docker on a dedicated EC2 host, exposes HTTPS publicly, and brokers SSH / RDP / VNC sessions to every other lab machine. It is the only public SSH endpoint in the deployment.

At a glance
Hostname guac
Public URL https://<guac-eip>/guacamole
UI login guacadmin / lab password (from terraform output deployment_info)
Containers guacamole/guacamole, guacamole/guacd, postgres:15
Pre-registered connections Windows (RDP), Mythic / Sliver / Havoc / Redirector / Kali / Guacamole (SSH), Havoc Desktop (VNC), optional Kali (XRDP)
TLS Self-signed cert in front of Nginx; terminates HTTPS, proxies to Guacamole on localhost:8080
Public SSH endpoint guac only (port 22 on Guacamole's Elastic IP)
SSH user admin on every Linux host
SSH auth RSA key from public sources; password from 10.0.0.0/8 and 172.16.0.0/12

Considerations

Why Guacamole and not SSH?

Three reasons:

  1. Single public ingress: The redirector exposes only 80/443 (for C2 callbacks); the seven other lab hosts have no public IPs. Without Guacamole you'd need either: (a) a public SSH endpoint on every host (sprawl + risk), or (b) a separate bastion host (extra cost + complexity). Guacamole is the bastion, plus it gives you RDP and VNC for free.
  2. No client install: Web browser, that's it. Useful when working from a kiosk, a locked-down corporate laptop, an iPad, or any environment where you can't install a full SSH/RDP/VNC client stack.
  3. Centralized session recording (potential): Guacamole supports session recording natively. Not enabled in redStack by default but can be flipped on for training scenarios where you want to review what an operator did during a session.

Tradeoffs

  • Latency: A web-based RDP session has more latency than a native RDP client because every keystroke and frame goes browser → HTTPS → guacd → RDP. Most operators don't notice for terminal work; you do notice for graphical work like the Havoc Katana client.
  • Single point of failure: If guac is down, the entire lab is unreachable from the public internet. Mitigation: Guacamole has its own EIP and an SG rule for direct SSH from localPub_ip. This is a break-glass path for advanced users who need headless access when the portal is unavailable. For most issues, terraform apply -replace=aws_instance.guacamole is the faster fix.
  • Public attack surface: HTTPS to guacadmin/lab_password is exposed to the entire internet. The lab password is randomly generated per deploy (16 characters with special characters) but it's still a credential pair on the public internet. The localPub_ip SG restriction is strongly recommended.

Why Postgres backend?

Guacamole stores its connection database in Postgres rather than MySQL. Postgres handles the auto-creation of connections at deploy time cleanly, and psql is more debuggable than the MySQL alternative. Both work; Postgres was the redStack pick.

Sizing

Default t3.small (2 vCPU, 2 GB RAM) handles 1-2 concurrent operator sessions cleanly. Bump to t3.medium if multiple operators will be working simultaneously or if you enable session recording (which writes captures to disk under /var/lib/guacamole/recordings/).


Configuration

What runs where

guac (Debian 12) [t3.small, EBS gp3 20 GB]
├── nginx                     :443 SSL termination, reverse-proxy to localhost:8080
├── docker
│   ├── guacamole/guacamole   :8080 (web UI + REST API)
│   ├── guacamole/guacd       :4822 (Guacamole daemon: SSH/RDP/VNC client implementation)
│   └── postgres:15           :5432 (connection database)
└── /etc/hosts                pre-populated with all lab hostnames

How connections get registered

Cloud-init runs terraform/setup_scripts/guacamole_setup.sh, which after Guacamole boots:

  1. Logs in to the Guacamole REST API as guacadmin / default password
  2. Changes the password to the lab password
  3. Re-authenticates with the new password to get a fresh token
  4. Issues POST /api/session/data/postgresql/connections for each host

This is why the connections appear automatically on first deploy. If you re-deploy, the database is destroyed and recreated, so connections are re-registered.

Self-signed TLS

The HTTPS cert at /etc/nginx/ssl/ is a 4096-bit RSA cert with the public IP as the SAN. Browsers will show a "not secure" warning on first visit; accept it.

If you want a real Let's Encrypt cert:

  1. Point a DNS A record at the Guacamole EIP.
  2. SSH to guac, run sudo certbot --nginx -d yourdomain.tld.
  3. Re-run on every deploy if you destroy/redeploy.

TLS termination model

Operator browser
   |
   v  HTTPS (self-signed, port 443)
nginx (on guac)
   |
   v  HTTP (port 8080, localhost)
guacamole/guacamole container
   |
   v  Guacamole protocol (port 4822, localhost)
guacd container
   |
   v  SSH / RDP / VNC to target (private IPs)
mythic / sliver / havoc / windows / kali / redirector

Using the Portal

  1. Open the portal URL from terraform output deployment_info.
  2. Allow clipboard access when prompted. On first connection, Guacamole asks for permission to read and write the system clipboard. Allow it so you can copy and paste between your local machine and any Guacamole session.
  3. Login as guacadmin / lab password.
  4. Click any connection to open it in a new browser tab.

Guacamole portal connection list after a clean deploy
[Figure 5.1.1: Guacamole portal after login showing all pre-registered connections (Windows RDP, the SSH connections to each lab host, the Havoc Desktop VNC, and the optional Kali XRDP)]

The Sidebar

Press Ctrl+Alt+Shift inside any active session to open the Guacamole sidebar. This is the control panel for everything outside of keyboard input to the session itself.

Shortcut Action
Ctrl+Alt+Shift Open / close the sidebar
Ctrl+Alt+End Send Ctrl+Alt+Del to a Windows RDP session
Shift+Insert Paste from system clipboard (after opening sidebar once to enable sync)

The sidebar contains:

  • Home icon (top): Returns to the connection list without closing the active session.
  • Clipboard: Paste text to and from the session. Open it once to enable two-way sync.
  • Devices: Shows the GuacShare virtual drive (RDP sessions only).
  • On-screen keyboard: Useful for special characters on non-standard layouts.
  • Zoom: Rescale the session viewport.

Switching sessions

Each connection opens in its own browser tab. Open multiple connections in separate tabs to work across machines simultaneously. Guacamole keeps each session alive independently.

Going back home

From inside any session, press Ctrl+Alt+Shift to open the sidebar, then click the home icon at the top of the sidebar. This returns you to the connection list without terminating the session. The session stays alive and reconnects immediately when you click it again.

GuacShare: file transfer

GuacShare is a virtual drive available only during a Windows (RDP) session. It maps between your browser and C:\Users\Administrator\GuacShare\ on the Windows operator.

Upload (browser → Windows):

  1. Open the sidebar (Ctrl+Alt+Shift) → Devices → GuacShare.
  2. Click the upload icon and select a file from your local machine.
  3. The file lands in C:\Users\Administrator\GuacShare\ on Windows.

Download (Windows → browser):

  1. Move or copy the file to C:\Users\Administrator\GuacShare\ on Windows.
  2. Open the sidebar → Devices → GuacShare.
  3. Click the filename to download it to your local machine.

[!NOTE] GuacShare is only available in RDP sessions with drive mapping enabled. It is not available in SSH or VNC sessions. For SSH-based file transfer, use scp. See SSH Access below.

Clipboard sync

By default, the host clipboard and the session clipboard are isolated. To sync:

  1. Open the sidebar (Ctrl+Alt+Shift) → Clipboard.
  2. Paste text into the box to send it into the session.
  3. Text copied inside the session appears in the box automatically once the panel is open.

SSH Access

The two-mode SSH model

Two facts make the access pattern simple:

  1. The same rs-rsa-key.pem (the AWS key pair from Prerequisites) is authorized on every Linux instance.
  2. sshd on each Linux host has PasswordAuthentication no for public sources, but flips to yes for addresses inside 172.16.0.0/12 or 10.0.0.0/8 (the lab's private space).

In practice:

  • ssh -i rs-rsa-key.pem admin@<GUAC_PUBLIC_IP> works (key auth from public)
  • admin@guac:~$ ssh admin@mythic works (password auth from inside the lab)
  • ssh admin@<MYTHIC_PUBLIC_IP> fails because Mythic has no public IP

Three ways to get a shell

1. Guacamole Web Portal (easiest)

Log in as guacadmin and click any pre-configured SSH connection. No manual auth needed; Guacamole has the lab password baked into each connection profile.

2. SSH directly to Guacamole (the bastion)

ssh -i rs-rsa-key.pem admin@<GUAC_PUBLIC_IP>

You land at admin@guac:~$. From here hop to any internal host by hostname.

3. Jump from your host to any internal box

ssh -i rs-rsa-key.pem -J admin@<GUAC_PUBLIC_IP> admin@mythic
ssh -i rs-rsa-key.pem -J admin@<GUAC_PUBLIC_IP> admin@sliver
ssh -i rs-rsa-key.pem -J admin@<GUAC_PUBLIC_IP> admin@havoc
ssh -i rs-rsa-key.pem -J admin@<GUAC_PUBLIC_IP> admin@redirector
ssh -i rs-rsa-key.pem -J admin@<GUAC_PUBLIC_IP> admin@kali

The same key satisfies both hops. No password prompts.

From inside the lab

From any lab Linux host, hostnames resolve directly via /etc/hosts:

admin@guac:~$ ssh admin@mythic
admin@guac:~$ ssh admin@sliver
admin@guac:~$ ssh admin@havoc
admin@guac:~$ ssh admin@redirector
admin@guac:~$ ssh admin@kali

From PowerShell on Windows:

ssh admin@mythic
ssh admin@kali
scp .\payload.exe admin@havoc:/tmp/

SCP and file transfer

# From your host workstation:
scp -i rs-rsa-key.pem -J admin@<GUAC_PUBLIC_IP> ./localfile admin@mythic:/tmp/

# From inside the lab:
scp ./localfile admin@mythic:/tmp/

Special cases

Kali: The Kali AMI ships with a kali user. redStack renames it to admin during provisioning. Don't SSH as kali; that user no longer exists. See Kali.

Redirector (port 22 closed publicly): Use Guacamole > Redirector (SSH), or jump through Guacamole: ssh -i rs-rsa-key.pem -J admin@<GUAC_PUBLIC_IP> admin@redirector.

Windows operator: No SSH server. Access is RDP only via Guacamole > Windows (RDP). PowerShell on Windows can SSH out to any Linux host once the desktop is loaded.


Connection Reference

Connection Protocol Use for Auth
Windows (RDP) RDP Driving the Windows operator workstation: Mythic UI, agent execution, payload work Auto (Administrator + AWS-decrypted password)
Mythic (SSH) SSH Shell on Mythic host: mythic-cli operations, Docker container management admin + lab password
Sliver (SSH) SSH Shell on Sliver host: launch sliver-client, manage daemon admin + lab password
Havoc (SSH) SSH Shell on Havoc host: build script, teamserver management, log review admin + lab password
Havoc Desktop (VNC) VNC Run the Havoc Katana GUI client (must use this connection, not SSH) Lab password
Redirector (SSH) SSH Shell on the redirector: Apache config, certbot, log review admin + lab password
Guacamole (SSH) SSH Shell on the Guacamole host: container management, nginx config, debugging admin + lab password
Kali (SSH) SSH Shell on Kali: AD tooling, proxychains, install-kali-tools admin + lab password
Kali (XRDP) RDP Kali XFCE desktop (only registered when kali_deployment_mode = "gui") admin + lab password

Troubleshooting

Portal returns 502 Bad Gateway

Nginx is up but can't reach the Guacamole container. Check Docker:

ssh admin@guac
docker ps

Expected: three containers (guacamole, guacd, postgres_guacamole) all Up. If any are restarting, check logs:

docker logs guacamole
docker logs guacd
docker logs postgres_guacamole

Most common cause is Postgres taking longer than expected on first deploy. Wait 2-3 minutes and retry.

Can't log in as guacadmin

The default password is changed to the lab password during cloud-init. If cloud-init was interrupted, try guacadmin/guacadmin. If that works, change the password manually:

  1. Login as guacadmin/guacadmin.
  2. Settings → Users → guacadmin → Change password.
  3. Set it to the lab password from terraform output deployment_info.

Connections list is empty

Cloud-init's connection-registration step failed. Query Postgres to confirm:

ssh admin@guac
docker exec -it postgres_guacamole psql -U guacamole_user -d guacamole_db \
  -c "SELECT connection_id, connection_name, protocol FROM guacamole_connection;"

If empty, re-run the registration script:

sudo bash /opt/redstack/register-connections.sh

Specific connection fails: "connection refused"

  1. Is the target host up? From guac: ping mythic
  2. Is the target port listening? nc -zv mythic 22 / nc -zv windows 3389 / nc -zv havoc 5901
  3. Is the target service running? SSH into the target: systemctl status sshd / systemctl status xrdp

RDP works but GuacShare drive not visible in Windows

The Windows host loads the drive lazily. Open File Explorer → This PC and wait 10-30 seconds. If it still doesn't appear, disconnect and reconnect the RDP session.

Havoc VNC connects but desktop is black

TigerVNC sometimes loses its X session after a host restart. SSH to havoc and restart the service:

sudo systemctl restart havoc-vnc.service

Then reconnect via Guacamole.

Latency feels high

The Guacamole protocol is sensitive to round-trip time. If your laptop is far from the AWS region, browser RDP will feel sluggish. Options:

  1. Deploy to a closer region (aws_region = "eu-west-1" in tfvars).
  2. Use SSH directly (ssh -i rs-rsa-key.pem admin@guac) for terminal work, reserve RDP for graphical needs.

Locked out / forgot the lab password

The lab password is in Terraform state: terraform output deployment_info. If state is gone: terraform destroy && terraform apply.

Can't SSH as kali

Expected. redStack renames the kali user to admin during provisioning. Use admin on all Linux hosts.


← Previous: Lab Inventory | Next: Redirector →


"Modern Attackers Don't Break In – They Log In"

Bret Arsenault, Microsoft CISO (~2018)

Clone this wiki locally