# REPRO-2026-00291: systeminformation networkInterfaces() Linux source-directive command injection ## Summary Status: published Severity: high Type: security Confidence: high ## Identifiers REPRO ID: REPRO-2026-00291 CVE: CVE-2026-50289 ## Package Name: sebhildebrandt/systeminformation Ecosystem: github Affected: <= 5.31.6 Fixed: 5.31.7 ## Root Cause # CVE-2026-50289 — systeminformation `networkInterfaces()` command injection (Linux) ## Summary On Linux, `sebhildebrandt/systeminformation`'s public `networkInterfaces()` API reaches `lib/network.js` → `getLinuxDHCPNics()` → `checkLinuxDCHPInterfaces('/etc/network/interfaces')`. That helper reads the Debian/Ubuntu network-interface file, follows `source` directives recursively, and in versions `<5.31.7` interpolates the parsed source path **unquoted** into a shell command executed via `child_process.execSync()`: ```js function checkLinuxDCHPInterfaces(file) { let result = []; try { const cmd = `cat ${file} 2> /dev/null | grep 'iface\\|source'`; const lines = execSync(cmd, util.execOptsLinux).toString().split('\n'); ... if (line.toLowerCase().includes('source')) { const file = line.split(' ')[1]; result = result.concat(checkLinuxDCHPInterfaces(file)); // recursive, unquoted } ... ``` Because `${file}` is treated as shell text rather than a filename argument, shell metacharacters inside a `source` target execute arbitrary commands as the Node.js process user. The injected value originates from internal file content (a `source` line in a file reachable through `/etc/network/interfaces` source recursion, e.g. a file under `/etc/network/interfaces.d/*`), not from a direct API argument. ## Impact - **Package/component:** `systeminformation` npm package, `lib/network.js`, `checkLinuxDCHPInterfaces()` (private helper reached by the public `networkInterfaces()` API and public aggregate callers `getStaticData()`/`getAllData()`). - **Affected versions:** `<5.31.7` (verified on 5.31.6 / commit `da1cbf5`). - **Risk level:** High. Arbitrary command execution as the Node.js process user whenever an attacker can write or influence a file reached through the `/etc/network/interfaces` `source` recursion on a Debian/Ubuntu-style Linux host that calls the public network API. ## Impact Parity - **Disclosed/claimed maximum impact:** Arbitrary command execution (code execution) via the public `networkInterfaces()` API on Linux. - **Reproduced impact from this run:** Arbitrary command execution confirmed — a harmless marker oracle file was created by the injected `touch` command only when the vulnerable 5.31.6 build processed the crafted `source` directive, and the call resolved normally (`NETWORK_INTERFACES_RESOLVED`, `marker_exists=true`). - **Parity:** `full`. The proof demonstrates attacker-controlled shell command execution (creation of an attacker-chosen file) through the public API on the vulnerable version, with the fixed version negative control showing no execution while the same benign source chain still parses. - **Not demonstrated:** A destructive payload was intentionally not used (safety constraint); only a harmless marker oracle was employed. ## Root Cause `checkLinuxDCHPInterfaces(file)` builds a shell command with an unquoted JS template-string interpolation of `file`: ```js const cmd = `cat ${file} 2> /dev/null | grep 'iface\\|source'`; const lines = execSync(cmd, util.execOptsLinux).toString().split('\n'); ``` `file` is initially `/etc/network/interfaces` but, on each `source` directive line, the code extracts `line.split(' ')[1]` and recurses into `checkLinuxDCHPInterfaces(file)` with that raw, attacker-influenced value. Since the value is spliced into a shell string (not passed as an argument to `execFileSync`/`readFileSync`), any shell metacharacters in the source target are interpreted by `/bin/sh`. A `source` line such as: ``` source /tmp/pruva_inj_nonexist;touch/tmp/pruva_cve_2026_50289_marker ``` is split by JS on the single space into `file = "/tmp/pruva_inj_nonexist;touch\t/tmp/pruva_cve_2026_50289_marker"` (the literal TAB survives because `split(' ')` only splits on the space character). The shell then parses `cat /tmp/pruva_inj_nonexist;touch\t/tmp/marker 2> /dev/null | grep ...` as two commands separated by `;`, executing `touch /tmp/marker` (TAB is shell whitespace), creating the marker file. **Fix commit:** `bbfddde48672d0ee124fefdb3cb4442fd9dd4f03` (https://github.com/sebhildebrandt/systeminformation/commit/bbfddde48672d0ee124fefdb3cb4442fd9dd4f03) replaced `execSync('cat ... | grep ...')` with `fs.readFileSync(file, { encoding: 'utf8' })` plus an in-JS `.filter((l) => /iface|source/.test(l))`, eliminating the shell entirely while preserving source-directive recursion. ## Reproduction Steps 1. **Script:** `bundle/repro/reproduction_steps.sh` (self-contained, idempotent). 2. **What it does:** - Reuses the prepared project-cache repo (or clones `sebhildebrandt/systeminformation` when no cache exists). - Checks out the vulnerable release `v5.31.6` (`da1cbf5`, parent of the fix) and the fixed release `5.31.7` (`bbfddde`). - Crafts a controlled `/etc/network/interfaces` containing a **benign** `source` directive (functional control pointing to a real DHCP iface config) and a **malicious** `source` directive whose target embeds `;touch` followed by a harmless marker path under `/tmp`. - Invokes **only** the public `si.networkInterfaces(true)` API for each build (no private helper is called directly). - Observes a harmless marker oracle file: created on 5.31.6, absent on 5.31.7. - Restores the original `/etc/network/interfaces`. 3. **Expected evidence:** - `logs/vulnerable_5_31_6_api.log` → `NETWORK_INTERFACES_RESOLVED`, `marker_exists=true`. - `logs/fixed_5_31_7_api.log` → `NETWORK_INTERFACES_RESOLVED`, `marker_exists=false`. - `logs/reproduction_steps.log` → `VERDICT: CONFIRMED`. - `repro/runtime_manifest.json` → `target_path_reached=true`, `confirmed=true`. ## Evidence - **Log files:** - `bundle/logs/reproduction_steps.log` — full run transcript. - `bundle/logs/vulnerable_5_31_6_api.log` — vulnerable API output. - `bundle/logs/fixed_5_31_7_api.log` — fixed API output (negative control). - `bundle/logs/interfaces.backup` — original `/etc/network/interfaces` backup. - **Key excerpts:** Vulnerable (5.31.6): ``` NETWORK_INTERFACES_RESOLVED marker_exists=true iface_count=2 ``` Fixed (5.31.7, same crafted source chain): ``` NETWORK_INTERFACES_RESOLVED marker_exists=false iface_count=2 ``` Summary: ``` vulnerable 5.31.6 marker created : true fixed 5.31.7 marker created : false VERDICT: CONFIRMED - command injection via public networkInterfaces() on 5.31.6; absent on fixed 5.31.7 ``` - **Environment:** Linux, Node v24.18.0, `systeminformation` built from source at commits `da1cbf5` (v5.31.6) and `bbfddde` (5.31.7). No sanitizers; the marker oracle is a real filesystem side effect of an attacker-controlled shell command. ## Recommendations / Next Steps - **Upgrade:** Update to `systeminformation >= 5.31.7`. The fix removes the shell entirely by using `fs.readFileSync`. - **Defense in depth:** For any remaining `execSync`/`exec` call sites in the package that interpolate file-system-derived values, switch to `execFileSync`/`readFileSync` with arguments passed as array elements, or validate paths against an allow-list. - **Testing:** Add a regression test that places a `source` directive with shell metacharacters in a controlled interfaces file and asserts no shell side effect occurs, while a benign `source` chain still parses. ## Additional Notes - **Idempotency:** The script was run twice consecutively; both runs produced `confirmed=true` with identical evidence (`vulnerable marker=true`, `fixed marker=false`). The script backs up and restores the original `/etc/network/interfaces` on every run. - **Safety:** Only a harmless marker oracle (`touch` of an empty file under `/tmp`) was used; no destructive payload. The private `checkLinuxDCHPInterfaces()` helper was never invoked directly — only the public `networkInterfaces()` API was called. - **Scope:** The claim surface is `library_api` (`function_call`), matching the accepted claim contract. The proof exercises the real public API end-to-end on a real Linux runtime with the real package source; the fixed-version negative control confirms the patch blocks the injection while preserving benign `source` parsing. ## Reproduction Details Reproduced: 2026-07-16T11:51:20.182Z Duration: 884 seconds Tool calls: 126 Turns: Unknown Handoffs: 2 ## Quick Verification Run one of these commands to verify locally: pruva-verify REPRO-2026-00291 pruva-verify CVE-2026-50289 Or open in GitHub Codespaces (zero-friction, auto-runs): https://github.com/codespaces/new?ref=repro/REPRO-2026-00291&repo=N3mes1s/pruva-sandbox Or download and run the script manually: curl -O https://api.pruva.dev/v1/reproductions/REPRO-2026-00291/artifacts/bundle/repro/reproduction_steps.sh chmod +x reproduction_steps.sh ./reproduction_steps.sh WARNING: Run in a sandboxed environment. This exploits a real vulnerability. ## References - NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-50289 - Source: https://github.com/sebhildebrandt/systeminformation ## Artifacts - bundle/repro/reproduction_steps.sh (reproduction_script, 8613 bytes) - bundle/repro/rca_report.md (analysis, 8307 bytes) - bundle/vuln_variant/reproduction_steps.sh (reproduction_script, 12544 bytes) - bundle/vuln_variant/rca_report.md (analysis, 11537 bytes) - bundle/vuln_variant/source_identity.json (other, 1428 bytes) - bundle/vuln_variant/root_cause_equivalence.json (other, 1891 bytes) - bundle/logs/reproduction_steps.log (log, 1858 bytes) - bundle/repro/validation_verdict.json (other, 988 bytes) - bundle/repro/runtime_manifest.json (other, 797 bytes) - bundle/logs/vulnerable_5_31_6_api.log (log, 61 bytes) - bundle/logs/fixed_5_31_7_api.log (log, 62 bytes) - bundle/logs/interfaces.backup (other, 75 bytes) - bundle/vuln_variant/validation_verdict.json (other, 2597 bytes) - bundle/vuln_variant/patch_analysis.md (documentation, 5040 bytes) - bundle/vuln_variant/variant_manifest.json (other, 3483 bytes) - bundle/vuln_variant/runtime_manifest.json (other, 2285 bytes) - bundle/logs/C_vuln_5_31_6_api.log (log, 74 bytes) - bundle/logs/A_vuln_5_31_6_api.log (log, 75 bytes) - bundle/logs/A_fixed_5_31_7_api.log (log, 75 bytes) - bundle/logs/B_fixed_5_31_7_api.log (log, 144 bytes) - bundle/logs/B_latest_5_31_17_api.log (log, 144 bytes) ## API Access - JSON: https://api.pruva.dev/v1/reproductions/REPRO-2026-00291 - Script: https://api.pruva.dev/v1/reproductions/REPRO-2026-00291/artifacts/bundle/repro/reproduction_steps.sh - Web: https://pruva.dev/r/REPRO-2026-00291 ## For AI Assistants To help users with this reproduction: 1. The `pruva-verify` command is the simplest way to run the reproduction 2. The script downloads and runs automatically in an isolated temp directory 3. Always recommend running in a VM, container, or disposable environment 4. The root cause explains the technical vulnerability --- Generated by Pruva | https://pruva.dev