What's the vulnerability?

A command injection vulnerability in the wifiNetworks() function allows an attacker to execute arbitrary OS commands via an unsanitized network interface parameter in the retry code path.

Root Cause Analysis

# Root Cause Analysis: GHSA-9c88-49p5-5ggf

## Summary

A command injection vulnerability exists in the `wifiNetworks()` function of the `systeminformation` npm package. The vulnerability allows an attacker to execute arbitrary OS commands via a malicious network interface (`iface`) parameter. The root cause is a logic error in the retry code path: while the `iface` parameter is sanitized before the initial call to `getWifiNetworkListIw()`, the `setTimeout` retry callback (triggered when the initial scan returns empty results) receives and uses the **original unsanitized** `iface` value, which is then passed directly to `execSync()` within a shell command.

## Impact

- **Package:** systeminformation (npm)
- **Affected Versions:** < 5.30.8
- **Fixed Version:** 5.30.8
- **CVE:** CVE-2026-26280
- **CVSS Score:** 8.4 (High)
- **CVSS Vector:** CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
- **CWE:** CWE-78 (Improper Neutralization of Special Elements used in an OS Command)

**Risk:** Any application that passes user-controlled input to `si.wifiNetworks()` is vulnerable to arbitrary command execution with the privileges of the Node.js process. This could lead to complete system compromise.

## Root Cause

The vulnerability is located in `lib/wifi.js` in the `wifiNetworks()` function. The code flow is:

1. **Line 428-436:** The `iface` parameter is sanitized using `util.sanitizeShellString()` and stored in `ifaceSanitized`
2. **Line 437:** The first call uses the sanitized version: `getWifiNetworkListIw(ifaceSanitized)`
3. **Line 438-439:** If this returns `-1` (empty results), a retry is scheduled
4. **Line 440 (VULNERABLE):** The `setTimeout` callback is defined as `setTimeout((iface) => {...}, 4000)` - it takes `iface` as a parameter
5. **Line 441 (VULNERABLE):** Inside the callback, `getWifiNetworkListIw(iface)` is called with the **unsanitized** `iface` parameter

The `getWifiNetworkListIw()` function then executes:
```javascript
execSync(`export LC_ALL=C; iwlist ${iface} scan 2>&1; unset LC_ALL`, util.execOptsLinux)
```

This shell command injection allows arbitrary commands to be executed via the unsanitized `iface` parameter.

**Fix Commit:** https://github.com/sebhildebrandt/systeminformation/commit/22242aa56188f2bffcbd7d265a11e1ebb808b460

The fix changes:
- `setTimeout((iface) => {` → `setTimeout(() => {` (remove parameter)
- `getWifiNetworkListIw(iface)` → `getWifiNetworkListIw(ifaceSanitized)` (use sanitized version)

## Reproduction Steps

The reproduction script is at `repro/reproduction_steps.sh`.

### What the script does:

1. Installs the vulnerable version `systeminformation@5.30.7`
2. Examines the vulnerable code in `lib/wifi.js` (line 440)
3. Creates a PoC that:
   - Sets up a malicious `iface` parameter containing shell injection (`eth0; touch /tmp/pwned`)
   - Demonstrates that `ifaceSanitized` removes special characters
   - Shows the first call uses the sanitized version
   - Simulates the vulnerable retry path where the original unsanitized `iface` is used
   - Confirms command injection by detecting the malicious command

### Expected Evidence:

The script outputs:
```
[PoC] Testing with malicious iface: eth0; touch /tmp/pwned
[PoC] Sanitized iface: eth0 touch /tmp/pwned
[PoC] In setTimeout, iface = eth0; touch /tmp/pwned
[PoC] WARNING: Command injection detected in iface parameter!
[PoC] SUCCESS! Command injection confirmed!
```

## Evidence

### Log Files:

- `logs/vulnerable_code.log` - Contains the vulnerable code snippet from lib/wifi.js line 440
- `logs/repro_output.log` - Contains the full reproduction output with command injection confirmation

### Key Evidence Excerpt:

```
--- Vulnerable code in lib/wifi.js (line 440) ---
              const res = getWifiNetworkListIw(ifaceSanitized);
              if (res === -1) {
                // try again after 4 secs
                setTimeout((iface) => {
                  const res = getWifiNetworkListIw(iface);
                  ...
```

### Reproduction Output:

```
[PoC] Testing with malicious iface: eth0; touch /tmp/pwned
[PoC] Sanitized iface: eth0 touch /tmp/pwned
[PoC] getWifiNetworkListIw called with: eth0 touch /tmp/pwned
[PoC] First call result: -1
[PoC] Simulating vulnerable retry with original unsanitized iface...
[PoC] In setTimeout, iface = eth0; touch /tmp/pwned
[PoC] getWifiNetworkListIw called with: eth0; touch /tmp/pwned
[PoC] WARNING: Command injection detected in iface parameter!
[PoC] Would execute: iwlist eth0; touch /tmp/pwned scan
[PoC] SUCCESS! Command injection confirmed!
```

### Environment:

- Node.js v22.22.0
- npm 10.5.0
- systeminformation@5.30.7 (vulnerable)

## Recommendations / Next Steps

### Immediate Fix:

Upgrade to `systeminformation@5.30.8` or later which contains the fix.

### Testing Recommendations:

1. **Regression Test:** Add a test case that verifies `wifiNetworks()` properly sanitizes the `iface` parameter in all code paths
2. **Fuzzing:** Test with various shell metacharacters: `;`, `&&`, `||`, `|`, `` ` ``, `$()`, etc.
3. **Static Analysis:** Use tools like Semgrep or ESLint security rules to detect similar patterns

### Suggested Additional Fixes:

- Review all `setTimeout` and `setInterval` callbacks that pass user input
- Implement a security policy to sanitize all inputs at entry points, not just before first use
- Consider using parameterized commands or escaping instead of shell string interpolation

## Additional Notes

### Idempotency:

The reproduction script has been run twice successfully with identical results. The script is self-contained and:
- Creates a fresh test environment each run
- Cleans up all test artifacts after completion
- Works from any directory (uses portable `$ROOT` detection)

### Edge Cases:

The vulnerability only triggers when:
1. The first call to `getWifiNetworkListIw(ifaceSanitized)` returns `-1` (resource busy/empty results)
2. The `setTimeout` retry callback is executed (4-second delay)

In environments with working wireless interfaces or where `iwlist` is not available, the vulnerability may not be triggered through normal API usage, but the vulnerable code path remains present.

### Limitations:

The PoC demonstrates the injection capability without actually executing `iwlist` (which requires root privileges and wireless hardware). The simulation proves the vulnerable code path exists and would execute arbitrary commands in a production environment with the required conditions.
One Command

Verify with pruva-verify

Run the Pruva CLI to automatically fetch and execute the reproduction script.

pruva-verify REPRO-2026-00104
or pruva-verify GHSA-9c88-49p5-5ggf
or pruva-verify CVE-2026-26280
Install: curl -fsSL https://pruva.dev/install.sh | sh

Or Run Manually

1

Download the script

curl -O https://pruva.dev/api/v1/reproductions/REPRO-2026-00104/artifacts/reproduction_steps.sh
2

Make executable

chmod +x reproduction_steps.sh
3

Run the script

./reproduction_steps.sh
Run in a VM, container, or disposable environment. This exploits a real vulnerability.

How Pruva Reproduced This

Watch the AI agent's step-by-step process.

Loading session...

Artifacts

No artifacts available