CVE-2021-21315: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection') (CWE-78)
value: The System Information Library for Node.JS (npm package "systeminformation") is an open source collection of functions to retrieve detailed hardware, system and OS information. In systeminformation before version 5.3.1 there is a command injection vulnerability. Problem was fixed in version 5.3.1. As a workaround instead of upgrading, be sure to check or sanitize service parameters that are passed to si.inetLatency(), si.inetChecksite(), si.services(), si.processLoad() ... do only allow strings, reject any arrays. String sanitation works as expected.
CWE description: The product constructs all or part of an OS command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended OS command when it is sent to a downstream component.
CVSS v3 base score: 7.8
Published: 2021-02-16T17:15Z
Repository: https://github.com/sebhildebrandt/systeminformation
Language: JavaScript
Files changed: lib/docker.js, lib/internet.js, lib/network.js, lib/processes.js
Commit date: 2021-02-14 11:59:07 +0100
Commit message: docker, processLoad fixed potential security issue

Diff: diff --git a/lib/docker.js b/lib/docker.js
index 24b56b2f..2dd1e2a9 100644
--- a/lib/docker.js
+++ b/lib/docker.js
@@ -107,10 +107,13 @@ function dockerContainers(all, callback) {
   // fallback - if only callback is given
   if (util.isFunction(all) && !callback) {
     callback = all;
     all = false;
   }
+  if (typeof all !== 'boolean' && all !== undefined) {
+    all = false;
+  }
 
   all = all || false;
   let result = [];
   return new Promise((resolve) => {
     process.nextTick(() => {
@@ -183,20 +186,24 @@ function dockerContainers(all, callback) {
 
 // --------------------------
 // container inspect (for one container)
 
 function dockerContainerInspect(containerID, payload) {
-  containerID = containerID || '';
   return new Promise((resolve) => {
     process.nextTick(() => {
-      if (containerID) {
+      containerID = containerID || '';
+      if (typeof containerID !== 'string') {
+        resolve();
+      }
+      const containerIdSanitized = (util.isPrototypePolluted() ? '' : util.sanitizeShellString(containerID, true)).trim();
+      if (containerIdSanitized) {
 
         if (!_docker_socket) {
           _docker_socket = new DockerSocket();
         }
 
-        _docker_socket.getInspect(containerID.trim(), data => {
+        _docker_socket.getInspect(containerIdSanitized.trim(), data => {
           try {
             resolve({
               id: payload.Id,
               name: payload.Name,
               image: payload.Image,
@@ -323,23 +330,43 @@ function docker_calcBlockIO(blkio_stats) {
 }
 
 function dockerContainerStats(containerIDs, callback) {
 
   let containerArray = [];
-  // fallback - if only callback is given
-  if (util.isFunction(containerIDs) && !callback) {
-    callback = containerIDs;
-    containerArray = ['*'];
-  } else {
-    containerIDs = containerIDs || '*';
-    containerIDs = containerIDs.trim().toLowerCase().replace(/,+/g, '|');
-    containerArray = containerIDs.split('|');
-  }
-
   return new Promise((resolve) => {
     process.nextTick(() => {
 
+      // fallback - if only callback is given
+      if (util.isFunction(containerIDs) && !callback) {
+        callback = containerIDs;
+        containerArray = ['*'];
+      } else {
+        containerIDs = containerIDs || '*';
+        if (typeof containerIDs !== 'string') {
+          if (callback) { callback([]); }
+          return resolve([]);
+        }
+        let containerIDsSanitized = '';
+        containerIDsSanitized.__proto__.toLowerCase = util.stringToLower;
+        containerIDsSanitized.__proto__.replace = util.stringReplace;
+        containerIDsSanitized.__proto__.trim = util.stringTrim;
+
+        const s = (util.isPrototypePolluted() ? '' : util.sanitizeShellString(containerIDs, true)).trim();
+        for (let i = 0; i <= 2000; i++) {
+          if (!(s[i] === undefined)) {
+            s[i].__proto__.toLowerCase = util.stringToLower;
+            const sl = s[i].toLowerCase();
+            if (sl && sl[0] && !sl[1]) {
+              containerIDsSanitized = containerIDsSanitized + sl[0];
+            }
+          }
+        }
+
+        containerIDsSanitized = containerIDsSanitized.trim().toLowerCase().replace(/,+/g, '|');
+        containerArray = containerIDs.split('|');
+      }
+
       const result = [];
 
       const workload = [];
       if (containerArray.length && containerArray[0].trim() === '*') {
         containerArray = [];
@@ -442,21 +469,26 @@ exports.dockerContainerStats = dockerContainerStats;
 
 // --------------------------
 // container processes (for one container)
 
 function dockerContainerProcesses(containerID, callback) {
-  containerID = containerID || '';
   let result = [];
   return new Promise((resolve) => {
     process.nextTick(() => {
-      if (containerID) {
+      containerID = containerID || '';
+      if (typeof containerID !== 'string') {
+        resolve(result);
+      }
+      const containerIdSanitized = (util.isPrototypePolluted() ? '' : util.sanitizeShellString(containerID, true)).trim();
+
+      if (containerIdSanitized) {
 
         if (!_docker_socket) {
           _docker_socket = new DockerSocket();
         }
 
-        _docker_socket.getProcesses(containerID, data => {
+        _docker_socket.getProcesses(containerIdSanitized, data => {
           /**
            * @namespace
            * @property {Array}  Titles
            * @property {Array}  Processes
            **/
diff --git a/lib/internet.js b/lib/internet.js
index 21936b6c..c71f80dc 100644
--- a/lib/internet.js
+++ b/lib/internet.js
@@ -38,11 +38,11 @@ function inetChecksite(url, callback) {
         url: url,
         ok: false,
         status: 404,
         ms: null
       };
-      if (typeof url !== "string") {
+      if (typeof url !== 'string') {
         if (callback) { callback(result); }
         return resolve(result);
       }
       let urlSanitized = '';
       const s = util.sanitizeShellString(url, true);
@@ -129,11 +129,11 @@ function inetLatency(host, callback) {
 
   host = host || '8.8.8.8';
 
   return new Promise((resolve) => {
     process.nextTick(() => {
-      if (typeof host !== "string") {
+      if (typeof host !== 'string') {
         if (callback) { callback(null); }
         return resolve(null);
       }
       let hostSanitized = '';
       const s = (util.isPrototypePolluted() ? '8.8.8.8' : util.sanitizeShellString(host, true)).trim();
diff --git a/lib/network.js b/lib/network.js
index 4e10af7c..34cfa0af 100644
--- a/lib/network.js
+++ b/lib/network.js
@@ -971,23 +971,33 @@ function calcNetworkSpeed(iface, rx_bytes, tx_bytes, operstate, rx_dropped, rx_e
 }
 
 function networkStats(ifaces, callback) {
 
   let ifacesArray = [];
-  // fallback - if only callback is given
-  if (util.isFunction(ifaces) && !callback) {
-    callback = ifaces;
-    ifacesArray = [getDefaultNetworkInterface()];
-  } else {
-    ifaces = ifaces || getDefaultNetworkInterface();
-    ifaces = ifaces.trim().toLowerCase().replace(/,+/g, '|');
-    ifacesArray = ifaces.split('|');
-  }
 
   return new Promise((resolve) => {
     process.nextTick(() => {
 
+      // fallback - if only callback is given
+      if (util.isFunction(ifaces) && !callback) {
+        callback = ifaces;
+        ifacesArray = [getDefaultNetworkInterface()];
+      } else {
+        if (typeof ifaces !== 'string' && ifaces !== undefined) {
+          if (callback) { callback([]); }
+          return resolve([]);
+        }
+        ifaces = ifaces || getDefaultNetworkInterface();
+
+        ifaces.__proto__.toLowerCase = util.stringToLower;
+        ifaces.__proto__.replace = util.stringReplace;
+        ifaces.__proto__.trim = util.stringTrim;
+
+        ifaces = ifaces.trim().toLowerCase().replace(/,+/g, '|');
+        ifacesArray = ifaces.split('|');
+      }
+
       const result = [];
 
       const workload = [];
       if (ifacesArray.length && ifacesArray[0].trim() === '*') {
         ifacesArray = [];
diff --git a/lib/processes.js b/lib/processes.js
index 772b932c..83a90ccc 100644
--- a/lib/processes.js
+++ b/lib/processes.js
@@ -97,11 +97,11 @@ function services(srv, callback) {
     srv = '';
   }
 
   return new Promise((resolve) => {
     process.nextTick(() => {
-      if (typeof srv !== "string") {
+      if (typeof srv !== 'string') {
         if (callback) { callback([]); }
         return resolve([]);
       }
 
       if (srv) {
@@ -890,10 +890,17 @@ function processLoad(proc, callback) {
   }
 
   return new Promise((resolve) => {
     process.nextTick(() => {
 
+      proc = proc || '';
+
+      if (typeof proc !== 'string') {
+        if (callback) { callback([]); }
+        return resolve([]);
+      }
+
       let processesString = '';
       processesString.__proto__.toLowerCase = util.stringToLower;
       processesString.__proto__.replace = util.stringReplace;
       processesString.__proto__.trim = util.stringTrim;
 

Vulnerable code:   containerID = containerID || '';
      if (containerID) {
        _docker_socket.getInspect(containerID.trim(), data => {
  // fallback - if only callback is given
  if (util.isFunction(containerIDs) && !callback) {
    callback = containerIDs;
    containerArray = ['*'];
  } else {
    containerIDs = containerIDs || '*';
    containerIDs = containerIDs.trim().toLowerCase().replace(/,+/g, '|');
    containerArray = containerIDs.split('|');
  }

  containerID = containerID || '';
      if (containerID) {
        _docker_socket.getProcesses(containerID, data => {
      if (typeof url !== "string") {
      if (typeof host !== "string") {
  // fallback - if only callback is given
  if (util.isFunction(ifaces) && !callback) {
    callback = ifaces;
    ifacesArray = [getDefaultNetworkInterface()];
  } else {
    ifaces = ifaces || getDefaultNetworkInterface();
    ifaces = ifaces.trim().toLowerCase().replace(/,+/g, '|');
    ifacesArray = ifaces.split('|');
  }
      if (typeof srv !== "string") {
Fixed code:   if (typeof all !== 'boolean' && all !== undefined) {
    all = false;
  }
      containerID = containerID || '';
      if (typeof containerID !== 'string') {
        resolve();
      }
      const containerIdSanitized = (util.isPrototypePolluted() ? '' : util.sanitizeShellString(containerID, true)).trim();
      if (containerIdSanitized) {
        _docker_socket.getInspect(containerIdSanitized.trim(), data => {
      // fallback - if only callback is given
      if (util.isFunction(containerIDs) && !callback) {
        callback = containerIDs;
        containerArray = ['*'];
      } else {
        containerIDs = containerIDs || '*';
        if (typeof containerIDs !== 'string') {
          if (callback) { callback([]); }
          return resolve([]);
        }
        let containerIDsSanitized = '';
        containerIDsSanitized.__proto__.toLowerCase = util.stringToLower;
        containerIDsSanitized.__proto__.replace = util.stringReplace;
        containerIDsSanitized.__proto__.trim = util.stringTrim;

        const s = (util.isPrototypePolluted() ? '' : util.sanitizeShellString(containerIDs, true)).trim();
        for (let i = 0; i <= 2000; i++) {
          if (!(s[i] === undefined)) {
            s[i].__proto__.toLowerCase = util.stringToLower;
            const sl = s[i].toLowerCase();
            if (sl && sl[0] && !sl[1]) {
              containerIDsSanitized = containerIDsSanitized + sl[0];
            }
          }
        }

        containerIDsSanitized = containerIDsSanitized.trim().toLowerCase().replace(/,+/g, '|');
        containerArray = containerIDs.split('|');
      }

      containerID = containerID || '';
      if (typeof containerID !== 'string') {
        resolve(result);
      }
      const containerIdSanitized = (util.isPrototypePolluted() ? '' : util.sanitizeShellString(containerID, true)).trim();

      if (containerIdSanitized) {
        _docker_socket.getProcesses(containerIdSanitized, data => {
      if (typeof url !== 'string') {
      if (typeof host !== 'string') {
      // fallback - if only callback is given
      if (util.isFunction(ifaces) && !callback) {
        callback = ifaces;
        ifacesArray = [getDefaultNetworkInterface()];
      } else {
        if (typeof ifaces !== 'string' && ifaces !== undefined) {
          if (callback) { callback([]); }
          return resolve([]);
        }
        ifaces = ifaces || getDefaultNetworkInterface();

        ifaces.__proto__.toLowerCase = util.stringToLower;
        ifaces.__proto__.replace = util.stringReplace;
        ifaces.__proto__.trim = util.stringTrim;

        ifaces = ifaces.trim().toLowerCase().replace(/,+/g, '|');
        ifacesArray = ifaces.split('|');
      }

      if (typeof srv !== 'string') {
      proc = proc || '';

      if (typeof proc !== 'string') {
        if (callback) { callback([]); }
        return resolve([]);
      }