From 44546a67c5ec2b687ac95c6e99c561b321faeb67 Mon Sep 17 00:00:00 2001 From: platomav Date: Sat, 16 Apr 2022 23:39:56 +0300 Subject: [PATCH] Fixed path symlink resolutions --- AMI_UCP_Extract.py | 6 ++--- common/path_ops.py | 57 +++++++++++++++++++++++++--------------------- 2 files changed, 34 insertions(+), 29 deletions(-) diff --git a/AMI_UCP_Extract.py b/AMI_UCP_Extract.py index 53b8b3b..32c4ae2 100644 --- a/AMI_UCP_Extract.py +++ b/AMI_UCP_Extract.py @@ -7,7 +7,7 @@ AMI UCP BIOS Extractor Copyright (C) 2021-2022 Plato Mavropoulos """ -title = 'AMI UCP BIOS Extractor v2.0_a7' +title = 'AMI UCP BIOS Extractor v2.0_a8' import os import re @@ -24,7 +24,7 @@ sys.dont_write_bytecode = True from common.a7z_comp import a7z_decompress, is_7z_supported from common.checksums import get_chk_16 from common.efi_comp import efi_decompress, is_efi_compressed -from common.path_ops import get_safe_name, get_safe_path +from common.path_ops import get_comp_path, get_safe_name, get_safe_path from common.patterns import PAT_AMI_UCP, PAT_INTEL_ENG from common.struct_ops import get_struct, char, uint8_t, uint16_t, uint32_t from common.system import script_init, argparse_init, printer @@ -387,7 +387,7 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, is_checksum=False, na printer(info_tag + ' : ' + info_value, padding + 8, False) # Print @NAL Module Tag-Path Info - info_part = PurePath(info_value.replace('\\', os.sep)).parts # Split path in parts + info_part = PurePath(get_comp_path(info_value)).parts # Split OS agnostic path in parts info_path = to_string(info_part[1:-1], os.sep) # Get path without drive/root or file info_name = info_part[-1] # Get file from last path part diff --git a/common/path_ops.py b/common/path_ops.py index 9722a63..0254a1c 100644 --- a/common/path_ops.py +++ b/common/path_ops.py @@ -11,29 +11,30 @@ from common.text_ops import to_string # Fix illegal/reserved Windows characters def get_safe_name(in_name): - raw_name = repr(in_name).strip("'") + name_repr = repr(in_name).strip("'") - fix_name = re.sub(r'[\\/*?:"<>|]', '_', raw_name) - - return fix_name + return re.sub(r'[\\/:"*?<>|]+', '_', name_repr) # Check and attempt to fix illegal/unsafe OS path traversals -def get_safe_path(base_path, user_paths, follow_symlinks=False): - # Convert user path(s) to string w/ OS separators +def get_safe_path(base_path, user_paths): + # Convert base path to absolute path + base_path = get_real_path(base_path) + + # Convert user path(s) to absolute path with OS separators user_path = to_string(user_paths, os.sep) # Create target path from base + requested user path target_path = get_norm_path(base_path, user_path) # Check if target path is OS illegal/unsafe - if is_safe_path(base_path, target_path, follow_symlinks): + if is_safe_path(base_path, target_path): return target_path # Re-create target path from base + leveled/safe illegal "path" (now file) nuked_path = get_norm_path(base_path, get_safe_name(user_path)) # Check if illegal path leveling worked - if is_safe_path(base_path, nuked_path, follow_symlinks): + if is_safe_path(base_path, nuked_path): return nuked_path # Still illegal, create fallback base path + placeholder file @@ -42,13 +43,12 @@ def get_safe_path(base_path, user_paths, follow_symlinks=False): return failed_path # Check for illegal/unsafe OS path traversal -def is_safe_path(base_path, target_path, follow_symlinks=True): - if follow_symlinks: - actual_path = os.path.realpath(target_path) - else: - actual_path = os.path.abspath(target_path) +def is_safe_path(base_path, target_path): + base_path = get_real_path(base_path) - common_path = os.path.commonpath((base_path, actual_path)) + target_path = get_real_path(target_path) + + common_path = os.path.commonpath((base_path, target_path)) return base_path == common_path @@ -56,6 +56,14 @@ def is_safe_path(base_path, target_path, follow_symlinks=True): def get_norm_path(base_path, user_path): return os.path.normpath(base_path + os.sep + user_path) +# Get absolute path, resolving any symlinks +def get_real_path(in_path): + return str(Path(in_path).resolve()) + +# Get Windows/Posix OS compatible path +def get_comp_path(in_path): + return in_path.replace('\\', os.sep) + # Walk path to get all files def get_path_files(in_path): path_files = [] @@ -70,8 +78,8 @@ def get_path_files(in_path): def get_path_parent(in_path): return Path(in_path).parent.absolute() -# Get absolute file path (argparse object) -def get_absolute_path(argparse_path): +# Get absolute file path of argparse object +def get_argparse_path(argparse_path): script_dir = get_path_parent(get_script_dir()) if not argparse_path: @@ -91,30 +99,27 @@ def process_input_files(argparse_args, sys_argv=None): # Drag & Drop or CLI if argparse_args.input_dir: input_path_user = argparse_args.input_dir - input_path_full = get_absolute_path(input_path_user) if input_path_user else '' + input_path_full = get_argparse_path(input_path_user) if input_path_user else '' input_files = get_path_files(input_path_full) else: input_files = [file.name for file in argparse_args.files] - output_path = get_absolute_path(argparse_args.output_dir or argparse_args.input_dir) + output_path = get_argparse_path(argparse_args.output_dir or argparse_args.input_dir) else: # Script w/o parameters input_path_user = input('\nEnter input directory path: ') - input_path_full = get_absolute_path(input_path_user) if input_path_user else '' + input_path_full = get_argparse_path(input_path_user) if input_path_user else '' input_files = get_path_files(input_path_full) - output_path = get_absolute_path(input('\nEnter output directory path: ')) + output_path = get_argparse_path(input('\nEnter output directory path: ')) return input_files, output_path # https://stackoverflow.com/a/22881871 by jfs -def get_script_dir(follow_symlinks=True): +def get_script_dir(): if getattr(sys, 'frozen', False): - path = os.path.abspath(sys.executable) + path = sys.executable else: path = inspect.getabsfile(get_script_dir) - - if follow_symlinks: - path = os.path.realpath(path) - return os.path.dirname(path) + return os.path.dirname(get_real_path(path))