From 130a7a09c0f636e228056fb40825fa17b4535fdd Mon Sep 17 00:00:00 2001 From: TrellixVulnTeam Date: Sat, 10 Dec 2022 21:56:42 +0000 Subject: [PATCH] Adding tarfile member sanitization to extractall() --- arkos/applications.py | 21 +++++++++++++- arkos/backup.py | 21 +++++++++++++- arkos/utilities/utils.py | 63 ++++++++++++++++++++++++++++++++++++++-- 3 files changed, 100 insertions(+), 5 deletions(-) diff --git a/arkos/applications.py b/arkos/applications.py index bf545e4..9e006f2 100644 --- a/arkos/applications.py +++ b/arkos/applications.py @@ -609,7 +609,26 @@ def _install(id, load=True, cry=True): with open(path, "wb") as f: f.write(data) with tarfile.open(path, "r:gz") as t: - t.extractall(app_dir) + 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(t, app_dir) os.unlink(path) # Read the app's metadata and create an object with open(os.path.join(app_dir, id, "manifest.json")) as f: diff --git a/arkos/backup.py b/arkos/backup.py index ea5f09c..bf2c7ae 100644 --- a/arkos/backup.py +++ b/arkos/backup.py @@ -174,7 +174,26 @@ def restore(self, backup, data=True, nthread=NotificationThread()): for x in t.getnames(): if x.startswith("etc/nginx/sites-available"): sitename = os.path.basename(x) - t.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(t, "/") # If it's a website that had a database, restore DB via SQL file too dbpasswd = "" diff --git a/arkos/utilities/utils.py b/arkos/utilities/utils.py index cb99d07..70b543d 100644 --- a/arkos/utilities/utils.py +++ b/arkos/utilities/utils.py @@ -346,10 +346,48 @@ def extract(pin, pout, delete=False): name = os.path.basename(pin) if name.endswith((".tar.gz", ".tgz")): with tarfile.open(pin, "r:gz") as t: - t.extractall(pout) + 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(t, pout) elif name.endswith(".tar"): with tarfile.open(pin, "r") as t: - t.extractall(pout) + 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(t, pout) elif name.endswith(".gz"): f = gzip.open(pin, "rb") i = f.read() @@ -358,7 +396,26 @@ def extract(pin, pout, delete=False): f.write(i) elif name.endswith((".tar.bz2", ".tbz2")): with tarfile.open(pin, "r:bz2") as t: - t.extractall(f[0]) + 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(t, f["0"]) elif name.endswith(".bz2"): f = bz2.BZ2File(pin, "r") i = f.read()