Skip to content

Commit 8d8d4a4

Browse files
authored
Update test_download_and_extract.py
New test cases added as required Signed-off-by: h3rrr <81402797+h3rrr@users.noreply.github.com>
1 parent aaecf77 commit 8d8d4a4

File tree

1 file changed

+185
-0
lines changed

1 file changed

+185
-0
lines changed

tests/apps/test_download_and_extract.py

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313

1414
import tempfile
1515
import unittest
16+
import os
17+
import zipfile
18+
import tarfile
1619
from pathlib import Path
1720
from urllib.error import ContentTooShortError, HTTPError
1821

@@ -66,5 +69,187 @@ def test_default(self, key, file_type):
6669
)
6770

6871

72+
class TestPathTraversalProtection(unittest.TestCase):
73+
"""Test cases for path traversal attack protection in extractall function."""
74+
75+
def test_valid_zip_extraction(self):
76+
"""Test that valid zip files extract successfully without raising exceptions."""
77+
with tempfile.TemporaryDirectory() as tmp_dir:
78+
# Create a valid zip file
79+
zip_path = Path(tmp_dir) / "valid_test.zip"
80+
extract_dir = Path(tmp_dir) / "extract"
81+
extract_dir.mkdir()
82+
83+
# Create zip with normal file structure
84+
with zipfile.ZipFile(zip_path, 'w') as zf:
85+
zf.writestr("normal_file.txt", "This is a normal file")
86+
zf.writestr("subdir/nested_file.txt", "This is a nested file")
87+
zf.writestr("another_file.json", '{"key": "value"}')
88+
89+
# This should not raise any exception
90+
try:
91+
extractall(str(zip_path), str(extract_dir))
92+
93+
# Verify files were extracted correctly
94+
self.assertTrue((extract_dir / "normal_file.txt").exists())
95+
self.assertTrue((extract_dir / "subdir" / "nested_file.txt").exists())
96+
self.assertTrue((extract_dir / "another_file.json").exists())
97+
98+
# Verify content
99+
with open(extract_dir / "normal_file.txt", 'r') as f:
100+
self.assertEqual(f.read(), "This is a normal file")
101+
102+
except Exception as e:
103+
self.fail(f"Valid zip extraction should not raise exception: {e}")
104+
105+
def test_malicious_zip_path_traversal(self):
106+
"""Test that malicious zip files with path traversal attempts raise ValueError."""
107+
with tempfile.TemporaryDirectory() as tmp_dir:
108+
# Create malicious zip file with path traversal
109+
zip_path = Path(tmp_dir) / "malicious_test.zip"
110+
extract_dir = Path(tmp_dir) / "extract"
111+
extract_dir.mkdir()
112+
113+
# Create zip with malicious paths
114+
with zipfile.ZipFile(zip_path, 'w') as zf:
115+
# Try to write outside extraction directory
116+
zf.writestr("../../../etc/malicious.txt", "malicious content")
117+
zf.writestr("normal_file.txt", "normal content")
118+
119+
# This should raise ValueError due to path traversal detection
120+
with self.assertRaises(ValueError) as context:
121+
extractall(str(zip_path), str(extract_dir))
122+
123+
self.assertIn("unsafe path", str(context.exception).lower())
124+
125+
def test_valid_tar_extraction(self):
126+
"""Test that valid tar files extract successfully without raising exceptions."""
127+
with tempfile.TemporaryDirectory() as tmp_dir:
128+
# Create a valid tar file
129+
tar_path = Path(tmp_dir) / "valid_test.tar.gz"
130+
extract_dir = Path(tmp_dir) / "extract"
131+
extract_dir.mkdir()
132+
133+
# Create tar with normal file structure
134+
with tarfile.open(tar_path, 'w:gz') as tf:
135+
# Create temporary files to add to tar
136+
temp_file1 = Path(tmp_dir) / "temp1.txt"
137+
temp_file1.write_text("This is a normal file")
138+
tf.add(temp_file1, arcname="normal_file.txt")
139+
140+
temp_file2 = Path(tmp_dir) / "temp2.txt"
141+
temp_file2.write_text("This is a nested file")
142+
tf.add(temp_file2, arcname="subdir/nested_file.txt")
143+
144+
# This should not raise any exception
145+
try:
146+
extractall(str(tar_path), str(extract_dir))
147+
148+
# Verify files were extracted correctly
149+
self.assertTrue((extract_dir / "normal_file.txt").exists())
150+
self.assertTrue((extract_dir / "subdir" / "nested_file.txt").exists())
151+
152+
# Verify content
153+
with open(extract_dir / "normal_file.txt", 'r') as f:
154+
self.assertEqual(f.read(), "This is a normal file")
155+
156+
except Exception as e:
157+
self.fail(f"Valid tar extraction should not raise exception: {e}")
158+
159+
def test_malicious_tar_path_traversal(self):
160+
"""Test that malicious tar files with path traversal attempts raise ValueError."""
161+
with tempfile.TemporaryDirectory() as tmp_dir:
162+
# Create malicious tar file with path traversal
163+
tar_path = Path(tmp_dir) / "malicious_test.tar.gz"
164+
extract_dir = Path(tmp_dir) / "extract"
165+
extract_dir.mkdir()
166+
167+
# Create tar with malicious paths
168+
with tarfile.open(tar_path, 'w:gz') as tf:
169+
# Create a temporary file
170+
temp_file = Path(tmp_dir) / "temp.txt"
171+
temp_file.write_text("malicious content")
172+
173+
# Add with malicious path (trying to write outside extraction directory)
174+
tf.add(temp_file, arcname="../../../etc/malicious.txt")
175+
176+
# This should raise ValueError due to path traversal detection
177+
with self.assertRaises(ValueError) as context:
178+
extractall(str(tar_path), str(extract_dir))
179+
180+
self.assertIn("unsafe path", str(context.exception).lower())
181+
182+
def test_absolute_path_protection(self):
183+
"""Test protection against absolute paths in archives."""
184+
with tempfile.TemporaryDirectory() as tmp_dir:
185+
# Create zip with absolute path
186+
zip_path = Path(tmp_dir) / "absolute_path_test.zip"
187+
extract_dir = Path(tmp_dir) / "extract"
188+
extract_dir.mkdir()
189+
190+
with zipfile.ZipFile(zip_path, 'w') as zf:
191+
# Try to use absolute path
192+
zf.writestr("/etc/passwd", "malicious content")
193+
194+
# This should raise ValueError due to absolute path detection
195+
with self.assertRaises(ValueError) as context:
196+
extractall(str(zip_path), str(extract_dir))
197+
198+
self.assertIn("unsafe path", str(context.exception).lower())
199+
200+
def test_malicious_symlink_protection(self):
201+
"""Test protection against malicious symlinks in tar archives."""
202+
with tempfile.TemporaryDirectory() as tmp_dir:
203+
# Create malicious tar file with symlink
204+
tar_path = Path(tmp_dir) / "malicious_symlink_test.tar.gz"
205+
extract_dir = Path(tmp_dir) / "extract"
206+
extract_dir.mkdir()
207+
208+
# Create tar with malicious symlink
209+
with tarfile.open(tar_path, 'w:gz') as tf:
210+
temp_file = Path(tmp_dir) / "normal.txt"
211+
temp_file.write_text("normal content")
212+
tf.add(temp_file, arcname="normal.txt")
213+
214+
symlink_info = tarfile.TarInfo(name="malicious_symlink.txt")
215+
symlink_info.type = tarfile.SYMTYPE
216+
symlink_info.linkname = "../../../etc/passwd"
217+
symlink_info.size = 0
218+
tf.addfile(symlink_info)
219+
220+
with self.assertRaises(ValueError) as context:
221+
extractall(str(tar_path), str(extract_dir))
222+
223+
error_msg = str(context.exception).lower()
224+
self.assertTrue("unsafe path" in error_msg or "symlink" in error_msg)
225+
226+
def test_malicious_hardlink_protection(self):
227+
"""Test protection against malicious hard links in tar archives."""
228+
with tempfile.TemporaryDirectory() as tmp_dir:
229+
# Create malicious tar file with hard link
230+
tar_path = Path(tmp_dir) / "malicious_hardlink_test.tar.gz"
231+
extract_dir = Path(tmp_dir) / "extract"
232+
extract_dir.mkdir()
233+
234+
# Create tar with malicious hard link
235+
with tarfile.open(tar_path, 'w:gz') as tf:
236+
temp_file = Path(tmp_dir) / "normal.txt"
237+
temp_file.write_text("normal content")
238+
tf.add(temp_file, arcname="normal.txt")
239+
240+
hardlink_info = tarfile.TarInfo(name="malicious_hardlink.txt")
241+
hardlink_info.type = tarfile.LNKTYPE
242+
hardlink_info.linkname = "/etc/passwd"
243+
hardlink_info.size = 0
244+
tf.addfile(hardlink_info)
245+
246+
with self.assertRaises(ValueError) as context:
247+
extractall(str(tar_path), str(extract_dir))
248+
249+
error_msg = str(context.exception).lower()
250+
self.assertTrue("unsafe path" in error_msg or "hardlink" in error_msg)
251+
252+
253+
69254
if __name__ == "__main__":
70255
unittest.main()

0 commit comments

Comments
 (0)