Skip to content
This repository has been archived by the owner on Feb 25, 2021. It is now read-only.

Remote server console control utilizing daemon websocket

High
DaneEveritt published GHSA-8p9f-vqjm-q27g Mar 31, 2020

Package

src/controllers/server.js

Affected versions

< 0.6.12

Patched versions

0.6.13

Description

Impact

A malicious user with enough system knowledge can take control of another server on the same node by utilizing improper permissions checking in the websocket connection for the daemon.

Limitations of Scope

  1. Malicious user must have an account on the Panel and have access to at least one server on the same node as the target.
  2. User must know the full UUID of their target server.
  3. Target server must have received a valid websocket connection from an authorized account at least once since the last Daemon restart.

This bug is not exploitable by unauthenticated users, or users whom do not have access to a server on the same node as their target.

Source

When a new websocket request is initiated with the Daemon, a remote endpoint is hit by the Daemon requesting validation of the token passed. If the token is valid at a system level, the Daemon then moves to check if the server the token belongs to is the same as the server whose websocket connection is being requested.

Initially, if they do not match, an error is returned and nothing else will happen. However, upon successful connection the key is stored in the cache to avoid excessive HTTP calls from the Daemon. This is where the bug comes into play.

Once a valid user connects to a server's websocket, the key is cached in the Daemon. A malicious user can then connect using their own key from a server they control. This key will validate successfully since it does belong to a server, however the subsequent permission checking calls do not validate that the server being accessed is the same server that the key belongs to.

This stems from the cache behavior where the cached key would skip the server UUID checking, and proceed straight through to permission checking.

Patch

The patch for this particular bug involved moving the UUID checking logic out of the key checking logic, and always checking that the server assigned to the key is the same as the server we are checking permissions on.

diff --git a/src/controllers/server.js b/src/controllers/server.js
index 9d125ee..8a47636 100644
--- a/src/controllers/server.js
+++ b/src/controllers/server.js
@@ -140,13 +140,9 @@ class Server extends EventEmitter {
     hasPermission(perm, token, next) {
         const tokenData = Cache.get(`auth:token:${token}`);
         if (_.isNull(tokenData)) {
-            this.validateToken(token, (err, data) => {
+            this.getTokenData(token, (err, data) => {
                 if (err) return next(err);

-                if (_.get(data, 'server') !== this.uuid) {
-                    return next(null, false, 'uuidDoesNotMatch');
-                }
-
                 Cache.put(`auth:token:${token}`, data, data.expires_at);

                 return this.validatePermission(data, perm, next);
@@ -157,16 +153,21 @@ class Server extends EventEmitter {
     }

     validatePermission(data, permission, next) {
+        if (_.get(data, 'server') !== this.uuid) {
+            return next(null, false, 'uuidDoesNotMatch');
+        }
+
         if (!_.isUndefined(permission)) {
             if (_.includes(data.permissions, permission) || _.includes(data.permissions, 's:*')) {
                 // Check Suspension Status
                 return next(null, !this.isSuspended(), 'isSuspended');
             }
         }
+
         return next(null, false, 'isUndefined');
     }

-    validateToken(token, next) {
+    getTokenData(token, next) {
         Request.get({
             url: `${Config.get('remote.base')}/api/remote/authenticate/${token}`,
             headers: {

Workarounds

No known workarounds exist, the only way to protect against this vulnerability is to update your daemon.

Severity

High

CVE ID

No known CVE

Weaknesses

No CWEs