CVE-2020-26245: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection') (CWE-78)
value: npm package systeminformation before version 4.30.5 is vulnerable to Prototype Pollution leading to Command Injection. The issue was fixed with a rewrite of shell sanitations to avoid prototyper pollution problems. The issue is fixed in version 4.30.5. If you cannot upgrade, be sure to check or sanitize service parameter strings that are passed to si.inetChecksite().
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: 9.8
Published: 2020-11-27T20:15Z
Repository: https://github.com/sebhildebrandt/systeminformation
Language: JavaScript
Files changed: CHANGELOG.md, docs/history.html, docs/index.html, lib/index.js, lib/internet.js, lib/network.js, lib/processes.js, lib/util.js
Commit date: 2020-11-26 18:15:15 +0100
Commit message: adapted security update (prototype pollution prevention)

Diff: diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0777810..5f4f31d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -28,10 +28,11 @@ For major (breaking) changes - version 3 and 2 see end of page.
 
 ## Version history
 
 | Version        | Date           | Comment  |
 | -------------- | -------------- | -------- |
+| 4.30.5         | 2020-11-26     | adapted security update (prototype pollution prevention) |
 | 4.30.4         | 2020-11-25     | reverted Object.freeze because it broke some projects |
 | 4.30.3         | 2020-11-25     | security update (prototype pollution prevention) Object.freeze |
 | 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 |
diff --git a/docs/history.html b/docs/history.html
index 77e7c0d..87f3926 100644
--- a/docs/history.html
+++ b/docs/history.html
@@ -81,10 +81,15 @@
                     <th scope="col">Date</th>
                     <th scope="col">Comment</th>
                   </tr>
                 </thead>
                 <tbody>
+                  <tr>
+                    <th scope="row">4.30.5</th>
+                    <td>2020-11-26</td>
+                    <td>adapted security update (prototype pollution prevention)</td>
+                  </tr>
                   <tr>
                     <th scope="row">4.30.4</th>
                     <td>2020-11-25</td>
                     <td>reverted Object.freeze because it broke some projects</td>
                   </tr>
diff --git a/docs/index.html b/docs/index.html
index 085bb83..ac7add9 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.4</span></div>
+      <div class="version">Current Version: <span id="version">4.30.5</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/index.js b/lib/index.js
index d23ed9e..3c77372 100755
--- a/lib/index.js
+++ b/lib/index.js
@@ -19,13 +19,10 @@
 
 // ----------------------------------------------------------------------------------
 // Dependencies
 // ----------------------------------------------------------------------------------
 
-// Object.freeze(String.prototype);
-// Object.freeze(Object.prototype);
-
 const lib_version = require('../package.json').version;
 const util = require('./util');
 const system = require('./system');
 const osInfo = require('./osinfo');
 const cpu = require('./cpu');
diff --git a/lib/internet.js b/lib/internet.js
index f6f7fc5..30c2e12 100644
--- a/lib/internet.js
+++ b/lib/internet.js
@@ -38,10 +38,11 @@ function inetChecksite(url, callback) {
       for (let i = 0; i <= 2000; i++) {
         if (!(s[i] === undefined ||
           s[i] === ' ' ||
           s[i] === '{' ||
           s[i] === '}')) {
+          s[i].__proto__.toLowerCase = util.stringToLower;
           const sl = s[i].toLowerCase();
           if (sl && sl[0] && !sl[1]) {
             urlSanitized = urlSanitized + sl[0];
           }
         }
diff --git a/lib/network.js b/lib/network.js
index c969398..d1e8c60 100644
--- a/lib/network.js
+++ b/lib/network.js
@@ -1038,12 +1038,17 @@ function networkStatsSingle(iface) {
     return perfData;
   }
 
   return new Promise((resolve) => {
     process.nextTick(() => {
-
-      const ifaceSanitized = util.isPrototypePolluted() ? '---' : util.sanitizeShellString(iface);
+      let ifaceSanitized = '';
+      const s = util.isPrototypePolluted() ? '---' : util.sanitizeShellString(iface);
+      for (let i = 0; i <= 2000; i++) {
+        if (!(s[i] === undefined)) {
+          ifaceSanitized = ifaceSanitized + s[i];
+        }
+      }
 
       let result = {
         iface: ifaceSanitized,
         operstate: 'unknown',
         rx_bytes: 0,
diff --git a/lib/processes.js b/lib/processes.js
index b942260..81eadcb 100644
--- a/lib/processes.js
+++ b/lib/processes.js
@@ -96,11 +96,22 @@ function services(srv, callback) {
   }
 
   return new Promise((resolve) => {
     process.nextTick(() => {
       if (srv) {
-        let srvString = util.sanitizeShellString(srv);
+        let srvString = '';
+        srvString.__proto__.toLowerCase = util.stringToLower;
+        srvString.__proto__.replace = util.stringReplace;
+        srvString.__proto__.trim = util.stringTrim;
+
+        const s = util.sanitizeShellString(srv);
+        for (let i = 0; i <= 2000; i++) {
+          if (!(s[i] === undefined)) {
+            srvString = srvString + s[i];
+          }
+        }
+
         srvString = srvString.trim().toLowerCase().replace(/, /g, '|').replace(/,+/g, '|');
         if (srvString === '') {
           srvString = '*';
         }
         if (util.isPrototypePolluted() && srvString !== '*') {
diff --git a/lib/util.js b/lib/util.js
index 877627e..9d6b4e0 100644
--- a/lib/util.js
+++ b/lib/util.js
@@ -46,10 +46,17 @@ function toInt(value) {
     result = 0;
   }
   return result;
 }
 
+
+const stringReplace = new String().replace;
+const stringToLower = new String().toLowerCase;
+const stringToString = new String().toString;
+const stringSubstr = new String().substr;
+const stringTrim = new String().trim;
+
 function isFunction(functionToCheck) {
   let getType = {};
   return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]';
 }
 
@@ -521,10 +528,16 @@ function sanitizeShellString(str) {
 
 function isPrototypePolluted() {
   const s = '1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
   let notPolluted = true;
   let st = '';
+
+  st.__proto__.replace = stringReplace;
+  st.__proto__.toLowerCase = stringToLower;
+  st.__proto__.toString = stringToString;
+  st.__proto__.substr = stringSubstr;
+
   notPolluted = notPolluted || !(s.length === 62)
   const ms = Date.now();
   if (typeof ms === 'number' && ms > 1600000000000) {
     const l = ms % 100 + 15;
     for (let i = 0; i < l; i++) {
@@ -540,10 +553,11 @@ function isPrototypePolluted() {
     }
     notPolluted = notPolluted && st.length === l;
     // string manipulation
     let p = Math.random() * l * 0.9999999999;
     let stm = st.substr(0, p) + ' ' + st.substr(p, 2000);
+    stm.__proto__.replace = stringReplace;
     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, '');
@@ -560,10 +574,11 @@ function isPrototypePolluted() {
     // lower
     const stl = st.toLowerCase();
     notPolluted = notPolluted && (stl.length === l) && stl[l - 1] && !(stl[l])
     for (let i = 0; i < l; i++) {
       const s1 = st[i];
+      s1.__proto__.toLowerCase = stringToLower;
       const s2 = stl ? stl[i] : '';
       const s1l = s1.toLowerCase();
       notPolluted = notPolluted && s1l[0] === s2 && s1l[0] && !(s1l[1]);
     }
   }
@@ -804,5 +819,10 @@ exports.noop = noop;
 exports.isRaspberry = isRaspberry;
 exports.isRaspbian = isRaspbian;
 exports.sanitizeShellString = sanitizeShellString;
 exports.isPrototypePolluted = isPrototypePolluted;
 exports.decodePiCpuinfo = decodePiCpuinfo;
+exports.stringReplace = stringReplace;
+exports.stringToLower = stringToLower;
+exports.stringToString = stringToString;
+exports.stringSubstr = stringSubstr;
+exports.stringTrim = stringTrim;

Vulnerable code:       <div class="version">Current Version: <span id="version">4.30.4</span></div>
// Object.freeze(String.prototype);
// Object.freeze(Object.prototype);


      const ifaceSanitized = util.isPrototypePolluted() ? '---' : util.sanitizeShellString(iface);
        let srvString = util.sanitizeShellString(srv);
Fixed code: | 4.30.5         | 2020-11-26     | adapted security update (prototype pollution prevention) |
                  <tr>
                    <th scope="row">4.30.5</th>
                    <td>2020-11-26</td>
                    <td>adapted security update (prototype pollution prevention)</td>
                  </tr>
      <div class="version">Current Version: <span id="version">4.30.5</span></div>
          s[i].__proto__.toLowerCase = util.stringToLower;
      let ifaceSanitized = '';
      const s = util.isPrototypePolluted() ? '---' : util.sanitizeShellString(iface);
      for (let i = 0; i <= 2000; i++) {
        if (!(s[i] === undefined)) {
          ifaceSanitized = ifaceSanitized + s[i];
        }
      }
        let srvString = '';
        srvString.__proto__.toLowerCase = util.stringToLower;
        srvString.__proto__.replace = util.stringReplace;
        srvString.__proto__.trim = util.stringTrim;

        const s = util.sanitizeShellString(srv);
        for (let i = 0; i <= 2000; i++) {
          if (!(s[i] === undefined)) {
            srvString = srvString + s[i];
          }
        }


const stringReplace = new String().replace;
const stringToLower = new String().toLowerCase;
const stringToString = new String().toString;
const stringSubstr = new String().substr;
const stringTrim = new String().trim;


  st.__proto__.replace = stringReplace;
  st.__proto__.toLowerCase = stringToLower;
  st.__proto__.toString = stringToString;
  st.__proto__.substr = stringSubstr;

    stm.__proto__.replace = stringReplace;
      s1.__proto__.toLowerCase = stringToLower;
exports.stringReplace = stringReplace;
exports.stringToLower = stringToLower;
exports.stringToString = stringToString;
exports.stringSubstr = stringSubstr;
exports.stringTrim = stringTrim;