Prototype Pollution
Description
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.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Prototype pollution in systeminformation before 4.30.2 allows an attacker to overwrite object properties and execute arbitrary OS commands.
Vulnerability
Overview
The vulnerability is a prototype pollution flaw in the npm package systeminformation prior to version 4.30.2 [1][2]. By polluting the prototype of a base object, an attacker can overwrite internal properties and functions used by the library. This type of attack leverages JavaScript's dynamic nature, where properties like __proto__, constructor, and prototype can be injected into the prototype chain, affecting all objects that inherit from it [2].
Exploitation
Mechanism
The exploitable code path resides in lib/internet.js, specifically within the inetChecksite() function [3][4]. The attacker can set obj.__proto__.replace to a malicious function that executes arbitrary OS commands via child_process.execSync(). The library's sanitization routine calls String.prototype.replace() (which is itself a function on the prototype) to clean user input, so by polluting that function, the attacker hijacks the sanitization step [4]. The attack requires the attacker to control an object passed to inetChecksite() — no authentication is needed if the application passes attacker-controlled data to this function [1][4].
Impact
Successful exploitation allows arbitrary OS command execution with the privileges of the Node.js process [1][4]. This can lead to full compromise of the server, including data exfiltration, lateral movement, or denial of service. The vulnerability is classified as critical, with a CVSS score reflecting remote code execution potential [1].
Mitigation
The issue is patched in systeminformation version 4.30.5 and later [4]. Users should update immediately. If an upgrade is not possible, avoid passing untrusted input to the inetChecksite() function or any function that calls it. No other workaround is available [2][4].
AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
systeminformationnpm | < 4.30.2 | 4.30.2 |
Affected products
2- systeminformation/systeminformationdescription
Patches
211103a447ab9security update (prototype pollution prevention)
3 files changed · +13 −13
lib/internet.js+2 −2 modified@@ -35,14 +35,14 @@ function inetChecksite(url, callback) { process.nextTick(() => { let urlSanitized = ''; const s = util.sanitizeShellString(url); - for (i = 0; i <= 2000; i++) { + for (let 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]; + urlSanitized = urlSanitized + sl[0]; } } }
lib/network.js+7 −7 modified@@ -1134,13 +1134,13 @@ function networkStatsSingle(iface) { // skip header line // use the second line because it is tied to the NIC instead of the ipv4 or ipv6 address stats = lines[1].replace(/ +/g, ' ').split(' '); - rx_bytes = parseInt(stats[6]); - rx_dropped = parseInt(stats[11]); - rx_errors = parseInt(stats[5]); - tx_bytes = parseInt(stats[9]); - tx_dropped = parseInt(stats[11]); - tx_errors = parseInt(stats[8]); - + const offset = stats.length > 11 ? 1 : 0; + rx_bytes = parseInt(stats[offset + 5]); + rx_dropped = parseInt(stats[offset + 10]); + rx_errors = parseInt(stats[offset + 4]); + tx_bytes = parseInt(stats[offset + 8]); + tx_dropped = parseInt(stats[offset + 10]); + tx_errors = parseInt(stats[offset + 7]); result = calcNetworkSpeed(ifaceSanitized, rx_bytes, tx_bytes, result.operstate, rx_dropped, rx_errors, tx_dropped, tx_errors); } }
lib/util.js+4 −4 modified@@ -492,7 +492,7 @@ function countLines(lines, startingWith) { function sanitizeShellString(str) { const s = str || ''; let result = ''; - for (i = 0; i <= 2000; i++) { + for (let i = 0; i <= 2000; i++) { if (!(s[i] === undefined || s[i] === '>' || s[i] === '<' || @@ -520,15 +520,15 @@ function sanitizeShellString(str) { } function isPrototypePolluted() { - s = '1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' + const 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++) { + for (let 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); @@ -561,7 +561,7 @@ function isPrototypePolluted() { // lower const stl = st.toLowerCase(); notPolluted = notPolluted && (stl.length === l) && stl[l - 1] && !(stl[l]) - for (i = 0; i < l; i++) { + for (let i = 0; i < l; i++) { const s1 = st[i]; const s2 = stl[i]; const s1l = s1.toLowerCase();
73dce8d717casecurity update (prototype pollution prevention)
7 files changed · +112 −33
CHANGELOG.md+2 −0 modified@@ -30,6 +30,8 @@ For major (breaking) changes - version 3 and 2 see end of page. | 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 |
docs/history.html+10 −0 modified@@ -83,6 +83,16 @@ <h3>Full version history</h3> </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>
docs/index.html+1 −1 modified@@ -168,7 +168,7 @@ <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">
lib/internet.js+15 −10 modified@@ -33,21 +33,26 @@ 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'; @@ -114,7 +119,7 @@ function inetLatency(host, callback) { } 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(() => {
lib/network.js+1 −1 modified@@ -1041,7 +1041,7 @@ 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,
lib/processes.js+4 −1 modified@@ -103,6 +103,9 @@ function services(srv, callback) { if (srvString === '') { srvString = '*'; } + if (util.isPrototypePolluted() && srvString !== '*') { + srvString = '------'; + } let srvs = srvString.split('|'); let result = []; let dataSrv = []; @@ -837,7 +840,7 @@ function processLoad(proc, callback) { return new Promise((resolve) => { process.nextTick(() => { - const procSanitized = util.sanitizeShellString(proc); + const procSanitized = util.isPrototypePolluted() ? '' : util.sanitizeShellString(proc); let result = { 'proc': procSanitized,
lib/util.js+79 −20 modified@@ -490,29 +490,87 @@ function countLines(lines, startingWith) { } 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); } @@ -747,4 +805,5 @@ exports.noop = noop; exports.isRaspberry = isRaspberry; exports.isRaspbian = isRaspbian; exports.sanitizeShellString = sanitizeShellString; +exports.isPrototypePolluted = isPrototypePolluted; exports.decodePiCpuinfo = decodePiCpuinfo;
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
7- github.com/advisories/GHSA-8j36-q8x7-pm6qghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2020-7778ghsaADVISORY
- gist.github.com/EffectRenan/b434438938eed0b21b376cedf5c81e80ghsax_refsource_MISCWEB
- github.com/sebhildebrandt/systeminformation/blob/master/lib/internet.jsghsax_refsource_MISCWEB
- github.com/sebhildebrandt/systeminformation/commit/11103a447ab9550c25f1fbec7e6d903720b3fea8%23diff-970ae648187190f86bafc8f193b7538200eba164fad0674428b6487582c089ccghsax_refsource_MISCWEB
- github.com/sebhildebrandt/systeminformation/commit/73dce8d717ca9c3b7b0d0688254b8213b957f0fa%23diff-970ae648187190f86bafc8f193b7538200eba164fad0674428b6487582c089ccghsax_refsource_MISCWEB
- snyk.io/vuln/SNYK-JS-SYSTEMINFORMATION-1043753ghsax_refsource_MISCWEB
News mentions
0No linked articles in our index yet.