Merge remote-tracking branch 'origin/refactor-advanced-multiselect' into fix_swimm_units

This commit is contained in:
omerr 2021-01-28 19:49:06 +02:00
commit bdf1dc2213
73 changed files with 1027 additions and 349 deletions

View File

@ -22,17 +22,18 @@
"hunkComments": [] "hunkComments": []
}, },
"hunkDiffLines": [ "hunkDiffLines": [
"@@ -62,15 +62,7 @@", "@@ -68,16 +68,7 @@",
" \"Removes the file afterwards.\",", " \"Removes the file afterwards.\",",
" \"attack_techniques\": [\"T1166\"]", " \"attack_techniques\": [\"T1166\"]",
" },", " },",
"- {", "- {",
"+ # Swimmer: ADD DETAILS HERE!\",", "+ # Swimmer: ADD DETAILS HERE!",
"- \"type\": \"string\",", "- \"type\": \"string\",",
"- \"enum\": [", "- \"enum\": [",
"- \"ScheduleJobs\"", "- \"ScheduleJobs\"",
"- ],", "- ],",
"- \"title\": \"Job scheduling\",", "- \"title\": \"Job scheduling\",",
"- \"safe\": True,",
"- \"info\": \"Attempts to create a scheduled job on the system and remove it.\",", "- \"info\": \"Attempts to create a scheduled job on the system and remove it.\",",
"- \"attack_techniques\": [\"T1168\", \"T1053\"]", "- \"attack_techniques\": [\"T1168\", \"T1053\"]",
"- },", "- },",
@ -45,5 +46,9 @@
} }
}, },
"app_version": "0.3.5-1", "app_version": "0.3.5-1",
"file_version": "1.0.4" "file_version": "1.0.4",
"hunksOrder": [
"monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py_0"
],
"last_commit_sha_for_swimm_patch": "9d9e8168fb2c23367b9947273aa1a041687b3e2e"
} }

View File

@ -23,26 +23,29 @@
"hunkComments": [] "hunkComments": []
}, },
"hunkDiffLines": [ "hunkDiffLines": [
"@@ -10,12 +10,6 @@", "@@ -10,11 +10,5 @@",
" \"\"\"", " \"\"\"",
" ", " ",
" def __init__(self):", " def __init__(self):",
"- linux_cmds, windows_cmds = get_commands_to_schedule_jobs()", "- linux_cmds, windows_cmds = get_commands_to_schedule_jobs()",
"+ pass", "+ pass",
" ", "-",
"- super(ScheduleJobs, self).__init__(name=POST_BREACH_JOB_SCHEDULING,",
"+ # Swimmer: IMPLEMENT HERE!", "+ # Swimmer: IMPLEMENT HERE!",
"- super(ScheduleJobs, self).__init__(name=POST_BREACH_JOB_SCHEDULING,",
"- linux_cmd=' '.join(linux_cmds),", "- linux_cmd=' '.join(linux_cmds),",
"- windows_cmd=windows_cmds)", "- windows_cmd=windows_cmds)",
"- ", "- ",
"- def run(self):", "- def run(self):",
"- super(ScheduleJobs, self).run()", "- super(ScheduleJobs, self).run()"
"- remove_scheduled_jobs()"
] ]
} }
] ]
} }
}, },
"app_version": "0.3.5-1", "app_version": "0.3.5-1",
"file_version": "1.0.4" "file_version": "1.0.4",
"hunksOrder": [
"monkey/infection_monkey/post_breach/actions/schedule_jobs.py_0"
],
"last_commit_sha_for_swimm_patch": "44fd1ab69cfbab33cec638dcbbaa8831992a9a9f"
} }

View File

@ -81,7 +81,7 @@
"@@ -1,5 +1,5 @@", "@@ -1,5 +1,5 @@",
" from common.data.post_breach_consts import (", " from common.data.post_breach_consts import (",
"- POST_BREACH_BACKDOOR_USER, POST_BREACH_COMMUNICATE_AS_NEW_USER)", "- POST_BREACH_BACKDOOR_USER, POST_BREACH_COMMUNICATE_AS_NEW_USER)",
"+ POST_BREACH_BACKDOOR_USER)", "+ POST_BREACH_COMMUNICATE_AS_NEW_USER)",
" from monkey_island.cc.services.attack.technique_reports.pba_technique import \\", " from monkey_island.cc.services.attack.technique_reports.pba_technique import \\",
" PostBreachTechnique", " PostBreachTechnique",
" " " "
@ -97,7 +97,7 @@
" scanned_msg = \"Monkey tried creating a new user on the network's systems, but failed.\"", " scanned_msg = \"Monkey tried creating a new user on the network's systems, but failed.\"",
" used_msg = \"Monkey created a new user on the network's systems.\"", " used_msg = \"Monkey created a new user on the network's systems.\"",
"- pba_names = [POST_BREACH_BACKDOOR_USER, POST_BREACH_COMMUNICATE_AS_NEW_USER]", "- pba_names = [POST_BREACH_BACKDOOR_USER, POST_BREACH_COMMUNICATE_AS_NEW_USER]",
"+ pba_names = [POST_BREACH_BACKDOOR_USER]" "+ pba_names = [POST_BREACH_COMMUNICATE_AS_NEW_USER]"
] ]
} }
] ]
@ -111,7 +111,7 @@
"hunkComments": [] "hunkComments": []
}, },
"hunkDiffLines": [ "hunkDiffLines": [
"@@ -4,15 +4,7 @@", "@@ -4,16 +4,7 @@",
" \"might do after breaching a new machine. Used in ATT&CK and Zero trust reports.\",", " \"might do after breaching a new machine. Used in ATT&CK and Zero trust reports.\",",
" \"type\": \"string\",", " \"type\": \"string\",",
" \"anyOf\": [", " \"anyOf\": [",
@ -122,6 +122,7 @@
"- \"BackdoorUser\"", "- \"BackdoorUser\"",
"- ],", "- ],",
"- \"title\": \"Back door user\",", "- \"title\": \"Back door user\",",
"- \"safe\": True,",
"- \"info\": \"Attempts to create a new user on the system and delete it afterwards.\",", "- \"info\": \"Attempts to create a new user on the system and delete it afterwards.\",",
"- \"attack_techniques\": [\"T1136\"]", "- \"attack_techniques\": [\"T1136\"]",
"- },", "- },",
@ -134,5 +135,13 @@
} }
}, },
"app_version": "0.3.5-1", "app_version": "0.3.5-1",
"file_version": "1.0.4" "file_version": "1.0.4",
"hunksOrder": [
"monkey/common/data/post_breach_consts.py_0",
"monkey/infection_monkey/post_breach/actions/add_user.py_0",
"monkey/monkey_island/cc/services/attack/technique_reports/T1136.py_0",
"monkey/monkey_island/cc/services/attack/technique_reports/T1136.py_1",
"monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py_0"
],
"last_commit_sha_for_swimm_patch": "9d9e8168fb2c23367b9947273aa1a041687b3e2e"
} }

View File

@ -60,23 +60,20 @@ before_script:
script: script:
# Check Python code # Check Python code
## Check syntax errors and fail the build if any are found. - flake8 ./monkey --config=./ci_scripts/flake8_syntax_check.ini
- flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics
## Warn about linter issues. ## Warn about linter issues.
### --exit-zero forces Flake8 to use the exit status code 0 even if there are errors, which means this will NOT fail the build. ### --exit-zero forces Flake8 to use the exit status code 0 even if there are errors, which means this will NOT fail the build.
### --count will print the total number of errors.
### --statistics Count the number of occurrences of each error/warning code and print a report.
### The output is redirected to a file. ### The output is redirected to a file.
- flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics > flake8_warnings.txt - flake8 ./monkey --exit-zero --config=./ci_scripts/flake8_linter_check.ini > ./ci_scripts/flake8_warnings.txt
## Display the linter issues ## Display the linter issues
- cat flake8_warnings.txt - cat ./ci_scripts/flake8_warnings.txt
## Make sure that we haven't increased the amount of warnings. ## Make sure that we haven't increased the amount of warnings.
- PYTHON_WARNINGS_AMOUNT_UPPER_LIMIT=80 - PYTHON_WARNINGS_AMOUNT_UPPER_LIMIT=80
- if [ $(tail -n 1 flake8_warnings.txt) -gt $PYTHON_WARNINGS_AMOUNT_UPPER_LIMIT ]; then echo "Too many python linter warnings! Failing this build. Lower the amount of linter errors in this and try again. " && exit 1; fi - if [ $(tail -n 1 ./ci_scripts/flake8_warnings.txt) -gt $PYTHON_WARNINGS_AMOUNT_UPPER_LIMIT ]; then echo "Too many python linter warnings! Failing this build. Lower the amount of linter errors in this and try again. " && exit 1; fi
## Check import order ## Check import order
- python -m isort . -c -p common -p infection_monkey -p monkey_island - python -m isort ./monkey --settings-file ./ci_scripts/isort.cfg
## Run unit tests ## Run unit tests
- cd monkey # This is our source dir - cd monkey # This is our source dir
@ -98,10 +95,11 @@ script:
# verify swimm # verify swimm
- cd $TRAVIS_BUILD_DIR - cd $TRAVIS_BUILD_DIR
- wget https://firebasestorage.googleapis.com/v0/b/swimmio.appspot.com/o/Release%2Fv018%2Fswimm-0.1.8-linux-executable\?alt\=media\&token\=e59c0a18-577f-4b77-bb3b-91b22c3d8b2a -O swimm - wget "https://firebasestorage.googleapis.com/v0/b/swimmio.appspot.com/o/Release%2Fv029%2FSwimm_0.2.9_Setup.deb?alt=media&token=774ebd98-cb4e-4615-900c-aada224c1608" -O swimm
- sudo dpkg -i swimm || (sudo apt-get update && sudo apt-get -f install)
- chmod +x ./swimm - chmod +x ./swimm
- ./swimm --version - swimm --version
- ./swimm verify - swimm verify
after_success: after_success:
# Upload code coverage results to codecov.io, see https://github.com/codecov/codecov-bash for more information # Upload code coverage results to codecov.io, see https://github.com/codecov/codecov-bash for more information

2
ci_scripts/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
./validation-env
./flake8_warnings.txt

8
ci_scripts/README.md Normal file
View File

@ -0,0 +1,8 @@
# About
Run this script to validate your code locally and auto fix/format the problems before pushing.
# Usage
You've got to manually download swimm for swimm validation.
run from `infection_monkey` directory: `powershell .\ci_scripts\validate.ps1`

View File

@ -0,0 +1,15 @@
[flake8]
## Warn about linter issues.
exclude = ../monkey/monkey_island/cc/ui,
../monkey/common/cloud
show-source = True
max-complexity = 10
max-line-length = 127
### --statistics Count the number of occurrences of each error/warning code and print a report.
statistics = True
### --count will print the total number of errors.
count = True

View File

@ -0,0 +1,15 @@
[flake8]
## Check syntax errors and fail the build if any are found.
exclude =
../monkey/monkey_island/cc/ui,
../monkey/common/cloud
select =
E901,
E999,
F821,
F822,
F823
count = True
show-source = True
statistics = True

View File

@ -0,0 +1,5 @@
python -m venv validation-env
.\validation-env\Scripts\activate.ps1
python -m pip install -r .\requirements.txt
npm i -g eslint
deactivate

6
ci_scripts/isort.cfg Normal file
View File

@ -0,0 +1,6 @@
[isort]
# Possible options: https://pycqa.github.io/isort/docs/configuration/options/
known_first_party=common,infection_monkey,monkey_island
skip=monkey/common/cloud/scoutsuite,monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators_list.py,monkey/monkey_island/cc/ui,monkey/common/cloud/scoutsuite

View File

@ -0,0 +1,6 @@
flake8
pytest
dlint
isort
coverage
black

39
ci_scripts/validate.ps1 Normal file
View File

@ -0,0 +1,39 @@
.\ci_scripts\validation-env\Scripts\activate.ps1
$ErrorActionPreference = "Stop"
python -m pip install -r monkey/monkey_island/requirements.txt
python -m pip install -r monkey/infection_monkey/requirements.txt
flake8 ./monkey --config ./ci_scripts/flake8_syntax_check.cfg
flake8 ./monkey --exit-zero --config ./ci_scripts/flake8_linter_check.cfg | Out-File -FilePath .\ci_scripts\flake8_warnings.txt
Get-Content -Path .\ci_scripts\flake8_warnings.txt
$PYTHON_WARNINGS_AMOUNT_UPPER_LIMIT = 80
if ((Get-Item -Path .\ci_scripts\flake8_warnings.txt | Get-Content -Tail 1) -gt $PYTHON_WARNINGS_AMOUNT_UPPER_LIMIT){
"Too many python linter warnings! Failing this build. Lower the amount of linter errors in this and try again. "
exit
}
python -m isort ./monkey -c --settings-file ./ci_scripts/isort.cfg
if (!$?) {
$confirmation = Read-Host "Isort found errors. Do you want to attmpt to fix them automatically? (y/n)"
if ($confirmation -eq 'y') {
python -m isort ./monkey --settings-file ./ci_scripts/isort.cfg
}
}
Push-Location -Path ./monkey
python ./monkey_island/cc/environment/set_server_config.py testing
python -m pytest
$lastCommandSucceeded = $?
python ./monkey_island/cc/environment/set_server_config.py restore
Pop-Location
if (!$lastCommandSucceeded) {
exit
}
Push-Location -Path .\monkey\monkey_island\cc\ui
eslint ./src -c ./.eslintrc
Pop-Location
swimm verify
Write-Host "Script finished. Press any key to continue"
$null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown');
deactivate

View File

@ -39,6 +39,7 @@ Your user must have root permissions; however, don't run the script as root!
```sh ```sh
wget https://raw.githubusercontent.com/guardicore/monkey/develop/deployment_scripts/deploy_linux.sh wget https://raw.githubusercontent.com/guardicore/monkey/develop/deployment_scripts/deploy_linux.sh
chmod u+x ./deploy_linux.sh
``` ```
This will download our deploy script. It's a good idea to read it quickly before executing it! This will download our deploy script. It's a good idea to read it quickly before executing it!
@ -53,3 +54,12 @@ After downloading that script, execute it in a shell. The first argument should
- `./deploy_linux.sh "/home/user/new" "master"` (if directory "new" is not found creates it and clones master branch into it) - `./deploy_linux.sh "/home/user/new" "master"` (if directory "new" is not found creates it and clones master branch into it)
You may also pass in an optional third `false` parameter to disable downloading the latest agent binaries. You may also pass in an optional third `false` parameter to disable downloading the latest agent binaries.
### Run on Linux
After the `deploy_linux.sh` script completes, you can start the monkey island.
```sh
cd infection_monkey/monkey
./monkey_island/linux/run.sh
```

View File

@ -4,41 +4,42 @@ export MONKEY_FOLDER_NAME="infection_monkey"
# Url of public git repository that contains monkey's source code # Url of public git repository that contains monkey's source code
export MONKEY_GIT_URL="https://github.com/guardicore/monkey" export MONKEY_GIT_URL="https://github.com/guardicore/monkey"
exists() {
command -v "$1" >/dev/null 2>&1
}
get_latest_release() { get_latest_release() {
curl --silent "https://api.github.com/repos/$1/releases/latest" | # Get latest release from GitHub API RELEASE_URL="https://api.github.com/repos/$1/releases/latest"
if exists wget; then
RELEASE_INFO=$(wget --quiet -O - "$RELEASE_URL") # Get latest release from GitHub API
else
RELEASE_INFO=$(curl --silent "$RELEASE_URL") # Get latest release from GitHub API
fi
echo "$RELEASE_INFO" |
grep '"tag_name":' | # Get tag line grep '"tag_name":' | # Get tag line
sed -E 's/.*"([^"]+)".*/\1/' # Pluck JSON value sed -E 's/.*"([^"]+)".*/\1/' # Pluck JSON value
} }
MONKEY_LATEST_RELEASE=$(get_latest_release "monkey/guardicore") MONKEY_LATEST_RELEASE=$(get_latest_release "guardicore/monkey")
# Monkey binaries # Monkey binaries
LINUX_32_BINARY_NAME="monkey-linux-32" export LINUX_32_BINARY_NAME="monkey-linux-32"
LINUX_32_BINARY_URL="https://github.com/guardicore/monkey/releases/download/$($MONKEY_LATEST_RELEASE)/monkey-linux-32" export LINUX_32_BINARY_URL="https://github.com/guardicore/monkey/releases/download/$MONKEY_LATEST_RELEASE/monkey-linux-32"
export LINUX_32_BINARY_URL
export LINUX_32_BINARY_NAME
LINUX_64_BINARY_NAME="monkey-linux-64" export LINUX_64_BINARY_NAME="monkey-linux-64"
LINUX_64_BINARY_URL="https://github.com/guardicore/monkey/releases/download/$($MONKEY_LATEST_RELEASE)/monkey-linux-64" export LINUX_64_BINARY_URL="https://github.com/guardicore/monkey/releases/download/$MONKEY_LATEST_RELEASE/monkey-linux-64"
export LINUX_64_BINARY_URL
export LINUX_64_BINARY_NAME
WINDOWS_32_BINARY_NAME="monkey-windows-32.exe" export WINDOWS_32_BINARY_NAME="monkey-windows-32.exe"
WINDOWS_32_BINARY_URL="https://github.com/guardicore/monkey/releases/download/$($MONKEY_LATEST_RELEASE)/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_32_BINARY_URL
export WINDOWS_32_BINARY_NAME
WINDOWS_64_BINARY_NAME="monkey-windows-64.exe" export WINDOWS_64_BINARY_NAME="monkey-windows-64.exe"
WINDOWS_64_BINARY_URL="https://github.com/guardicore/monkey/releases/download/$($MONKEY_LATEST_RELEASE)/monkey-windows-64.exe" export WINDOWS_64_BINARY_URL="https://github.com/guardicore/monkey/releases/download/$MONKEY_LATEST_RELEASE/monkey-windows-64.exe"
export WINDOWS_64_BINARY_URL
export WINDOWS_64_BINARY_NAME
# Other binaries for monkey # Other binaries for monkey
TRACEROUTE_64_BINARY_URL="https://github.com/guardicore/monkey/releases/download/$($MONKEY_LATEST_RELEASE)/traceroute64" export TRACEROUTE_64_BINARY_URL="https://github.com/guardicore/monkey/releases/download/$MONKEY_LATEST_RELEASE/traceroute64"
export TRACEROUTE_64_BINARY_URL export TRACEROUTE_32_BINARY_URL="https://github.com/guardicore/monkey/releases/download/$MONKEY_LATEST_RELEASE/traceroute32"
TRACEROUTE_32_BINARY_URL="https://github.com/guardicore/monkey/releases/download/$($MONKEY_LATEST_RELEASE)/traceroute32"
export TRACEROUTE_32_BINARY_URL export SAMBACRY_64_BINARY_URL="https://github.com/guardicore/monkey/releases/download/$MONKEY_LATEST_RELEASE/sc_monkey_runner64.so"
SAMBACRY_64_BINARY_URL="https://github.com/guardicore/monkey/releases/download/$($MONKEY_LATEST_RELEASE)/sc_monkey_runner64.so" export SAMBACRY_32_BINARY_URL="https://github.com/guardicore/monkey/releases/download/$MONKEY_LATEST_RELEASE/sc_monkey_runner32.so"
export SAMBACRY_64_BINARY_URL
SAMBACRY_32_BINARY_URL="https://github.com/guardicore/monkey/releases/download/$($MONKEY_LATEST_RELEASE)/sc_monkey_runner32.so"
export SAMBACRY_32_BINARY_URL

View File

@ -10,7 +10,7 @@ is_root() {
has_sudo() { has_sudo() {
# 0 true, 1 false # 0 true, 1 false
timeout 1 sudo id && return 0 || return 1 return $(sudo -nv > /dev/null 2>&1)
} }
handle_error() { handle_error() {
@ -23,6 +23,11 @@ log_message() {
echo -e "DEPLOYMENT SCRIPT: $1" echo -e "DEPLOYMENT SCRIPT: $1"
} }
if is_root; then
log_message "Please don't run this script as root"
exit 1
fi
config_branch=${2:-"develop"} config_branch=${2:-"develop"}
config_url="https://raw.githubusercontent.com/guardicore/monkey/${config_branch}/deployment_scripts/config" config_url="https://raw.githubusercontent.com/guardicore/monkey/${config_branch}/deployment_scripts/config"
@ -62,14 +67,9 @@ ISLAND_BINARIES_PATH="$ISLAND_PATH/cc/binaries"
INFECTION_MONKEY_DIR="$monkey_home/monkey/infection_monkey" INFECTION_MONKEY_DIR="$monkey_home/monkey/infection_monkey"
MONKEY_BIN_DIR="$INFECTION_MONKEY_DIR/bin" MONKEY_BIN_DIR="$INFECTION_MONKEY_DIR/bin"
if is_root; then if ! has_sudo; then
log_message "Please don't run this script as root" log_message "You need root permissions for some of this script operations. \
exit 1 Run \`sudo -v\`, enter your password, and then re-run this script."
fi
HAS_SUDO=$(has_sudo)
if [[ ! $HAS_SUDO ]]; then
log_message "You need root permissions for some of this script operations. Quiting."
exit 1 exit 1
fi fi
@ -110,13 +110,16 @@ if [[ ${python_cmd} == "" ]]; then
log_message "Python 3.7 command not found. Installing python 3.7." log_message "Python 3.7 command not found. Installing python 3.7."
sudo add-apt-repository ppa:deadsnakes/ppa sudo add-apt-repository ppa:deadsnakes/ppa
sudo apt-get update sudo apt-get update
sudo apt install python3.7 python3.7-dev sudo apt-get install -y python3.7 python3.7-dev
log_message "Python 3.7 is now available with command 'python3.7'." log_message "Python 3.7 is now available with command 'python3.7'."
python_cmd="python3.7" python_cmd="python3.7"
fi fi
log_message "Installing build-essential" log_message "Installing build-essential"
sudo apt install build-essential sudo apt-get install -y build-essential
log_message "Installing python3-distutils"
sudo apt-get install -y python3-distutils
log_message "Installing or updating pip" log_message "Installing or updating pip"
# shellcheck disable=SC2086 # shellcheck disable=SC2086
@ -134,7 +137,7 @@ requirements_island="$ISLAND_PATH/requirements.txt"
${python_cmd} -m pip install -r "${requirements_island}" --user --upgrade || handle_error ${python_cmd} -m pip install -r "${requirements_island}" --user --upgrade || handle_error
log_message "Installing monkey requirements" log_message "Installing monkey requirements"
sudo apt-get install libffi-dev upx libssl-dev libc++1 sudo apt-get install -y libffi-dev upx libssl-dev libc++1
requirements_monkey="$INFECTION_MONKEY_DIR/requirements.txt" requirements_monkey="$INFECTION_MONKEY_DIR/requirements.txt"
${python_cmd} -m pip install -r "${requirements_monkey}" --user --upgrade || handle_error ${python_cmd} -m pip install -r "${requirements_monkey}" --user --upgrade || handle_error
@ -162,15 +165,19 @@ 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 # If a user haven't installed mongo manually check if we can install it with our script
if ! exists mongod; then if ! exists mongod; then
log_message "Installing libcurl4"
sudo apt-get install -y libcurl4
log_message "Installing MongoDB" log_message "Installing MongoDB"
"${ISLAND_PATH}"/linux/install_mongo.sh ${MONGO_PATH} || handle_error "${ISLAND_PATH}"/linux/install_mongo.sh ${MONGO_PATH} || handle_error
fi fi
log_message "Installing openssl" log_message "Installing openssl"
sudo apt-get install openssl sudo apt-get install -y openssl
# Generate SSL certificate # Generate SSL certificate
log_message "Generating certificate" log_message "Generating certificate"
chmod u+x "${ISLAND_PATH}"/linux/create_certificate.sh
"${ISLAND_PATH}"/linux/create_certificate.sh ${ISLAND_PATH}/cc "${ISLAND_PATH}"/linux/create_certificate.sh ${ISLAND_PATH}/cc
# Update node # Update node

View File

@ -9,6 +9,7 @@ Here are some of the most common questions we receive about the Infection Monkey
- [Where can I get the latest Monkey version? 📰](#where-can-i-get-the-latest-monkey-version) - [Where can I get the latest Monkey version? 📰](#where-can-i-get-the-latest-monkey-version)
- [How long does a single Monkey run for? Is there a time limit?](#how-long-does-a-single-monkey-run-for-is-there-a-time-limit) - [How long does a single Monkey run for? Is there a time limit?](#how-long-does-a-single-monkey-run-for-is-there-a-time-limit)
- [How to reset the password?](#how-to-reset-the-password)
- [Should I run the Monkey continuously?](#should-i-run-the-monkey-continuously) - [Should I run the Monkey continuously?](#should-i-run-the-monkey-continuously)
- [Which queries does Monkey perform to the Internet exactly?](#which-queries-does-monkey-perform-to-the-internet-exactly) - [Which queries does Monkey perform to the Internet exactly?](#which-queries-does-monkey-perform-to-the-internet-exactly)
- [Where can I find the log files of the Monkey and the Monkey Island, and how can I read them?](#where-can-i-find-the-log-files-of-the-monkey-and-the-monkey-island-and-how-can-i-read-them) - [Where can I find the log files of the Monkey and the Monkey Island, and how can I read them?](#where-can-i-find-the-log-files-of-the-monkey-and-the-monkey-island-and-how-can-i-read-them)
@ -35,6 +36,23 @@ If you want to see what has changed between versions, refer to the [releases pag
The Monkey shuts off either when it can't find new victims, or when it has exceeded the quota of victims as defined in the configuration. The Monkey shuts off either when it can't find new victims, or when it has exceeded the quota of victims as defined in the configuration.
## How to reset the password?
On your first access of Monkey Island server, you'll be prompted to create an account. If you forgot the credentials you
entered or just want to change them, you need to manually alter the `server_config.json` file. On Linux, this file is
located on `/var/monkey/monkey_island/cc/server_config.json`. On windows, it's based on your install directory (typically
`C:\Program Files\Guardicore\Monkey Island\monkey_island\cc\server_config.json`). Reset the contents of this file
leaving the **deployment option unchanged** (it might be "vmware" or "linux" in your case):
```json
{
"server_config": "password",
"deployment": "windows"
}
```
Then reset the Island process (`sudo systemctl restart monkey-island.service` for linux, restart program for windows).
Finally, go to the Island's URL and create a new account.
## Should I run the Monkey continuously? ## Should I run the Monkey continuously?
Yes! This will allow you to verify that no new security issues were identified by the Monkey since the last time you ran it. Yes! This will allow you to verify that no new security issues were identified by the Monkey since the last time you ran it.

View File

@ -1,7 +1,7 @@
+++ +++
title = "Reports" title = "Reports"
date = 2020-06-24T21:16:03+03:00 date = 2020-06-24T21:16:03+03:00
weight = 5 weight = 40
chapter = true chapter = true
pre = "<i class='fas fa-scroll'></i> " pre = "<i class='fas fa-scroll'></i> "
+++ +++
@ -10,4 +10,4 @@ pre = "<i class='fas fa-scroll'></i> "
The Monkey offers three reports: The Monkey offers three reports:
{{% children %}} {{% children description=true style="p"%}}

View File

@ -1,5 +1,6 @@
--- ---
title: "MITRE ATT&CK report" title: "MITRE ATT&CK report"
description: "Maps the Monkey's actions to the MITRE ATT&CK knowledge base"
date: 2020-06-24T21:17:18+03:00 date: 2020-06-24T21:17:18+03:00
draft: false draft: false
--- ---

View File

@ -2,6 +2,7 @@
title: "Security report" title: "Security report"
date: 2020-06-24T21:16:10+03:00 date: 2020-06-24T21:16:10+03:00
draft: false draft: false
description: "Provides actionable recommendations and insight into an attacker's view of your network"
--- ---
{{% notice info %}} {{% notice info %}}

View File

@ -2,6 +2,7 @@
title: "Zero Trust report" title: "Zero Trust report"
date: 2020-06-24T21:16:18+03:00 date: 2020-06-24T21:16:18+03:00
draft: false draft: false
description: "Generates a status report with detailed explanations of Zero Trust security gaps and prescriptive instructions on how to rectify them"
--- ---
{{% notice info %}} {{% notice info %}}
@ -17,7 +18,7 @@ This diagram provides a quick glance at how your organization scores on each com
- {{< label danger Failed >}} At least one of the tests related to this component failed. This means that the Infection Monkey detected an unmet Zero Trust requirement. - {{< label danger Failed >}} At least one of the tests related to this component failed. This means that the Infection Monkey detected an unmet Zero Trust requirement.
- {{< label warning Verify >}} At least one of the tests results related to this component requires further manual verification. - {{< label warning Verify >}} At least one of the tests results related to this component requires further manual verification.
- {{< label success Passed >}} All Tests related to this pillar passed. No violation of a Zero Trust guiding principle was detected. - {{< label success Passed >}} All Tests related to this pillar passed. No violation of a Zero Trust guiding principle was detected.
- {{< label other Unexecuted >}} This status means no tests were executed for this pillar. - {{< label unused Unexecuted >}} This status means no tests were executed for this pillar.
![Zero Trust Report summary](/images/usage/reports/ztreport1.png "Zero Trust Report summary") ![Zero Trust Report summary](/images/usage/reports/ztreport1.png "Zero Trust Report summary")

View File

@ -54,16 +54,15 @@ See [Amazon's documentation about working with SSM agents](https://docs.aws.amaz
### Running the monkey ### Running the monkey
When you run the monkey island on an AWS instance, the island detects it's running on AWS and present the following option in the _"Run Monkey"_ page, like so: When you run the Monkey Island on an AWS instance, the island detects it's running on AWS and present the following option in the _"Run Monkey"_ page, like so:
![Running a Monkey on EC2 Instance](/images/usage/integrations/monkey-island-aws-screenshot-1.png "Running a Monkey on EC2 Instance") ![Running a Monkey on EC2 Instance](/images/usage/integrations/monkey-island-aws-screenshot-1.png "Running a Monkey on EC2 Instance")
And then you can choose one of the available instances as "patient zero" like so: After you click on "AWS run" you can choose one of the available instances as "patient zero" like so:
1. Click on "Run on AWS" 1. Choose the relevant Network Interface
2. Choose the relevant Network Interface 2. Select the machines you'd like to run the Monkey on
3. Select the machines you'd like to run the Monkey on 3. Click "Run on Selected Machines", and watch the monkey go! 🐒
4. Click "Run on Selected Machines", and watch the monkey go! 🐒
![Running a Monkey on EC2 Instance](/images/usage/integrations/monkey-island-aws-screenshot-2.png "Running a Monkey on EC2 Instance") ![Running a Monkey on EC2 Instance](/images/usage/integrations/monkey-island-aws-screenshot-2.png "Running a Monkey on EC2 Instance")

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 101 KiB

View File

@ -228,7 +228,6 @@ class ShellShockExploiter(HostExploiter):
Checks if which urls exist Checks if which urls exist
:return: Sequence of URLs to try and attack :return: Sequence of URLs to try and attack
""" """
import requests
attack_path = 'http://' attack_path = 'http://'
if is_https: if is_https:
attack_path = 'https://' attack_path = 'https://'

View File

@ -1,7 +1,6 @@
from logging import getLogger from logging import getLogger
from impacket.dcerpc.v5 import scmr, transport from impacket.dcerpc.v5 import scmr, transport
from impacket.smbconnection import SMB_DIALECT
from common.utils.attack_utils import ScanStatus, UsageEnum from common.utils.attack_utils import ScanStatus, UsageEnum
from common.utils.exploit_enum import ExploitType from common.utils.exploit_enum import ExploitType
@ -104,7 +103,7 @@ class SmbExploiter(HostExploiter):
LOG.debug("Exploiter SmbExec is giving up...") LOG.debug("Exploiter SmbExec is giving up...")
return False return False
self.set_vulnerable_port(self.host) self.set_vulnerable_port()
# execute the remote dropper in case the path isn't final # 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() != self._config.dropper_target_path_win_32.lower():
cmdline = DROPPER_CMDLINE_DETACHED_WINDOWS % {'dropper_path': remote_full_path} + \ cmdline = DROPPER_CMDLINE_DETACHED_WINDOWS % {'dropper_path': remote_full_path} + \
@ -121,8 +120,7 @@ class SmbExploiter(HostExploiter):
for str_bind_format, port in SmbExploiter.KNOWN_PROTOCOLS.values(): for str_bind_format, port in SmbExploiter.KNOWN_PROTOCOLS.values():
rpctransport = transport.DCERPCTransportFactory(str_bind_format % (self.host.ip_addr,)) rpctransport = transport.DCERPCTransportFactory(str_bind_format % (self.host.ip_addr,))
rpctransport.set_dport(port) rpctransport.set_dport(port)
if hasattr(rpctransport, 'preferred_dialect'): rpctransport.setRemoteHost(self.host.ip_addr)
rpctransport.preferred_dialect(SMB_DIALECT)
if hasattr(rpctransport, 'set_credentials'): if hasattr(rpctransport, 'set_credentials'):
# This method exists only for selected protocol sequences. # This method exists only for selected protocol sequences.
rpctransport.set_credentials(user, password, '', lm_hash, ntlm_hash, None) rpctransport.set_credentials(user, password, '', lm_hash, ntlm_hash, None)
@ -168,7 +166,7 @@ class SmbExploiter(HostExploiter):
SmbExploiter.KNOWN_PROTOCOLS['445/SMB'][1])) SmbExploiter.KNOWN_PROTOCOLS['445/SMB'][1]))
return True return True
def set_vulnerable_port(self, host: VictimHost): def set_vulnerable_port(self):
if 'tcp-445' in self.host.services: if 'tcp-445' in self.host.services:
self.vulnerable_port = "445" self.vulnerable_port = "445"
elif 'tcp-139' in self.host.services: elif 'tcp-139' in self.host.services:

View File

@ -56,7 +56,7 @@ class WebRCE(HostExploiter):
Method that creates a dictionary of configuration values for exploit Method that creates a dictionary of configuration values for exploit
:return: configuration dict :return: configuration dict
""" """
exploit_config = dict() exploit_config = {}
# dropper: If true monkey will use dropper parameter that will detach monkey's process and try to copy # dropper: If true monkey will use dropper parameter that will detach monkey's process and try to copy
# it's file to the default destination path. # it's file to the default destination path.

View File

@ -113,7 +113,7 @@ class NetworkScanner(object):
:return: Victim or None if victim isn't alive :return: Victim or None if victim isn't alive
""" """
LOG.debug("Scanning target address: %r", victim) LOG.debug("Scanning target address: %r", victim)
if any([scanner.is_host_alive(victim) for scanner in self.scanners]): if any(scanner.is_host_alive(victim) for scanner in self.scanners):
LOG.debug("Found potential target_ip: %r", victim) LOG.debug("Found potential target_ip: %r", victim)
return victim return victim
else: else:

View File

@ -139,7 +139,7 @@ def check_tcp_ports(ip, ports, timeout=DEFAULT_TIMEOUT, get_banner=False):
timeout = int(round(timeout)) # clamp to integer, to avoid checking input timeout = int(round(timeout)) # clamp to integer, to avoid checking input
sockets_to_try = possible_ports[:] sockets_to_try = possible_ports[:]
connected_ports_sockets = [] connected_ports_sockets = []
while (timeout >= 0) and len(sockets_to_try): while (timeout >= 0) and sockets_to_try:
sock_objects = [s[1] for s in sockets_to_try] sock_objects = [s[1] for s in sockets_to_try]
_, writeable_sockets, _ = select.select(sock_objects, sock_objects, sock_objects, 0) _, writeable_sockets, _ = select.select(sock_objects, sock_objects, sock_objects, 0)

View File

@ -67,8 +67,8 @@ def _get_windows_cred(pypykatz_cred: PypykatzCredential):
def _hash_to_string(hash_: Any): def _hash_to_string(hash_: Any):
if type(hash_) == str: if type(hash_) is str:
return hash_ return hash_
if type(hash_) == bytes: if type(hash_) is bytes:
return binascii.hexlify(bytearray(hash_)).decode() return binascii.hexlify(bytearray(hash_)).decode()
raise Exception(f"Can't convert hash_ to string, unsupported hash_ type {type(hash_)}") raise Exception(f"Can't convert hash_ to string, unsupported hash_ type {type(hash_)}")

View File

@ -1,3 +1,7 @@
from gevent import monkey as gevent_monkey
gevent_monkey.patch_all()
from monkey_island.cc.main import main from monkey_island.cc.main import main
@ -5,8 +9,9 @@ def parse_cli_args():
import argparse import argparse
parser = argparse.ArgumentParser(description="Infection Monkey Island CnC Server. See https://infectionmonkey.com") parser = argparse.ArgumentParser(description="Infection Monkey Island CnC Server. See https://infectionmonkey.com")
parser.add_argument("-s", "--setup-only", action="store_true", parser.add_argument("-s", "--setup-only", action="store_true",
help="Pass this flag to cause the Island to setup and exit without actually starting. This is useful " help="Pass this flag to cause the Island to setup and exit without actually starting. "
"for preparing Island to boot faster later-on, so for compiling/packaging Islands.") "This is useful for preparing Island to boot faster later-on, so for "
"compiling/packaging Islands.")
args = parser.parse_args() args = parser.parse_args()
return args.setup_only return args.setup_only

View File

@ -3,6 +3,7 @@ import json
import logging import logging
import sys import sys
from pathlib import Path from pathlib import Path
from shutil import move
def add_monkey_dir_to_sys_path(): def add_monkey_dir_to_sys_path():
@ -16,6 +17,7 @@ add_monkey_dir_to_sys_path()
from monkey_island.cc.environment.environment_config import EnvironmentConfig # noqa: E402 isort:skip from monkey_island.cc.environment.environment_config import EnvironmentConfig # noqa: E402 isort:skip
SERVER_CONFIG = "server_config" SERVER_CONFIG = "server_config"
BACKUP_CONFIG_FILENAME = "./server_config.backup"
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
logger.addHandler(logging.StreamHandler()) logger.addHandler(logging.StreamHandler())
@ -26,10 +28,19 @@ def main():
args = parse_args() args = parse_args()
file_path = EnvironmentConfig.get_config_file_path() file_path = EnvironmentConfig.get_config_file_path()
if args.server_config == "restore":
restore_previous_config(file_path)
quit()
# Read config # Read config
with open(file_path) as config_file: with open(file_path) as config_file:
config_data = json.load(config_file) config_data = json.load(config_file)
# Backup the config
with open(BACKUP_CONFIG_FILENAME, "w") as backup_file:
json.dump(config_data, backup_file, indent=4)
backup_file.write("\n")
# Edit the config # Edit the config
config_data[SERVER_CONFIG] = args.server_config config_data[SERVER_CONFIG] = args.server_config
@ -42,10 +53,14 @@ def main():
def parse_args(): def parse_args():
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument("server_config", choices=["standard", "testing", "password"]) parser.add_argument("server_config", choices=["standard", "testing", "password", "restore"])
args = parser.parse_args() args = parser.parse_args()
return args return args
def restore_previous_config(config_path):
move(BACKUP_CONFIG_FILENAME, config_path)
if __name__ == '__main__': if __name__ == '__main__':
main() main()

View File

@ -6,6 +6,8 @@ from pathlib import Path
from threading import Thread from threading import Thread
# Add the monkey_island directory to the path, to make sure imports that don't start with "monkey_island." work. # Add the monkey_island directory to the path, to make sure imports that don't start with "monkey_island." work.
from gevent.pywsgi import WSGIServer
MONKEY_ISLAND_DIR_BASE_PATH = str(Path(__file__).parent.parent) MONKEY_ISLAND_DIR_BASE_PATH = str(Path(__file__).parent.parent)
if str(MONKEY_ISLAND_DIR_BASE_PATH) not in sys.path: if str(MONKEY_ISLAND_DIR_BASE_PATH) not in sys.path:
sys.path.insert(0, MONKEY_ISLAND_DIR_BASE_PATH) sys.path.insert(0, MONKEY_ISLAND_DIR_BASE_PATH)
@ -46,9 +48,6 @@ def main(should_setup_only=False):
def start_island_server(should_setup_only): def start_island_server(should_setup_only):
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop
from tornado.wsgi import WSGIContainer
mongo_url = os.environ.get('MONGO_URL', env_singleton.env.get_mongo_url()) mongo_url = os.environ.get('MONGO_URL', env_singleton.env.get_mongo_url())
wait_for_mongo_db_server(mongo_url) wait_for_mongo_db_server(mongo_url)
@ -69,12 +68,11 @@ def start_island_server(should_setup_only):
if env_singleton.env.is_debug(): if env_singleton.env.is_debug():
app.run(host='0.0.0.0', debug=True, ssl_context=(crt_path, key_path)) app.run(host='0.0.0.0', debug=True, ssl_context=(crt_path, key_path))
else: else:
http_server = HTTPServer(WSGIContainer(app), http_server = WSGIServer(('0.0.0.0', env_singleton.env.get_island_port()), app,
ssl_options={'certfile': os.environ.get('SERVER_CRT', crt_path), certfile=os.environ.get('SERVER_CRT', crt_path),
'keyfile': os.environ.get('SERVER_KEY', key_path)}) keyfile=os.environ.get('SERVER_KEY', key_path))
http_server.listen(env_singleton.env.get_island_port())
log_init_info() log_init_info()
IOLoop.instance().start() http_server.serve_forever()
def log_init_info(): def log_init_info():

View File

@ -1,7 +1,7 @@
import flask_restful import flask_restful
from flask import send_from_directory from flask import send_from_directory
from monkey_island.cc.services.post_breach_files import UPLOADS_DIR from monkey_island.cc.services.post_breach_files import UPLOADS_DIR_NAME
__author__ = 'VakarisZ' __author__ = 'VakarisZ'
@ -13,4 +13,4 @@ class PBAFileDownload(flask_restful.Resource):
# Used by monkey. can't secure. # Used by monkey. can't secure.
def get(self, path): def get(self, path):
return send_from_directory(UPLOADS_DIR, path) return send_from_directory(UPLOADS_DIR_NAME, path)

View File

@ -12,6 +12,7 @@ EXPLOITER_CLASSES = {
"SmbExploiter" "SmbExploiter"
], ],
"title": "SMB Exploiter", "title": "SMB Exploiter",
"safe": True,
"attack_techniques": ["T1110", "T1075", "T1035"], "attack_techniques": ["T1110", "T1075", "T1035"],
"info": "Brute forces using credentials provided by user and" "info": "Brute forces using credentials provided by user and"
" hashes gathered by mimikatz.", " hashes gathered by mimikatz.",
@ -23,6 +24,7 @@ EXPLOITER_CLASSES = {
"WmiExploiter" "WmiExploiter"
], ],
"title": "WMI Exploiter", "title": "WMI Exploiter",
"safe": True,
"attack_techniques": ["T1110", "T1106"], "attack_techniques": ["T1110", "T1106"],
"info": "Brute forces WMI (Windows Management Instrumentation) " "info": "Brute forces WMI (Windows Management Instrumentation) "
"using credentials provided by user and hashes gathered by mimikatz.", "using credentials provided by user and hashes gathered by mimikatz.",
@ -34,6 +36,7 @@ EXPLOITER_CLASSES = {
"MSSQLExploiter" "MSSQLExploiter"
], ],
"title": "MSSQL Exploiter", "title": "MSSQL Exploiter",
"safe": True,
"attack_techniques": ["T1110"], "attack_techniques": ["T1110"],
"info": "Tries to brute force into MsSQL server and uses insecure " "info": "Tries to brute force into MsSQL server and uses insecure "
"configuration to execute commands on server.", "configuration to execute commands on server.",
@ -44,7 +47,8 @@ EXPLOITER_CLASSES = {
"enum": [ "enum": [
"Ms08_067_Exploiter" "Ms08_067_Exploiter"
], ],
"title": "MS08-067 Exploiter (UNSAFE)", "title": "MS08-067 Exploiter",
"safe": False,
"info": "Unsafe exploiter, that might cause system crash due to the use of buffer overflow. " "info": "Unsafe exploiter, that might cause system crash due to the use of buffer overflow. "
"Uses MS08-067 vulnerability.", "Uses MS08-067 vulnerability.",
"link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/ms08-067/" "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/ms08-067/"
@ -55,6 +59,7 @@ EXPLOITER_CLASSES = {
"SSHExploiter" "SSHExploiter"
], ],
"title": "SSH Exploiter", "title": "SSH Exploiter",
"safe": True,
"attack_techniques": ["T1110", "T1145", "T1106"], "attack_techniques": ["T1110", "T1145", "T1106"],
"info": "Brute forces using credentials provided by user and SSH keys gathered from systems.", "info": "Brute forces using credentials provided by user and SSH keys gathered from systems.",
"link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/sshexec/" "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/sshexec/"
@ -65,6 +70,7 @@ EXPLOITER_CLASSES = {
"ShellShockExploiter" "ShellShockExploiter"
], ],
"title": "ShellShock Exploiter", "title": "ShellShock Exploiter",
"safe": True,
"info": "CVE-2014-6271, based on logic from " "info": "CVE-2014-6271, based on logic from "
"https://github.com/nccgroup/shocker/blob/master/shocker.py .", "https://github.com/nccgroup/shocker/blob/master/shocker.py .",
"link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/shellshock/" "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/shellshock/"
@ -75,6 +81,7 @@ EXPLOITER_CLASSES = {
"SambaCryExploiter" "SambaCryExploiter"
], ],
"title": "SambaCry Exploiter", "title": "SambaCry Exploiter",
"safe": True,
"info": "Bruteforces and searches for anonymous shares. Uses Impacket.", "info": "Bruteforces and searches for anonymous shares. Uses Impacket.",
"link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/sambacry/" "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/sambacry/"
}, },
@ -84,6 +91,7 @@ EXPLOITER_CLASSES = {
"ElasticGroovyExploiter" "ElasticGroovyExploiter"
], ],
"title": "ElasticGroovy Exploiter", "title": "ElasticGroovy Exploiter",
"safe": True,
"info": "CVE-2015-1427. Logic is based on Metasploit module.", "info": "CVE-2015-1427. Logic is based on Metasploit module.",
"link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/elasticgroovy/" "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/elasticgroovy/"
}, },
@ -93,6 +101,7 @@ EXPLOITER_CLASSES = {
"Struts2Exploiter" "Struts2Exploiter"
], ],
"title": "Struts2 Exploiter", "title": "Struts2 Exploiter",
"safe": True,
"info": "Exploits struts2 java web framework. CVE-2017-5638. Logic based on " "info": "Exploits struts2 java web framework. CVE-2017-5638. Logic based on "
"https://www.exploit-db.com/exploits/41570 .", "https://www.exploit-db.com/exploits/41570 .",
"link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/struts2/" "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/struts2/"
@ -103,6 +112,7 @@ EXPLOITER_CLASSES = {
"WebLogicExploiter" "WebLogicExploiter"
], ],
"title": "WebLogic Exploiter", "title": "WebLogic Exploiter",
"safe": True,
"info": "Exploits CVE-2017-10271 and CVE-2019-2725 vulnerabilities on WebLogic server.", "info": "Exploits CVE-2017-10271 and CVE-2019-2725 vulnerabilities on WebLogic server.",
"link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/weblogic/" "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/weblogic/"
}, },
@ -112,6 +122,7 @@ EXPLOITER_CLASSES = {
"HadoopExploiter" "HadoopExploiter"
], ],
"title": "Hadoop/Yarn Exploiter", "title": "Hadoop/Yarn Exploiter",
"safe": True,
"info": "Remote code execution on HADOOP server with YARN and default settings. " "info": "Remote code execution on HADOOP server with YARN and default settings. "
"Logic based on https://github.com/vulhub/vulhub/tree/master/hadoop/unauthorized-yarn.", "Logic based on https://github.com/vulhub/vulhub/tree/master/hadoop/unauthorized-yarn.",
"link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/hadoop/" "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/hadoop/"
@ -122,6 +133,7 @@ EXPLOITER_CLASSES = {
"VSFTPDExploiter" "VSFTPDExploiter"
], ],
"title": "VSFTPD Exploiter", "title": "VSFTPD Exploiter",
"safe": True,
"info": "Exploits a malicious backdoor that was added to the VSFTPD download archive. " "info": "Exploits a malicious backdoor that was added to the VSFTPD download archive. "
"Logic based on Metasploit module.", "Logic based on Metasploit module.",
"link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/vsftpd/" "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/vsftpd/"
@ -132,6 +144,7 @@ EXPLOITER_CLASSES = {
"DrupalExploiter" "DrupalExploiter"
], ],
"title": "Drupal Exploiter", "title": "Drupal Exploiter",
"safe": True,
"info": "Exploits a remote command execution vulnerability in a Drupal server," "info": "Exploits a remote command execution vulnerability in a Drupal server,"
"for which certain modules (such as RESTful Web Services) are enabled.", "for which certain modules (such as RESTful Web Services) are enabled.",
"link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/drupal/" "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/drupal/"

View File

@ -10,6 +10,7 @@ FINGER_CLASSES = {
"SMBFinger" "SMBFinger"
], ],
"title": "SMBFinger", "title": "SMBFinger",
"safe": True,
"info": "Figures out if SMB is running and what's the version of it.", "info": "Figures out if SMB is running and what's the version of it.",
"attack_techniques": ["T1210"] "attack_techniques": ["T1210"]
}, },
@ -19,6 +20,7 @@ FINGER_CLASSES = {
"SSHFinger" "SSHFinger"
], ],
"title": "SSHFinger", "title": "SSHFinger",
"safe": True,
"info": "Figures out if SSH is running.", "info": "Figures out if SSH is running.",
"attack_techniques": ["T1210"] "attack_techniques": ["T1210"]
}, },
@ -28,6 +30,7 @@ FINGER_CLASSES = {
"PingScanner" "PingScanner"
], ],
"title": "PingScanner", "title": "PingScanner",
"safe": True,
"info": "Tries to identify if host is alive and which OS it's running by ping scan." "info": "Tries to identify if host is alive and which OS it's running by ping scan."
}, },
{ {
@ -36,6 +39,7 @@ FINGER_CLASSES = {
"HTTPFinger" "HTTPFinger"
], ],
"title": "HTTPFinger", "title": "HTTPFinger",
"safe": True,
"info": "Checks if host has HTTP/HTTPS ports open." "info": "Checks if host has HTTP/HTTPS ports open."
}, },
{ {
@ -44,6 +48,7 @@ FINGER_CLASSES = {
"MySQLFinger" "MySQLFinger"
], ],
"title": "MySQLFinger", "title": "MySQLFinger",
"safe": True,
"info": "Checks if MySQL server is running and tries to get it's version.", "info": "Checks if MySQL server is running and tries to get it's version.",
"attack_techniques": ["T1210"] "attack_techniques": ["T1210"]
}, },
@ -53,6 +58,7 @@ FINGER_CLASSES = {
"MSSQLFinger" "MSSQLFinger"
], ],
"title": "MSSQLFinger", "title": "MSSQLFinger",
"safe": True,
"info": "Checks if Microsoft SQL service is running and tries to gather information about it.", "info": "Checks if Microsoft SQL service is running and tries to gather information about it.",
"attack_techniques": ["T1210"] "attack_techniques": ["T1210"]
}, },
@ -62,6 +68,7 @@ FINGER_CLASSES = {
"ElasticFinger" "ElasticFinger"
], ],
"title": "ElasticFinger", "title": "ElasticFinger",
"safe": True,
"info": "Checks if ElasticSearch is running and attempts to find it's version.", "info": "Checks if ElasticSearch is running and attempts to find it's version.",
"attack_techniques": ["T1210"] "attack_techniques": ["T1210"]
}, },
@ -71,6 +78,7 @@ FINGER_CLASSES = {
"WindowsServerFinger" "WindowsServerFinger"
], ],
"title": "WindowsServerFinger", "title": "WindowsServerFinger",
"safe": True,
"info": "Checks if server is a Windows Server and tests if it is vulnerable to Zerologon.", "info": "Checks if server is a Windows Server and tests if it is vulnerable to Zerologon.",
"attack_techniques": ["T1210"] "attack_techniques": ["T1210"]
} }

View File

@ -10,6 +10,7 @@ POST_BREACH_ACTIONS = {
"BackdoorUser" "BackdoorUser"
], ],
"title": "Back door user", "title": "Back door user",
"safe": True,
"info": "Attempts to create a new user on the system and delete it afterwards.", "info": "Attempts to create a new user on the system and delete it afterwards.",
"attack_techniques": ["T1136"] "attack_techniques": ["T1136"]
}, },
@ -19,6 +20,7 @@ POST_BREACH_ACTIONS = {
"CommunicateAsNewUser" "CommunicateAsNewUser"
], ],
"title": "Communicate as new user", "title": "Communicate as new user",
"safe": True,
"info": "Attempts to create a new user, create HTTPS requests as that user and delete the user " "info": "Attempts to create a new user, create HTTPS requests as that user and delete the user "
"afterwards.", "afterwards.",
"attack_techniques": ["T1136"] "attack_techniques": ["T1136"]
@ -29,6 +31,7 @@ POST_BREACH_ACTIONS = {
"ModifyShellStartupFiles" "ModifyShellStartupFiles"
], ],
"title": "Modify shell startup files", "title": "Modify shell startup files",
"safe": True,
"info": "Attempts to modify shell startup files, like ~/.profile, ~/.bashrc, ~/.bash_profile " "info": "Attempts to modify shell startup files, like ~/.profile, ~/.bashrc, ~/.bash_profile "
"in linux, and profile.ps1 in windows. Reverts modifications done afterwards.", "in linux, and profile.ps1 in windows. Reverts modifications done afterwards.",
"attack_techniques": ["T1156", "T1504"] "attack_techniques": ["T1156", "T1504"]
@ -39,6 +42,7 @@ POST_BREACH_ACTIONS = {
"HiddenFiles" "HiddenFiles"
], ],
"title": "Hidden files and directories", "title": "Hidden files and directories",
"safe": True,
"info": "Attempts to create a hidden file and remove it afterward.", "info": "Attempts to create a hidden file and remove it afterward.",
"attack_techniques": ["T1158"] "attack_techniques": ["T1158"]
}, },
@ -48,6 +52,7 @@ POST_BREACH_ACTIONS = {
"TrapCommand" "TrapCommand"
], ],
"title": "Trap", "title": "Trap",
"safe": True,
"info": "On Linux systems, attempts to trap an interrupt signal in order to execute a command " "info": "On Linux systems, attempts to trap an interrupt signal in order to execute a command "
"upon receiving that signal. Removes the trap afterwards.", "upon receiving that signal. Removes the trap afterwards.",
"attack_techniques": ["T1154"] "attack_techniques": ["T1154"]
@ -58,6 +63,7 @@ POST_BREACH_ACTIONS = {
"ChangeSetuidSetgid" "ChangeSetuidSetgid"
], ],
"title": "Setuid and Setgid", "title": "Setuid and Setgid",
"safe": True,
"info": "On Linux systems, attempts to set the setuid and setgid bits of a new file. " "info": "On Linux systems, attempts to set the setuid and setgid bits of a new file. "
"Removes the file afterwards.", "Removes the file afterwards.",
"attack_techniques": ["T1166"] "attack_techniques": ["T1166"]
@ -68,6 +74,7 @@ POST_BREACH_ACTIONS = {
"ScheduleJobs" "ScheduleJobs"
], ],
"title": "Job scheduling", "title": "Job scheduling",
"safe": True,
"info": "Attempts to create a scheduled job on the system and remove it.", "info": "Attempts to create a scheduled job on the system and remove it.",
"attack_techniques": ["T1168", "T1053"] "attack_techniques": ["T1168", "T1053"]
}, },
@ -77,6 +84,7 @@ POST_BREACH_ACTIONS = {
"Timestomping" "Timestomping"
], ],
"title": "Timestomping", "title": "Timestomping",
"safe": True,
"info": "Creates a temporary file and attempts to modify its time attributes. Removes the file afterwards.", "info": "Creates a temporary file and attempts to modify its time attributes. Removes the file afterwards.",
"attack_techniques": ["T1099"] "attack_techniques": ["T1099"]
}, },
@ -86,7 +94,8 @@ POST_BREACH_ACTIONS = {
"SignedScriptProxyExecution" "SignedScriptProxyExecution"
], ],
"title": "Signed script proxy execution", "title": "Signed script proxy execution",
"info": "On Windows systems, attemps to execute an arbitrary file " "safe": False,
"info": "On Windows systems, attempts to execute an arbitrary file "
"with the help of a pre-existing signed script.", "with the help of a pre-existing signed script.",
"attack_techniques": ["T1216"] "attack_techniques": ["T1216"]
}, },
@ -96,6 +105,7 @@ POST_BREACH_ACTIONS = {
"AccountDiscovery" "AccountDiscovery"
], ],
"title": "Account Discovery", "title": "Account Discovery",
"safe": True,
"info": "Attempts to get a listing of user accounts on the system.", "info": "Attempts to get a listing of user accounts on the system.",
"attack_techniques": ["T1087"] "attack_techniques": ["T1087"]
}, },
@ -105,6 +115,7 @@ POST_BREACH_ACTIONS = {
"ClearCommandHistory" "ClearCommandHistory"
], ],
"title": "Clear command history", "title": "Clear command history",
"safe": False,
"info": "Attempts to clear the command history.", "info": "Attempts to clear the command history.",
"attack_techniques": ["T1146"] "attack_techniques": ["T1146"]
} }

View File

@ -16,6 +16,7 @@ SYSTEM_INFO_COLLECTOR_CLASSES = {
ENVIRONMENT_COLLECTOR ENVIRONMENT_COLLECTOR
], ],
"title": "Environment collector", "title": "Environment collector",
"safe": True,
"info": "Collects information about machine's environment (on premise/GCP/AWS).", "info": "Collects information about machine's environment (on premise/GCP/AWS).",
"attack_techniques": ["T1082"] "attack_techniques": ["T1082"]
}, },
@ -25,6 +26,7 @@ SYSTEM_INFO_COLLECTOR_CLASSES = {
MIMIKATZ_COLLECTOR MIMIKATZ_COLLECTOR
], ],
"title": "Mimikatz collector", "title": "Mimikatz collector",
"safe": True,
"info": "Collects credentials from Windows credential manager.", "info": "Collects credentials from Windows credential manager.",
"attack_techniques": ["T1003", "T1005"] "attack_techniques": ["T1003", "T1005"]
}, },
@ -34,6 +36,7 @@ SYSTEM_INFO_COLLECTOR_CLASSES = {
AWS_COLLECTOR AWS_COLLECTOR
], ],
"title": "AWS collector", "title": "AWS collector",
"safe": True,
"info": "If on AWS, collects more information about the AWS instance currently running on.", "info": "If on AWS, collects more information about the AWS instance currently running on.",
"attack_techniques": ["T1082"] "attack_techniques": ["T1082"]
}, },
@ -43,6 +46,7 @@ SYSTEM_INFO_COLLECTOR_CLASSES = {
HOSTNAME_COLLECTOR HOSTNAME_COLLECTOR
], ],
"title": "Hostname collector", "title": "Hostname collector",
"safe": True,
"info": "Collects machine's hostname.", "info": "Collects machine's hostname.",
"attack_techniques": ["T1082", "T1016"] "attack_techniques": ["T1082", "T1016"]
}, },
@ -52,6 +56,7 @@ SYSTEM_INFO_COLLECTOR_CLASSES = {
PROCESS_LIST_COLLECTOR PROCESS_LIST_COLLECTOR
], ],
"title": "Process list collector", "title": "Process list collector",
"safe": True,
"info": "Collects a list of running processes on the machine.", "info": "Collects a list of running processes on the machine.",
"attack_techniques": ["T1082"] "attack_techniques": ["T1082"]
}, },
@ -61,6 +66,7 @@ SYSTEM_INFO_COLLECTOR_CLASSES = {
AZURE_CRED_COLLECTOR AZURE_CRED_COLLECTOR
], ],
"title": "Azure credential collector", "title": "Azure credential collector",
"safe": True,
"info": "Collects password credentials from Azure VMs", "info": "Collects password credentials from Azure VMs",
"attack_techniques": ["T1003", "T1005"] "attack_techniques": ["T1003", "T1005"]
} }

View File

@ -11,7 +11,8 @@ logger = logging.getLogger(__name__)
# Where to find file names in config # Where to find file names in config
PBA_WINDOWS_FILENAME_PATH = ['monkey', 'post_breach', 'PBA_windows_filename'] PBA_WINDOWS_FILENAME_PATH = ['monkey', 'post_breach', 'PBA_windows_filename']
PBA_LINUX_FILENAME_PATH = ['monkey', 'post_breach', 'PBA_linux_filename'] PBA_LINUX_FILENAME_PATH = ['monkey', 'post_breach', 'PBA_linux_filename']
PBA_UPLOAD_PATH = ['monkey_island', 'cc', 'userUploads'] UPLOADS_DIR_NAME = 'userUploads'
PBA_UPLOAD_PATH = ['monkey_island', 'cc', UPLOADS_DIR_NAME]
UPLOADS_DIR = Path(*PBA_UPLOAD_PATH) UPLOADS_DIR = Path(*PBA_UPLOAD_PATH)

View File

@ -1,5 +1,6 @@
import logging import logging
import threading
from gevent.lock import BoundedSemaphore
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -7,9 +8,9 @@ logger = logging.getLogger(__name__)
# Report generation can be quite slow if there is a lot of data, and the UI queries the Root service often; without # Report generation can be quite slow if there is a lot of data, and the UI queries the Root service often; without
# the locks, these requests would accumulate, overload the server, eventually causing it to crash. # the locks, these requests would accumulate, overload the server, eventually causing it to crash.
logger.debug("Initializing report generation locks.") logger.debug("Initializing report generation locks.")
__report_generating_lock = threading.Semaphore() __report_generating_lock = BoundedSemaphore()
__attack_report_generating_lock = threading.Semaphore() __attack_report_generating_lock = BoundedSemaphore()
__regular_report_generating_lock = threading.Semaphore() __regular_report_generating_lock = BoundedSemaphore()
def safe_generate_reports(): def safe_generate_reports():

View File

@ -1,6 +1,5 @@
from monkey_island.cc.database import mongo from monkey_island.cc.database import mongo
from monkey_island.cc.models import Monkey from monkey_island.cc.models import Monkey
from monkey_island.cc.services.edge.edge import EdgeService
from monkey_island.cc.services.node import NodeService from monkey_island.cc.services.node import NodeService
from monkey_island.cc.services.telemetry.processing.utils import \ from monkey_island.cc.services.telemetry.processing.utils import \
get_edge_by_scan_or_exploit_telemetry get_edge_by_scan_or_exploit_telemetry

View File

@ -35,7 +35,8 @@
"comma-dangle": 1, "comma-dangle": 1,
"quotes": [ "quotes": [
1, 1,
"single" "single",
{"allowTemplateLiterals": true}
], ],
"no-undef": 1, "no-undef": 1,
"global-strict": 0, "global-strict": 0,

View File

@ -1,12 +1,12 @@
import React, {useEffect, useState} from 'react'; import React, {useEffect, useState} from 'react';
import InlineSelection from '../../ui-components/inline-selection/InlineSelection'; import InlineSelection from '../../../ui-components/inline-selection/InlineSelection';
import DropdownSelect from '../../ui-components/DropdownSelect'; import DropdownSelect from '../../../ui-components/DropdownSelect';
import {OS_TYPES} from './OsTypes'; import {OS_TYPES} from '../utils/OsTypes';
import GenerateLocalWindowsCmd from './commands/local_windows_cmd'; import GenerateLocalWindowsCmd from '../commands/local_windows_cmd';
import GenerateLocalWindowsPowershell from './commands/local_windows_powershell'; import GenerateLocalWindowsPowershell from '../commands/local_windows_powershell';
import GenerateLocalLinuxWget from './commands/local_linux_wget'; import GenerateLocalLinuxWget from '../commands/local_linux_wget';
import GenerateLocalLinuxCurl from './commands/local_linux_curl'; import GenerateLocalLinuxCurl from '../commands/local_linux_curl';
import CommandDisplay from './CommandDisplay'; import CommandDisplay from '../utils/CommandDisplay';
const LocalManualRunOptions = (props) => { const LocalManualRunOptions = (props) => {

View File

@ -0,0 +1,119 @@
import React, {useState} from 'react';
import ReactTable from 'react-table'
import checkboxHOC from 'react-table/lib/hoc/selectTable';
import PropTypes from 'prop-types';
const CheckboxTable = checkboxHOC(ReactTable);
const columns = [
{
Header: 'Machines',
columns: [
{Header: 'Machine', accessor: 'name'},
{Header: 'Instance ID', accessor: 'instance_id'},
{Header: 'IP Address', accessor: 'ip_address'},
{Header: 'OS', accessor: 'os'}
]
}
];
const pageSize = 10;
function AWSInstanceTable(props) {
const [allToggled, setAllToggled] = useState(false);
let checkboxTable = null;
function toggleSelection(key) {
key = key.replace('select-', '');
// start off with the existing state
let modifiedSelection = [...props.selection];
const keyIndex = modifiedSelection.indexOf(key);
// check to see if the key exists
if (keyIndex >= 0) {
// it does exist so we will remove it using destructing
modifiedSelection = [
...modifiedSelection.slice(0, keyIndex),
...modifiedSelection.slice(keyIndex + 1)
];
} else {
// it does not exist so add it
modifiedSelection.push(key);
}
// update the state
props.setSelection(modifiedSelection);
}
function isSelected(key) {
return props.selection.includes(key);
}
function toggleAll() {
const selectAll = !allToggled;
const selection = [];
if (selectAll) {
// we need to get at the internals of ReactTable
const wrappedInstance = checkboxTable.getWrappedInstance();
// the 'sortedData' property contains the currently accessible records based on the filter and sort
const currentRecords = wrappedInstance.getResolvedState().sortedData;
// we just push all the IDs onto the selection array
currentRecords.forEach(item => {
selection.push(item._original.instance_id);
});
}
setAllToggled(selectAll);
props.setSelection(selection);
}
function getTrProps(_, r) {
let color = 'inherit';
if (r) {
let instId = r.original.instance_id;
if (isSelected(instId)) {
color = '#ffed9f';
} else if (Object.prototype.hasOwnProperty.call(props.results, instId)) {
color = props.results[instId] ? '#00f01b' : '#f00000'
}
}
return {
style: {backgroundColor: color}
};
}
return (
<div className="data-table-container">
<CheckboxTable
ref={r => (checkboxTable = r)}
keyField="instance_id"
columns={columns}
data={props.data}
showPagination={true}
defaultPageSize={pageSize}
className="-highlight"
selectType="checkbox"
toggleSelection={toggleSelection}
isSelected={isSelected}
toggleAll={toggleAll}
selectAll={allToggled}
getTrProps={getTrProps}
/>
</div>
);
}
AWSInstanceTable.propTypes = {
data: PropTypes.arrayOf(PropTypes.exact({
instance_id: PropTypes.string,
name: PropTypes.string,
os: PropTypes.string,
ip_address: PropTypes.string
})),
results: PropTypes.arrayOf(PropTypes.string),
selection: PropTypes.arrayOf(PropTypes.string),
setSelection: PropTypes.func
}
export default AWSInstanceTable;

View File

@ -0,0 +1,80 @@
import React, {useEffect, useState} from 'react';
import AuthComponent from '../../../AuthComponent';
import '../../../../styles/components/RunOnIslandButton.scss';
import {faCloud} from '@fortawesome/free-solid-svg-icons';
import AWSRunOptions from './AWSRunOptions';
import NextSelectionButton from '../../../ui-components/inline-selection/NextSelectionButton';
import {Alert, Button} from 'react-bootstrap';
import LoadingIcon from '../../../ui-components/LoadingIcon';
function AWSRunButton(props) {
const authComponent = new AuthComponent({});
const [isOnAWS, setIsOnAWS] = useState(false);
const [AWSInstances, setAWSInstances] = useState([]);
const [awsMachineCollectionError, setAwsMachineCollectionError] = useState('');
const [componentLoading, setComponentLoading] = useState(true);
useEffect(() => {
checkIsOnAWS();
}, []);
function checkIsOnAWS() {
authComponent.authFetch('/api/remote-monkey?action=list_aws')
.then(res => res.json())
.then(res => {
let isAws = res['is_aws'];
setComponentLoading(false);
if (isAws) {
// On AWS!
// Checks if there was an error while collecting the aws machines.
let isErrorWhileCollectingAwsMachines = (res['error'] != null);
if (isErrorWhileCollectingAwsMachines) {
// There was an error. Finish loading, and display error message.
setIsOnAWS(true);
setAwsMachineCollectionError(res['error']);
} else {
// No error! Finish loading and display machines for user
setIsOnAWS(true);
setAWSInstances(res['instances']);
}
}
});
}
function getAWSButton() {
return <NextSelectionButton title={'AWS run'}
description={'Run on a chosen AWS instance in the cloud.'}
icon={faCloud}
onButtonClick={() => {
props.setComponent(AWSRunOptions,
{AWSInstances: AWSInstances, setComponent: props.setComponent})
}}/>
}
function getErrorDisplay() {
return (
<Alert variant={'info'}>Detected ability to run on different AWS instances.
To enable this feature, follow the &nbsp;
<Button variant={'link'} className={'inline-link'}
href={'https://www.guardicore.com/infectionmonkey/docs/usage/integrations/aws-run-on-ec2-machine/'}>
Tutorial
</Button> and refresh the page. Error received while trying to list AWS instances: {awsMachineCollectionError}
</Alert> );
}
let displayed = '';
if (componentLoading) {
displayed = LoadingIcon();
}
if (awsMachineCollectionError !== '') {
displayed = getErrorDisplay();
} else if (isOnAWS) {
displayed = getAWSButton();
}
return displayed;
}
export default AWSRunButton;

View File

@ -0,0 +1,117 @@
import React, {useEffect, useState} from 'react';
import {Button, Nav} from 'react-bootstrap';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faSync} from '@fortawesome/free-solid-svg-icons/faSync';
import {faInfoCircle} from '@fortawesome/free-solid-svg-icons/faInfoCircle';
import AwsRunTable from './AWSInstanceTable';
import AuthComponent from '../../../AuthComponent';
import InlineSelection from '../../../ui-components/inline-selection/InlineSelection';
const AWSRunOptions = (props) => {
return InlineSelection(getContents, {
...props,
onBackButtonClick: () => {props.setComponent()}
})
}
const getContents = (props) => {
const authComponent = new AuthComponent({});
let [allIPs, setAllIPs] = useState([]);
let [selectedIp, setSelectedIp] = useState(null);
let [AWSClicked, setAWSClicked] = useState(false);
let [runResults, setRunResults] = useState([]);
let [selectedInstances, setSelectedInstances] = useState([]);
useEffect(() => {
getIps();
}, []);
function getIps() {
authComponent.authFetch('/api')
.then(res => res.json())
.then(res => {
setAllIPs(res['ip_addresses']);
setSelectedIp(res['ip_addresses'][0]);
});
}
function runOnAws() {
setAWSClicked(true);
let instances = selectedInstances.map(x => instanceIdToInstance(x));
authComponent.authFetch('/api/remote-monkey',
{
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({type: 'aws', instances: instances, island_ip: selectedIp})
}).then(res => res.json())
.then(res => {
let result = res['result'];
// update existing state, not run-over
let prevRes = result;
for (let key in result) {
if (result.hasOwnProperty(key)) {
prevRes[key] = result[key];
}
}
setRunResults(prevRes);
setSelectedInstances([]);
setAWSClicked(false);
});
}
function instanceIdToInstance(instance_id) {
let instance = props.AWSInstances.find(
function (inst) {
return inst['instance_id'] === instance_id;
});
return {'instance_id': instance_id, 'os': instance['os']}
}
return (
<div style={{'marginBottom': '2em'}}>
<div style={{'marginTop': '1em', 'marginBottom': '1em'}}>
<p className="alert alert-info">
<FontAwesomeIcon icon={faInfoCircle} style={{'marginRight': '5px'}}/>
Not sure what this is? Not seeing your AWS EC2 instances? <a
href="https://github.com/guardicore/monkey/wiki/Monkey-Island:-Running-the-monkey-on-AWS-EC2-instances"
rel="noopener noreferrer" target="_blank">Read the documentation</a>!
</p>
</div>
{
allIPs.length > 1 ?
<Nav variant="pills" activeKey={selectedIp} onSelect={setSelectedIp}
style={{'marginBottom': '2em'}}>
{allIPs.map(ip => <Nav.Item key={ip}><Nav.Link eventKey={ip}>{ip}</Nav.Link></Nav.Item>)}
</Nav>
: <div style={{'marginBottom': '2em'}}/>
}
<AwsRunTable
data={props.AWSInstances}
results={runResults}
selection={selectedInstances}
setSelection={setSelectedInstances}
/>
<div className={'aws-run-button-container'}>
<Button
size={'lg'}
onClick={runOnAws}
className={'btn btn-default btn-md center-block'}
disabled={AWSClicked}>
Run on selected machines
{AWSClicked ?
<FontAwesomeIcon icon={faSync} className={`text-success spinning-icon`} style={{'marginLeft': '5px'}}/> : null}
</Button>
</div>
</div>
);
}
export default AWSRunOptions;

View File

@ -1,12 +1,13 @@
import React, {useEffect, useState} from 'react'; import React, {useEffect, useState} from 'react';
import NextSelectionButton from '../../ui-components/inline-selection/NextSelectionButton'; import NextSelectionButton from '../../ui-components/inline-selection/NextSelectionButton';
import LocalManualRunOptions from './LocalManualRunOptions'; import LocalManualRunOptions from './RunManually/LocalManualRunOptions';
import AuthComponent from '../../AuthComponent'; import AuthComponent from '../../AuthComponent';
import {faLaptopCode} from '@fortawesome/free-solid-svg-icons/faLaptopCode'; import {faLaptopCode} from '@fortawesome/free-solid-svg-icons/faLaptopCode';
import InlineSelection from '../../ui-components/inline-selection/InlineSelection'; import InlineSelection from '../../ui-components/inline-selection/InlineSelection';
import {cloneDeep} from 'lodash'; import {cloneDeep} from 'lodash';
import {faExpandArrowsAlt} from '@fortawesome/free-solid-svg-icons'; import {faExpandArrowsAlt} from '@fortawesome/free-solid-svg-icons';
import RunOnIslandButton from './RunOnIslandButton'; import RunOnIslandButton from './RunOnIslandButton';
import AWSRunButton from './RunOnAWS/AWSRunButton';
function RunOptions(props) { function RunOptions(props) {
@ -61,6 +62,7 @@ function RunOptions(props) {
setComponent(LocalManualRunOptions, setComponent(LocalManualRunOptions,
{ips: ips, setComponent: setComponent}) {ips: ips, setComponent: setComponent})
}}/> }}/>
<AWSRunButton setComponent={setComponent}/>
</> </>
); );
} }

View File

@ -1,4 +1,4 @@
import {OS_TYPES} from '../OsTypes'; import {OS_TYPES} from '../utils/OsTypes';
export default function generateLocalLinuxCurl(ip, osType) { export default function generateLocalLinuxCurl(ip, osType) {

View File

@ -1,4 +1,4 @@
import {OS_TYPES} from '../OsTypes'; import {OS_TYPES} from '../utils/OsTypes';
export default function generateLocalLinuxWget(ip, osType) { export default function generateLocalLinuxWget(ip, osType) {

View File

@ -1,4 +1,4 @@
import {OS_TYPES} from '../OsTypes'; import {OS_TYPES} from '../utils/OsTypes';
export default function generateLocalWindowsCmd(ip, osType) { export default function generateLocalWindowsCmd(ip, osType) {

View File

@ -1,4 +1,4 @@
import {OS_TYPES} from '../OsTypes'; import {OS_TYPES} from '../utils/OsTypes';
export default function generateLocalWindowsPowershell(ip, osType) { export default function generateLocalWindowsPowershell(ip, osType) {

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import InlineSelection from '../../ui-components/inline-selection/InlineSelection'; import InlineSelection from '../../../ui-components/inline-selection/InlineSelection';
import LocalManualRunOptions from './LocalManualRunOptions'; import LocalManualRunOptions from '../RunManually/LocalManualRunOptions';
function InterfaceSelection(props) { function InterfaceSelection(props) {
return InlineSelection(getContents, props, LocalManualRunOptions) return InlineSelection(getContents, props, LocalManualRunOptions)

View File

@ -1,109 +0,0 @@
import React from 'react';
import ReactTable from 'react-table'
import checkboxHOC from 'react-table/lib/hoc/selectTable';
const CheckboxTable = checkboxHOC(ReactTable);
const columns = [
{
Header: 'Machines',
columns: [
{Header: 'Machine', accessor: 'name'},
{Header: 'Instance ID', accessor: 'instance_id'},
{Header: 'IP Address', accessor: 'ip_address'},
{Header: 'OS', accessor: 'os'}
]
}
];
const pageSize = 10;
class AwsRunTableComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
selection: [],
selectAll: false,
result: {}
}
}
toggleSelection = (key) => {
// start off with the existing state
let selection = [...this.state.selection];
const keyIndex = selection.indexOf(key);
// check to see if the key exists
if (keyIndex >= 0) {
// it does exist so we will remove it using destructing
selection = [
...selection.slice(0, keyIndex),
...selection.slice(keyIndex + 1)
];
} else {
// it does not exist so add it
selection.push(key);
}
// update the state
this.setState({selection});
};
isSelected = key => {
return this.state.selection.includes(key);
};
toggleAll = () => {
const selectAll = !this.state.selectAll;
const selection = [];
if (selectAll) {
// we need to get at the internals of ReactTable
const wrappedInstance = this.checkboxTable.getWrappedInstance();
// the 'sortedData' property contains the currently accessible records based on the filter and sort
const currentRecords = wrappedInstance.getResolvedState().sortedData;
// we just push all the IDs onto the selection array
currentRecords.forEach(item => {
selection.push(item._original.instance_id);
});
}
this.setState({selectAll, selection});
};
getTrProps = (_, r) => {
let color = 'inherit';
if (r) {
let instId = r.original.instance_id;
if (this.isSelected(instId)) {
color = '#ffed9f';
} else if (Object.prototype.hasOwnProperty.call(this.state.result, instId)) {
color = this.state.result[instId] ? '#00f01b' : '#f00000'
}
}
return {
style: {backgroundColor: color}
};
};
render() {
return (
<div className="data-table-container">
<CheckboxTable
ref={r => (this.checkboxTable = r)}
keyField="instance_id"
columns={columns}
data={this.props.data}
showPagination={true}
defaultPageSize={pageSize}
className="-highlight"
selectType="checkbox"
toggleSelection={this.toggleSelection}
isSelected={this.isSelected}
toggleAll={this.toggleAll}
selectAll={this.state.selectAll}
getTrProps={this.getTrProps}
/>
</div>
);
}
}
export default AwsRunTableComponent;

View File

@ -1,121 +1,170 @@
import React, {useState} from 'react'; import React from 'react';
import {Button, Card} from 'react-bootstrap';
import {Card, Button, Form} from 'react-bootstrap';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faCheckSquare} from '@fortawesome/free-solid-svg-icons';
import {faSquare} from '@fortawesome/free-regular-svg-icons';
import {cloneDeep} from 'lodash'; import {cloneDeep} from 'lodash';
import {getComponentHeight} from './utils/HeightCalculator'; import {getDefaultPaneParams, InfoPane} from './InfoPane';
import {resolveObjectPath} from './utils/ObjectPathResolver'; import {MasterCheckbox, MasterCheckboxState} from './MasterCheckbox';
import InfoPane from './InfoPane'; import ChildCheckboxContainer from './ChildCheckbox';
import {getFullDefinitionByKey} from './JsonSchemaHelpers';
function AdvancedMultiSelectHeader(props) {
const {
title,
disabled,
onCheckboxClick,
checkboxState,
hideReset,
onResetClick
} = props;
return (
<Card.Header className="d-flex justify-content-between">
<MasterCheckbox title={title} disabled={disabled} onClick={onCheckboxClick} checkboxState={checkboxState}/>
<Button className={'reset-safe-defaults'} type={'reset'} variant={'warning'}
hidden={hideReset} onClick={onResetClick}>
Reset to safe defaults
</Button>
</Card.Header>
);
}
class AdvancedMultiSelect extends React.Component {
constructor(props) {
super(props);
this.enumOptions = props.options.enumOptions;
this.defaultValues = props.schema.default;
this.infoPaneRefString = props.schema.items.$ref;
this.registry = props.registry;
this.state = {
masterCheckboxState: this.getMasterCheckboxState(props.value),
hideReset: this.getHideResetState(props.value),
infoPaneParams: getDefaultPaneParams(this.infoPaneRefString, this.registry)
};
}
onMasterCheckboxClick = () => {
if (this.state.masterCheckboxState === MasterCheckboxState.ALL) {
var newValues = [];
} else {
newValues = this.enumOptions.map(({value}) => value);
}
this.props.onChange(newValues);
this.setMasterCheckboxState(newValues);
this.setHideResetState(newValues);
}
onChildCheckboxClick = (value) => {
let selectValues = this.getSelectValuesAfterClick(value);
this.props.onChange(selectValues);
this.setMasterCheckboxState(selectValues);
this.setHideResetState(selectValues);
}
getSelectValuesAfterClick(clickedValue) {
const valueArray = cloneDeep(this.props.value);
function getSelectValuesAfterClick(valueArray, clickedValue) {
if (valueArray.includes(clickedValue)) { if (valueArray.includes(clickedValue)) {
return valueArray.filter((e) => { return valueArray.filter(e => e !== clickedValue);
return e !== clickedValue;
});
} else { } else {
valueArray.push(clickedValue); valueArray.push(clickedValue);
return valueArray; return valueArray;
} }
}
function onMasterCheckboxClick(checkboxValue, defaultArray, onChangeFnc) {
if (checkboxValue) {
onChangeFnc([]);
} else {
onChangeFnc(defaultArray);
} }
}
// Definitions passed to components only contains value and label, setMasterCheckboxState(selectValues) {
// custom fields like "info" or "links" must be pulled from registry object using this function this.setState(() => ({
function getFullDefinitionsFromRegistry(refString, registry) { masterCheckboxState: this.getMasterCheckboxState(selectValues)
return getObjectFromRegistryByRef(refString, registry).anyOf; }));
} }
function getObjectFromRegistryByRef(refString, registry) { getMasterCheckboxState(selectValues) {
let refArray = refString.replace('#', '').split('/'); if (selectValues.length === 0) {
return resolveObjectPath(refArray, registry); return MasterCheckboxState.NONE;
} }
function getFullDefinitionByKey(refString, registry, itemKey) { if (selectValues.length != this.enumOptions.length) {
let fullArray = getFullDefinitionsFromRegistry(refString, registry); return MasterCheckboxState.MIXED;
return fullArray.filter(e => (e.enum[0] === itemKey))[0]; }
}
function setPaneInfo(refString, registry, itemKey, setPaneInfoFnc) { return MasterCheckboxState.ALL;
let definitionObj = getFullDefinitionByKey(refString, registry, itemKey); }
setPaneInfoFnc({title: definitionObj.title, content: definitionObj.info, link: definitionObj.link});
}
function getDefaultPaneParams(refString, registry) { onResetClick = () => {
let configSection = getObjectFromRegistryByRef(refString, registry); this.props.onChange(this.defaultValues);
return ({title: configSection.title, content: configSection.description}); this.setHideResetState(this.defaultValues);
} this.setMasterCheckboxState(this.defaultValues);
this.setPaneInfoToDefault();
}
function AdvancedMultiSelect(props) { setHideResetState(selectValues) {
const [masterCheckbox, setMasterCheckbox] = useState(true); this.setState(() => ({
hideReset: this.getHideResetState(selectValues)
}));
}
getHideResetState(selectValues) {
return selectValues.every((value) => this.defaultValues.includes(value));
}
setPaneInfo = (itemKey) => {
let definitionObj = getFullDefinitionByKey(this.infoPaneRefString, this.registry, itemKey);
this.setState(
{
infoPaneParams: {
title: definitionObj.title,
content: definitionObj.info,
link: definitionObj.link,
showWarning: !(this.isSafe(itemKey))
}
}
);
}
setPaneInfoToDefault() {
this.setState(() => ({
infoPaneParams: getDefaultPaneParams(this.props.schema.items.$ref, this.props.registry)
}));
}
isSafe = (itemKey) => {
return getFullDefinitionByKey(this.infoPaneRefString, this.registry, itemKey).safe;
}
render() {
const { const {
schema, schema,
id, id,
options,
value,
required, required,
disabled, disabled,
readonly, readonly,
multiple, multiple,
autofocus, autofocus
onChange, } = this.props;
registry
} = props;
const {enumOptions} = options;
const [infoPaneParams, setInfoPaneParams] = useState(getDefaultPaneParams(schema.items.$ref, registry));
getDefaultPaneParams(schema.items.$ref, registry);
const selectValue = cloneDeep(value);
return ( return (
<div className={'advanced-multi-select'}> <div className={'advanced-multi-select'}>
<Card.Header> <AdvancedMultiSelectHeader title={schema.title}
<Button key={`${props.schema.title}-button`} value={value} disabled={disabled} onCheckboxClick={this.onMasterCheckboxClick}
variant={'link'} disabled={disabled} checkboxState={this.state.masterCheckboxState}
onClick={() => { hideReset={this.state.hideReset} onResetClick={this.onResetClick}/>
onMasterCheckboxClick(masterCheckbox, schema.default, onChange);
setMasterCheckbox(!masterCheckbox); <ChildCheckboxContainer id={id} multiple={multiple} required={required}
}} disabled={disabled || readonly} autoFocus={autofocus} isSafe={this.isSafe}
> onPaneClick={this.setPaneInfo} onCheckboxClick={this.onChildCheckboxClick}
<FontAwesomeIcon icon={masterCheckbox ? faCheckSquare : faSquare}/> selectedValues={this.props.value} enumOptions={this.enumOptions}/>
</Button>
<span className={'header-title'}>{props.schema.title}</span> <InfoPane title={this.state.infoPaneParams.title}
</Card.Header> body={this.state.infoPaneParams.content}
<Form.Group link={this.state.infoPaneParams.link}
style={{height: `${getComponentHeight(enumOptions.length)}px`}} showWarning={this.state.infoPaneParams.showWarning}/>
id={id}
multiple={multiple}
className='choice-block form-control'
required={required}
disabled={disabled || readonly}
autoFocus={autofocus}>
{enumOptions.map(({value, label}, i) => {
return (
<Form.Group
key={i}
onClick={() => setPaneInfo(schema.items.$ref, registry, value, setInfoPaneParams)}>
<Button value={value} variant={'link'} disabled={disabled}
onClick={() => onChange(getSelectValuesAfterClick(selectValue, value))}>
<FontAwesomeIcon icon={selectValue.includes(value) ? faCheckSquare : faSquare}/>
</Button>
<span className={'option-text'}>
{label}
</span>
</Form.Group>
);
})}
</Form.Group>
<InfoPane title={infoPaneParams.title} body={infoPaneParams.content} link={infoPaneParams.link}/>
</div> </div>
); );
}
} }
export default AdvancedMultiSelect; export default AdvancedMultiSelect;

View File

@ -0,0 +1,71 @@
import React from 'react';
import {Button, Form} from 'react-bootstrap';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faCheckSquare} from '@fortawesome/free-solid-svg-icons';
import {faSquare} from '@fortawesome/free-regular-svg-icons';
import {getComponentHeight} from './utils/HeightCalculator';
import WarningIcon from './WarningIcon';
function ChildCheckboxContainer(props) {
const {
enumOptions,
id,
multiple,
required,
disabled,
autofocus,
onPaneClick,
onCheckboxClick,
selectedValues,
isSafe
} = props;
return(
<Form.Group
style={{height: `${getComponentHeight(enumOptions.length)}px`}}
id={id} multiple={multiple} className='choice-block form-control'
required={required} disabled={disabled} autoFocus={autofocus}>
{
enumOptions.map(({value, label}, i) => {
return (
<ChildCheckbox key={i} onPaneClick={onPaneClick}
onClick={onCheckboxClick} value={value}
disabled={disabled} label={label} checkboxState={selectedValues.includes(value)}
safe={isSafe(value)}/>
);
}
)}
</Form.Group>
);
}
function ChildCheckbox(props) {
const {
onPaneClick,
onClick,
value,
disabled,
label,
checkboxState,
safe
} = props;
let displayLabel = [<span key={'label'} className={'option-text'}>{label}</span>];
if (!safe) {
displayLabel.push(<WarningIcon key="warning-icon"/>)
}
return (
<Form.Group onClick={() => onPaneClick(value)}>
<Button value={value} variant={'link'} disabled={disabled} onClick={() => onClick(value)}>
<FontAwesomeIcon icon={checkboxState ? faCheckSquare : faSquare}/>
</Button>
{displayLabel}
</Form.Group>
);
}
export default ChildCheckboxContainer;

View File

@ -3,6 +3,18 @@ import React from 'react';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faQuestionCircle} from '@fortawesome/free-solid-svg-icons'; import {faQuestionCircle} from '@fortawesome/free-solid-svg-icons';
import {getObjectFromRegistryByRef} from './JsonSchemaHelpers';
import WarningIcon from './WarningIcon';
function getDefaultPaneParams(refString, registry) {
let configSection = getObjectFromRegistryByRef(refString, registry);
return (
{
title: configSection.title,
content: configSection.description,
showWarning: false
});
}
function InfoPane(props) { function InfoPane(props) {
return ( return (
@ -42,11 +54,27 @@ function getSubtitle(props) {
} }
function getBody(props) { function getBody(props) {
let body = [<span key={'body'}>{props.body}</span>];
if (props.showWarning) {
body.push(getWarning());
}
return ( return (
<Card.Body className={'pane-body'}> <Card.Body className={'pane-body'}>
{props.body} {body}
</Card.Body> </Card.Body>
) )
} }
export default InfoPane function getWarning() {
return (
<div className={'info-pane-warning'} key={'warning'}>
<WarningIcon/>This option may cause a system to become unstable or
change the system's state in undesirable ways. Therefore, this option
is not recommended for use in production or other sensitive environments.
</div>
);
}
export {getDefaultPaneParams, InfoPane}

View File

@ -0,0 +1,19 @@
import {resolveObjectPath} from './utils/ObjectPathResolver';
function getFullDefinitionByKey(refString, registry, itemKey) {
let fullArray = getFullDefinitionsFromRegistry(refString, registry);
return fullArray.filter(e => (e.enum[0] === itemKey))[0];
}
// Definitions passed to components only contains value and label,
// custom fields like "info" or "links" must be pulled from registry object using this function
function getFullDefinitionsFromRegistry(refString, registry) {
return getObjectFromRegistryByRef(refString, registry).anyOf;
}
function getObjectFromRegistryByRef(refString, registry) {
let refArray = refString.replace('#', '').split('/');
return resolveObjectPath(refArray, registry);
}
export {getFullDefinitionByKey, getObjectFromRegistryByRef};

View File

@ -0,0 +1,9 @@
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faSync} from '@fortawesome/free-solid-svg-icons';
import React from 'react';
function LoadingIcon() {
return <FontAwesomeIcon icon={faSync} className={`spinning-icon loading-icon`} />
}
export default LoadingIcon;

View File

@ -0,0 +1,41 @@
import React from 'react';
import {Button} from 'react-bootstrap';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faCheckSquare} from '@fortawesome/free-solid-svg-icons';
import {faMinusSquare} from '@fortawesome/free-solid-svg-icons';
import {faSquare} from '@fortawesome/free-regular-svg-icons';
const MasterCheckboxState = {
NONE: 0,
MIXED: 1,
ALL: 2
}
function MasterCheckbox(props) {
const {
title,
disabled,
onClick,
checkboxState
} = props;
let newCheckboxIcon = faCheckSquare;
if (checkboxState === MasterCheckboxState.NONE) {
newCheckboxIcon = faSquare;
} else if (checkboxState === MasterCheckboxState.MIXED) {
newCheckboxIcon = faMinusSquare;
}
return (
<div className={'master-checkbox'}>
<Button key={`${title}-button`} variant={'link'} disabled={disabled} onClick={onClick}>
<FontAwesomeIcon icon={newCheckboxIcon}/>
</Button>
<span className={'header-title'}>{title}</span>
</div>
);
}
export {MasterCheckboxState, MasterCheckbox};

View File

@ -0,0 +1,11 @@
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faExclamationTriangle} from '@fortawesome/free-solid-svg-icons';
import React from 'react';
function WarningIcon() {
return (
<FontAwesomeIcon className="warning-icon" icon={faExclamationTriangle}/>
);
}
export default WarningIcon;

View File

@ -17,6 +17,7 @@
@import 'components/inline-selection/BackButton'; @import 'components/inline-selection/BackButton';
@import 'components/inline-selection/CommandDisplay'; @import 'components/inline-selection/CommandDisplay';
@import 'components/Icons'; @import 'components/Icons';
@import 'components/Buttons';
// Define custom elements after bootstrap import // Define custom elements after bootstrap import

View File

@ -18,12 +18,14 @@
padding-bottom: 5px; padding-bottom: 5px;
} }
.advanced-multi-select .card-header button { .advanced-multi-select .card-header .master-checkbox span {
padding-top: 0; padding-bottom: 0.188rem;
} }
.advanced-multi-select .card-header .header-title { .advanced-multi-select .card-header .header-title {
font-size: 1.2em; font-size: 1.2em;
display: inline-block;
vertical-align: middle;
} }
.advanced-multi-select .choice-block .form-group { .advanced-multi-select .choice-block .form-group {

View File

@ -0,0 +1,6 @@
a.inline-link {
position: relative;
top: -1px;
margin: 0;
padding: 0;
}

View File

@ -1,3 +1,10 @@
.loading-icon {
color: $monkey-yellow;
font-size: 20px;
margin-left: 50%;
margin-right: 50%;
}
.spinning-icon { .spinning-icon {
animation: spin-animation 0.5s infinite; animation: spin-animation 0.5s infinite;
display: inline-block; display: inline-block;

View File

@ -27,3 +27,10 @@
margin: 10px 15px; margin: 10px 15px;
padding: 0; padding: 0;
} }
.info-pane-warning {
margin-top: 1em;
}
.info-pane-warning .warning-icon {
margin-left: 0em;
}

View File

@ -57,3 +57,10 @@
white-space: pre-wrap; white-space: pre-wrap;
} }
.warning-icon {
text-transform: uppercase;
color: #ffc107;
font-weight: 900;
margin-left: .75em;
margin-right: .75em;
}

View File

@ -14,3 +14,8 @@ div.run-on-os-buttons > .nav-item > .nav-link:hover{
color: $monkey-white; color: $monkey-white;
background-color: $monkey-alt; background-color: $monkey-alt;
} }
.aws-run-button-container {
margin-top: 2em;
text-align: center;
}

View File

@ -71,10 +71,12 @@ module.exports = {
publicPath: '/' publicPath: '/'
}, },
devServer: { devServer: {
host: '0.0.0.0',
proxy: { proxy: {
'/api': { '/api': {
target: 'https://localhost:5000', target: 'https://localhost:5000',
secure: false secure: false,
changeOrigin: true
} }
} }
} }

View File

@ -8,6 +8,7 @@ botocore==1.17.54
cffi>=1.8,!=1.11.3 cffi>=1.8,!=1.11.3
dpath>=2.0 dpath>=2.0
flask>=1.1 flask>=1.1
gevent>=20.9.0
ipaddress>=1.0.23 ipaddress>=1.0.23
jsonschema==3.2.0 jsonschema==3.2.0
mongoengine==0.20 mongoengine==0.20
@ -20,7 +21,6 @@ requests>=2.24
ring>=0.7.3 ring>=0.7.3
stix2>=2.0.2 stix2>=2.0.2
six>=1.13.0 six>=1.13.0
tornado>=6.0.4
tqdm>=4.47 tqdm>=4.47
virtualenv>=20.0.26 virtualenv>=20.0.26
werkzeug>=1.0.1 werkzeug>=1.0.1

View File

@ -3,5 +3,5 @@ log_cli = 1
log_cli_level = DEBUG log_cli_level = DEBUG
log_cli_format = %(asctime)s [%(levelname)s] %(module)s.%(funcName)s.%(lineno)d: %(message)s log_cli_format = %(asctime)s [%(levelname)s] %(module)s.%(funcName)s.%(lineno)d: %(message)s
log_cli_date_format=%H:%M:%S log_cli_date_format=%H:%M:%S
addopts = -v --capture=sys addopts = -v --capture=sys --ignore=common/cloud/scoutsuite
norecursedirs = node_modules dist norecursedirs = node_modules dist