forked from p15670423/monkey
Merge remote-tracking branch 'origin/refactor-advanced-multiselect' into fix_swimm_units
This commit is contained in:
commit
bdf1dc2213
|
@ -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"
|
||||||
}
|
}
|
|
@ -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"
|
||||||
}
|
}
|
|
@ -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"
|
||||||
}
|
}
|
20
.travis.yml
20
.travis.yml
|
@ -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
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
./validation-env
|
||||||
|
./flake8_warnings.txt
|
|
@ -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`
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1,6 @@
|
||||||
|
flake8
|
||||||
|
pytest
|
||||||
|
dlint
|
||||||
|
isort
|
||||||
|
coverage
|
||||||
|
black
|
|
@ -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
|
|
@ -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
|
||||||
|
```
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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"%}}
|
|
@ -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
|
||||||
---
|
---
|
|
@ -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 %}}
|
|
@ -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")
|
||||||
|
|
|
@ -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 |
|
@ -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://'
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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_)}")
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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():
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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/"
|
||||||
|
|
|
@ -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"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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():
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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) => {
|
|
@ -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;
|
|
@ -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
|
||||||
|
<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;
|
|
@ -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;
|
|
@ -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}/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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)
|
|
@ -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;
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
|
@ -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}
|
||||||
|
|
|
@ -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};
|
|
@ -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;
|
|
@ -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};
|
|
@ -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;
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
a.inline-link {
|
||||||
|
position: relative;
|
||||||
|
top: -1px;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue