Skip to content

Commit 5de16cc

Browse files
committed
fix CWE-23: prevent zipslip/directory traversal attacks
Add filter='data' parameter to all tar.extractall() calls to prevent directory traversal (zipslip) vulnerabilities. This change ensures that tar extraction operations reject any archive members that attempt to write outside the intended extraction directory. Affected modules: - amg: restore operations from Grafana archive files - aosm: helm package extraction for AOSM definitions - confcom: container image tar file processing and manifest extraction - connectedk8s: Arc Connectivity proxy binary extraction - containerapp: Java buildpack source code extraction - networkcloud: custom action result blob extraction - ssh: SSH proxy binary extraction from MCR packages The filter='data' parameter is available in Python 3.11.4+ and provides built-in protection against malicious tar archives containing entries with absolute paths or relative paths that traverse outside the extraction directory. References: - https://docs.python.org/3/library/tarfile.html#tarfile.TarFile.extractall - https://cwe.mitre.org/data/definitions/23.html
1 parent cd79464 commit 5de16cc

File tree

9 files changed

+14
-14
lines changed

9 files changed

+14
-14
lines changed

src/amg/azext_amg/restore.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ def restore(grafana_url, archive_file, components, http_headers, destination_dat
3838

3939
with tarfile.open(name=archive_file, mode='r:gz') as tar:
4040
with tempfile.TemporaryDirectory() as tmpdir:
41-
tar.extractall(tmpdir)
41+
tar.extractall(tmpdir, filter='data')
4242
tar.close()
4343
_restore_components(grafana_url, restore_functions, tmpdir, components, http_headers,
4444
destination_datasources=destination_datasources)

src/aosm/azext_aosm/common/utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,10 +101,10 @@ def extract_tarfile(file_path: Path, target_dir: Path) -> Path:
101101

102102
if file_extension in (".gz", ".tgz"):
103103
with tarfile.open(file_path, "r:gz") as tar:
104-
tar.extractall(path=target_dir)
104+
tar.extractall(path=target_dir, filter='data')
105105
elif file_extension == ".tar":
106106
with tarfile.open(file_path, "r:") as tar:
107-
tar.extractall(path=target_dir)
107+
tar.extractall(path=target_dir, filter='data')
108108
else:
109109
raise InvalidFileTypeError(
110110
f"ERROR: The helm package, '{file_path}', is not"

src/confcom/azext_confcom/os_util.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ def map_image_from_tar_backwards_compatibility(image_name: str, tar: TarFile, ta
170170
# extract just the manifest file and see if any of the RepoTags match the image_name we're searching for
171171
# the manifest.json should have a list of all the image tags
172172
# and what json files they map to to get env vars, startup cmd, etc.
173-
tar.extract("manifest.json", path=tar_dir)
173+
tar.extract("manifest.json", path=tar_dir, filter='data')
174174
manifest_path = os.path.join(tar_dir, "manifest.json")
175175
manifest = load_json_from_file(manifest_path)
176176
# if we match a RepoTag to the image, stop searching
@@ -187,7 +187,7 @@ def map_image_from_tar_backwards_compatibility(image_name: str, tar: TarFile, ta
187187

188188
if not info_file:
189189
return None
190-
tar.extract(info_file.name, path=tar_dir)
190+
tar.extract(info_file.name, path=tar_dir, filter='data')
191191

192192
# get the path of the json file and read it in
193193
image_info_file_path = os.path.join(tar_dir, info_file.name)
@@ -259,7 +259,7 @@ def map_image_from_tar(image_name: str, tar: TarFile, tar_location: str):
259259
# extract just the manifest file and see if any of the RepoTags match the image_name we're searching for
260260
# the manifest.json should have a list of all the image tags
261261
# and what json files they map to to get env vars, startup cmd, etc.
262-
tar.extract(info_file_name, path=tar_dir)
262+
tar.extract(info_file_name, path=tar_dir, filter='data')
263263
manifest_path = os.path.join(tar_dir, info_file_name)
264264
manifest = load_json_from_file(manifest_path)
265265
try:
@@ -274,7 +274,7 @@ def map_image_from_tar(image_name: str, tar: TarFile, tar_location: str):
274274

275275
if not info_file:
276276
return None
277-
tar.extract(info_file, path=tar_dir)
277+
tar.extract(info_file, path=tar_dir, filter='data')
278278

279279
# get the path of the json file and read it in
280280
image_info_file_path = os.path.join(tar_dir, info_file)

src/confcom/azext_confcom/tests/latest/test_confcom_fragment.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -498,7 +498,7 @@ def test_tar_file_fragment(self):
498498
tar_mapping_file = {"mcr.microsoft.com/aks/e2e/library-busybox:master.220314.1-linux-amd64": filename2}
499499
create_tar_file(filename)
500500
with TarFile(filename, "r") as tar:
501-
tar.extractall(path=folder)
501+
tar.extractall(path=folder, filter='data')
502502

503503
with TarFile.open(filename2, mode="w") as out_tar:
504504
out_tar.add(os.path.join(folder, "index.json"), "index.json")

src/confcom/azext_confcom/tests/latest/test_confcom_tar.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ def test_oci_tar_file(self):
180180
tar_mapping_file = {"mcr.microsoft.com/aks/e2e/library-busybox:master.220314.1-linux-amd64": filename2}
181181
create_tar_file(filename)
182182
with TarFile(filename, "r") as tar:
183-
tar.extractall(path=folder)
183+
tar.extractall(path=folder, filter='data')
184184

185185
with TarFile.open(filename2, mode="w") as out_tar:
186186
out_tar.add(os.path.join(folder, "index.json"), "index.json")

src/connectedk8s/azext_connectedk8s/clientproxyhelper/_binaryutils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ def _extract_proxy_tar_files(
182182

183183
members.append(member)
184184

185-
tar.extractall(members=members, path=install_dir)
185+
tar.extractall(members=members, path=install_dir, filter='data')
186186

187187

188188
def _check_proxy_installation(

src/containerapp/azext_containerapp/tests/latest/test_containerapp_create_update_up_java.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def download_java_source(source_path):
3737
shutil.rmtree(source_path)
3838

3939
with tarfile.open(temp_file.name, 'r:gz') as tar:
40-
tar.extractall(path=source_path)
40+
tar.extractall(path=source_path, filter='data')
4141

4242
os.remove(temp_file.name)
4343

src/networkcloud/azext_networkcloud/operations/custom_properties.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ def _output(parent_cmd, *args, **kwargs): # pylint: disable=unused-argument
5959
try:
6060
with urllib.request.urlopen(result_url) as result:
6161
with tarfile.open(fileobj=result, mode="r:gz") as tar:
62-
tar.extractall(path=output_directory)
62+
tar.extractall(path=output_directory, filter='data')
6363
logger.warning(
6464
"Extracted results are available in directory: %s",
6565
output_directory,
@@ -126,7 +126,7 @@ def _output(parent_cmd, *args, **kwargs): # pylint: disable=unused-argument
126126
try:
127127
# Extract the downloaded blob
128128
with tarfile.open(downloaded_blob_name, mode="r:gz") as tar:
129-
tar.extractall(path=output_directory)
129+
tar.extractall(path=output_directory, filter='data')
130130
logger.warning(
131131
"Extracted results are available in directory: %s",
132132
output_directory,

src/ssh/azext_ssh/connectivity_utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,7 @@ def _extract_proxy_tar_files(proxy_package_path, install_dir, proxy_name):
325325

326326
members.append(member)
327327

328-
tar.extractall(members=members, path=install_dir)
328+
tar.extractall(members=members, path=install_dir, filter='data')
329329

330330

331331
def _check_proxy_installation(install_dir, proxy_name):

0 commit comments

Comments
 (0)