CVE-2020-7778: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection') (CWE-78)
value: This affects the package systeminformation before 4.30.2. The attacker can overwrite the properties and functions of an object, which can lead to executing OS commands.
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.3
Published: 2020-11-26T11:15Z
Repository: https://github.com/sebhildebrandt/systeminformation
Language: JavaScript
Files changed: CHANGELOG.md, docs/history.html, docs/index.html, lib/internet.js, lib/network.js, lib/processes.js, lib/util.js
Commit date: 2020-11-25 04:41:31 +0100
Commit message: security update (prototype pollution prevention)

Diff: diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6e4e41e..703c71b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -28,10 +28,12 @@ For major (breaking) changes - version 3 and 2 see end of page.
 
 ## Version history
 
 | Version        | Date           | Comment  |
 | -------------- | -------------- | -------- |
+| 4.30.2         | 2020-11-25     | security update (prototype pollution prevention) |
+| 4.30.1         | 2020-11-12     | updated docs |
 | 4.30.0         | 2020-11-12     | `get()` possibility to provide params |
 | 4.29.3         | 2020-11-09     | `blockdevices()` catch errors adapted for just one line |
 | 4.29.2         | 2020-11-09     | `blockdevices()` catch errors |
 | 4.29.1         | 2020-11-08     | `cpu()`, `system()` better parsing Raspberry Pi revision codes |
 | 4.29.0         | 2020-11-08     | `fsSize()` correct fs type detection macOS (HFS, APFS, NFS) |
diff --git a/docs/history.html b/docs/history.html
index 4b5c158..efa636c 100644
--- a/docs/history.html
+++ b/docs/history.html
@@ -81,10 +81,20 @@
                     <th scope="col">Date</th>
                     <th scope="col">Comment</th>
                   </tr>
                 </thead>
                 <tbody>
+                  <tr>
+                    <th scope="row">4.30.2</th>
+                    <td>2020-11-25</td>
+                    <td>security update (prototype pollution prevention)</td>
+                  </tr>
+                  <tr>
+                    <th scope="row">4.30.1</th>
+                    <td>2020-11-12</td>
+                    <td>updated docs</td>
+                  </tr>
                   <tr>
                     <th scope="row">4.30.0</th>
                     <td>2020-11-11</td>
                     <td><span class="code">get()</span> added possibility to provide parameters</td>
                   </tr>
diff --git a/docs/index.html b/docs/index.html
index 80cc864..3a3d21f 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -166,11 +166,11 @@
   <header class="bg-image-full">
     <div class="container">
       <img class="logo" src="assets/logo.png">
       <div class="title">systeminformation</div>
       <div class="subtitle"><span id="typed"></span></div>
-      <div class="version">Current Version: <span id="version">4.30.0</span></div>
+      <div class="version">Current Version: <span id="version">4.30.2</span></div>
       <button class="btn btn-light" onclick="location.href='https://github.com/sebhildebrandt/systeminformation'">View on Github <i class=" fab fa-github"></i></button>
     </div>
     <div class="down">
       <button class="btn btn-primary mb-2" onclick="location.href='https://www.buymeacoffee.com/systeminfo'">Buy me a coffee&nbsp;&nbsp;<i class="far fa-mug-hot"></i></button>
       <br>Read Documentation<br>
diff --git a/lib/internet.js b/lib/internet.js
index ac81e36..c25f2bb 100644
--- a/lib/internet.js
+++ b/lib/internet.js
@@ -31,25 +31,30 @@ const _sunos = (_platform === 'sunos');
 
 function inetChecksite(url, callback) {
 
   return new Promise((resolve) => {
     process.nextTick(() => {
-
-      let urlSanitized = util.sanitizeShellString(url).toLowerCase();
-      urlSanitized = urlSanitized.replace(/ /g, '');
-      urlSanitized = urlSanitized.replace(/\$/g, '');
-      urlSanitized = urlSanitized.replace(/\(/g, '');
-      urlSanitized = urlSanitized.replace(/\)/g, '');
-      urlSanitized = urlSanitized.replace(/{/g, '');
-      urlSanitized = urlSanitized.replace(/}/g, '');
+      let urlSanitized = '';
+      const s = util.sanitizeShellString(url);
+      for (i = 0; i <= 2000; i++) {
+        if (!(s[i] === undefined ||
+          s[i] === ' ' ||
+          s[i] === '{' ||
+          s[i] === '}')) {
+          const sl = s[i].toLowerCase();
+          if (sl[0] && !sl[1]) {
+            urlSanitized = urlSanitized + sl[i];
+          }
+        }
+      }
       let result = {
         url: urlSanitized,
         ok: false,
         status: 404,
         ms: -1
       };
-      if (urlSanitized) {
+      if (urlSanitized && !util.isPrototypePolluted()) {
         let t = Date.now();
         if (_linux || _freebsd || _openbsd || _netbsd || _darwin || _sunos) {
           let args = ' -I --connect-timeout 5 -m 5 ' + urlSanitized + ' 2>/dev/null | head -n 1 | cut -d " " -f2';
           let cmd = 'curl';
           exec(cmd + args, function (error, stdout) {
@@ -112,11 +117,11 @@ function inetLatency(host, callback) {
     callback = host;
     host = '';
   }
 
   host = host || '8.8.8.8';
-  const hostSanitized = util.sanitizeShellString(host);
+  const hostSanitized = util.isPrototypePolluted() ? '8.8.8.8' : util.sanitizeShellString(host);
 
   return new Promise((resolve) => {
     process.nextTick(() => {
       let cmd;
       if (_linux || _freebsd || _openbsd || _netbsd || _darwin) {
diff --git a/lib/network.js b/lib/network.js
index e134ecd..4c5190d 100644
--- a/lib/network.js
+++ b/lib/network.js
@@ -1039,11 +1039,11 @@ function networkStatsSingle(iface) {
   }
 
   return new Promise((resolve) => {
     process.nextTick(() => {
 
-      const ifaceSanitized = util.sanitizeShellString(iface);
+      const ifaceSanitized = util.isPrototypePolluted() ? '---' : util.sanitizeShellString(iface);
 
       let result = {
         iface: ifaceSanitized,
         operstate: 'unknown',
         rx_bytes: 0,
diff --git a/lib/processes.js b/lib/processes.js
index c423442..b942260 100644
--- a/lib/processes.js
+++ b/lib/processes.js
@@ -101,10 +101,13 @@ function services(srv, callback) {
         let srvString = util.sanitizeShellString(srv);
         srvString = srvString.trim().toLowerCase().replace(/, /g, '|').replace(/,+/g, '|');
         if (srvString === '') {
           srvString = '*';
         }
+        if (util.isPrototypePolluted() && srvString !== '*') {
+          srvString = '------';
+        }
         let srvs = srvString.split('|');
         let result = [];
         let dataSrv = [];
         let allSrv = [];
 
@@ -835,11 +838,11 @@ function processLoad(proc, callback) {
     proc = '';
   }
 
   return new Promise((resolve) => {
     process.nextTick(() => {
-      const procSanitized = util.sanitizeShellString(proc);
+      const procSanitized = util.isPrototypePolluted() ? '' : util.sanitizeShellString(proc);
 
       let result = {
         'proc': procSanitized,
         'pid': -1,
         'cpu': 0,
diff --git a/lib/util.js b/lib/util.js
index a986bad..5f13173 100644
--- a/lib/util.js
+++ b/lib/util.js
@@ -488,33 +488,91 @@ function countLines(lines, startingWith) {
   });
   return uniqueLines.length;
 }
 
 function sanitizeShellString(str) {
-  let result = str || '';
-  result = result.replace(/>/g, '');
-  result = result.replace(/</g, '');
-  result = result.replace(/\*/g, '');
-  result = result.replace(/\?/g, '');
-  result = result.replace(/\[/g, '');
-  result = result.replace(/\]/g, '');
-  result = result.replace(/\|/g, '');
-  result = result.replace(/\`/g, '');
-  result = result.replace(/\$/g, '');
-  result = result.replace(/;/g, '');
-  result = result.replace(/&/g, '');
-  result = result.replace(/\)/g, '');
-  result = result.replace(/\(/g, '');
-  result = result.replace(/\$/g, '');
-  result = result.replace(/#/g, '');
-  result = result.replace(/\\/g, '');
-  result = result.replace(/\t/g, '');
-  result = result.replace(/\n/g, '');
-  result = result.replace(/\"/g, '');
+  const s = str || '';
+  let result = '';
+  for (i = 0; i <= 2000; i++) {
+    if (!(s[i] === undefined ||
+      s[i] === '>' ||
+      s[i] === '<' ||
+      s[i] === '*' ||
+      s[i] === '?' ||
+      s[i] === '[' ||
+      s[i] === ']' ||
+      s[i] === '|' ||
+      s[i] === '˚' ||
+      s[i] === '$' ||
+      s[i] === ';' ||
+      s[i] === '&' ||
+      s[i] === '(' ||
+      s[i] === ')' ||
+      s[i] === ']' ||
+      s[i] === '#' ||
+      s[i] === '\\' ||
+      s[i] === '\t' ||
+      s[i] === '\n' ||
+      s[i] === '"')) {
+      result = result + s[i];
+    }
+  }
   return result;
 }
 
+function isPrototypePolluted() {
+  s = '1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
+  let notPolluted = true;
+  let st = '';
+  notPolluted = notPolluted || !(s.length === 62)
+  const ms = Date.now();
+  if (typeof ms === 'number' && ms > 1600000000000) {
+    const l = ms % 100 + 15;
+    let c = 0;
+    for (i = 0; i < l; i++) {
+      const r = Math.random() * 61.99999999 + 1;
+      const rs = parseInt(Math.floor(r).toString(), 10)
+      const rs2 = parseInt(r.toString().split('.')[0], 10);
+      const q = Math.random() * 61.99999999 + 1;
+      const qs = parseInt(Math.floor(q).toString(), 10)
+      const qs2 = parseInt(q.toString().split('.')[0], 10);
+      notPolluted = notPolluted && !(r === q);
+      notPolluted = notPolluted && rs === rs2 && qs === qs2;
+      st += s[rs - 1];
+    }
+    notPolluted = notPolluted && st.length === l;
+    // string manipulation
+    let p = Math.random() * l * 0.9999999999;
+    let stm = st.substr(0, p) + ' ' + st.substr(p, 2000);
+    let sto = stm.replace(/ /g, '');
+    notPolluted = notPolluted && st === sto;
+    p = Math.random() * l * 0.9999999999;
+    stm = st.substr(0, p) + '{' + st.substr(p, 2000);
+    sto = stm.replace(/{/g, '');
+    notPolluted = notPolluted && st === sto;
+    p = Math.random() * l * 0.9999999999;
+    stm = st.substr(0, p) + '*' + st.substr(p, 2000);
+    sto = stm.replace(/\*/g, '');
+    notPolluted = notPolluted && st === sto;
+    p = Math.random() * l * 0.9999999999;
+    stm = st.substr(0, p) + '$' + st.substr(p, 2000);
+    sto = stm.replace(/\$/g, '');
+    notPolluted = notPolluted && st === sto;
+
+    // lower
+    const stl = st.toLowerCase();
+    notPolluted = notPolluted && (stl.length === l) && stl[l - 1] && !(stl[l])
+    for (i = 0; i < l; i++) {
+      const s1 = st[i];
+      const s2 = stl[i];
+      const s1l = s1.toLowerCase();
+      notPolluted = notPolluted && s1l[0] === s2 && s1l[0] && !(s1l[1]);
+    }
+  }
+  return !notPolluted;
+}
+
 function hex2bin(hex) {
   return ("00000000" + (parseInt(hex, 16)).toString(2)).substr(-8);
 }
 
 function decodePiCpuinfo(lines) {
@@ -745,6 +803,7 @@ exports.countUniqueLines = countUniqueLines;
 exports.countLines = countLines;
 exports.noop = noop;
 exports.isRaspberry = isRaspberry;
 exports.isRaspbian = isRaspbian;
 exports.sanitizeShellString = sanitizeShellString;
+exports.isPrototypePolluted = isPrototypePolluted;
 exports.decodePiCpuinfo = decodePiCpuinfo;

Vulnerable code:       <div class="version">Current Version: <span id="version">4.30.0</span></div>

      let urlSanitized = util.sanitizeShellString(url).toLowerCase();
      urlSanitized = urlSanitized.replace(/ /g, '');
      urlSanitized = urlSanitized.replace(/\$/g, '');
      urlSanitized = urlSanitized.replace(/\(/g, '');
      urlSanitized = urlSanitized.replace(/\)/g, '');
      urlSanitized = urlSanitized.replace(/{/g, '');
      urlSanitized = urlSanitized.replace(/}/g, '');
      if (urlSanitized) {
  const hostSanitized = util.sanitizeShellString(host);
      const ifaceSanitized = util.sanitizeShellString(iface);
      const procSanitized = util.sanitizeShellString(proc);
  let result = str || '';
  result = result.replace(/>/g, '');
  result = result.replace(/</g, '');
  result = result.replace(/\*/g, '');
  result = result.replace(/\?/g, '');
  result = result.replace(/\[/g, '');
  result = result.replace(/\]/g, '');
  result = result.replace(/\|/g, '');
  result = result.replace(/\`/g, '');
  result = result.replace(/\$/g, '');
  result = result.replace(/;/g, '');
  result = result.replace(/&/g, '');
  result = result.replace(/\)/g, '');
  result = result.replace(/\(/g, '');
  result = result.replace(/\$/g, '');
  result = result.replace(/#/g, '');
  result = result.replace(/\\/g, '');
  result = result.replace(/\t/g, '');
  result = result.replace(/\n/g, '');
  result = result.replace(/\"/g, '');
Fixed code: | 4.30.2         | 2020-11-25     | security update (prototype pollution prevention) |
| 4.30.1         | 2020-11-12     | updated docs |
                  <tr>
                    <th scope="row">4.30.2</th>
                    <td>2020-11-25</td>
                    <td>security update (prototype pollution prevention)</td>
                  </tr>
                  <tr>
                    <th scope="row">4.30.1</th>
                    <td>2020-11-12</td>
                    <td>updated docs</td>
                  </tr>
      <div class="version">Current Version: <span id="version">4.30.2</span></div>
      let urlSanitized = '';
      const s = util.sanitizeShellString(url);
      for (i = 0; i <= 2000; i++) {
        if (!(s[i] === undefined ||
          s[i] === ' ' ||
          s[i] === '{' ||
          s[i] === '}')) {
          const sl = s[i].toLowerCase();
          if (sl[0] && !sl[1]) {
            urlSanitized = urlSanitized + sl[i];
          }
        }
      }
      if (urlSanitized && !util.isPrototypePolluted()) {
  const hostSanitized = util.isPrototypePolluted() ? '8.8.8.8' : util.sanitizeShellString(host);
      const ifaceSanitized = util.isPrototypePolluted() ? '---' : util.sanitizeShellString(iface);
        if (util.isPrototypePolluted() && srvString !== '*') {
          srvString = '------';
        }
      const procSanitized = util.isPrototypePolluted() ? '' : util.sanitizeShellString(proc);
  const s = str || '';
  let result = '';
  for (i = 0; i <= 2000; i++) {
    if (!(s[i] === undefined ||
      s[i] === '>' ||
      s[i] === '<' ||
      s[i] === '*' ||
      s[i] === '?' ||
      s[i] === '[' ||
      s[i] === ']' ||
      s[i] === '|' ||
      s[i] === '˚' ||
      s[i] === '$' ||
      s[i] === ';' ||
      s[i] === '&' ||
      s[i] === '(' ||
      s[i] === ')' ||
      s[i] === ']' ||
      s[i] === '#' ||
      s[i] === '\\' ||
      s[i] === '\t' ||
      s[i] === '\n' ||
      s[i] === '"')) {
      result = result + s[i];
    }
  }
function isPrototypePolluted() {
  s = '1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
  let notPolluted = true;
  let st = '';
  notPolluted = notPolluted || !(s.length === 62)
  const ms = Date.now();
  if (typeof ms === 'number' && ms > 1600000000000) {
    const l = ms % 100 + 15;
    let c = 0;
    for (i = 0; i < l; i++) {
      const r = Math.random() * 61.99999999 + 1;
      const rs = parseInt(Math.floor(r).toString(), 10)
      const rs2 = parseInt(r.toString().split('.')[0], 10);
      const q = Math.random() * 61.99999999 + 1;
      const qs = parseInt(Math.floor(q).toString(), 10)
      const qs2 = parseInt(q.toString().split('.')[0], 10);
      notPolluted = notPolluted && !(r === q);
      notPolluted = notPolluted && rs === rs2 && qs === qs2;
      st += s[rs - 1];
    }
    notPolluted = notPolluted && st.length === l;
    // string manipulation
    let p = Math.random() * l * 0.9999999999;
    let stm = st.substr(0, p) + ' ' + st.substr(p, 2000);
    let sto = stm.replace(/ /g, '');
    notPolluted = notPolluted && st === sto;
    p = Math.random() * l * 0.9999999999;
    stm = st.substr(0, p) + '{' + st.substr(p, 2000);
    sto = stm.replace(/{/g, '');
    notPolluted = notPolluted && st === sto;
    p = Math.random() * l * 0.9999999999;
    stm = st.substr(0, p) + '*' + st.substr(p, 2000);
    sto = stm.replace(/\*/g, '');
    notPolluted = notPolluted && st === sto;
    p = Math.random() * l * 0.9999999999;
    stm = st.substr(0, p) + '$' + st.substr(p, 2000);
    sto = stm.replace(/\$/g, '');
    notPolluted = notPolluted && st === sto;

    // lower
    const stl = st.toLowerCase();
    notPolluted = notPolluted && (stl.length === l) && stl[l - 1] && !(stl[l])
    for (i = 0; i < l; i++) {
      const s1 = st[i];
      const s2 = stl[i];
      const s1l = s1.toLowerCase();
      notPolluted = notPolluted && s1l[0] === s2 && s1l[0] && !(s1l[1]);
    }
  }
  return !notPolluted;
}

exports.isPrototypePolluted = isPrototypePolluted;