Executive Summary
A critical broken access control vulnerability (CVE-2026-53546) in Termix allows any authenticated user to hijack SSH sessions belonging to other users on the same instance. Rated CVSS 9.6 (Critical), the flaw stems from the Termix terminal WebSocket accepting a user-controlled hostConfig.id parameter without verifying that the requesting user owns or has been granted access to the referenced host. This enables an attacker to decrypt and use another user's SSH credentials to establish a fully authenticated shell session to any server managed by any user on the platform. All versions of Termix prior to 2.3.2 are affected.
Vulnerability Overview
| Attribute | Value |
|---|---|
| CVE ID | CVE-2026-53546 |
| CVSS Score | 9.6 (Critical) |
| Attack Vector | Network |
| Privileges Required | Low (any authenticated user) |
| User Interaction | None |
| Scope | Changed |
| Confidentiality / Integrity / Availability | High / High / High |
| CWE | CWE-639: Authorization Bypass Through User-Controlled Key |
| Fixed In | Termix 2.3.2 |
Technical Analysis
The Termix terminal WebSocket initiates SSH connections based on a hostConfig.id value supplied by the client. The host resolution logic in src/backend/ssh/host-resolver.ts resolves the requested host by matching the supplied ID against all host records across all users — by display name or username@ip format — without verifying that the requesting user owns or has been granted access to that host.
When a match is found, the resolver decrypts the matched host's stored credentials using the real owner's encryption key and uses them to establish an SSH connection — handing the attacker a fully authenticated session to another user's server.
Vulnerable Resolution Flow
// VULNERABLE — resolves host across all users, no ownership check
const host = await db.hosts.findFirst({
where: {
OR: [
{ displayName: tunnelConfig.endpointHost },
{ connection: `${tunnelConfig.username}@${tunnelConfig.endpointIP}` }
]
// Missing: AND { ownerId: req.user.id }
}
});
// Credentials decrypted using OWNER's key and handed to ATTACKER
const decrypted = await decrypt(host.encryptedPassword, host.owner.encryptionKey);Exploitation
An attacker with one Termix account can:
- Guess or enumerate another user's host display name or
username@ip(both low-entropy on shared instances) - Initiate a terminal WebSocket session supplying that host as
hostConfig.id - Receive a fully authenticated SSH session to the victim's server using the victim's stored credentials
No knowledge of the victim's actual password is required — Termix decrypts and applies the credentials transparently.
Affected Versions
| Product | Affected Versions | Fixed Version |
|---|---|---|
| Termix | < 2.3.2 | 2.3.2 |
Attack Flow
1. Attacker authenticates to shared Termix instance with their own account
2. Enumerates or guesses victim's host display name (e.g. "prod-web-01")
3. Opens terminal WebSocket and sets hostConfig.id to victim's host name
4. host-resolver.ts resolves the host — matches victim's record, no owner check
5. Victim's stored SSH password is decrypted using owner's key
6. Termix establishes authenticated SSH session on behalf of attacker
7. Attacker has full shell access to victim's server without knowing credentialsImpact
| Impact Category | Description |
|---|---|
| Unauthorized SSH Access | Full terminal access to any server managed by any user |
| Credential Exposure | Victim's SSH credentials are decrypted and applied on attacker's behalf |
| Cross-User Pivot | One compromised low-privilege account exposes all servers in the instance |
| Data Exfiltration | Access to all data on victim-managed servers |
| Privilege Escalation | Attacker can target admin-managed servers via the bypass |
Remediation
Immediate Action: Upgrade to Termix 2.3.2
# Docker users
docker pull termix/termix:2.3.2
# Node.js direct install
npm install termix@2.3.2The patch adds an ownership validation step in host-resolver.ts. Before decrypting any credentials or establishing any connection, the resolver now confirms that the requesting user's ID matches the host record's ownerId:
// Fixed — ownership check enforced before credential decryption
const host = await db.hosts.findFirst({
where: {
id: hostConfig.id,
ownerId: req.user.id // Ownership enforced
}
});
if (!host) throw new ForbiddenError('Access denied');If Immediate Patching Is Not Possible
- Limit instance access: Restrict Termix to single-user deployments or tightly controlled user groups
- Network isolation: Place Termix behind a firewall visible only to trusted users
- Audit WebSocket connections: Monitor terminal WebSocket initiation logs for cross-user host references
- Rotate credentials: If shared-instance use is suspected, rotate all stored SSH credentials
Detection
| Indicator | Description |
|---|---|
| Terminal WebSocket connections to hosts not owned by the connecting user | Direct exploitation indicator |
| SSH session activity on servers outside normal user working hours | Post-exploitation lateral movement |
| Host record access patterns across user boundaries | Enumeration attempts |
Post-Remediation Steps
- Upgrade to Termix 2.3.2 and verify the ownership check is in effect
- Audit terminal WebSocket logs for any prior cross-user host access
- Rotate SSH credentials for all hosts where unauthorized access may have occurred
- Review audit trails on affected servers for unauthorized commands
- Enforce dedicated (single-user) Termix instances for sensitive environments