Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 9742c23

Browse files
Fixed possible security issue in TransferManager’s DownloadToDirectory operation where files could be downloaded to directories outside of the destination directory if the key contained specially-crafted relative paths.
1 parent bcae365 commit 9742c23

File tree

3 files changed

+146
-4
lines changed

3 files changed

+146
-4
lines changed

aws-cpp-sdk-transfer-tests/TransferTests.cpp

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1980,4 +1980,81 @@ TEST_F(TransferTests, TransferManager_TemplatesTest)
19801980
"text/plain",
19811981
Aws::Map<Aws::String, Aws::String>());
19821982
}
1983+
1984+
class TestHelperTransferManager : public Aws::Transfer::TransferManager
1985+
{
1986+
public:
1987+
static bool MakePublicIsWithinParentDirectory(Aws::String parentDirectory, Aws::String filePath)
1988+
{
1989+
return Aws::Transfer::TransferManager::IsWithinParentDirectory(parentDirectory, filePath);
1990+
}
1991+
1992+
struct TestCaseEntry
1993+
{
1994+
Aws::String parentDir;
1995+
Aws::String filePath;
1996+
1997+
bool expectedIsWithinParentDirectory;
1998+
};
1999+
};
2000+
2001+
TEST_F(TransferTests, TransferManager_TestRelativePrefix)
2002+
{
2003+
2004+
static const std::vector<TestHelperTransferManager::TestCaseEntry> TEST_CASES =
2005+
{
2006+
{R"(C:/temp/)", R"(C:/temp/filename.bmp)", true},
2007+
{R"(C:/temp)", R"(C:/temp/filename.bmp)", true},
2008+
{R"(C:/temp/)", R"(C:/temp/../temp-1/filename.bmp)", false},
2009+
{R"(C:/temp/)", R"(C:/temp/temp/filename..bmp)", true},
2010+
{R"(C:/temp/)", R"(C:/temp/temp/filename/..bmp)", true},
2011+
{R"(C:/temp/)", R"(C:/temp/temp/filename/../bmp)", true},
2012+
{R"(C:/temp/)", R"(C:/temp/../temp-1/filename..bmp)", false},
2013+
{R"(C:/temp/)", R"(C:/temp/../temp/temp-1/filename..bmp)", false},
2014+
{R"(C:/)", R"(C:/..../foo.txt)", true},
2015+
{R"(C:/)", R"(C:/./foo.txt)", true},
2016+
{R"(C:/)", R"(C:/.../foo.txt)", true},
2017+
{R"(C:/)", R"(C:/.../../foo.txt)", true},
2018+
{R"(C:/)", R"(C:/.../.../../../foo.txt)", true},
2019+
{R"(C:/)", R"(C:/...////./foo.txt)", true},
2020+
2021+
{R"(/home/user/my-intended-directory)", R"(/home/user/my-intended-directory/foo.txt)", true},
2022+
{R"(/home/user/my-intended-directory/)", R"(/home/user/my-intended-directory/foo.txt)", true},
2023+
{R"(/home/user/my-intended-directory/)", R"(/home/user/my-intended-directory//////foo.txt)", true},
2024+
{R"(/home/user/)", R"(/home/user/down/../down/../down/../foo.txt)", true},
2025+
{R"(/home/user/my-intended-directory)", R"(/home/user/my-intended-directory/../my-intended-directory-2/foo.txt)", false},
2026+
{R"(/home/user/)", R"(/home/user/../../root/foo.txt)", false},
2027+
{R"(/home/user)", R"(/home/user/../../root/foo.txt)", false},
2028+
2029+
{R"(/home/user/)", R"(/home/user/.. )", true},
2030+
{R"(/home/user/)", R"(/home/user/ .. )", true},
2031+
{R"(/home/user/)", R"(/home/user/ ..)", true},
2032+
{R"(/home/user/)", R"(/home/user/./foo)", true},
2033+
{R"(/home/user)", R"(/home/user/./foo)", true},
2034+
{R"(/home/user/)", R"(/home/user/./././././foo)", true},
2035+
{R"(/home/user/)", R"(/home/user/./././.././foo)", false},
2036+
2037+
{R"(./)", R"(./foo)", true},
2038+
{R"(./)", R"(./..foo)", true},
2039+
{R"(./)", R"(./../foo)", false},
2040+
{R"(./)", R"(./.../foo)", true},
2041+
{R"(./)", R"(./.../../foo)", true},
2042+
{R"(./)", R"(.//.../../foo)", true},
2043+
};
2044+
2045+
for(const TestHelperTransferManager::TestCaseEntry& TC_ENTRY : TEST_CASES)
2046+
{
2047+
char delimiter[] = { Aws::FileSystem::PATH_DELIM, 0 };
2048+
Aws::String osNormalizedParentDir = TC_ENTRY.parentDir;
2049+
Aws::Utils::StringUtils::Replace(osNormalizedParentDir, "/", delimiter);
2050+
Aws::String osNormalizedFilePath = TC_ENTRY.filePath;
2051+
Aws::Utils::StringUtils::Replace(osNormalizedFilePath, "/", delimiter);
2052+
2053+
bool actualResult = TestHelperTransferManager::MakePublicIsWithinParentDirectory(osNormalizedParentDir, osNormalizedFilePath);
2054+
2055+
ASSERT_EQ(TC_ENTRY.expectedIsWithinParentDirectory, actualResult)
2056+
<< (TC_ENTRY.expectedIsWithinParentDirectory ? "EXPECTED \"" : "NOT EXPECTED \"") << osNormalizedFilePath << "\" to be found in \"" << osNormalizedParentDir << "\"";
2057+
}
2058+
}
2059+
19832060
}

aws-cpp-sdk-transfer/include/aws/transfer/TransferManager.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,9 @@ namespace Aws
309309

310310
Aws::Utils::ExclusiveOwnershipResourceManager<unsigned char*> m_bufferManager;
311311
TransferManagerConfiguration m_transferConfig;
312+
313+
protected:
314+
static bool IsWithinParentDirectory(Aws::String parentDirectory, Aws::String filePath);
312315
};
313316

314317

aws-cpp-sdk-transfer/source/transfer/TransferManager.cpp

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1053,6 +1053,12 @@ namespace Aws
10531053
if (!IsS3KeyPrefix(content.GetKey()))
10541054
{
10551055
Aws::String fileName = DetermineFilePath(downloadContext->rootDirectory, downloadContext->prefix, content.GetKey());
1056+
if(!IsWithinParentDirectory(downloadContext->rootDirectory, fileName))
1057+
{
1058+
AWS_LOGSTREAM_ERROR(CLASS_TAG, "SKIPPING PREFIX: S3 bucket contains relative prefix: "
1059+
<< downloadContext->prefix <<" that goes outside local target directory: " << directory);
1060+
continue;
1061+
}
10561062
auto lastDelimter = fileName.find_last_of(Aws::FileSystem::PATH_DELIM);
10571063
if (lastDelimter != std::string::npos)
10581064
{
@@ -1077,6 +1083,53 @@ namespace Aws
10771083
}
10781084
}
10791085

1086+
bool TransferManager::IsWithinParentDirectory(Aws::String parentDirectory, Aws::String filePath)
1087+
{
1088+
char delimiter[] = { Aws::FileSystem::PATH_DELIM, 0 };
1089+
// normalize to unix ending style
1090+
Aws::Utils::StringUtils::Replace(parentDirectory, delimiter, "/");
1091+
Aws::Utils::StringUtils::Replace(filePath, delimiter, "/");
1092+
1093+
if(!parentDirectory.empty() && parentDirectory.back() == '/')
1094+
{
1095+
parentDirectory.resize(parentDirectory.size() - 1);
1096+
}
1097+
1098+
if (filePath.rfind(parentDirectory, 0) == 0) // if starts_with
1099+
{
1100+
filePath = filePath.substr(parentDirectory.size());
1101+
}
1102+
else
1103+
{
1104+
return false;
1105+
}
1106+
1107+
size_t level = 0;
1108+
for(size_t i = 0; i < filePath.size(); ++i)
1109+
{
1110+
if('/' == filePath[i])
1111+
{
1112+
if(i + 2 < filePath.size() && '.' == filePath[i+1] && '/' == filePath[i+2]) // if "/./"
1113+
{
1114+
continue;
1115+
}
1116+
1117+
if(i + 2 < filePath.size() && '.' == filePath[i+1] && '.' == filePath[i+2]) // if "/.."
1118+
{
1119+
if(i + 3 == filePath.size() || (i + 3 < filePath.size() && '/' == filePath[i+3])) // if "/.." or "/../"
1120+
{
1121+
if(0 == level) {
1122+
return false; // attempting to escape parent
1123+
}
1124+
level--;
1125+
}
1126+
}
1127+
level++;
1128+
}
1129+
}
1130+
return true;
1131+
}
1132+
10801133
Aws::String TransferManager::DetermineFilePath(const Aws::String& directory, const Aws::String& prefix, const Aws::String& keyName)
10811134
{
10821135
Aws::String shortenedFileName = keyName;
@@ -1088,11 +1141,20 @@ namespace Aws
10881141
}
10891142

10901143
char delimiter[] = { Aws::FileSystem::PATH_DELIM, 0 };
1091-
Aws::Utils::StringUtils::Replace(shortenedFileName, "/", delimiter);
1092-
Aws::StringStream ss;
1093-
ss << directory << shortenedFileName;
1144+
Aws::Utils::StringUtils::Replace(shortenedFileName, delimiter, "/");
1145+
1146+
Aws::String normalizedDirectory = directory;
1147+
Aws::Utils::StringUtils::Replace(normalizedDirectory, delimiter, "/");
10941148

1095-
return ss.str();
1149+
Aws::StringStream ss;
1150+
ss << normalizedDirectory;
1151+
if (!normalizedDirectory.empty() && normalizedDirectory.back() != '/')
1152+
ss << '/';
1153+
ss << shortenedFileName;
1154+
1155+
Aws::String result = ss.str();
1156+
Aws::Utils::StringUtils::Replace(result, "/", delimiter);
1157+
return result;
10961158
}
10971159

10981160
TransferStatus TransferManager::DetermineIfFailedOrCanceled(const TransferHandle& handle) const

0 commit comments

Comments
 (0)