Skip to content

Commit 4718027

Browse files
committed
Fixed possible security issue in s operation where files could be downloaded to a sibling directory of the destination directory if the key contained relative paths.
1 parent 17f220a commit 4718027

File tree

3 files changed

+41
-1
lines changed

3 files changed

+41
-1
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"category": "S3 Transfer Manager (Developer Preview)",
3+
"contributor": "",
4+
"type": "bugfix",
5+
"description": "Fixed possible security issue in `S3TransferManager`s `downloadDirectory` operation where files could be downloaded to a sibling directory of the destination directory if the key contained relative paths."
6+
}

services-custom/s3-transfer-manager/src/main/java/software/amazon/awssdk/transfer/s3/internal/DownloadDirectoryHelper.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ private void doDownloadDirectory(CompletableFuture<CompletedDirectoryDownload> r
121121

122122
allOfFutures.whenComplete((r, t) -> {
123123
if (t != null) {
124-
returnFuture.completeExceptionally(SdkClientException.create("Failed to call ListObjectsV2", t));
124+
returnFuture.completeExceptionally(SdkClientException.create("Failed to send request", t));
125125
} else {
126126
returnFuture.complete(CompletedDirectoryDownload.builder()
127127
.failedTransfers(failedFileDownloads)
@@ -150,9 +150,18 @@ private DownloadFileContext determineDestinationPath(DownloadDirectoryRequest do
150150
String key = normalizeKey(downloadDirectoryRequest, s3Object, delimiter);
151151
String relativePath = getRelativePath(fileSystem, delimiter, key);
152152
Path destinationPath = downloadDirectoryRequest.destinationDirectory().resolve(relativePath);
153+
154+
validatePath(downloadDirectoryRequest.destinationDirectory(), destinationPath, s3Object.key());
153155
return new DefaultDownloadFileContext(s3Object, destinationPath);
154156
}
155157

158+
private void validatePath(Path destinationDirectory, Path targetPath, String key) {
159+
if (!targetPath.toAbsolutePath().normalize().startsWith(destinationDirectory.toAbsolutePath().normalize())) {
160+
throw SdkClientException.create("Cannot download key " + key +
161+
", its relative path resolves outside the parent directory.");
162+
}
163+
}
164+
156165
private CompletableFuture<CompletedFileDownload> doDownloadSingleFile(DownloadDirectoryRequest downloadDirectoryRequest,
157166
Collection<FailedFileDownload> failedFileDownloads,
158167
DownloadFileContext downloadContext) {

services-custom/s3-transfer-manager/src/test/java/software/amazon/awssdk/transfer/s3/internal/DownloadDirectoryHelperTest.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import com.google.common.jimfs.Jimfs;
2828
import java.io.IOException;
2929
import java.nio.file.FileSystem;
30+
import java.nio.file.Files;
3031
import java.nio.file.Path;
3132
import java.nio.file.Paths;
3233
import java.util.UUID;
@@ -114,6 +115,30 @@ void downloadDirectory_allDownloadsSucceed_failedDownloadsShouldBeEmpty() throws
114115
"key2"));
115116
}
116117

118+
@Test
119+
void invalidKey_shouldThrowException() throws Exception {
120+
assertExceptionThrownForInvalidKeys("../key1");
121+
assertExceptionThrownForInvalidKeys("/../key1");
122+
assertExceptionThrownForInvalidKeys("blah/../../object.dat");
123+
assertExceptionThrownForInvalidKeys("blah/../object/../../blah/another/object.dat");
124+
assertExceptionThrownForInvalidKeys("../{directory-name}-2/object.dat");
125+
}
126+
127+
private void assertExceptionThrownForInvalidKeys(String key) throws IOException {
128+
Path destinationDirectory = Files.createTempDirectory("test");
129+
String lastElement = destinationDirectory.getName(destinationDirectory.getNameCount() - 1).toString();
130+
key = key.replace("{directory-name}", lastElement);
131+
stubSuccessfulListObjects(listObjectsHelper, key);
132+
DirectoryDownload downloadDirectory =
133+
downloadDirectoryHelper.downloadDirectory(DownloadDirectoryRequest.builder()
134+
.destinationDirectory(destinationDirectory)
135+
.bucket("bucket")
136+
.build());
137+
138+
assertThatThrownBy(() -> downloadDirectory.completionFuture().get(5, TimeUnit.SECONDS))
139+
.hasCauseInstanceOf(SdkClientException.class).getRootCause().hasMessageContaining("Cannot download key");
140+
}
141+
117142
@Test
118143
void downloadDirectory_cancel_shouldCancelAllFutures() throws Exception {
119144
stubSuccessfulListObjects(listObjectsHelper, "key1", "key2");

0 commit comments

Comments
 (0)