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": []
},
"hunkDiffLines": [
"@@ -62,15 +62,7 @@",
"@@ -68,16 +68,7 @@",
" \"Removes the file afterwards.\",",
" \"attack_techniques\": [\"T1166\"]",
" },",
"- {",
"+ # Swimmer: ADD DETAILS HERE!\",",
"+ # Swimmer: ADD DETAILS HERE!",
"- \"type\": \"string\",",
"- \"enum\": [",
"- \"ScheduleJobs\"",
"- ],",
"- \"title\": \"Job scheduling\",",
"- \"safe\": True,",
"- \"info\": \"Attempts to create a scheduled job on the system and remove it.\",",
"- \"attack_techniques\": [\"T1168\", \"T1053\"]",
"- },",
@ -45,5 +46,9 @@
}
},
"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": []
},
"hunkDiffLines": [
"@@ -10,12 +10,6 @@",
"@@ -10,11 +10,5 @@",
" \"\"\"",
" ",
" def __init__(self):",
"- linux_cmds, windows_cmds = get_commands_to_schedule_jobs()",
"+ pass",
" ",
"- super(ScheduleJobs, self).__init__(name=POST_BREACH_JOB_SCHEDULING,",
"-",
"+ # Swimmer: IMPLEMENT HERE!",
"- super(ScheduleJobs, self).__init__(name=POST_BREACH_JOB_SCHEDULING,",
"- linux_cmd=' '.join(linux_cmds),",
"- windows_cmd=windows_cmds)",
"- ",
"- def run(self):",
"- super(ScheduleJobs, self).run()",
"- remove_scheduled_jobs()"
"- super(ScheduleJobs, self).run()"
]
}
]
}
},
"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 @@",
" 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)",
" from monkey_island.cc.services.attack.technique_reports.pba_technique import \\",
" PostBreachTechnique",
" "
@ -97,7 +97,7 @@
" 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.\"",
"- 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": []
},
"hunkDiffLines": [
"@@ -4,15 +4,7 @@",
"@@ -4,16 +4,7 @@",
" \"might do after breaching a new machine. Used in ATT&CK and Zero trust reports.\",",
" \"type\": \"string\",",
" \"anyOf\": [",
@ -122,6 +122,7 @@
"- \"BackdoorUser\"",
"- ],",
"- \"title\": \"Back door user\",",
"- \"safe\": True,",
"- \"info\": \"Attempts to create a new user on the system and delete it afterwards.\",",
"- \"attack_techniques\": [\"T1136\"]",
"- },",
@ -134,5 +135,13 @@
}
},
"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:
# Check Python code
## Check syntax errors and fail the build if any are found.
- flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics
- flake8 ./monkey --config=./ci_scripts/flake8_syntax_check.ini
## 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.
### --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.
- 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
- cat flake8_warnings.txt
- cat ./ci_scripts/flake8_warnings.txt
## Make sure that we haven't increased the amount of warnings.
- 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
- 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
- cd monkey # This is our source dir
@ -98,10 +95,11 @@ script:
# verify swimm
- 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
- ./swimm --version
- ./swimm verify
- swimm --version
- swimm verify
after_success:
# 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
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!
@ -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)
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
export MONKEY_GIT_URL="https://github.com/guardicore/monkey"
exists() {
command -v "$1" >/dev/null 2>&1
}
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
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
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
export LINUX_32_BINARY_NAME
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"
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
export LINUX_64_BINARY_NAME
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"
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
export WINDOWS_32_BINARY_NAME
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"
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
export WINDOWS_64_BINARY_NAME
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"
# Other binaries for monkey
TRACEROUTE_64_BINARY_URL="https://github.com/guardicore/monkey/releases/download/$($MONKEY_LATEST_RELEASE)/traceroute64"
export TRACEROUTE_64_BINARY_URL
TRACEROUTE_32_BINARY_URL="https://github.com/guardicore/monkey/releases/download/$($MONKEY_LATEST_RELEASE)/traceroute32"
export TRACEROUTE_32_BINARY_URL
SAMBACRY_64_BINARY_URL="https://github.com/guardicore/monkey/releases/download/$($MONKEY_LATEST_RELEASE)/sc_monkey_runner64.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
export TRACEROUTE_64_BINARY_URL="https://github.com/guardicore/monkey/releases/download/$MONKEY_LATEST_RELEASE/traceroute64"
export TRACEROUTE_32_BINARY_URL="https://github.com/guardicore/monkey/releases/download/$MONKEY_LATEST_RELEASE/traceroute32"
export 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"

View File

@ -10,7 +10,7 @@ is_root() {
has_sudo() {
# 0 true, 1 false
timeout 1 sudo id && return 0 || return 1
return $(sudo -nv > /dev/null 2>&1)
}
handle_error() {
@ -23,6 +23,11 @@ log_message() {
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_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"
MONKEY_BIN_DIR="$INFECTION_MONKEY_DIR/bin"
if is_root; then
log_message "Please don't run this script as root"
exit 1
fi
HAS_SUDO=$(has_sudo)
if [[ ! $HAS_SUDO ]]; then
log_message "You need root permissions for some of this script operations. Quiting."
if ! has_sudo; then
log_message "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
@ -110,13 +110,16 @@ if [[ ${python_cmd} == "" ]]; then
log_message "Python 3.7 command not found. Installing python 3.7."
sudo add-apt-repository ppa:deadsnakes/ppa
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'."
python_cmd="python3.7"
fi
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"
# 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
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"
${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 ! exists mongod; then
log_message "Installing libcurl4"
sudo apt-get install -y libcurl4
log_message "Installing MongoDB"
"${ISLAND_PATH}"/linux/install_mongo.sh ${MONGO_PATH} || handle_error
fi
log_message "Installing openssl"
sudo apt-get install openssl
sudo apt-get install -y openssl
# Generate SSL certificate
log_message "Generating certificate"
chmod u+x "${ISLAND_PATH}"/linux/create_certificate.sh
"${ISLAND_PATH}"/linux/create_certificate.sh ${ISLAND_PATH}/cc
# 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)
- [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)
- [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)
@ -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.
## 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?
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"
date = 2020-06-24T21:16:03+03:00
weight = 5
weight = 40
chapter = true
pre = "<i class='fas fa-scroll'></i> "
+++
@ -10,4 +10,4 @@ pre = "<i class='fas fa-scroll'></i> "
The Monkey offers three reports:
{{% children %}}
{{% children description=true style="p"%}}

View File

@ -1,5 +1,6 @@
---
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
draft: false
---

View File

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

View File

@ -2,6 +2,7 @@
title: "Zero Trust report"
date: 2020-06-24T21:16:18+03:00
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 %}}
@ -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 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 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")

View File

@ -54,16 +54,15 @@ See [Amazon's documentation about working with SSM agents](https://docs.aws.amaz
### 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")
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"
2. Choose the relevant Network Interface
3. Select the machines you'd like to run the Monkey on
4. Click "Run on Selected Machines", and watch the monkey go! 🐒
1. Choose the relevant Network Interface
2. Select the machines you'd like to run the Monkey on
3. 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")

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
:return: Sequence of URLs to try and attack
"""
import requests
attack_path = 'http://'
if is_https:
attack_path = 'https://'

View File

@ -1,7 +1,6 @@
from logging import getLogger
from impacket.dcerpc.v5 import scmr, transport
from impacket.smbconnection import SMB_DIALECT
from common.utils.attack_utils import ScanStatus, UsageEnum
from common.utils.exploit_enum import ExploitType
@ -104,7 +103,7 @@ class SmbExploiter(HostExploiter):
LOG.debug("Exploiter SmbExec is giving up...")
return False
self.set_vulnerable_port(self.host)
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():
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():
rpctransport = transport.DCERPCTransportFactory(str_bind_format % (self.host.ip_addr,))
rpctransport.set_dport(port)
if hasattr(rpctransport, 'preferred_dialect'):
rpctransport.preferred_dialect(SMB_DIALECT)
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)
@ -168,7 +166,7 @@ class SmbExploiter(HostExploiter):
SmbExploiter.KNOWN_PROTOCOLS['445/SMB'][1]))
return True
def set_vulnerable_port(self, host: VictimHost):
def set_vulnerable_port(self):
if 'tcp-445' in self.host.services:
self.vulnerable_port = "445"
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
: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
# 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
"""
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)
return victim
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
sockets_to_try = possible_ports[:]
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]
_, 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):
if type(hash_) == str:
if type(hash_) is str:
return hash_
if type(hash_) == bytes:
if type(hash_) is bytes:
return binascii.hexlify(bytearray(hash_)).decode()
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
@ -5,8 +9,9 @@ def parse_cli_args():
import argparse
parser = argparse.ArgumentParser(description="Infection Monkey Island CnC Server. See https://infectionmonkey.com")
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 "
"for preparing Island to boot faster later-on, so for compiling/packaging Islands.")
help="Pass this flag to cause the Island to setup and exit without actually starting. "
"This is useful for preparing Island to boot faster later-on, so for "
"compiling/packaging Islands.")
args = parser.parse_args()
return args.setup_only

View File

@ -3,6 +3,7 @@ import json
import logging
import sys
from pathlib import Path
from shutil import move
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
SERVER_CONFIG = "server_config"
BACKUP_CONFIG_FILENAME = "./server_config.backup"
logger = logging.getLogger(__name__)
logger.addHandler(logging.StreamHandler())
@ -26,10 +28,19 @@ def main():
args = parse_args()
file_path = EnvironmentConfig.get_config_file_path()
if args.server_config == "restore":
restore_previous_config(file_path)
quit()
# Read config
with open(file_path) as 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
config_data[SERVER_CONFIG] = args.server_config
@ -42,10 +53,14 @@ def main():
def parse_args():
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()
return args
def restore_previous_config(config_path):
move(BACKUP_CONFIG_FILENAME, config_path)
if __name__ == '__main__':
main()

View File

@ -6,6 +6,8 @@ from pathlib import Path
from threading import Thread
# 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)
if str(MONKEY_ISLAND_DIR_BASE_PATH) not in sys.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):
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())
wait_for_mongo_db_server(mongo_url)
@ -69,12 +68,11 @@ def start_island_server(should_setup_only):
if env_singleton.env.is_debug():
app.run(host='0.0.0.0', debug=True, ssl_context=(crt_path, key_path))
else:
http_server = HTTPServer(WSGIContainer(app),
ssl_options={'certfile': os.environ.get('SERVER_CRT', crt_path),
'keyfile': os.environ.get('SERVER_KEY', key_path)})
http_server.listen(env_singleton.env.get_island_port())
http_server = WSGIServer(('0.0.0.0', env_singleton.env.get_island_port()), app,
certfile=os.environ.get('SERVER_CRT', crt_path),
keyfile=os.environ.get('SERVER_KEY', key_path))
log_init_info()
IOLoop.instance().start()
http_server.serve_forever()
def log_init_info():

View File

@ -1,7 +1,7 @@
import flask_restful
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'
@ -13,4 +13,4 @@ class PBAFileDownload(flask_restful.Resource):
# Used by monkey. can't secure.
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"
],
"title": "SMB Exploiter",
"safe": True,
"attack_techniques": ["T1110", "T1075", "T1035"],
"info": "Brute forces using credentials provided by user and"
" hashes gathered by mimikatz.",
@ -23,6 +24,7 @@ EXPLOITER_CLASSES = {
"WmiExploiter"
],
"title": "WMI Exploiter",
"safe": True,
"attack_techniques": ["T1110", "T1106"],
"info": "Brute forces WMI (Windows Management Instrumentation) "
"using credentials provided by user and hashes gathered by mimikatz.",
@ -34,6 +36,7 @@ EXPLOITER_CLASSES = {
"MSSQLExploiter"
],
"title": "MSSQL Exploiter",
"safe": True,
"attack_techniques": ["T1110"],
"info": "Tries to brute force into MsSQL server and uses insecure "
"configuration to execute commands on server.",
@ -44,7 +47,8 @@ EXPLOITER_CLASSES = {
"enum": [
"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. "
"Uses MS08-067 vulnerability.",
"link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/ms08-067/"
@ -55,6 +59,7 @@ EXPLOITER_CLASSES = {
"SSHExploiter"
],
"title": "SSH Exploiter",
"safe": True,
"attack_techniques": ["T1110", "T1145", "T1106"],
"info": "Brute forces using credentials provided by user and SSH keys gathered from systems.",
"link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/sshexec/"
@ -65,6 +70,7 @@ EXPLOITER_CLASSES = {
"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/"
@ -75,6 +81,7 @@ EXPLOITER_CLASSES = {
"SambaCryExploiter"
],
"title": "SambaCry Exploiter",
"safe": True,
"info": "Bruteforces and searches for anonymous shares. Uses Impacket.",
"link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/sambacry/"
},
@ -84,6 +91,7 @@ EXPLOITER_CLASSES = {
"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/"
},
@ -93,6 +101,7 @@ EXPLOITER_CLASSES = {
"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/"
@ -103,6 +112,7 @@ EXPLOITER_CLASSES = {
"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/"
},
@ -112,6 +122,7 @@ EXPLOITER_CLASSES = {
"HadoopExploiter"
],
"title": "Hadoop/Yarn Exploiter",
"safe": True,
"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.",
"link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/hadoop/"
@ -122,6 +133,7 @@ EXPLOITER_CLASSES = {
"VSFTPDExploiter"
],
"title": "VSFTPD Exploiter",
"safe": True,
"info": "Exploits a malicious backdoor that was added to the VSFTPD download archive. "
"Logic based on Metasploit module.",
"link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/vsftpd/"
@ -132,6 +144,7 @@ EXPLOITER_CLASSES = {
"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/"

View File

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

View File

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

View File

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

View File

@ -11,7 +11,8 @@ logger = logging.getLogger(__name__)
# Where to find file names in config
PBA_WINDOWS_FILENAME_PATH = ['monkey', 'post_breach', 'PBA_windows_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)

View File

@ -1,5 +1,6 @@
import logging
import threading
from gevent.lock import BoundedSemaphore
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
# the locks, these requests would accumulate, overload the server, eventually causing it to crash.
logger.debug("Initializing report generation locks.")
__report_generating_lock = threading.Semaphore()
__attack_report_generating_lock = threading.Semaphore()
__regular_report_generating_lock = threading.Semaphore()
__report_generating_lock = BoundedSemaphore()
__attack_report_generating_lock = BoundedSemaphore()
__regular_report_generating_lock = BoundedSemaphore()
def safe_generate_reports():

View File

@ -1,6 +1,5 @@
from monkey_island.cc.database import mongo
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.telemetry.processing.utils import \
get_edge_by_scan_or_exploit_telemetry

View File

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

View File

@ -1,12 +1,12 @@
import React, {useEffect, useState} from 'react';
import InlineSelection from '../../ui-components/inline-selection/InlineSelection';
import DropdownSelect from '../../ui-components/DropdownSelect';
import {OS_TYPES} from './OsTypes';
import GenerateLocalWindowsCmd from './commands/local_windows_cmd';
import GenerateLocalWindowsPowershell from './commands/local_windows_powershell';
import GenerateLocalLinuxWget from './commands/local_linux_wget';
import GenerateLocalLinuxCurl from './commands/local_linux_curl';
import CommandDisplay from './CommandDisplay';
import InlineSelection from '../../../ui-components/inline-selection/InlineSelection';
import DropdownSelect from '../../../ui-components/DropdownSelect';
import {OS_TYPES} from '../utils/OsTypes';
import GenerateLocalWindowsCmd from '../commands/local_windows_cmd';
import GenerateLocalWindowsPowershell from '../commands/local_windows_powershell';
import GenerateLocalLinuxWget from '../commands/local_linux_wget';
import GenerateLocalLinuxCurl from '../commands/local_linux_curl';
import CommandDisplay from '../utils/CommandDisplay';
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 NextSelectionButton from '../../ui-components/inline-selection/NextSelectionButton';
import LocalManualRunOptions from './LocalManualRunOptions';
import LocalManualRunOptions from './RunManually/LocalManualRunOptions';
import AuthComponent from '../../AuthComponent';
import {faLaptopCode} from '@fortawesome/free-solid-svg-icons/faLaptopCode';
import InlineSelection from '../../ui-components/inline-selection/InlineSelection';
import {cloneDeep} from 'lodash';
import {faExpandArrowsAlt} from '@fortawesome/free-solid-svg-icons';
import RunOnIslandButton from './RunOnIslandButton';
import AWSRunButton from './RunOnAWS/AWSRunButton';
function RunOptions(props) {
@ -61,6 +62,7 @@ function RunOptions(props) {
setComponent(LocalManualRunOptions,
{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) {

View File

@ -1,4 +1,4 @@
import {OS_TYPES} from '../OsTypes';
import {OS_TYPES} from '../utils/OsTypes';
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) {

View File

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

View File

@ -1,6 +1,6 @@
import React from 'react';
import InlineSelection from '../../ui-components/inline-selection/InlineSelection';
import LocalManualRunOptions from './LocalManualRunOptions';
import InlineSelection from '../../../ui-components/inline-selection/InlineSelection';
import LocalManualRunOptions from '../RunManually/LocalManualRunOptions';
function InterfaceSelection(props) {
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 {getComponentHeight} from './utils/HeightCalculator';
import {resolveObjectPath} from './utils/ObjectPathResolver';
import InfoPane from './InfoPane';
import {getDefaultPaneParams, InfoPane} from './InfoPane';
import {MasterCheckbox, MasterCheckboxState} from './MasterCheckbox';
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)) {
return valueArray.filter((e) => {
return e !== clickedValue;
});
return valueArray.filter(e => e !== clickedValue);
} else {
valueArray.push(clickedValue);
return valueArray;
}
}
function onMasterCheckboxClick(checkboxValue, defaultArray, onChangeFnc) {
if (checkboxValue) {
onChangeFnc([]);
} else {
onChangeFnc(defaultArray);
}
}
// 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;
}
setMasterCheckboxState(selectValues) {
this.setState(() => ({
masterCheckboxState: this.getMasterCheckboxState(selectValues)
}));
}
function getObjectFromRegistryByRef(refString, registry) {
let refArray = refString.replace('#', '').split('/');
return resolveObjectPath(refArray, registry);
}
getMasterCheckboxState(selectValues) {
if (selectValues.length === 0) {
return MasterCheckboxState.NONE;
}
function getFullDefinitionByKey(refString, registry, itemKey) {
let fullArray = getFullDefinitionsFromRegistry(refString, registry);
return fullArray.filter(e => (e.enum[0] === itemKey))[0];
}
if (selectValues.length != this.enumOptions.length) {
return MasterCheckboxState.MIXED;
}
function setPaneInfo(refString, registry, itemKey, setPaneInfoFnc) {
let definitionObj = getFullDefinitionByKey(refString, registry, itemKey);
setPaneInfoFnc({title: definitionObj.title, content: definitionObj.info, link: definitionObj.link});
}
return MasterCheckboxState.ALL;
}
function getDefaultPaneParams(refString, registry) {
let configSection = getObjectFromRegistryByRef(refString, registry);
return ({title: configSection.title, content: configSection.description});
}
onResetClick = () => {
this.props.onChange(this.defaultValues);
this.setHideResetState(this.defaultValues);
this.setMasterCheckboxState(this.defaultValues);
this.setPaneInfoToDefault();
}
function AdvancedMultiSelect(props) {
const [masterCheckbox, setMasterCheckbox] = useState(true);
setHideResetState(selectValues) {
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 {
schema,
id,
options,
value,
required,
disabled,
readonly,
multiple,
autofocus,
onChange,
registry
} = props;
const {enumOptions} = options;
const [infoPaneParams, setInfoPaneParams] = useState(getDefaultPaneParams(schema.items.$ref, registry));
getDefaultPaneParams(schema.items.$ref, registry);
const selectValue = cloneDeep(value);
autofocus
} = this.props;
return (
<div className={'advanced-multi-select'}>
<Card.Header>
<Button key={`${props.schema.title}-button`} value={value}
variant={'link'} disabled={disabled}
onClick={() => {
onMasterCheckboxClick(masterCheckbox, schema.default, onChange);
setMasterCheckbox(!masterCheckbox);
}}
>
<FontAwesomeIcon icon={masterCheckbox ? faCheckSquare : faSquare}/>
</Button>
<span className={'header-title'}>{props.schema.title}</span>
</Card.Header>
<Form.Group
style={{height: `${getComponentHeight(enumOptions.length)}px`}}
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}/>
<AdvancedMultiSelectHeader title={schema.title}
disabled={disabled} onCheckboxClick={this.onMasterCheckboxClick}
checkboxState={this.state.masterCheckboxState}
hideReset={this.state.hideReset} onResetClick={this.onResetClick}/>
<ChildCheckboxContainer id={id} multiple={multiple} required={required}
disabled={disabled || readonly} autoFocus={autofocus} isSafe={this.isSafe}
onPaneClick={this.setPaneInfo} onCheckboxClick={this.onChildCheckboxClick}
selectedValues={this.props.value} enumOptions={this.enumOptions}/>
<InfoPane title={this.state.infoPaneParams.title}
body={this.state.infoPaneParams.content}
link={this.state.infoPaneParams.link}
showWarning={this.state.infoPaneParams.showWarning}/>
</div>
);
}
}
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 {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) {
return (
@ -42,11 +54,27 @@ function getSubtitle(props) {
}
function getBody(props) {
let body = [<span key={'body'}>{props.body}</span>];
if (props.showWarning) {
body.push(getWarning());
}
return (
<Card.Body className={'pane-body'}>
{props.body}
{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/CommandDisplay';
@import 'components/Icons';
@import 'components/Buttons';
// Define custom elements after bootstrap import

View File

@ -18,12 +18,14 @@
padding-bottom: 5px;
}
.advanced-multi-select .card-header button {
padding-top: 0;
.advanced-multi-select .card-header .master-checkbox span {
padding-bottom: 0.188rem;
}
.advanced-multi-select .card-header .header-title {
font-size: 1.2em;
display: inline-block;
vertical-align: middle;
}
.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 {
animation: spin-animation 0.5s infinite;
display: inline-block;

View File

@ -27,3 +27,10 @@
margin: 10px 15px;
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;
}
.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;
background-color: $monkey-alt;
}
.aws-run-button-container {
margin-top: 2em;
text-align: center;
}

View File

@ -71,10 +71,12 @@ module.exports = {
publicPath: '/'
},
devServer: {
host: '0.0.0.0',
proxy: {
'/api': {
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
dpath>=2.0
flask>=1.1
gevent>=20.9.0
ipaddress>=1.0.23
jsonschema==3.2.0
mongoengine==0.20
@ -20,7 +21,6 @@ requests>=2.24
ring>=0.7.3
stix2>=2.0.2
six>=1.13.0
tornado>=6.0.4
tqdm>=4.47
virtualenv>=20.0.26
werkzeug>=1.0.1

View File

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