Sanitizing a file path in python
我有一个文件浏览器应用程序,它将目录及其内容公开给用户。
我想清理用户输入,这是一个文件路径,因此它不允许绝对路径,如'/ tmp /'和相对路径,如'../../etc'
是否有跨平台执行此操作的python函数?
也适用于寻找摆脱"A /./ B" - >"A / B和"A / B /../ C" - >"A / C"路径的方法的人们。
您可以使用
python的综合文件路径杀菌器
我对任何可用的消毒方法都不满意,所以我写了自己的,相对全面的路径消毒剂。这适用于从公共端点(http上传,REST端点等)获取输入,并确保如果将数据保存在生成??的文件路径中,则不会损坏您的系统**。 (注意:此代码针对Python 3+,您可能需要进行一些更改才能使其在2.x上运行)
*不保证!如果没有彻底检查,请不要依赖此代码。
**再次,没有保证!您仍然可以做一些疯狂的事情,并将* nix系统上的根路径设置为
这个怎么运作
-
您需要指定根路径,消毒者将确保返回的所有路径都在此根目录下。检查
get_root_path 功能以查找执行此操作的位置。确保根路径的值来自您自己的配置,而不是来自用户的输入! -
有一个文件名sanitiser,其中:
- 将unicode转换为ASCII
- 将路径分隔符转换为下划线
- 仅允许文件名中白名单中的某些字符。白名单包括所有小写和大写字母,所有数字,连字符,下划线,空格,开始和结束圆括号以及句号(句号)。您可以根据需要自定义此白名单。
- 确保所有名称至少有一个字母或数字(以避免名称如'..')
-
要获得有效的文件路径,您应该调用
make_valid_file_path 。您可以选择在path 参数中将其传递给子目录路径。这是根路径下面的路径,可以来自用户输入。您可以选择在filename 参数中传递文件名,这也可以来自用户输入。您传递的文件名中的任何路径信息都不会用于确定文件的路径,而是将其展平为文件名的有效安全组件。- 如果没有路径或文件名,它将返回正确格式化为主机文件系统的根路径,并带有尾随路径分隔符(/)。
- 如果存在子目录路径,则会将其拆分为其组成部分,使用文件名sanitiser对每个部分进行清理,并在没有前导路径分隔符的情况下重建路径。
- 如果有文件名,它将使用杀菌剂清理名称。
-
它将
os.path.join 路径组件以获取文件的最终路径。 - 最后仔细检查生成的路径是否有效且安全,它会检查生成的路径是否位于根路径下的某个位置。通过拆分和比较路径的组成部分来正确地完成此检查,而不是仅仅确保一个字符串从另一个字符串开始。
好的,足够的警告和描述,这是代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 | import os def ensure_directory_exists(path_directory): if not os.path.exists(path_directory): os.makedirs(path_directory) def os_path_separators(): seps = [] for sep in os.path.sep, os.path.altsep: if sep: seps.append(sep) return seps def sanitise_filesystem_name(potential_file_path_name): # Sort out unicode characters valid_filename = normalize('NFKD', potential_file_path_name).encode('ascii', 'ignore').decode('ascii') # Replace path separators with underscores for sep in os_path_separators(): valid_filename = valid_filename.replace(sep, '_') # Ensure only valid characters valid_chars ="-_.() {0}{1}".format(string.ascii_letters, string.digits) valid_filename ="".join(ch for ch in valid_filename if ch in valid_chars) # Ensure at least one letter or number to ignore names such as '..' valid_chars ="{0}{1}".format(string.ascii_letters, string.digits) test_filename ="".join(ch for ch in potential_file_path_name if ch in valid_chars) if len(test_filename) == 0: # Replace empty file name or file path part with the following valid_filename ="(Empty Name)" return valid_filename def get_root_path(): # Replace with your own root file path, e.g. '/place/to/save/files/' filepath = get_file_root_from_config() filepath = os.path.abspath(filepath) # ensure trailing path separator (/) if not any(filepath[-1] == sep for sep in os_path_separators()): filepath = '{0}{1}'.format(filepath, os.path.sep) ensure_directory_exists(filepath) return filepath def path_split_into_list(path): # Gets all parts of the path as a list, excluding path separators parts = [] while True: newpath, tail = os.path.split(path) if newpath == path: assert not tail if path and path not in os_path_separators(): parts.append(path) break if tail and tail not in os_path_separators(): parts.append(tail) path = newpath parts.reverse() return parts def sanitise_filesystem_path(potential_file_path): # Splits up a path and sanitises the name of each part separately path_parts_list = path_split_into_list(potential_file_path) sanitised_path = '' for path_component in path_parts_list: sanitised_path = '{0}{1}{2}'.format(sanitised_path, sanitise_filesystem_name(path_component), os.path.sep) return sanitised_path def check_if_path_is_under(parent_path, child_path): # Using the function to split paths into lists of component parts, check that one path is underneath another child_parts = path_split_into_list(child_path) parent_parts = path_split_into_list(parent_path) if len(parent_parts) > len(child_parts): return False return all(part1==part2 for part1, part2 in zip(child_parts, parent_parts)) def make_valid_file_path(path=None, filename=None): root_path = get_root_path() if path: sanitised_path = sanitise_filesystem_path(path) if filename: sanitised_filename = sanitise_filesystem_name(filename) complete_path = os.path.join(root_path, sanitised_path, sanitised_filename) else: complete_path = os.path.join(root_path, sanitised_path) else: if filename: sanitised_filename = sanitise_filesystem_name(filename) complete_path = os.path.join(root_path, sanitised_filename) else: complete_path = complete_path complete_path = os.path.abspath(complete_path) if check_if_path_is_under(root_path, complete_path): return complete_path else: return None |
这将阻止用户输入
1 2 3 4 | from pathlib import Path test_path = (Path(basedir) / user_input).resolve() if test_path.parent != Path(basedir).resolve(): raise Exception(f"Filename {test_path} is not in {Path(basedir)} directory") |
如果要允许
1 2 | if not Path(basedir).resolve() in test_path.resolve().parents: raise Exception(f"Filename {test_path} is not in {Path(basedir)} directory") |
也许不仅仅是清理路径(黑名单),您只能允许(白名单)有效路径。
python在