Skip to content

Commit 9baf7ab

Browse files
authored
Merge pull request #2012 from aws/zoewang/tmFix
Fixed possible security issue in s operation where files could be do…
2 parents d130bb9 + 5f82df4 commit 9baf7ab

File tree

3 files changed

+44
-1
lines changed

3 files changed

+44
-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: 28 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;
@@ -38,6 +39,8 @@
3839
import org.junit.jupiter.api.BeforeAll;
3940
import org.junit.jupiter.api.BeforeEach;
4041
import org.junit.jupiter.api.Test;
42+
import org.junit.jupiter.params.ParameterizedTest;
43+
import org.junit.jupiter.params.provider.ValueSource;
4144
import org.mockito.ArgumentCaptor;
4245
import software.amazon.awssdk.core.exception.SdkClientException;
4346
import software.amazon.awssdk.services.s3.model.EncodingType;
@@ -114,6 +117,31 @@ void downloadDirectory_allDownloadsSucceed_failedDownloadsShouldBeEmpty() throws
114117
"key2"));
115118
}
116119

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

0 commit comments

Comments
 (0)