Skip to content

Commit 10433ac

Browse files
authored
Fix tar/untar to never add current dir entry and avoid traversal attacks (#283)
2 parents ca96834 + a1ac398 commit 10433ac

2 files changed

Lines changed: 52 additions & 1 deletion

File tree

src/main/java/land/oras/utils/ArchiveUtils.java

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,13 @@ public static LocalPath tar(LocalPath sourceDir) {
9292
paths.forEach(path -> {
9393
LOG.trace("Visiting path: {}", path);
9494
try {
95-
String entryName = sourceDir.getPath().relativize(path).toString();
95+
96+
Path relativePath = sourceDir.getPath().relativize(path);
97+
if (relativePath.toString().isEmpty()) {
98+
LOG.trace("Skipping root directory: {}", path);
99+
return;
100+
}
101+
String entryName = relativePath.toString();
96102

97103
TarArchiveEntry entry = null;
98104
BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);
@@ -142,6 +148,21 @@ public static LocalPath tar(LocalPath sourceDir) {
142148
return LocalPath.of(tarFile, Const.DEFAULT_BLOB_MEDIA_TYPE);
143149
}
144150

151+
/**
152+
* Ensure that the entry is safe to extract
153+
* @param entry The tar entry
154+
* @param target The target directory
155+
* @throws IOException
156+
*/
157+
static void ensureSafeEntry(TarArchiveEntry entry, Path target) throws IOException {
158+
// Prevent path traversal attacks
159+
Path outputPath = target.resolve(entry.getName()).normalize();
160+
Path normalizedTarget = target.toAbsolutePath().normalize();
161+
if (!outputPath.startsWith(normalizedTarget)) {
162+
throw new IOException("Entry is outside of the target dir: " + target);
163+
}
164+
}
165+
145166
/**
146167
* Extract a tar file to a target directory
147168
* @param fis The archive stream
@@ -161,6 +182,9 @@ public static void untar(InputStream fis, Path target) {
161182
// Prevent path traversal attacks
162183
Path outputPath = target.resolve(entry.getName()).normalize();
163184

185+
// Check if the entry is outside the target directory
186+
ensureSafeEntry(entry, target);
187+
164188
LOG.trace("Extracting entry: {}", entry.getName());
165189

166190
if (entry.isDirectory()) {

src/test/java/land/oras/utils/ArchiveUtilsTest.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,18 @@
2020

2121
package land.oras.utils;
2222

23+
import static org.junit.jupiter.api.Assertions.assertThrows;
2324
import static org.junit.jupiter.api.Assertions.assertTrue;
25+
import static org.mockito.Mockito.doReturn;
26+
import static org.mockito.Mockito.mock;
2427

28+
import java.io.IOException;
2529
import java.nio.file.Files;
2630
import java.nio.file.Path;
2731
import java.nio.file.attribute.PosixFilePermission;
2832
import java.util.Set;
2933
import land.oras.LocalPath;
34+
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
3035
import org.junit.jupiter.api.BeforeAll;
3136
import org.junit.jupiter.api.Test;
3237
import org.junit.jupiter.api.io.CleanupMode;
@@ -96,6 +101,28 @@ static void beforeAll() throws Exception {
96101
PosixFilePermission.OTHERS_EXECUTE));
97102
}
98103

104+
@Test
105+
void testEnsureSafeEntry() throws Exception {
106+
TarArchiveEntry entry = mock(TarArchiveEntry.class);
107+
doReturn("test").when(entry).getName();
108+
ArchiveUtils.ensureSafeEntry(entry, archiveDir);
109+
}
110+
111+
@Test
112+
void throwOnUnsafeEntries() throws Exception {
113+
TarArchiveEntry entry = mock(TarArchiveEntry.class);
114+
115+
// Simulate a path traversal attack
116+
assertThrows(IOException.class, () -> {
117+
doReturn("/").when(entry).getName();
118+
ArchiveUtils.ensureSafeEntry(entry, archiveDir);
119+
});
120+
assertThrows(IOException.class, () -> {
121+
doReturn("foo/bar/../../../test").when(entry).getName();
122+
ArchiveUtils.ensureSafeEntry(entry, archiveDir);
123+
});
124+
}
125+
99126
@Test
100127
void shouldCreateTarGzAndExtractIt() throws Exception {
101128
LocalPath directory = LocalPath.of(archiveDir);

0 commit comments

Comments
 (0)