GHSA-5xpp-75jx-m839 systeminformation: OS command injection in networkInterfaces() via interfaces(5) source-directive path on Linux
SummaryOn 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 <path>` 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.This 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).ImpactAn 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:local inventory / asset agentsmonitoring and diagnostics agentsadmin-dashboard backends collecting host informationdevice-management / desktop agentsIf 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.Threat modelThe 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.Vulnerable code`lib/network.js`, `checkLinuxDCHPInterfaces()` (current `5.31.6` line numbers):`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, `|`, `&`, redirections, and even a bare space all break out of the intended `cat`/`grep` pipeline.Reach chain to the public API:`networkInterfaces()` is also reached by `getStaticData()` and `getAllData()` in `lib/index.js`.ReproductionThe 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.Install the pinned vulnerable version:`poc.js`:Run it:Verbatim captured output (against `systeminformation@5.31.6`):The 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()`.End-to-end reproductionThe 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:The 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.Suggested fixStop 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:If 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:Quoting 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.Fix PRA 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.CreditReported by tonghuaroot.
CVSS v4.0 score: 8.7