|
25 | 25 |
|
26 | 26 | from ebcli.core import fileoperations |
27 | 27 | from ebcli.objects.buildconfiguration import BuildConfiguration |
28 | | -from ebcli.objects.exceptions import NotInitializedError, NotFoundError |
| 28 | +from ebcli.objects.exceptions import NotInitializedError, NotFoundError, ValidationError |
29 | 29 |
|
30 | 30 |
|
31 | 31 | class TestFileOperations(unittest.TestCase): |
@@ -1060,3 +1060,63 @@ def test___validate_file_for_archive__ignores_socket_files( |
1060 | 1060 | actual = fileoperations._validate_file_for_archive(filepath) |
1061 | 1061 |
|
1062 | 1062 | self.assertFalse(actual) |
| 1063 | + |
| 1064 | + |
| 1065 | +class TestUnzipFolderPathTraversal(unittest.TestCase): |
| 1066 | + def setUp(self): |
| 1067 | + self.test_dir = os.path.join(os.path.dirname(__file__), '_test_unzip_traversal') |
| 1068 | + self.extract_dir = os.path.join(self.test_dir, 'extract') |
| 1069 | + os.makedirs(self.extract_dir, exist_ok=True) |
| 1070 | + |
| 1071 | + def tearDown(self): |
| 1072 | + shutil.rmtree(self.test_dir, ignore_errors=True) |
| 1073 | + |
| 1074 | + def _make_zip(self, entries, zip_path=None): |
| 1075 | + if zip_path is None: |
| 1076 | + zip_path = os.path.join(self.test_dir, 'test.zip') |
| 1077 | + with zipfile.ZipFile(zip_path, 'w') as zf: |
| 1078 | + for name, content in entries.items(): |
| 1079 | + zf.writestr(name, content) |
| 1080 | + return zip_path |
| 1081 | + |
| 1082 | + def test_unzip_folder__normal_entries_extract_correctly(self): |
| 1083 | + zip_path = self._make_zip({ |
| 1084 | + 'app.py': 'print("hello")', |
| 1085 | + 'subdir/config.yml': 'key: value', |
| 1086 | + 'a/b/c/deep.txt': 'deep content', |
| 1087 | + './foo/bar.txt': 'content', |
| 1088 | + }) |
| 1089 | + fileoperations.unzip_folder(zip_path, self.extract_dir) |
| 1090 | + |
| 1091 | + self.assertTrue(os.path.exists(os.path.join(self.extract_dir, 'app.py'))) |
| 1092 | + self.assertTrue(os.path.exists(os.path.join(self.extract_dir, 'subdir', 'config.yml'))) |
| 1093 | + self.assertTrue(os.path.exists(os.path.join(self.extract_dir, 'a', 'b', 'c', 'deep.txt'))) |
| 1094 | + self.assertTrue(os.path.exists(os.path.join(self.extract_dir, 'foo', 'bar.txt'))) |
| 1095 | + |
| 1096 | + def test_unzip_folder__path_traversal_raises_error(self): |
| 1097 | + zip_path = self._make_zip({ |
| 1098 | + '../../../tmp/pwned.txt': 'PWNED', |
| 1099 | + }) |
| 1100 | + with self.assertRaises(ValidationError): |
| 1101 | + fileoperations.unzip_folder(zip_path, self.extract_dir) |
| 1102 | + |
| 1103 | + self.assertFalse(os.path.exists(os.path.join(self.test_dir, 'pwned.txt'))) |
| 1104 | + |
| 1105 | + def test_unzip_folder__absolute_path_entry_raises_error(self): |
| 1106 | + zip_path = self._make_zip({ |
| 1107 | + '/tmp/absolute_pwned.txt': 'PWNED', |
| 1108 | + }) |
| 1109 | + with self.assertRaises(ValidationError): |
| 1110 | + fileoperations.unzip_folder(zip_path, self.extract_dir) |
| 1111 | + |
| 1112 | + def test_unzip_folder__legitimate_deep_and_redundant_paths(self): |
| 1113 | + zip_path = self._make_zip({ |
| 1114 | + 'a/b/c/d/e/deep.txt': 'deep content', |
| 1115 | + './foo/bar.txt': 'content1', |
| 1116 | + 'foo//baz.txt': 'content2', |
| 1117 | + }) |
| 1118 | + fileoperations.unzip_folder(zip_path, self.extract_dir) |
| 1119 | + |
| 1120 | + self.assertTrue(os.path.exists(os.path.join(self.extract_dir, 'a', 'b', 'c', 'd', 'e', 'deep.txt'))) |
| 1121 | + self.assertTrue(os.path.exists(os.path.join(self.extract_dir, 'foo', 'bar.txt'))) |
| 1122 | + self.assertTrue(os.path.exists(os.path.join(self.extract_dir, 'foo', 'baz.txt'))) |
0 commit comments