Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FIX: Zip slip directory traversal vulnerability in file handling functions #884

Open
wants to merge 5 commits into
base: master
Choose a base branch
from

Conversation

Unichronic
Copy link
Contributor

This fixes issue #882

The "Zip Slip" vulnerability occurs when an application extracts files from an archive without validating their paths, allowing attackers to overwrite arbitrary files on the system.

To fix this I checked the following:

  • Stripping directory traversal sequences (e.g., ../) from the filenames.
  • Refusing to extract files with absolute paths.

I added method to Sanitize the paths before use in Compress and Extract functions. It ensures ensures that any unexpected directory traversal attempts (such as ../) or adding files to unwanted locations are handled correctly.

Please review this PR @tfmoraes @paulojamorim @paulojamorim @henrikkauppi @rmatsuda

Added a condition to check if self.conf is of type None
Comment on lines 166 to 170
self.conf = self.session.GetConfig("mep_configuration")
if self.conf is None:
self.conf = {}
else:
self.conf = dict(self.conf)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think here it's better to use a try except:

try:
    self.conf = dict(self.session.GetConfig("mep_configuration"))
except TypeError:
     self.conf = {}

Copy link
Contributor Author

@Unichronic Unichronic Jan 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure thing! I will make the changes but this change was actually part of another PR #873 as I branched from that PR branch instead of branching from the master, so I the changes in #873 itself? @tfmoraes

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So it's better to remove this from this PR and make the change in #837 PR

Comment on lines 526 to 532
fname = os.path.join(folder, decode(t.name, "utf-8"))
fname = os.path.normpath(fname)
if not os.path.commonprefix(
[os.path.abspath(folder), os.path.abspath(fname)]
) == os.path.abspath(folder):
raise Exception(f"Directory traversal detected: {t.name}")

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are using tar_filter for avoid this type of problem. But it only works for python >= 3.12. Can you implement our own tar_filter if tar_filter is None.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tfmoraes Can you please review if this is the custom tar_filter you meant? It implements the custom filter if getattr(tarfile, "tar_filter", custom_tar_filter) returns None

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes that is the idea. I didn't tested yet. Also, I think it's better to put custom_tar_filter as normal function, not inside a function.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't keeping the custom_tar_filter within the scope of Extract() be cleaner? Unless and until it's reused for other stuff in the future

Copy link
Contributor Author

@Unichronic Unichronic Jan 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tfmoraes I have added put custom_tar_filter as a normal function outside Extract(). Also added a an ErrorMessageBox popup on directory traversal detection. I have also run test on the code to verify its working fine.
I am attaching the python scripts to reproduce a directory traversal attempt:

import os
import tarfile
import shutil

def create_zip_slip(original_tar_path, output_tar_path, malfile_name, file_cont):

    if not os.path.exists(output_tar_path):
        with tarfile.open(output_tar_path, mode='w') as tar:
            pass
        
    shutil.copy2(original_tar_path, output_tar_path)

    with tarfile.open(output_tar_path, mode='a') as tar:
        malfile_path = os.path.join("../../../../../../../../../../../../../../", malfile_name)
        
        with open(malfile_name, 'w') as f:
            f.write(file_cont)
        
        tar.add(malfile_name, arcname=malfile_path)
        
        os.remove(malfile_name)

    print(f"Zip Slip vulnerable tar file created at: {output_tar_path}")

original_tar_path = 'original.inv3'
output_tar_path = 'vulnerable.inv3'
malfile_name = 'malicious.txt'
file_cont = 'This is a malicious file!'

create_zip_slip(original_tar_path, output_tar_path, malfile_name, file_cont)

Replace "original.inv3" with any actual Invesalius project file and then try opening vulnerable.inv3

Added Error pop-up when malicious file is detected
Generated malicious .inv3 file and ran tests
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants