zip slip in ORAS
Description
ORAS is open source software which enables a way to push OCI Artifacts to OCI Conformant registries. ORAS is both a CLI for initial testing and a Go Module. In ORAS from version 0.4.0 and before version 0.9.0, there is a "zip-slip" vulnerability. The directory support feature allows the downloaded gzipped tarballs to be automatically extracted to the user-specified directory where the tarball can have symbolic links and hard links. A well-crafted tarball or tarballs allow malicious artifact providers linking, writing, or overwriting specific files on the host filesystem outside of the user-specified directory unexpectedly with the same permissions as the user who runs oras pull. Users of the affected versions are impacted if they are oras CLI users who runs oras pull, or if they are Go programs, which invoke github.com/deislabs/oras/pkg/content.FileStore. The problem has been fixed in version 0.9.0. For oras CLI users, there is no workarounds other than pulling from a trusted artifact provider. For oras package users, the workaround is to not use github.com/deislabs/oras/pkg/content.FileStore, and use other content stores instead, or pull from a trusted artifact provider.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
ORAS versions 0.4.0 to 0.9.0 contain a zip-slip vulnerability allowing arbitrary file writes via symbolic links in tarballs.
Vulnerability
Description
CVE-2021-21272 is a "zip-slip" vulnerability in ORAS (OCI Registry As Storage), affecting versions 0.4.0 through 0.9.0. The directory support feature automatically extracts downloaded gzipped tarballs, but does not properly validate symbolic or hard links within the archive. This allows a malicious artifact provider to craft a tarball that writes, overwrites, or links files outside the intended extraction directory [1].
Exploitation
An attacker must be able to provide a malicious OCI artifact to a user who runs oras pull (CLI) or when a Go program invokes github.com/deislabs/oras/pkg/content.FileStore. No special privileges are needed beyond the ability to provide the artifact. The exploitation occurs during extraction, where the tarball's links can escape the target directory [1][3].
Impact
Successful exploitation allows an attacker to write or overwrite arbitrary files on the host filesystem with the permissions of the user executing oras pull. This could lead to arbitrary code execution, privilege escalation, or data corruption [1].
Mitigation
The vulnerability is patched in ORAS version 0.9.0 [4]. For CLI users, upgrading is the only fix; no workaround is available. For Go package users, avoid using FileStore or only pull from trusted artifact providers [1].
AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/deislabs/orasGo | < 0.9.0 | 0.9.0 |
Affected products
9- osv-coords8 versionspkg:bitnami/oraspkg:golang/github.com/deislabs/oraspkg:rpm/opensuse/helm3&distro=openSUSE%20Tumbleweedpkg:rpm/opensuse/helm&distro=openSUSE%20Leap%2015.3pkg:rpm/opensuse/helm&distro=openSUSE%20Leap%2015.4pkg:rpm/opensuse/helm&distro=openSUSE%20Tumbleweedpkg:rpm/suse/helm&distro=SUSE%20Linux%20Enterprise%20Module%20for%20Containers%2015%20SP4pkg:rpm/suse/helm&distro=SUSE%20Linux%20Enterprise%20Module%20for%20Package%20Hub%2015%20SP4
>= 0.4.0, < 0.9.0+ 7 more
- (no CPE)range: >= 0.4.0, < 0.9.0
- (no CPE)range: < 0.9.0
- (no CPE)range: < 3.19.2-1.1
- (no CPE)range: < 3.10.3-150000.1.13.1
- (no CPE)range: < 3.10.3-150000.1.13.1
- (no CPE)range: < 3.9.4-3.1
- (no CPE)range: < 3.10.3-150000.1.13.1
- (no CPE)range: < 3.10.3-150000.1.13.1
- deislabs/orasv5Range: >= 0.4.0, < 0.9.0
Patches
196cd90423303Merge pull request from GHSA-g5v4-5x39-vwhx
2 files changed · +170 −4
pkg/content/utils.go+41 −4 modified@@ -101,15 +101,24 @@ func extractTarDirectory(root, prefix string, r io.Reader) error { // Name check name := header.Name - path, err := filepath.Rel(prefix, name) + path, err := ensureBasePath(root, prefix, name) if err != nil { return err } - if strings.HasPrefix(path, "../") { - return fmt.Errorf("%q does not have prefix %q", name, prefix) - } path = filepath.Join(root, path) + // Link check + switch header.Typeflag { + case tar.TypeLink, tar.TypeSymlink: + link := header.Linkname + if !filepath.IsAbs(link) { + link = filepath.Join(filepath.Dir(name), link) + } + if _, err := ensureBasePath(root, prefix, link); err != nil { + return err + } + } + // Create content switch header.Typeflag { case tar.TypeReg: @@ -132,6 +141,34 @@ func extractTarDirectory(root, prefix string, r io.Reader) error { } } +// ensureBasePath ensures the target path is in the base path, +// returning its relative path to the base path. +func ensureBasePath(root, base, target string) (string, error) { + path, err := filepath.Rel(base, target) + if err != nil { + return "", err + } + cleanPath := filepath.ToSlash(filepath.Clean(path)) + if cleanPath == ".." || strings.HasPrefix(cleanPath, "../") { + return "", fmt.Errorf("%q is outside of %q", target, base) + } + + // No symbolic link allowed in the relative path + dir := filepath.Dir(path) + for dir != "." { + if info, err := os.Lstat(filepath.Join(root, dir)); err != nil { + if !os.IsNotExist(err) { + return "", err + } + } else if info.Mode()&os.ModeSymlink != 0 { + return "", fmt.Errorf("no symbolic link allowed between %q and %q", base, target) + } + dir = filepath.Dir(dir) + } + + return path, nil +} + func writeFile(path string, r io.Reader, perm os.FileMode) error { file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm) if err != nil {
pkg/oras/oras_test.go+129 −0 modified@@ -1,8 +1,13 @@ package oras import ( + "archive/tar" + "bytes" + "compress/gzip" "context" + _ "crypto/sha256" "fmt" + "io" "io/ioutil" "os" "path/filepath" @@ -17,6 +22,7 @@ import ( "github.com/docker/distribution/configuration" "github.com/docker/distribution/registry" _ "github.com/docker/distribution/registry/storage/driver/inmemory" + digest "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/phayes/freeport" "github.com/stretchr/testify/suite" @@ -313,6 +319,129 @@ func (suite *ORASTestSuite) Test_3_Conditional_Pull() { } } +// Test for vulnerability GHSA-g5v4-5x39-vwhx +func (suite *ORASTestSuite) Test_4_GHSA_g5v4_5x39_vwhx() { + var testVulnerability = func(headers []tar.Header, tag string, expectedError string) { + // Step 1: build malicious tar+gzip + buf := bytes.NewBuffer(nil) + digester := digest.Canonical.Digester() + zw := gzip.NewWriter(io.MultiWriter(buf, digester.Hash())) + tarDigester := digest.Canonical.Digester() + tw := tar.NewWriter(io.MultiWriter(zw, tarDigester.Hash())) + for _, header := range headers { + err := tw.WriteHeader(&header) + suite.Nil(err, "error writing header") + } + err := tw.Close() + suite.Nil(err, "error closing tar") + err = zw.Close() + suite.Nil(err, "error closing gzip") + + // Step 2: construct malicious descriptor + evilDesc := ocispec.Descriptor{ + MediaType: ocispec.MediaTypeImageLayerGzip, + Digest: digester.Digest(), + Size: int64(buf.Len()), + Annotations: map[string]string{ + orascontent.AnnotationDigest: tarDigester.Digest().String(), + orascontent.AnnotationUnpack: "true", + ocispec.AnnotationTitle: "foo", + }, + } + + // Step 3: upload malicious artifact to registry + memoryStore := orascontent.NewMemoryStore() + memoryStore.Set(evilDesc, buf.Bytes()) + ref := fmt.Sprintf("%s/evil:%s", suite.DockerRegistryHost, tag) + _, err = Push(newContext(), newResolver(), ref, memoryStore, []ocispec.Descriptor{evilDesc}) + suite.Nil(err, "no error pushing test data") + + // Step 4: pull malicious tar with oras filestore and ensure error + tempDir, err := ioutil.TempDir("", "oras_test") + if err != nil { + suite.FailNow("error creating temp directory", err) + } + defer os.RemoveAll(tempDir) + store := orascontent.NewFileStore(tempDir) + defer store.Close() + ref = fmt.Sprintf("%s/evil:%s", suite.DockerRegistryHost, tag) + _, _, err = Pull(newContext(), newResolver(), ref, store) + suite.NotNil(err, "error expected pulling malicious tar") + suite.Contains(err.Error(), + expectedError, + "did not get correct error message", + ) + } + + tests := []struct { + name string + headers []tar.Header + tag string + expectedError string + }{ + { + name: "Test symbolic link path traversal", + headers: []tar.Header{ + { + Typeflag: tar.TypeDir, + Name: "foo/subdir/", + Mode: 0755, + }, + { // Symbolic link to `foo` + Typeflag: tar.TypeSymlink, + Name: "foo/subdir/parent", + Linkname: "..", + Mode: 0755, + }, + { // Symbolic link to `../etc/passwd` + Typeflag: tar.TypeSymlink, + Name: "foo/subdir/parent/passwd", + Linkname: "../../etc/passwd", + Mode: 0644, + }, + { // Symbolic link to `../etc` + Typeflag: tar.TypeSymlink, + Name: "foo/subdir/parent/etc", + Linkname: "../../etc", + Mode: 0644, + }, + }, + tag: "symlink_path", + expectedError: "no symbolic link allowed", + }, + { + name: "Test symbolic link pointing to outside", + headers: []tar.Header{ + { // Symbolic link to `/etc/passwd` + Typeflag: tar.TypeSymlink, + Name: "foo/passwd", + Linkname: "../../../etc/passwd", + Mode: 0644, + }, + }, + tag: "symlink", + expectedError: "is outside of", + }, + { + name: "Test hard link pointing to outside", + headers: []tar.Header{ + { // Hard link to `/etc/passwd` + Typeflag: tar.TypeLink, + Name: "foo/passwd", + Linkname: "../../../etc/passwd", + Mode: 0644, + }, + }, + tag: "hardlink", + expectedError: "is outside of", + }, + } + for _, test := range tests { + suite.T().Log(test.name) + testVulnerability(test.headers, test.tag, test.expectedError) + } +} + func TestORASTestSuite(t *testing.T) { suite.Run(t, new(ORASTestSuite)) }
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
7- github.com/advisories/GHSA-g5v4-5x39-vwhxghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2021-21272ghsaADVISORY
- github.com/deislabs/oras/commit/96cd90423303f1bb42bd043cb4c36085e6e91e8eghsax_refsource_MISCWEB
- github.com/deislabs/oras/releases/tag/v0.9.0ghsax_refsource_MISCWEB
- github.com/deislabs/oras/security/advisories/GHSA-g5v4-5x39-vwhxghsax_refsource_CONFIRMWEB
- pkg.go.dev/github.com/deislabs/oras/pkg/orasghsax_refsource_MISCWEB
- pkg.go.dev/vuln/GO-2021-0099ghsaWEB
News mentions
0No linked articles in our index yet.