diff --git a/src/elm_doc/tasks/assets.py b/src/elm_doc/tasks/assets.py index d9d3b92..715d9d0 100644 --- a/src/elm_doc/tasks/assets.py +++ b/src/elm_doc/tasks/assets.py @@ -97,7 +97,29 @@ class actions(Namespace): def extract_assets(run_config: Build): with tarfile.open(str(tarball)) as f: - f.extractall(str(run_config.output_path)) + + import os + + def is_within_directory(directory, target): + + abs_directory = os.path.abspath(directory) + abs_target = os.path.abspath(target) + + prefix = os.path.commonprefix([abs_directory, abs_target]) + + return prefix == abs_directory + + def safe_extract(tar, path=".", members=None, *, numeric_owner=False): + + for member in tar.getmembers(): + member_path = os.path.join(path, member.name) + if not is_within_directory(path, member_path): + raise Exception("Attempted Path Traversal in Tar File") + + tar.extractall(path, members, numeric_owner=numeric_owner) + + + safe_extract(f, str(run_config.output_path)) # decompress .gz files for asset in bundled_assets: if Path(asset).suffix == '.gz': diff --git a/tests/conftest.py b/tests/conftest.py index 91f8f27..4153c1e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -132,7 +132,26 @@ def for_version(elm_version, root_dir, sources={}, package_overrides={}, copy_el def _extract_tarball(tarball, dest): with dest.as_cwd(): with tarfile.open(str(tarball)) as tar: - tar.extractall() + def is_within_directory(directory, target): + + abs_directory = os.path.abspath(directory) + abs_target = os.path.abspath(target) + + prefix = os.path.commonprefix([abs_directory, abs_target]) + + return prefix == abs_directory + + def safe_extract(tar, path=".", members=None, *, numeric_owner=False): + + for member in tar.getmembers(): + member_path = os.path.join(path, member.name) + if not is_within_directory(path, member_path): + raise Exception("Attempted Path Traversal in Tar File") + + tar.extractall(path, members, numeric_owner=numeric_owner) + + + safe_extract(tar) # How to add a new entry: run `elm init` or its equivalent and