VYPR
High severityNVD Advisory· Published Jan 25, 2021· Updated Aug 3, 2024

zip slip in ORAS

CVE-2021-21272

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.

PackageAffected versionsPatched versions
github.com/deislabs/orasGo
< 0.9.00.9.0

Affected products

9

Patches

1
96cd90423303

Merge pull request from GHSA-g5v4-5x39-vwhx

https://github.com/deislabs/orasShiwei ZhangJan 22, 2021via ghsa
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

News mentions

0

No linked articles in our index yet.