GHSA-8XWF-RJM4-XVHV

Vulnerability from github – Published: 2026-07-01 21:43 – Updated: 2026-07-01 21:43
VLAI
Summary
oras-go has file store write outside workingDir via symlink traversal
Details

The file content store in oras-go attempts to confine writes to workingDir when AllowPathTraversalOnWrite=false, but the guard is lexical and does not account for symlink traversal. If workingDir contains a symlink path component and an attacker-controlled blob title (via ocispec.AnnotationTitle) targets a path under that symlink, pushFile() can create a file outside workingDir.

relevant links

  • repository: https://github.com/oras-project/oras-go
  • commit: 03243809936cce826494b5506f724c6dc11115b1
  • callsite: content/file/file.go:609 resolveWritePath() (used by pushFile())

vulnerability details

pins: oras-project/oras-go@03243809936cce826494b5506f724c6dc11115b1

as-of: 2026-02-17

policy: GitHub Security Advisory (oras-project/oras-go)

callsite: content/file/file.go:609 resolveWritePath()pushFile()

attacker control: Attacker controls the pushed name (ocispec.AnnotationTitle) and can select a path with a symlink path component under workingDirresolveWritePath() blocks .. via filepath.Rel but does not prevent symlink traversal → pushFile() opens/creates the final path and follows the symlink → a file is created outside workingDir

root cause

resolveWritePath() enforces the write boundary using a filepath.Rel-style check against workingDir. This prevents ../ escapes but is purely lexical and does not resolve symlinks. If a path component under workingDir is a symlink to an external location, the subsequent filesystem operation in pushFile() follows that symlink and performs the write outside workingDir while still passing the lexical boundary check.

attack path

  1. Attacker provides a blob title (via ocispec.AnnotationTitle) that contains a path like out/pwn.txt.
  2. Victim uses oras-go file store with AllowPathTraversalOnWrite=false and a workingDir that contains a symlink directory out -> /some/outside/dir.
  3. The lexical boundary check accepts out/pwn.txt as being under workingDir.
  4. The write follows the symlink and creates /some/outside/dir/pwn.txt.

impact

This is a filesystem boundary bypass that permits writes outside workingDir when a symlink path component exists under workingDir. The concrete security impact depends on the runtime environment (what filesystem locations are writable by the process and what downstream consumers do with the written file), but the intended confinement guarantee is violated.

proof of concept

the attached poc.zip contains a small, self-contained go harness that demonstrates:

  • canonical (vulnerable): prints [CALLSITE_HIT] and [PROOF_MARKER] and shows the file is created outside workingDir
  • control (no symlink component): prints [NC_MARKER] and confirms no outside write occurs

run:

unzip -q -o poc.zip -d /tmp
cd /tmp/poc-F-ORAS-SYMLINK-WRITE-001
make test

expected: when AllowPathTraversalOnWrite=false, file store writes should not be able to escape workingDir, including via symlink traversal.

actual: A symlink path component under workingDir allows writes to escape workingDir even when AllowPathTraversalOnWrite=false.

recommended fix

ensure confinement checks account for symlink traversal. Options include rejecting symlinks in any path component (walk components with os.Lstat), validating the resolved parent directory via EvalSymlinks and enforcing it remains under the resolved workingDir, or using an openat()-style approach so the check and open happen relative to a trusted directory file descriptor.

fix accepted when: The canonical PoC no longer prints [PROOF_MARKER] for the same attacker-controlled inputs.

cheers, Oleh

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Go",
        "name": "oras.land/oras-go/v2"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "2.6.1"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-50162"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-73"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-07-01T21:43:10Z",
    "nvd_published_at": null,
    "severity": "MODERATE"
  },
  "details": "The file content store in `oras-go` attempts to confine writes to `workingDir` when `AllowPathTraversalOnWrite=false`, but the guard is lexical and does not account for symlink traversal. If `workingDir` contains a symlink path component and an attacker-controlled blob title (via `ocispec.AnnotationTitle`) targets a path under that symlink, `pushFile()` can create a file outside `workingDir`.\n\n## relevant links\n\n- repository: https://github.com/oras-project/oras-go\n- commit: 03243809936cce826494b5506f724c6dc11115b1\n- callsite: content/file/file.go:609 `resolveWritePath()` (used by `pushFile()`)\n\n## vulnerability details\n\n**pins:** oras-project/oras-go@03243809936cce826494b5506f724c6dc11115b1\n\n**as-of:** 2026-02-17\n\n**policy:** GitHub Security Advisory (oras-project/oras-go)\n\n**callsite:** content/file/file.go:609 `resolveWritePath()` \u2192 `pushFile()`\n\n**attacker control:** Attacker controls the pushed name (`ocispec.AnnotationTitle`) and can select a path with a symlink path component under `workingDir` \u2192 `resolveWritePath()` blocks `..` via `filepath.Rel` but does not prevent symlink traversal \u2192 `pushFile()` opens/creates the final path and follows the symlink \u2192 a file is created outside `workingDir`\n\n### root cause\n\n`resolveWritePath()` enforces the write boundary using a `filepath.Rel`-style check against `workingDir`. This prevents `../` escapes but is purely lexical and does not resolve symlinks. If a path component under `workingDir` is a symlink to an external location, the subsequent filesystem operation in `pushFile()` follows that symlink and performs the write outside `workingDir` while still passing the lexical boundary check.\n\n### attack path\n\n1. Attacker provides a blob title (via `ocispec.AnnotationTitle`) that contains a path like `out/pwn.txt`.\n2. Victim uses `oras-go` file store with `AllowPathTraversalOnWrite=false` and a `workingDir` that contains a symlink directory `out -\u003e /some/outside/dir`.\n3. The lexical boundary check accepts `out/pwn.txt` as being under `workingDir`.\n4. The write follows the symlink and creates `/some/outside/dir/pwn.txt`.\n\n## impact\n\nThis is a filesystem boundary bypass that permits writes outside `workingDir` when a symlink path component exists under `workingDir`. The concrete security impact depends on the runtime environment (what filesystem locations are writable by the process and what downstream consumers do with the written file), but the intended confinement guarantee is violated.\n\n## proof of concept\n\nthe attached `poc.zip` contains a small, self-contained go harness that demonstrates:\n\n- canonical (vulnerable): prints `[CALLSITE_HIT]` and `[PROOF_MARKER]` and shows the file is created outside `workingDir`\n- control (no symlink component): prints `[NC_MARKER]` and confirms no outside write occurs\n\nrun:\n\n```bash\nunzip -q -o poc.zip -d /tmp\ncd /tmp/poc-F-ORAS-SYMLINK-WRITE-001\nmake test\n```\n\n**expected:** when `AllowPathTraversalOnWrite=false`, file store writes should not be able to escape `workingDir`, including via symlink traversal.\n\n**actual:** A symlink path component under `workingDir` allows writes to escape `workingDir` even when `AllowPathTraversalOnWrite=false`.\n\n## recommended fix\n\nensure confinement checks account for symlink traversal. Options include rejecting symlinks in any path component (walk components with `os.Lstat`), validating the resolved parent directory via `EvalSymlinks` and enforcing it remains under the resolved `workingDir`, or using an `openat()`-style approach so the check and open happen relative to a trusted directory file descriptor.\n\n**fix accepted when:** The canonical PoC no longer prints `[PROOF_MARKER]` for the same attacker-controlled inputs.\n\n\ncheers,\nOleh",
  "id": "GHSA-8xwf-rjm4-xvhv",
  "modified": "2026-07-01T21:43:11Z",
  "published": "2026-07-01T21:43:10Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/oras-project/oras-go/security/advisories/GHSA-8xwf-rjm4-xvhv"
    },
    {
      "type": "WEB",
      "url": "https://github.com/oras-project/oras-go/commit/cc323e564d90c6b5b4bdd71d3c8d2ee2713b37e5"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/oras-project/oras-go"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N",
      "type": "CVSS_V4"
    }
  ],
  "summary": "oras-go has file store write outside workingDir via symlink traversal"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…