UnzipFile
Unzip a file using fileName or entryID to specify a file. Unzipped files will be loaded to the War Room and names will be put into the context.
python · Common Scripts
Source
import os import shutil import sys import zipfile as z from os.path import isdir, isfile from pathlib import Path from subprocess import PIPE, Popen from tempfile import mkdtemp import demistomock as demisto from CommonServerPython import * from CommonServerUserPython import * MAX_FILENAME_SIZE_BYTES = 255 SLICE_FILENAME_SIZE_BYTES = 235 def get_zip_path(args): """ :param args: arg from demisto :return: path of zip file """ file_entry_id = "" if args.get("fileName") or args.get("lastZipFileInWarroom"): entries = demisto.executeCommand("getEntries", {}) for entry in entries: fn = demisto.get(entry, "File") # We check the python version to prevent encoding issues. Effects Demisto 4.5+ is_text = type(fn) is str # pylint: disable=E0602 is_correct_file = args.get("fileName", "").lower() == fn.lower() is_zip = fn.lower().endswith(".zip") if is_text and is_zip: if args.get("fileName") and is_correct_file: file_entry_id = entry["ID"] break if args.get("lastZipFileInWarroom"): file_entry_id = entry["ID"] # after the for loop above checks if a entry was found if not file_entry_id: if args.get("fileName"): demisto.results( { "Type": entryTypes["error"], "ContentsFormat": formats["text"], "Contents": args.get("fileName", "") + " not such file in war room", } ) if args.get("lastZipFileInWarroom"): demisto.results( {"Type": entryTypes["error"], "ContentsFormat": formats["text"], "Contents": "Not found zip file in war room"} ) sys.exit(0) if "entryID" in args: file_entry_id = args.get("entryID") # type: ignore if not file_entry_id: return_error("You must set entryID or fileName or lastZipFileInWarroom=true when executing Unzip script") res = demisto.executeCommand("getFilePath", {"id": file_entry_id}) if res[0]["Type"] == entryTypes["error"]: demisto.results( { "Type": entryTypes["error"], "ContentsFormat": formats["text"], "Contents": "Failed to get the file path for entry: " + file_entry_id, } ) sys.exit(0) return res[0]["Contents"] def extract(file_info, dir_path, zip_tool="7z", password=None): """ :param file_info: The file data. :param dir_path: directory that the file will be extract to :param zip_tool: The tool to extract files with (7z or zipfile). :param password: password if the zip file is encrypted :return: excluded_dirs: the excluded dirs which are in dir_path excluded_files: the excludedfiles which are in dir_path """ # remembering which files and dirs we currently have so we add them later as newly extracted files. file_path = file_info["path"] file_name = file_info["name"] excluded_files = [f for f in os.listdir(".") if isfile(f)] excluded_dirs = [d for d in os.listdir(".") if isdir(d)] # extracting the zip file """ We check the python version to ensure the docker image contains the necessary packages. 4.5+ use the new docker image. """ stdout = "" if ".rar" in file_name and sys.version_info > (3, 0): stdout = extract_using_unrar(file_path, dir_path, password=password) elif ".tar" in file_name: stdout = extract_using_tarfile(file_path, dir_path, file_name) else: if zip_tool == "7z": stdout = extract_using_7z(file_path, dir_path, password=password) elif zip_tool == "zipfile": if password: password = bytes(password, "utf-8") extract_using_zipfile(file_path, dir_path, password=password) else: return_error(f"There is no zipTool named: {zip_tool}") if "Wrong password?" in stdout: demisto.debug(stdout) return_error("Data Error in encrypted file. Wrong password?") return excluded_dirs, excluded_files def extract_using_unrar(file_path, dir_path, password=None): """ :param file_path: The file path. :param dir_path: directory that the file will be extract to :param password: password if the zip file is encrypted """ if password: cmd = ["unrar", "x", f"-p{password}", file_path, dir_path] else: cmd = ["unrar", "x", "-p-", file_path, dir_path] process = Popen(cmd, stdout=PIPE, stderr=PIPE) # process = Popen([cmd], shell=True, stdout=PIPE, stderr=PIPE) stdout, stderr = process.communicate() stdout = str(stdout) if stderr: if "Incorrect password" in str(stderr): return_error("The .rar file provided requires a password.") else: return_error(str(stderr)) return stdout def extract_using_tarfile(file_path: str, dir_path: str, file_name: str) -> str: if ".tar.gz" in file_name: cmd = ["tar", "-xzvf", file_path, "-C", dir_path, "--no-same-owner", "--no-same-permissions"] elif file_name.endswith(".tar"): cmd = ["tar", "-xf", file_path, "-C", dir_path, "--no-same-owner", "--no-same-permissions"] else: cmd = [] demisto.debug(f"{file_name=} didn't match any condition. {cmd=}") process = Popen(cmd, stdout=PIPE, stderr=PIPE) stdout, stderr = process.communicate() stdout = str(stdout) if stderr: demisto.info(str(stderr)) return stdout def extract_using_7z(file_path, dir_path, password=None): """ :param file_path: The file path. :param dir_path: directory that the file will be extract to :param password: password if the zip file is encrypted """ cmd = ["7z", "x", f"-p{password}", f"-o{dir_path}", file_path] process = Popen(cmd, stdout=PIPE, stderr=PIPE) # process = Popen([cmd], shell=True, stdout=PIPE, stderr=PIPE) stdout, stderr = process.communicate() stdout = str(stdout) if "Errors" in stdout: return_error( "7z couldn't extract this file - try using zipTool=zipfile\n" "If you already tried both zipfile and 7z check that the zip file is valid." ) return stdout def extract_using_zipfile(file_path, dir_path, password=None): """ :param file_path: The file path. :param dir_path: directory that the file will be extract to :param password: password if the zip file is encrypted """ try: with z.ZipFile(file_path, "r") as given_zip: for f in given_zip.filelist: full_filename = f.filename filename_length = len(full_filename.encode("utf-8")) if filename_length > MAX_FILENAME_SIZE_BYTES: file_name_splited = full_filename.rsplit(".", 1) file_name = file_name_splited[0] extension = file_name_splited[1] slice_object = slice(SLICE_FILENAME_SIZE_BYTES) file_name_bytes = file_name.encode("utf-8")[slice_object] # Rename the filename to a shorten filename. f.filename = f"{file_name_bytes.decode('utf-8', errors='ignore')}_shortened_.{extension}" demisto.results(f"The filename {full_filename} is too long - change file name to:\n{f.filename}") for filename in given_zip.filelist: given_zip.extract(filename, path=dir_path, pwd=password) given_zip.close() except Exception as e: return_error( f"zipfile couldn't extract this file - try using zipTool=7z\n" f"If you already tried both zipfile and 7z check that the zip file is valid. {e}" ) def upload_files(excluded_dirs, excluded_files, dir_path): """ :param excluded_dirs: excluded dirs :param excluded_files: excluded files :param dir_path: dir path for the files :return: """ filenames = [] # type: ignore file_entry_id = dir_path # recursive call over the file system top down for root, directories, files in os.walk(dir_path): # removing the previously existing dirs from the search directories[:] = [d for d in directories if d not in excluded_dirs] for f in files: # skipping previously existing files and verifying that the current file is a file and # then adding it to the extracted files list if f not in excluded_files and isfile(os.path.join(root, f)): filenames.append(os.path.join(root, f)) if len(filenames) == 0: demisto.results( {"Type": entryTypes["error"], "ContentsFormat": formats["text"], "Contents": "Could not find files in archive"} ) else: results = [] # extracted files can be in sub directories so we save the base names of # the files and also the full path of the file files_dic = {file_path: os.path.basename(file_path) for file_path in filenames} # Filter out symlinks and out-of-tree entries before building outputs safe_base = os.path.realpath(dir_path) + os.sep safe_files_dic = {} for file_path, file_name in files_dic.items(): real = os.path.realpath(file_path) if Path(file_path).is_symlink() or not real.startswith(safe_base): demisto.debug(f"Skipping out-of-tree entry: {file_path} -> {real}") continue safe_files_dic[file_path] = file_name with open(file_path, "rb") as _file: demisto.results(fileResult(file_name, _file.read())) # Build outputs from the filtered (safe) set only files_base_names = list(safe_files_dic.values()) # noqa: F812 results.append( { "Type": entryTypes["note"], "ContentsFormat": formats["json"], "Contents": {"extractedFiles": files_base_names}, "EntryContext": {"ExtractedFiles": files_base_names, 'File(val.EntryID=="' + file_entry_id + '").Unzipped': True}, "ReadableContentsFormat": formats["markdown"], "HumanReadable": tableToMarkdown( "Extracted Files", [{"name": file_name, "path": file_path} for file_path, file_name in safe_files_dic.items()], ), } ) demisto.results(results) def get_password(args): """ Get the file's password argument inserted by the user. The password can be inserted either in the sensitive argument (named 'password') or nonsensitive argument (named 'nonsensitive_password). This function asserts these arguments are used properly and raises an error if both are inserted and have a different value. so this Args: args: script's arguments Returns: the password given for the file. """ sensitive_password = args.get("password") nonsensitive_password = args.get("nonsensitive_password") if sensitive_password and nonsensitive_password and sensitive_password != nonsensitive_password: raise ValueError("Please use either the password argument or the non_sensitive_password argument, and not both.") return sensitive_password or nonsensitive_password def main(): dir_path = mkdtemp() try: args = demisto.args() zip_tool = args.get("zipTool", "7z") file_info = get_zip_path(args) password = get_password(args) excluded_dirs, excluded_files = extract(file_info=file_info, dir_path=dir_path, password=password, zip_tool=zip_tool) # Remove symlinks before uploading - use pure Python to avoid dependency on 'find' command for root, dirs, files_in_dir in os.walk(dir_path): for name in files_in_dir + dirs: full_path = Path(root) / name if full_path.is_symlink(): try: full_path.unlink() demisto.debug(f"Removed symlink: {full_path}") except OSError: demisto.debug(f"Failed to remove symlink: {full_path}") upload_files(excluded_dirs, excluded_files, dir_path) except Exception as e: return_error(str(e)) finally: shutil.rmtree(dir_path) if __name__ in ("__builtin__", "builtins"): main()
README
Unzip a file using fileName or entryID to specify a file. Unzipped files will be loaded to the War Room and names will be put into the context.
Script Data
| Name | Description |
|---|---|
| Script Type | python3 |
| Tags | Utility, file |
| Cortex XSOAR Version | 5.0.0 |
Used In
Sample usage of this script can be found in the following playbooks and scripts.
- Comprehensive PAN-OS Best Practice Assessment
- Cortex XDR - Retrieve File by sha256
- CrowdStrike Falcon - Retrieve File
- Get File Sample By Hash - Cylance Protect
- Local Analysis alert Investigation
- MDE - Retrieve File
- PS Remote Get File Sample From Path
- PS-Remote Get MFT
- PS-Remote Get Registry
- Pull Request Creation - Generic
Inputs
| Argument Name | Description |
|---|---|
| fileName | The file name. |
| password | Password to protect the ZIP file. |
| nonsensitive_password | Password to protect the ZIP file, inserted as a non sensative argument. |
| entryID | The entry ID of the attached ZIPp file in the War Room. |
| lastZipFileInWarroom | Enter ‘yes’ (or any other value) if the ZIP file is last ZIP file in the War Room. |
| zipTool | Tool to extract zip |
Outputs
| Path | Description | Type |
|---|---|---|
| ExtractedFiles | A list of file names that were extracted from the ZIP file. | Unknown |