GHSA-5vv4-hvf7-2h46: Command Injection via Unsanitized `locate` Output in `versions()` — systeminformation
Command Injection via Unsanitized `locate` Output in `versions()` — systeminformation**Package:** systeminformation (npm)  
**Tested Version:** 5.30.7  
**Affected Platform:** Linux  
**Author:** Sebastian Hildebrandt  
**Weekly Downloads:** ~5,000,000+  
**Repository:** https://github.com/sebhildebrandt/systeminformation  
**Severity:** Medium  
**CWE:** CWE-78 (OS Command Injection)The Vulnerable Code PathInside the `versions()` function, when detecting the PostgreSQL version on Linux, the code does this:Here's what happens step by step:It runs `locate bin/postgres` to search the filesystem for PostgreSQL binariesIt splits the output by newline and sorts the results alphabeticallyIt takes the **last element** (highest alphabetically)It concatenates that path directly into a new `exec()` call with `+ ' -V'`**No `sanitizeShellString()`. No path validation. No `execFile()`. Raw string concatenation into `exec()`.**The `locate` command reads from a system-wide database (`plocate.db` or `mlocate.db`) that indexes all filenames on the system. If any indexed filename contains shell metacharacters — specifically semicolons — those characters will be interpreted by the shell when passed to `exec()`.ExploitationPrerequisitesFor this vulnerability to be exploitable, the following conditions must be met:**Target system runs Linux** — the vulnerable code path is inside an `if (_linux)` block**`locate` / `plocate` is installed** — common on Ubuntu, Debian, Fedora, RHEL**PostgreSQL binary exists in the locate database** — so `locate bin/postgres` returns results (otherwise the code falls through to a safe `psql -V` fallback)**The attacker can create files on the filesystem** — in any directory that gets indexed by `updatedb`**The locate database gets updated** — `updatedb` runs daily via systemd timer (`plocate-updatedb.timer`) or cron on most distrosStep 1 — Verify the EnvironmentOn the target machine, confirm locate is available and running:Check who owns the locate database:Database is root-owned and updated by root. Regular users cannot update it directly, but `updatedb` runs on a daily schedule and indexes all readable files.Step 2 — Craft the Malicious File PathThe key insight is that **Linux allows semicolons in filenames**, and `exec()` passes strings through `/bin/sh -c` which **interprets semicolons as command separators**.Create a file whose path contains an injected command:Verify it exists:This file needs to end up in the `locate` database. On a real system, this happens automatically when `updatedb` runs overnight. For testing purposes:Then verify locate picks it up:Step 3 — Understand the Sort TrickThe vulnerable code sorts the locate results alphabetically and takes the **last** element:Alphabetically, `/var/` sorts **after** `/usr/`. So our malicious path naturally becomes the selected one:Quick verification:Output:Step 4 — Trigger the VulnerabilityNow when any application using systeminformation calls `versions()` requesting the postgresql version, the injected command fires:Internally, the library builds and executes this command:The shell (`/bin/sh -c`) interprets this as three separate commands:Step 5 — Verify Code ExecutionThe file exists. Arbitrary command execution confirmed.The injected command runs with **whatever privileges the Node.js process has**. In a monitoring dashboard or backend API context, that's typically the application service account.Real-World Attack ScenariosScenario 1 — Shared Hosting / Multi-Tenant ServerA low-privileged user on a shared server creates the malicious file in `/tmp` or their home directory. The hosting provider runs a monitoring agent that uses `systeminformation` for health dashboards. Next time the agent calls `versions()`, the attacker's command executes under the monitoring agent's (higher-privileged) service account.Scenario 2 — CI/CD Pipeline PoisoningA malicious contributor submits a PR that includes a build step creating files with crafted names. If the CI pipeline uses `systeminformation` for environment reporting (common in test harnesses and build dashboards), the injected commands execute in the CI runner context — potentially leaking secrets, tokens, and deployment keys.Scenario 3 — Container / Kubernetes EscapeIn containerized environments where `/var` or `/tmp` sits on a shared volume, a compromised container creates the malicious file. When the host-level monitoring agent (running `systeminformation`) calls `versions()`, the injected command executes on the host, breaking out of the container boundary.Suggested FixReplace `exec()` with `execFile()` for the PostgreSQL binary version check. `execFile()` does not spawn a shell, so metacharacters in the path are treated as literal characters:Additionally, the locate output should be validated against a safe path pattern before use:Disclosure**Reported via:** GitHub Private Security Advisory**Advisory URL:** https://github.com/sebhildebrandt/systeminformation/security/advisories/new**Security Contact:** security@systeminformation.io
CVSS v3.1: 8.8