Merge remote-tracking branch 'upstream/develop' into old_machine_bootloader

# Conflicts:
#	monkey/monkey_island/cc/main.py
This commit is contained in:
VakarisZ 2020-03-16 12:08:45 +02:00
commit a22cd893d8
82 changed files with 1427 additions and 563 deletions

View File

@ -15,6 +15,7 @@ install:
# Python
- pip install -r monkey/monkey_island/requirements.txt # for unit tests
- pip install flake8 pytest dlint # for next stages
- pip install coverage # for code coverage
- pip install -r monkey/infection_monkey/requirements.txt # for unit tests
before_script:
@ -23,24 +24,28 @@ before_script:
script:
# Check Python code
# Check syntax errors and fail the build if any are found.
## Check syntax errors and fail the build if any are found.
- flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics
# Warn about linter issues.
# --exit-zero forces Flake8 to use the exit status code 0 even if there are errors, which means this will NOT fail the build.
# --count will print the total number of errors.
# --statistics Count the number of occurrences of each error/warning code and print a report.
# The output is redirected to a file.
## Warn about linter issues.
### --exit-zero forces Flake8 to use the exit status code 0 even if there are errors, which means this will NOT fail the build.
### --count will print the total number of errors.
### --statistics Count the number of occurrences of each error/warning code and print a report.
### The output is redirected to a file.
- flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics > flake8_warnings.txt
# Display the linter issues
## Display the linter issues
- cat 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=190
- if [ $(tail -n 1 flake8_warnings.txt) -gt $PYTHON_WARNINGS_AMOUNT_UPPER_LIMIT ]; then echo "Too many warnings! Failing this build. Lower the amount of linter errors in this and try again. " && exit 1; fi
- 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
## Run unit tests
- cd monkey # This is our source dir
- python -m pytest # Have to use `python -m pytest` instead of `pytest` to add "{$builddir}/monkey/monkey" to sys.path.
## Calculate Code Coverage
- coverage run -m pytest
# Check JS code. The npm install must happen AFTER the flake8 because the node_modules folder will cause a lot of errors.
- cd monkey_island/cc/ui
- npm i
@ -51,6 +56,10 @@ script:
- JS_WARNINGS_AMOUNT_UPPER_LIMIT=37
- eslint ./src --max-warnings $JS_WARNINGS_AMOUNT_UPPER_LIMIT
after_success:
# Upload code coverage results to codecov.io, see https://github.com/codecov/codecov-bash for more information
- bash <(curl -s https://codecov.io/bash)
notifications:
slack: # Notify to slack
rooms:

View File

@ -1,6 +1,9 @@
# Infection Monkey
[![Build Status](https://travis-ci.com/guardicore/monkey.svg?branch=develop)](https://travis-ci.com/guardicore/monkey)
[![GitHub release (latest by date)](https://img.shields.io/github/v/release/guardicore/monkey)](https://github.com/guardicore/monkey/releases)
[![Build Status](https://travis-ci.com/guardicore/monkey.svg?branch=develop)](https://travis-ci.com/guardicore/monkey)
[![codecov](https://codecov.io/gh/guardicore/monkey/branch/develop/graph/badge.svg)](https://codecov.io/gh/guardicore/monkey)
![GitHub stars](https://img.shields.io/github/stars/guardicore/monkey)
![GitHub commit activity](https://img.shields.io/github/commit-activity/m/guardicore/monkey)
@ -14,14 +17,15 @@ The Infection Monkey is an open source security tool for testing a data center's
<img src=".github/Security-overview.png" width="800" height="500">
The Infection Monkey is comprised of two parts:
* Monkey - A tool which infects other machines and propagates to them
* Monkey Island - A dedicated server to control and visualize the Infection Monkey's progress inside the data center
To read more about the Monkey, visit http://infectionmonkey.com
* **Monkey** - A tool which infects other machines and propagates to them.
* **Monkey Island** - A dedicated server to control and visualize the Infection Monkey's progress inside the data center.
To read more about the Monkey, visit [infectionmonkey.com](https://infectionmonkey.com).
## Main Features
The Infection Monkey uses the following techniques and exploits to propagate to other machines.
* Multiple propagation techniques:
@ -42,12 +46,10 @@ Check out the [Setup](https://github.com/guardicore/monkey/wiki/setup) page in t
The Infection Monkey supports a variety of platforms, documented [in the wiki](https://github.com/guardicore/monkey/wiki/OS-compatibility).
## Building the Monkey from source
To deploy development version of monkey you should refer to readme in the [deployment scripts](deployment_scripts) folder.
If you only want to build the monkey from source, see [Setup](https://github.com/guardicore/monkey/wiki/Setup#compile-it-yourself)
and follow the instructions at the readme files under [infection_monkey](infection_monkey) and [monkey_island](monkey_island).
and follow the instructions at the readme files under [infection_monkey](monkey/infection_monkey) and [monkey_island](monkey/monkey_island).
### Build status
| Branch | Status |
@ -56,13 +58,21 @@ and follow the instructions at the readme files under [infection_monkey](infecti
| Master | [![Build Status](https://travis-ci.com/guardicore/monkey.svg?branch=master)](https://travis-ci.com/guardicore/monkey) |
## Tests
### Unit Tests
In order to run all of the Unit Tests, run the command `python -m pytest` in the `monkey` directory.
To get a coverage report, first make sure the `coverage` package is installed using `pip install coverage`. Run the command
`coverage run -m unittest` in the `monkey` directory and then `coverage html`. The coverage report can be found in
`htmlcov.index`.
### Blackbox tests
In order to run the Blackbox tests, refer to `envs/monkey_zoo/blackbox/README.md`.
# License
Copyright (c) Guardicore Ltd
See the [LICENSE](LICENSE) file for license rights and limitations (GPLv3).

View File

@ -1,25 +1,55 @@
# Files used to deploy development version of infection monkey
## Windows
# Deployment guide for a development environemnt
Before running the script you must have git installed.<br>
Cd to scripts directory and use the scripts.<br>
First argument is an empty directory (script can create one) and second is branch you want to clone.
Example usages:<br>
./run_script.bat (Sets up monkey in current directory under .\infection_monkey)<br>
./run_script.bat "C:\test" (Sets up monkey in C:\test)<br>
powershell -ExecutionPolicy ByPass -Command ". .\deploy_windows.ps1; Deploy-Windows -monkey_home C:\test" (Same as above)<br>
./run_script.bat "" "master"(Sets up master branch instead of develop in current dir)
Don't forget to add python to PATH or do so while installing it via this script.<br>
This guide is for you if you wish to develop for Infection Monkey. If you only want to use it, please download the relevant version from [our website](https://infectionmonkey.com).
## Linux
## Prerequisites
Linux deployment script is meant for Ubuntu 16.x machines.
You must have root permissions, but don't run the script as root.<br>
Launch deploy_linux.sh from scripts directory.<br>
First argument should be an absolute path of an empty directory (script will create one if doesn't exist, default is ./infection_monkey).
Second parameter is the branch you want to clone (develop by default).
Example usages:<br>
./deploy_linux.sh (deploys under ./infection_monkey)<br>
./deploy_linux.sh "/home/test/monkey" (deploys under /home/test/monkey)<br>
./deploy_linux.sh "" "master" (deploys master branch in script directory)<br>
./deploy_linux.sh "/home/user/new" "master" (if directory "new" is not found creates it and clones master branch into it)<br>
Before running the script you must have `git` installed. If you don't have `git` installed, please follow [this guide](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git).
## Deploy on Windows
Run the following command in powershell:
```powershell
Invoke-WebRequest https://raw.githubusercontent.com/guardicore/monkey/develop/deployment_scripts/deploy_windows.ps1 -OutFile deploy_windows.ps1
```
This will download our deploy script. It's a good idea to read it quickly before executing it!
After downloading that script, execute it in `powershell`.
The first argument is an empty directory (script can create one). The second argument is which branch you want to clone - by default, the script will check out the `develop` branch. Some example usages:
- `.\deploy_windows.ps1` (Sets up monkey in current directory under .\infection_monkey)
- `.\deploy_windows.ps1 -monkey_home "C:\test"` (Sets up monkey in C:\test)
- `.\deploy_windows.ps1 -branch "master"` (Sets up master branch instead of develop in current dir)
You may also pass in an optional `agents=$false` parameter to disable downloading the latest agent binaries.
### Troubleshooting
- If you run into Execution Policy warnings, you can disable them by prefixing the following snippet: `powershell -ExecutionPolicy ByPass -Command "[original command here]"`
- Don't forget to add python to PATH or do so while installing it via this script.
## Deploy on Linux
Linux deployment script is meant for Ubuntu 16 and Ubuntu 18 machines.
Your user must have root permissions; however, don't run the script as root!
```sh
wget https://raw.githubusercontent.com/guardicore/monkey/develop/deployment_scripts/deploy_linux.sh
```
This will download our deploy script. It's a good idea to read it quickly before executing it!
Then execute the resulting script with your shell.
After downloading that script, execute it in a shell. The first argument should be an absolute path of an empty directory (the script will create one if doesn't exist, default is ./infection_monkey). The second parameter is the branch you want to clone (develop by default). Some example usages:
- `./deploy_linux.sh` (deploys under ./infection_monkey)
- `./deploy_linux.sh "/home/test/monkey"` (deploys under /home/test/monkey)
- `./deploy_linux.sh "" "master"` (deploys master branch in script directory)
- `./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.

View File

@ -5,21 +5,17 @@ MONKEY_FOLDER_NAME="infection_monkey"
MONKEY_GIT_URL="https://github.com/guardicore/monkey"
# Monkey binaries
LINUX_32_BINARY_URL="https://github.com/guardicore/monkey/releases/download/1.6/monkey-linux-32"
LINUX_32_BINARY_URL="https://github.com/guardicore/monkey/releases/download/v1.7.0/monkey-linux-32"
LINUX_32_BINARY_NAME="monkey-linux-32"
LINUX_64_BINARY_URL="https://github.com/guardicore/monkey/releases/download/1.6/monkey-linux-64"
LINUX_64_BINARY_URL="https://github.com/guardicore/monkey/releases/download/v1.7.0/monkey-linux-64"
LINUX_64_BINARY_NAME="monkey-linux-64"
WINDOWS_32_BINARY_URL="https://github.com/guardicore/monkey/releases/download/1.6/monkey-windows-32.exe"
WINDOWS_32_BINARY_URL="https://github.com/guardicore/monkey/releases/download/v1.7.0/monkey-windows-32.exe"
WINDOWS_32_BINARY_NAME="monkey-windows-32.exe"
WINDOWS_64_BINARY_URL="https://github.com/guardicore/monkey/releases/download/1.6/monkey-windows-64.exe"
WINDOWS_64_BINARY_URL="https://github.com/guardicore/monkey/releases/download/v1.7.0/monkey-windows-64.exe"
WINDOWS_64_BINARY_NAME="monkey-windows-64.exe"
# Other binaries for monkey
TRACEROUTE_64_BINARY_URL="https://github.com/guardicore/monkey/releases/download/1.6/traceroute64"
TRACEROUTE_32_BINARY_URL="https://github.com/guardicore/monkey/releases/download/1.6/traceroute32"
SAMBACRY_64_BINARY_URL="https://github.com/guardicore/monkey/releases/download/1.6/sc_monkey_runner64.so"
SAMBACRY_32_BINARY_URL="https://github.com/guardicore/monkey/releases/download/1.6/sc_monkey_runner32.so"
# Mongo url's
MONGO_DEBIAN_URL="https://downloads.mongodb.org/linux/mongodb-linux-x86_64-debian81-latest.tgz"
MONGO_UBUNTU_URL="https://downloads.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1604-latest.tgz"
TRACEROUTE_64_BINARY_URL="https://github.com/guardicore/monkey/releases/download/v1.7.0/traceroute64"
TRACEROUTE_32_BINARY_URL="https://github.com/guardicore/monkey/releases/download/v1.7.0/traceroute32"
SAMBACRY_64_BINARY_URL="https://github.com/guardicore/monkey/releases/download/v1.7.0/sc_monkey_runner64.so"
SAMBACRY_32_BINARY_URL="https://github.com/guardicore/monkey/releases/download/v1.7.0/sc_monkey_runner32.so"

View File

@ -2,28 +2,33 @@
$MONKEY_FOLDER_NAME = "infection_monkey"
# Url of public git repository that contains monkey's source code
$MONKEY_GIT_URL = "https://github.com/guardicore/monkey"
$MONKEY_RELEASES_URL = $MONKEY_GIT_URL + "/releases"
$MONKEY_LATEST_VERSION = "v1.7.0"
$MONKEY_DOWNLOAD_URL = $MONKEY_RELEASES_URL + "/download/" + $MONKEY_LATEST_VERSION + "/"
# Link to the latest python download or install it manually
$PYTHON_URL = "https://www.python.org/ftp/python/3.7.4/python-3.7.4-amd64.exe"
$PYTHON_URL = "https://www.python.org/ftp/python/3.7.6/python-3.7.6-amd64.exe"
# Monkey binaries
$LINUX_32_BINARY_URL = "https://github.com/guardicore/monkey/releases/download/1.6/monkey-linux-32"
$LINUX_32_BINARY_URL = $MONKEY_DOWNLOAD_URL + "monkey-linux-32"
$LINUX_32_BINARY_PATH = "monkey-linux-32"
$LINUX_64_BINARY_URL = "https://github.com/guardicore/monkey/releases/download/1.6/monkey-linux-64"
$LINUX_64_BINARY_URL = $MONKEY_DOWNLOAD_URL + "monkey-linux-64"
$LINUX_64_BINARY_PATH = "monkey-linux-64"
$WINDOWS_32_BINARY_URL = "https://github.com/guardicore/monkey/releases/download/1.6/monkey-windows-32.exe"
$WINDOWS_32_BINARY_URL = $MONKEY_DOWNLOAD_URL + "monkey-windows-32.exe"
$WINDOWS_32_BINARY_PATH = "monkey-windows-32.exe"
$WINDOWS_64_BINARY_URL = "https://github.com/guardicore/monkey/releases/download/1.6/monkey-windows-64.exe"
$WINDOWS_64_BINARY_URL = $MONKEY_DOWNLOAD_URL + "monkey-windows-64.exe"
$WINDOWS_64_BINARY_PATH = "monkey-windows-64.exe"
$SAMBA_32_BINARY_URL = "https://github.com/VakarisZ/tempBinaries/raw/master/sc_monkey_runner32.so"
$SAMBA_32_BINARY_NAME= "sc_monkey_runner32.so"
$SAMBA_64_BINARY_URL = "https://github.com/VakarisZ/tempBinaries/raw/master/sc_monkey_runner64.so"
$SAMBA_32_BINARY_URL = $MONKEY_DOWNLOAD_URL + "sc_monkey_runner32.so"
$SAMBA_32_BINARY_NAME = "sc_monkey_runner32.so"
$SAMBA_64_BINARY_URL = $MONKEY_DOWNLOAD_URL + "sc_monkey_runner64.so"
$SAMBA_64_BINARY_NAME = "sc_monkey_runner64.so"
$TRACEROUTE_64_BINARY_URL = $MONKEY_DOWNLOAD_URL + "traceroute64"
$TRACEROUTE_32_BINARY_URL = $MONKEY_DOWNLOAD_URL + "traceroute32"
# Other directories and paths ( most likely you dont need to configure)
$MONKEY_ISLAND_DIR = "\monkey\monkey_island"
$MONKEY_DIR = "\monkey\infection_monkey"
$MONKEY_ISLAND_DIR = Join-Path "\monkey" -ChildPath "monkey_island"
$MONKEY_DIR = Join-Path "\monkey" -ChildPath "infection_monkey"
$SAMBA_BINARIES_DIR = Join-Path -Path $MONKEY_DIR -ChildPath "\bin"
$PYTHON_DLL = "C:\Windows\System32\python27.dll"
$MK32_DLL = "mk32.zip"
$MK64_DLL = "mk64.zip"
$TEMP_PYTHON_INSTALLER = ".\python.exe"
@ -31,16 +36,14 @@ $TEMP_MONGODB_ZIP = ".\mongodb.zip"
$TEMP_OPEN_SSL_ZIP = ".\openssl.zip"
$TEMP_CPP_INSTALLER = "cpp.exe"
$TEMP_NPM_INSTALLER = "node.msi"
$TEMP_PYWIN32_INSTALLER = "pywin32.exe"
$TEMP_UPX_ZIP = "upx.zip"
$UPX_FOLDER = "upx394w"
$UPX_FOLDER = "upx-3.96-win64"
# Other url's
$MONGODB_URL = "https://downloads.mongodb.org/win32/mongodb-win32-x86_64-2008plus-ssl-latest.zip"
$OPEN_SSL_URL = "https://indy.fulgan.com/SSL/Archive/openssl-1.0.2l-i386-win32.zip"
$MONGODB_URL = "https://downloads.mongodb.org/win32/mongodb-win32-x86_64-2012plus-v4.2-latest.zip"
$OPEN_SSL_URL = "https://indy.fulgan.com/SSL/openssl-1.0.2u-x64_86-win64.zip"
$CPP_URL = "https://go.microsoft.com/fwlink/?LinkId=746572"
$NPM_URL = "https://nodejs.org/dist/v10.13.0/node-v10.13.0-x64.msi"
$PYWIN32_URL = "https://github.com/mhammond/pywin32/releases/download/b225/pywin32-225.win-amd64-py3.7.exe"
$NPM_URL = "https://nodejs.org/dist/v12.14.1/node-v12.14.1-x64.msi"
$MK32_DLL_URL = "https://github.com/guardicore/mimikatz/releases/download/1.1.0/mk32.zip"
$MK64_DLL_URL = "https://github.com/guardicore/mimikatz/releases/download/1.1.0/mk64.zip"
$UPX_URL = "https://github.com/upx/upx/releases/download/v3.94/upx394w.zip"
$UPX_URL = "https://github.com/upx/upx/releases/download/v3.96/upx-3.96-win64.zip"

View File

@ -1,10 +1,54 @@
#!/bin/bash
source config
exists() {
command -v "$1" >/dev/null 2>&1
}
is_root() {
return $(id -u)
}
has_sudo() {
# 0 true, 1 false
timeout 1 sudo id && return 0 || return 1
}
handle_error() {
echo "Fix the errors above and rerun the script"
exit 1
}
log_message() {
echo -e "\n\n"
echo -e "DEPLOYMENT SCRIPT: $1"
}
config_branch=${2:-"develop"}
config_url="https://raw.githubusercontent.com/guardicore/monkey/${config_branch}/deployment_scripts/config"
if (! exists curl) && (! exists wget); then
log_message 'Your system does not have curl or wget, exiting'
exit 1
fi
file=$(mktemp)
# shellcheck disable=SC2086
if exists wget; then
# shellcheck disable=SC2086
wget --output-document=$file "$config_url"
else
# shellcheck disable=SC2086
curl -s -o $file "$config_url"
fi
log_message "downloaded configuration"
# shellcheck source=deployment_scripts/config
# shellcheck disable=SC2086
source $file
log_message "loaded configuration"
# shellcheck disable=SC2086
# rm $file
# Setup monkey either in dir required or current dir
monkey_home=${1:-$(pwd)}
if [[ $monkey_home == $(pwd) ]]; then
@ -13,26 +57,19 @@ fi
# We can set main paths after we know the home dir
ISLAND_PATH="$monkey_home/monkey/monkey_island"
MONKEY_COMMON_PATH="$monkey_home/monkey/common/"
MONGO_PATH="$ISLAND_PATH/bin/mongodb"
ISLAND_BINARIES_PATH="$ISLAND_PATH/cc/binaries"
INFECTION_MONKEY_DIR="$monkey_home/monkey/infection_monkey"
MONKEY_BIN_DIR="$INFECTION_MONKEY_DIR/bin"
handle_error() {
echo "Fix the errors above and rerun the script"
if is_root; then
log_message "Please don't run this script as root"
exit 1
}
fi
log_message() {
echo -e "\n\n-------------------------------------------"
echo -e "DEPLOYMENT SCRIPT: $1"
echo -e "-------------------------------------------\n"
}
sudo -v
if [[ $? != 0 ]]; then
echo "You need root permissions for some of this script operations. Quiting."
HAS_SUDO=$(has_sudo)
if [[ ! $HAS_SUDO ]]; then
log_message "You need root permissions for some of this script operations. Quiting."
exit 1
fi
@ -41,15 +78,10 @@ if [[ ! -d ${monkey_home} ]]; then
fi
if ! exists git; then
echo "Please install git and re-run this script"
log_message "Please install git and re-run this script"
exit 1
fi
if ! exists wget; then
echo 'Your system does have wget, please install and re-run this script'
exit 1
fi
log_message "Cloning files from git"
branch=${2:-"develop"}
if [[ ! -d "$monkey_home/monkey" ]]; then # If not already cloned
@ -59,7 +91,7 @@ fi
# Create folders
log_message "Creating island dirs under $ISLAND_PATH"
mkdir -p "${MONGO_PATH}"
mkdir -p "${MONGO_PATH}" || handle_error
mkdir -p "${ISLAND_BINARIES_PATH}" || handle_error
# Detecting command that calls python 3.7
@ -78,87 +110,114 @@ if [[ ${python_cmd} == "" ]]; then
log_message "Python 3.7 command not found. Installing python 3.7."
sudo add-apt-repository ppa:deadsnakes/ppa
sudo apt-get update
sudo apt install python3.7
sudo apt install python3.7 python3.7-dev
log_message "Python 3.7 is now available with command 'python3.7'."
python_cmd="python3.7"
fi
log_message "Updating package list"
sudo apt-get update
log_message "Installing build-essential"
sudo apt install build-essential
log_message "Installing pip"
sudo apt install python3-pip
${python_cmd} -m pip install pip
log_message "Install python3.7-dev"
sudo apt-get install python3.7-dev
log_message "Installing or updating pip"
# shellcheck disable=SC2086
pip_url=https://bootstrap.pypa.io/get-pip.py
if exists wget; then
wget --output-document=get-pip.py $pip_url
else
curl $pip_url -o get-pip.py
fi
${python_cmd} get-pip.py
rm get-pip.py
log_message "Installing island requirements"
requirements="$ISLAND_PATH/requirements.txt"
${python_cmd} -m pip install --user --upgrade -r ${requirements} || handle_error
requirements_island="$ISLAND_PATH/requirements.txt"
${python_cmd} -m pip install -r "${requirements_island}" --user --upgrade || handle_error
log_message "Installing monkey requirements"
sudo apt-get install libffi-dev upx libssl-dev libc++1
cd "${monkey_home}"/monkey/infection_monkey || handle_error
${python_cmd} -m pip install -r requirements.txt --user --upgrade || handle_error
requirements_monkey="$INFECTION_MONKEY_DIR/requirements.txt"
${python_cmd} -m pip install -r "${requirements_monkey}" --user --upgrade || handle_error
agents=${3:-true}
# Download binaries
log_message "Downloading binaries"
wget -c -N -P ${ISLAND_BINARIES_PATH} ${LINUX_32_BINARY_URL}
wget -c -N -P ${ISLAND_BINARIES_PATH} ${LINUX_64_BINARY_URL}
wget -c -N -P ${ISLAND_BINARIES_PATH} ${WINDOWS_32_BINARY_URL}
wget -c -N -P ${ISLAND_BINARIES_PATH} ${WINDOWS_64_BINARY_URL}
if [ "$agents" = true ] ; then
log_message "Downloading binaries"
if exists wget; then
wget -c -N -P ${ISLAND_BINARIES_PATH} ${LINUX_32_BINARY_URL}
wget -c -N -P ${ISLAND_BINARIES_PATH} ${LINUX_64_BINARY_URL}
wget -c -N -P ${ISLAND_BINARIES_PATH} ${WINDOWS_32_BINARY_URL}
wget -c -N -P ${ISLAND_BINARIES_PATH} ${WINDOWS_64_BINARY_URL}
else
curl -o ${ISLAND_BINARIES_PATH}\monkey-linux-32 ${LINUX_32_BINARY_URL}
curl -o ${ISLAND_BINARIES_PATH}\monkey-linux-64 ${LINUX_64_BINARY_URL}
curl -o ${ISLAND_BINARIES_PATH}\monkey-windows-32.exe ${WINDOWS_32_BINARY_URL}
curl -o ${ISLAND_BINARIES_PATH}\monkey-windows-64.exe ${WINDOWS_64_BINARY_URL}
fi
fi
# Allow them to be executed
chmod a+x "$ISLAND_BINARIES_PATH/$LINUX_32_BINARY_NAME"
chmod a+x "$ISLAND_BINARIES_PATH/$LINUX_64_BINARY_NAME"
# Get machine type/kernel version
kernel=$(uname -m)
linux_dist=$(lsb_release -a 2>/dev/null)
# If a user haven't installed mongo manually check if we can install it with our script
log_message "Installing MongoDB"
"${ISLAND_PATH}"/linux/install_mongo.sh ${MONGO_PATH} || handle_error
if ! exists mongod; then
log_message "Installing MongoDB"
"${ISLAND_PATH}"/linux/install_mongo.sh ${MONGO_PATH} || handle_error
fi
log_message "Installing openssl"
sudo apt-get install openssl
# Generate SSL certificate
log_message "Generating certificate"
cd "${ISLAND_PATH}" || {
echo "cd failed"
exit 1
}
openssl genrsa -out cc/server.key 2048
openssl req -new -key cc/server.key -out cc/server.csr -subj "/C=GB/ST=London/L=London/O=Global Security/OU=Monkey Department/CN=monkey.com"
openssl x509 -req -days 366 -in cc/server.csr -signkey cc/server.key -out cc/server.crt
"${ISLAND_PATH}"/linux/create_certificate.sh ${ISLAND_PATH}/cc
# Update node
log_message "Installing nodejs"
cd "$ISLAND_PATH/cc/ui" || handle_error
sudo apt-get install curl
curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash -
sudo apt-get install -y nodejs
if ! exists npm; then
log_message "Installing nodejs"
node_src=https://deb.nodesource.com/setup_12.x
if exists curl; then
curl -sL $node_src | sudo -E bash -
else
wget -q -O - $node_src | sudo -E bash -
fi
sudo apt-get install -y nodejs
fi
pushd "$ISLAND_PATH/cc/ui" || handle_error
npm install sass-loader node-sass webpack --save-dev
npm update
log_message "Generating front end"
npm run dist
popd || handle_error
# Making dir for binaries
mkdir "${MONKEY_BIN_DIR}"
# Download sambacry binaries
log_message "Downloading sambacry binaries"
wget -c -N -P "${MONKEY_BIN_DIR}" "${SAMBACRY_64_BINARY_URL}"
wget -c -N -P "${MONKEY_BIN_DIR}" "${SAMBACRY_32_BINARY_URL}"
# shellcheck disable=SC2086
if exists wget; then
wget -c -N -P "${MONKEY_BIN_DIR}" ${SAMBACRY_64_BINARY_URL}
wget -c -N -P "${MONKEY_BIN_DIR}" ${SAMBACRY_32_BINARY_URL}
else
curl -o ${MONKEY_BIN_DIR}/sc_monkey_runner64.so ${SAMBACRY_64_BINARY_URL}
curl -o ${MONKEY_BIN_DIR}/sc_monkey_runner32.so ${SAMBACRY_32_BINARY_URL}
fi
# Download traceroute binaries
log_message "Downloading traceroute binaries"
wget -c -N -P "${MONKEY_BIN_DIR}" "${TRACEROUTE_64_BINARY_URL}"
wget -c -N -P "${MONKEY_BIN_DIR}" "${TRACEROUTE_32_BINARY_URL}"
# shellcheck disable=SC2086
if exists wget; then
wget -c -N -P "${MONKEY_BIN_DIR}" ${TRACEROUTE_64_BINARY_URL}
wget -c -N -P "${MONKEY_BIN_DIR}" ${TRACEROUTE_32_BINARY_URL}
else
curl -o ${MONKEY_BIN_DIR}/traceroute64 ${TRACEROUTE_64_BINARY_URL}
curl -o ${MONKEY_BIN_DIR}/traceroute32 ${TRACEROUTE_32_BINARY_URL}
fi
sudo chmod +x "${monkey_home}"/monkey/infection_monkey/build_linux.sh
sudo chmod +x "${INFECTION_MONKEY_DIR}/build_linux.sh"
log_message "Deployment script finished."
exit 0

View File

@ -1,17 +1,40 @@
function Deploy-Windows([String] $monkey_home = (Get-Item -Path ".\").FullName, [String] $branch = "develop"){
# Import the config variables
. ./config.ps1
"Config variables from config.ps1 imported"
# If we want monkey in current dir we need to create an empty folder for source files
if ( (Join-Path $monkey_home '') -eq (Join-Path (Get-Item -Path ".\").FullName '') ){
$monkey_home = Join-Path -Path $monkey_home -ChildPath $MONKEY_FOLDER_NAME
}
param(
[Parameter(Mandatory = $false, Position = 0)]
[String] $monkey_home = (Get-Item -Path ".\").FullName,
[Parameter(Mandatory = $false, Position = 1)]
[System.String]
$branch = "develop",
[Parameter(Mandatory = $false, Position = 2)]
[Bool]
$agents = $true
)
function Deploy-Windows([String] $monkey_home = (Get-Item -Path ".\").FullName, [String] $branch = "develop")
{
Write-Output "Downloading to $monkey_home"
Write-Output "Branch $branch"
# Set variables for script execution
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$webClient = New-Object System.Net.WebClient
# Import the config variables
$config_filename = New-TemporaryFile
$config_filename = "config.ps1"
$config_url = "https://raw.githubusercontent.com/guardicore/monkey/" + $branch + "/deployment_scripts/config.ps1"
$webClient.DownloadFile($config_url, $config_filename)
. ./config.ps1
"Config variables from config.ps1 imported"
Remove-Item $config_filename
# If we want monkey in current dir we need to create an empty folder for source files
if ((Join-Path $monkey_home '') -eq (Join-Path (Get-Item -Path ".\").FullName ''))
{
$monkey_home = Join-Path -Path $monkey_home -ChildPath $MONKEY_FOLDER_NAME
}
# We check if git is installed
try
{
@ -25,15 +48,22 @@ function Deploy-Windows([String] $monkey_home = (Get-Item -Path ".\").FullName,
}
# Download the monkey
$output = cmd.exe /c "git clone --single-branch -b $branch $MONKEY_GIT_URL $monkey_home 2>&1"
$command = "git clone --single-branch -b $branch $MONKEY_GIT_URL $monkey_home 2>&1"
Write-Output $command
$output = cmd.exe /c $command
$binDir = (Join-Path -Path $monkey_home -ChildPath $MONKEY_ISLAND_DIR | Join-Path -ChildPath "\bin")
if ( $output -like "*already exists and is not an empty directory.*"){
if ($output -like "*already exists and is not an empty directory.*")
{
"Assuming you already have the source directory. If not, make sure to set an empty directory as monkey's home directory."
} elseif ($output -like "fatal:*"){
}
elseif ($output -like "fatal:*")
{
"Error while cloning monkey from the repository:"
$output
return
} else {
}
else
{
"Monkey cloned from the repository"
# Create bin directory
New-Item -ItemType directory -path $binDir
@ -44,9 +74,12 @@ function Deploy-Windows([String] $monkey_home = (Get-Item -Path ".\").FullName,
try
{
$version = cmd.exe /c '"python" --version 2>&1'
if ( $version -like 'Python 3.*' ) {
if ($version -like 'Python 3.*')
{
"Python 3.* was found, installing dependencies"
} else {
}
else
{
throw System.Management.Automation.CommandNotFoundException
}
}
@ -56,11 +89,12 @@ function Deploy-Windows([String] $monkey_home = (Get-Item -Path ".\").FullName,
"Select 'add to PATH' when installing"
$webClient.DownloadFile($PYTHON_URL, $TEMP_PYTHON_INSTALLER)
Start-Process -Wait $TEMP_PYTHON_INSTALLER -ErrorAction Stop
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User")
$env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path", "User")
Remove-Item $TEMP_PYTHON_INSTALLER
# Check if installed correctly
$version = cmd.exe /c '"python" --version 2>&1'
if ( $version -like '* is not recognized*' ) {
if ($version -like '* is not recognized*')
{
"Python is not found in PATH. Add it to PATH and relaunch the script."
return
}
@ -69,7 +103,8 @@ function Deploy-Windows([String] $monkey_home = (Get-Item -Path ".\").FullName,
"Upgrading pip..."
$output = cmd.exe /c 'python -m pip install --user --upgrade pip 2>&1'
$output
if ( $output -like '*No module named pip*' ) {
if ($output -like '*No module named pip*')
{
"Make sure pip module is installed and re-run this script."
return
}
@ -83,20 +118,24 @@ function Deploy-Windows([String] $monkey_home = (Get-Item -Path ".\").FullName,
$user_python_dir = cmd.exe /c 'py -m site --user-site'
$user_python_dir = Join-Path (Split-Path $user_python_dir) -ChildPath "\Scripts"
if(!($ENV:PATH | Select-String -SimpleMatch $user_python_dir)){
if (!($ENV:Path | Select-String -SimpleMatch $user_python_dir))
{
"Adding python scripts path to user's env"
$env:Path += ";"+$user_python_dir
[Environment]::SetEnvironmentVariable("Path",$env:Path,"User")
$env:Path += ";" + $user_python_dir
[Environment]::SetEnvironmentVariable("Path", $env:Path, "User")
}
# Download mongodb
if(!(Test-Path -Path (Join-Path -Path $binDir -ChildPath "mongodb") )){
if (!(Test-Path -Path (Join-Path -Path $binDir -ChildPath "mongodb")))
{
"Downloading mongodb ..."
$webClient.DownloadFile($MONGODB_URL, $TEMP_MONGODB_ZIP)
"Unzipping mongodb"
Expand-Archive $TEMP_MONGODB_ZIP -DestinationPath $binDir
# Get unzipped folder's name
$mongodb_folder = Get-ChildItem -Path $binDir | Where-Object -FilterScript {($_.Name -like "mongodb*")} | Select-Object -ExpandProperty Name
$mongodb_folder = Get-ChildItem -Path $binDir | Where-Object -FilterScript {
($_.Name -like "mongodb*")
} | Select-Object -ExpandProperty Name
# Move all files from extracted folder to mongodb folder
New-Item -ItemType directory -Path (Join-Path -Path $binDir -ChildPath "mongodb")
New-Item -ItemType directory -Path (Join-Path -Path $monkey_home -ChildPath $MONKEY_ISLAND_DIR | Join-Path -ChildPath "db")
@ -127,23 +166,30 @@ function Deploy-Windows([String] $monkey_home = (Get-Item -Path ".\").FullName,
. .\windows\create_certificate.bat
Pop-Location
# Adding binaries
"Adding binaries"
$binaries = (Join-Path -Path $monkey_home -ChildPath $MONKEY_ISLAND_DIR | Join-Path -ChildPath "\cc\binaries")
New-Item -ItemType directory -path $binaries -ErrorAction SilentlyContinue
$webClient.DownloadFile($LINUX_32_BINARY_URL, (Join-Path -Path $binaries -ChildPath $LINUX_32_BINARY_PATH))
$webClient.DownloadFile($LINUX_64_BINARY_URL, (Join-Path -Path $binaries -ChildPath $LINUX_64_BINARY_PATH))
$webClient.DownloadFile($WINDOWS_32_BINARY_URL, (Join-Path -Path $binaries -ChildPath $WINDOWS_32_BINARY_PATH))
$webClient.DownloadFile($WINDOWS_64_BINARY_URL, (Join-Path -Path $binaries -ChildPath $WINDOWS_64_BINARY_PATH))
if ($agents)
{
# Adding binaries
"Adding binaries"
$binaries = (Join-Path -Path $monkey_home -ChildPath $MONKEY_ISLAND_DIR | Join-Path -ChildPath "\cc\binaries")
New-Item -ItemType directory -path $binaries -ErrorAction SilentlyContinue
$webClient.DownloadFile($LINUX_32_BINARY_URL, (Join-Path -Path $binaries -ChildPath $LINUX_32_BINARY_PATH))
$webClient.DownloadFile($LINUX_64_BINARY_URL, (Join-Path -Path $binaries -ChildPath $LINUX_64_BINARY_PATH))
$webClient.DownloadFile($WINDOWS_32_BINARY_URL, (Join-Path -Path $binaries -ChildPath $WINDOWS_32_BINARY_PATH))
$webClient.DownloadFile($WINDOWS_64_BINARY_URL, (Join-Path -Path $binaries -ChildPath $WINDOWS_64_BINARY_PATH))
}
# Check if NPM installed
"Installing npm"
try
{
$version = cmd.exe /c '"npm" --version 2>&1'
if ( $version -like "*is not recognized*"){
if ($version -like "*is not recognized*")
{
throw System.Management.Automation.CommandNotFoundException
} else {
}
else
{
"Npm already installed"
}
}
@ -152,7 +198,7 @@ function Deploy-Windows([String] $monkey_home = (Get-Item -Path ".\").FullName,
"Downloading npm ..."
$webClient.DownloadFile($NPM_URL, $TEMP_NPM_INSTALLER)
Start-Process -Wait $TEMP_NPM_INSTALLER
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine")
$env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine")
Remove-Item $TEMP_NPM_INSTALLER
}
@ -162,18 +208,13 @@ function Deploy-Windows([String] $monkey_home = (Get-Item -Path ".\").FullName,
& npm run dist
Pop-Location
# Install pywin32
"Downloading pywin32"
$webClient.DownloadFile($PYWIN32_URL, $TEMP_PYWIN32_INSTALLER)
Start-Process -Wait $TEMP_PYWIN32_INSTALLER -ErrorAction Stop
Remove-Item $TEMP_PYWIN32_INSTALLER
# Create infection_monkey/bin directory if not already present
$binDir = (Join-Path -Path $monkey_home -ChildPath $MONKEY_DIR | Join-Path -ChildPath "\bin")
New-Item -ItemType directory -path $binaries -ErrorAction SilentlyContinue
# Download upx
if(!(Test-Path -Path (Join-Path -Path $binDir -ChildPath "upx.exe") )){
if (!(Test-Path -Path (Join-Path -Path $binDir -ChildPath "upx.exe")))
{
"Downloading upx ..."
$webClient.DownloadFile($UPX_URL, $TEMP_UPX_ZIP)
"Unzipping upx"
@ -187,12 +228,14 @@ function Deploy-Windows([String] $monkey_home = (Get-Item -Path ".\").FullName,
# Download mimikatz binaries
$mk32_path = Join-Path -Path $binDir -ChildPath $MK32_DLL
if(!(Test-Path -Path $mk32_path )){
if (!(Test-Path -Path $mk32_path))
{
"Downloading mimikatz 32 binary"
$webClient.DownloadFile($MK32_DLL_URL, $mk32_path)
}
$mk64_path = Join-Path -Path $binDir -ChildPath $MK64_DLL
if(!(Test-Path -Path $mk64_path )){
if (!(Test-Path -Path $mk64_path))
{
"Downloading mimikatz 64 binary"
$webClient.DownloadFile($MK64_DLL_URL, $mk64_path)
}
@ -200,12 +243,14 @@ function Deploy-Windows([String] $monkey_home = (Get-Item -Path ".\").FullName,
# Download sambacry binaries
$samba_path = Join-Path -Path $monkey_home -ChildPath $SAMBA_BINARIES_DIR
$samba32_path = Join-Path -Path $samba_path -ChildPath $SAMBA_32_BINARY_NAME
if(!(Test-Path -Path $samba32_path )){
if (!(Test-Path -Path $samba32_path))
{
"Downloading sambacry 32 binary"
$webClient.DownloadFile($SAMBA_32_BINARY_URL, $samba32_path)
}
$samba64_path = Join-Path -Path $samba_path -ChildPath $SAMBA_64_BINARY_NAME
if(!(Test-Path -Path $samba64_path )){
if (!(Test-Path -Path $samba64_path))
{
"Downloading sambacry 64 binary"
$webClient.DownloadFile($SAMBA_64_BINARY_URL, $samba64_path)
}
@ -213,3 +258,4 @@ function Deploy-Windows([String] $monkey_home = (Get-Item -Path ".\").FullName,
"Script finished"
}
Deploy-Windows -monkey_home $monkey_home -branch $branch

View File

@ -1,8 +0,0 @@
SET command=. .\deploy_windows.ps1; Deploy-Windows
if NOT "%~1" == "" (
SET "command=%command% -monkey_home %~1"
)
if NOT "%~2" == "" (
SET "command=%command% -branch %~2"
)
powershell -ExecutionPolicy ByPass -Command %command%

20
monkey/codecov.yml Normal file
View File

@ -0,0 +1,20 @@
codecov:
require_ci_to_pass: yes
coverage:
precision: 2
round: down
range: "50...90"
parsers:
gcov:
branch_detection:
conditional: yes
loop: yes
method: no
macro: no
comment:
layout: "reach,diff,flags,tree"
behavior: default
require_changes: no

1
monkey/common/BUILD Normal file
View File

@ -0,0 +1 @@
dev

View File

@ -0,0 +1,12 @@
from typing import List
from common.cloud.aws.aws_instance import AwsInstance
from common.cloud.azure.azure_instance import AzureInstance
from common.cloud.gcp.gcp_instance import GcpInstance
from common.cloud.instance import CloudInstance
all_cloud_instances = [AwsInstance(), AzureInstance(), GcpInstance()]
def get_all_cloud_instances() -> List[CloudInstance]:
return all_cloud_instances

View File

View File

@ -6,6 +6,9 @@ import logging
__author__ = 'itay.mizeretz'
from common.cloud.environment_names import Environment
from common.cloud.instance import CloudInstance
AWS_INSTANCE_METADATA_LOCAL_IP_ADDRESS = "169.254.169.254"
AWS_LATEST_METADATA_URI_PREFIX = 'http://{0}/latest/'.format(AWS_INSTANCE_METADATA_LOCAL_IP_ADDRESS)
ACCOUNT_ID_KEY = "accountId"
@ -13,10 +16,15 @@ ACCOUNT_ID_KEY = "accountId"
logger = logging.getLogger(__name__)
class AwsInstance(object):
class AwsInstance(CloudInstance):
"""
Class which gives useful information about the current instance you're on.
"""
def is_instance(self):
return self.instance_id is not None
def get_cloud_provider_name(self) -> Environment:
return Environment.AWS
def __init__(self):
self.instance_id = None
@ -57,9 +65,6 @@ class AwsInstance(object):
def get_region(self):
return self.region
def is_aws_instance(self):
return self.instance_id is not None
@staticmethod
def _extract_account_id(instance_identity_document_response):
"""

View File

@ -4,7 +4,7 @@ import boto3
import botocore
from botocore.exceptions import ClientError
from common.cloud.aws_instance import AwsInstance
from common.cloud.aws.aws_instance import AwsInstance
__author__ = ['itay.mizeretz', 'shay.nehmad']

View File

View File

@ -0,0 +1,55 @@
import logging
import requests
from common.cloud.environment_names import Environment
from common.cloud.instance import CloudInstance
LATEST_AZURE_METADATA_API_VERSION = "2019-04-30"
AZURE_METADATA_SERVICE_URL = "http://169.254.169.254/metadata/instance?api-version=%s" % LATEST_AZURE_METADATA_API_VERSION
logger = logging.getLogger(__name__)
class AzureInstance(CloudInstance):
"""
Access to useful information about the current machine if it's an Azure VM.
Based on Azure metadata service: https://docs.microsoft.com/en-us/azure/virtual-machines/windows/instance-metadata-service
"""
def is_instance(self):
return self.on_azure
def get_cloud_provider_name(self) -> Environment:
return Environment.AZURE
def __init__(self):
"""
Determines if on Azure and if so, gets some basic metadata on this instance.
"""
self.instance_name = None
self.instance_id = None
self.location = None
self.on_azure = False
try:
response = requests.get(AZURE_METADATA_SERVICE_URL, headers={"Metadata": "true"})
self.on_azure = True
# If not on cloud, the metadata URL is non-routable and the connection will fail.
# If on AWS, should get 404 since the metadata service URL is different, so bool(response) will be false.
if response:
logger.debug("On Azure. Trying to parse metadata.")
self.try_parse_response(response)
else:
logger.warning("On Azure, but metadata response not ok: {}".format(response.status_code))
except requests.RequestException:
logger.debug("Failed to get response from Azure metadata service: This instance is not on Azure.")
self.on_azure = False
def try_parse_response(self, response):
try:
response_data = response.json()
self.instance_name = response_data["compute"]["name"]
self.instance_id = response_data["compute"]["vmId"]
self.location = response_data["compute"]["location"]
except KeyError:
logger.exception("Error while parsing response from Azure metadata service.")

View File

@ -0,0 +1,15 @@
from enum import Enum
class Environment(Enum):
UNKNOWN = "Unknown"
ON_PREMISE = "On Premise"
AZURE = "Azure"
AWS = "AWS"
GCP = "GCP"
ALIBABA = "Alibaba Cloud"
IBM = "IBM Cloud"
DigitalOcean = "Digital Ocean"
ALL_ENVIRONMENTS_NAMES = [x.value for x in Environment]

View File

View File

@ -0,0 +1,43 @@
import logging
import requests
from common.cloud.environment_names import Environment
from common.cloud.instance import CloudInstance
logger = logging.getLogger(__name__)
GCP_METADATA_SERVICE_URL = "http://metadata.google.internal/"
class GcpInstance(CloudInstance):
"""
Used to determine if on GCP. See https://cloud.google.com/compute/docs/storing-retrieving-metadata#runninggce
"""
def is_instance(self):
return self.on_gcp
def get_cloud_provider_name(self) -> Environment:
return Environment.GCP
def __init__(self):
self.on_gcp = False
try:
# If not on GCP, this domain shouldn't resolve.
response = requests.get(GCP_METADATA_SERVICE_URL)
if response:
logger.debug("Got ok metadata response: on GCP")
self.on_gcp = True
if "Metadata-Flavor" not in response.headers:
logger.warning("Got unexpected GCP Metadata format")
else:
if not response.headers["Metadata-Flavor"] == "Google":
logger.warning("Got unexpected Metadata flavor: {}".format(response.headers["Metadata-Flavor"]))
else:
logger.warning("On GCP, but metadata response not ok: {}".format(response.status_code))
except requests.RequestException:
logger.debug("Failed to get response from GCP metadata service: This instance is not on GCP")
self.on_gcp = False

View File

@ -0,0 +1,14 @@
from common.cloud.environment_names import Environment
class CloudInstance(object):
"""
This is an abstract class which represents a cloud instance.
The current machine can be a cloud instance (for example EC2 instance or Azure VM).
"""
def is_instance(self) -> bool:
raise NotImplementedError()
def get_cloud_provider_name(self) -> Environment:
raise NotImplementedError()

View File

@ -1,6 +1,6 @@
import logging
from common.cloud.aws_service import AwsService
from common.cloud.aws.aws_service import AwsService
from common.cmd.aws.aws_cmd_result import AwsCmdResult
from common.cmd.cmd_runner import CmdRunner
from common.cmd.cmd_status import CmdStatus

View File

@ -0,0 +1,4 @@
AWS_COLLECTOR = "AwsCollector"
HOSTNAME_COLLECTOR = "HostnameCollector"
ENVIRONMENT_COLLECTOR = "EnvironmentCollector"
PROCESS_LIST_COLLECTOR = "ProcessListCollector"

View File

@ -46,6 +46,7 @@ class NetworkRange(object, metaclass=ABCMeta):
def get_range_obj(address_str):
if not address_str: # Empty string
return None
address_str = address_str.strip()
if NetworkRange.check_if_range(address_str):
return IpRange(ip_range=address_str)
if -1 != address_str.find('/'):

25
monkey/common/version.py Normal file
View File

@ -0,0 +1,25 @@
# To get the version from shell, run `python ./version.py` (see `python ./version.py -h` for details).
import argparse
from pathlib import Path
MAJOR = "1"
MINOR = "8"
PATCH = "0"
build_file_path = Path(__file__).parent.joinpath("BUILD")
with open(build_file_path, "r") as build_file:
BUILD = build_file.read()
def get_version(build=BUILD):
return f"{MAJOR}.{MINOR}.{PATCH}+{build}"
def print_version():
parser = argparse.ArgumentParser()
parser.add_argument("-b", "--build", default=BUILD, help="Choose the build string for this version.", type=str)
args = parser.parse_args()
print(get_version(args.build))
if __name__ == '__main__':
print_version()

View File

@ -1,2 +1,17 @@
#!/bin/bash
# Allow custom build ID
# If the first argument is not empty...
if [[ -n "$1" ]]
then
# Validate argument is a valid build string
if [[ "$1" =~ ^[\da-zA-Z]*$ ]]
then
# And put it in the BUILD file
echo "$1" > ../common/BUILD
else
echo "Build ID $1 invalid!"
fi
fi
pyinstaller -F --log-level=DEBUG --clean monkey.spec

View File

@ -1 +1,12 @@
REM Check if build ID was passed to the build script.
if "%1"=="" GOTO START_BUILD
REM Validate build ID
echo %1|findstr /r "^[0-9a-zA-Z]*$"
if %errorlevel% neq 0 (exit /b %errorlevel%)
REM replace build ID
echo %1> ../common/BUILD
:START_BUILD
pyinstaller -F --log-level=DEBUG --clean --upx-dir=.\bin monkey.spec

View File

@ -1,6 +1,6 @@
import hashlib
import os
import json
import os
import sys
import uuid
from abc import ABCMeta
@ -125,6 +125,7 @@ class Configuration(object):
finger_classes = []
exploiter_classes = []
system_info_collectors_classes = []
# how many victims to look for in a single scan iteration
victims_max_find = 100

View File

@ -12,6 +12,7 @@ from infection_monkey.config import WormConfiguration, EXTERNAL_CONFIG_FILE
from infection_monkey.dropper import MonkeyDrops
from infection_monkey.model import MONKEY_ARG, DROPPER_ARG
from infection_monkey.monkey import InfectionMonkey
from common.version import get_version
# noinspection PyUnresolvedReferences
import infection_monkey.post_breach # dummy import for pyinstaller
@ -117,6 +118,8 @@ def main():
LOG.info(">>>>>>>>>> Initializing monkey (%s): PID %s <<<<<<<<<<",
monkey_cls.__name__, os.getpid())
LOG.info(f"version: {get_version()}")
monkey = monkey_cls(monkey_args)
monkey.initialize()

View File

@ -30,13 +30,20 @@ from infection_monkey.network.tools import get_interface_to_target
from infection_monkey.exploit.tools.exceptions import ExploitingVulnerableMachineError, FailedExploitationError
from infection_monkey.telemetry.attack.t1106_telem import T1106Telem
from common.utils.attack_utils import ScanStatus, UsageEnum
from common.version import get_version
from infection_monkey.exploit.HostExploiter import HostExploiter
MAX_DEPTH_REACHED_MESSAGE = "Reached max depth, shutting down"
__author__ = 'itamar'
LOG = logging.getLogger(__name__)
class PlannedShutdownException(Exception):
pass
class InfectionMonkey(object):
def __init__(self, args):
self._keep_running = False
@ -87,143 +94,158 @@ class InfectionMonkey(object):
LOG.debug("Default server: %s is already in command servers list" % self._default_server)
def start(self):
LOG.info("Monkey is running...")
try:
LOG.info("Monkey is starting...")
# Sets island's IP and port for monkey to communicate to
if not self.set_default_server():
return
self.set_default_port()
LOG.debug("Starting the setup phase.")
# Sets island's IP and port for monkey to communicate to
self.set_default_server()
self.set_default_port()
# Create a dir for monkey files if there isn't one
create_monkey_dir()
# Create a dir for monkey files if there isn't one
create_monkey_dir()
if WindowsUpgrader.should_upgrade():
self._upgrading_to_64 = True
self._singleton.unlock()
LOG.info("32bit monkey running on 64bit Windows. Upgrading.")
WindowsUpgrader.upgrade(self._opts)
return
self.upgrade_to_64_if_needed()
ControlClient.wakeup(parent=self._parent)
ControlClient.load_control_config()
ControlClient.wakeup(parent=self._parent)
ControlClient.load_control_config()
if is_windows_os():
T1106Telem(ScanStatus.USED, UsageEnum.SINGLETON_WINAPI).send()
if is_windows_os():
T1106Telem(ScanStatus.USED, UsageEnum.SINGLETON_WINAPI).send()
if not WormConfiguration.alive:
LOG.info("Marked not alive from configuration")
return
self.shutdown_by_not_alive_config()
if firewall.is_enabled():
firewall.add_firewall_rule()
if firewall.is_enabled():
firewall.add_firewall_rule()
monkey_tunnel = ControlClient.create_control_tunnel()
if monkey_tunnel:
monkey_tunnel.start()
monkey_tunnel = ControlClient.create_control_tunnel()
if monkey_tunnel:
monkey_tunnel.start()
StateTelem(is_done=False).send()
TunnelTelem().send()
StateTelem(is_done=False, version=get_version()).send()
TunnelTelem().send()
LOG.debug("Starting the post-breach phase.")
self.collect_system_info_if_configured()
PostBreach().execute_all_configured()
LOG.debug("Starting the propagation phase.")
self.shutdown_by_max_depth_reached()
for iteration_index in range(WormConfiguration.max_iterations):
ControlClient.keepalive()
ControlClient.load_control_config()
self._network.initialize()
self._fingerprint = HostFinger.get_instances()
self._exploiters = HostExploiter.get_classes()
if not self._keep_running or not WormConfiguration.alive:
break
machines = self._network.get_victim_machines(max_find=WormConfiguration.victims_max_find,
stop_callback=ControlClient.check_for_stop)
is_empty = True
for machine in machines:
if ControlClient.check_for_stop():
break
is_empty = False
for finger in self._fingerprint:
LOG.info("Trying to get OS fingerprint from %r with module %s",
machine, finger.__class__.__name__)
finger.get_host_fingerprint(machine)
ScanTelem(machine).send()
# skip machines that we've already exploited
if machine in self._exploited_machines:
LOG.debug("Skipping %r - already exploited",
machine)
continue
elif machine in self._fail_exploitation_machines:
if WormConfiguration.retry_failed_explotation:
LOG.debug("%r - exploitation failed before, trying again", machine)
else:
LOG.debug("Skipping %r - exploitation failed before", machine)
continue
if monkey_tunnel:
monkey_tunnel.set_tunnel_for_host(machine)
if self._default_server:
if self._network.on_island(self._default_server):
machine.set_default_server(get_interface_to_target(machine.ip_addr) +
(':' + self._default_server_port if self._default_server_port else ''))
else:
machine.set_default_server(self._default_server)
LOG.debug("Default server for machine: %r set to %s" % (machine, machine.default_server))
# Order exploits according to their type
if WormConfiguration.should_exploit:
self._exploiters = sorted(self._exploiters, key=lambda exploiter_: exploiter_.EXPLOIT_TYPE.value)
host_exploited = False
for exploiter in [exploiter(machine) for exploiter in self._exploiters]:
if self.try_exploiting(machine, exploiter):
host_exploited = True
VictimHostTelem('T1210', ScanStatus.USED, machine=machine).send()
break
if not host_exploited:
self._fail_exploitation_machines.add(machine)
VictimHostTelem('T1210', ScanStatus.SCANNED, machine=machine).send()
if not self._keep_running:
break
if (not is_empty) and (WormConfiguration.max_iterations > iteration_index + 1):
time_to_sleep = WormConfiguration.timeout_between_iterations
LOG.info("Sleeping %d seconds before next life cycle iteration", time_to_sleep)
time.sleep(time_to_sleep)
if self._keep_running and WormConfiguration.alive:
LOG.info("Reached max iterations (%d)", WormConfiguration.max_iterations)
elif not WormConfiguration.alive:
LOG.info("Marked not alive from configuration")
# if host was exploited, before continue to closing the tunnel ensure the exploited host had its chance to
# connect to the tunnel
if len(self._exploited_machines) > 0:
time_to_sleep = WormConfiguration.keep_tunnel_open_time
LOG.info("Sleeping %d seconds for exploited machines to connect to tunnel", time_to_sleep)
time.sleep(time_to_sleep)
if monkey_tunnel:
monkey_tunnel.stop()
monkey_tunnel.join()
except PlannedShutdownException:
LOG.info("A planned shutdown of the Monkey occurred. Logging the reason and finishing execution.")
LOG.exception("Planned shutdown, reason:")
def shutdown_by_max_depth_reached(self):
if 0 == WormConfiguration.depth:
TraceTelem(MAX_DEPTH_REACHED_MESSAGE).send()
raise PlannedShutdownException(MAX_DEPTH_REACHED_MESSAGE)
else:
LOG.debug("Running with depth: %d" % WormConfiguration.depth)
def collect_system_info_if_configured(self):
if WormConfiguration.collect_system_info:
LOG.debug("Calling system info collection")
system_info_collector = SystemInfoCollector()
system_info = system_info_collector.get_info()
SystemInfoTelem(system_info).send()
# Executes post breach actions
PostBreach().execute()
def shutdown_by_not_alive_config(self):
if not WormConfiguration.alive:
raise PlannedShutdownException("Marked 'not alive' from configuration.")
if 0 == WormConfiguration.depth:
TraceTelem("Reached max depth, shutting down").send()
return
else:
LOG.debug("Running with depth: %d" % WormConfiguration.depth)
for iteration_index in range(WormConfiguration.max_iterations):
ControlClient.keepalive()
ControlClient.load_control_config()
self._network.initialize()
self._fingerprint = HostFinger.get_instances()
self._exploiters = HostExploiter.get_classes()
if not self._keep_running or not WormConfiguration.alive:
break
machines = self._network.get_victim_machines(max_find=WormConfiguration.victims_max_find,
stop_callback=ControlClient.check_for_stop)
is_empty = True
for machine in machines:
if ControlClient.check_for_stop():
break
is_empty = False
for finger in self._fingerprint:
LOG.info("Trying to get OS fingerprint from %r with module %s",
machine, finger.__class__.__name__)
finger.get_host_fingerprint(machine)
ScanTelem(machine).send()
# skip machines that we've already exploited
if machine in self._exploited_machines:
LOG.debug("Skipping %r - already exploited",
machine)
continue
elif machine in self._fail_exploitation_machines:
if WormConfiguration.retry_failed_explotation:
LOG.debug("%r - exploitation failed before, trying again", machine)
else:
LOG.debug("Skipping %r - exploitation failed before", machine)
continue
if monkey_tunnel:
monkey_tunnel.set_tunnel_for_host(machine)
if self._default_server:
if self._network.on_island(self._default_server):
machine.set_default_server(get_interface_to_target(machine.ip_addr) +
(':' + self._default_server_port if self._default_server_port else ''))
else:
machine.set_default_server(self._default_server)
LOG.debug("Default server for machine: %r set to %s" % (machine, machine.default_server))
# Order exploits according to their type
if WormConfiguration.should_exploit:
self._exploiters = sorted(self._exploiters, key=lambda exploiter_: exploiter_.EXPLOIT_TYPE.value)
host_exploited = False
for exploiter in [exploiter(machine) for exploiter in self._exploiters]:
if self.try_exploiting(machine, exploiter):
host_exploited = True
VictimHostTelem('T1210', ScanStatus.USED, machine=machine).send()
break
if not host_exploited:
self._fail_exploitation_machines.add(machine)
VictimHostTelem('T1210', ScanStatus.SCANNED, machine=machine).send()
if not self._keep_running:
break
if (not is_empty) and (WormConfiguration.max_iterations > iteration_index + 1):
time_to_sleep = WormConfiguration.timeout_between_iterations
LOG.info("Sleeping %d seconds before next life cycle iteration", time_to_sleep)
time.sleep(time_to_sleep)
if self._keep_running and WormConfiguration.alive:
LOG.info("Reached max iterations (%d)", WormConfiguration.max_iterations)
elif not WormConfiguration.alive:
LOG.info("Marked not alive from configuration")
# if host was exploited, before continue to closing the tunnel ensure the exploited host had its chance to
# connect to the tunnel
if len(self._exploited_machines) > 0:
time_to_sleep = WormConfiguration.keep_tunnel_open_time
LOG.info("Sleeping %d seconds for exploited machines to connect to tunnel", time_to_sleep)
time.sleep(time_to_sleep)
if monkey_tunnel:
monkey_tunnel.stop()
monkey_tunnel.join()
def upgrade_to_64_if_needed(self):
if WindowsUpgrader.should_upgrade():
self._upgrading_to_64 = True
self._singleton.unlock()
LOG.info("32bit monkey running on 64bit Windows. Upgrading.")
WindowsUpgrader.upgrade(self._opts)
raise PlannedShutdownException("Finished upgrading from 32bit to 64bit.")
def cleanup(self):
LOG.info("Monkey cleanup started")
@ -233,7 +255,7 @@ class InfectionMonkey(object):
InfectionMonkey.close_tunnel()
firewall.close()
else:
StateTelem(is_done=True).send() # Signal the server (before closing the tunnel)
StateTelem(is_done=True, version=get_version()).send() # Signal the server (before closing the tunnel)
InfectionMonkey.close_tunnel()
firewall.close()
if WormConfiguration.send_log_to_server:
@ -346,9 +368,11 @@ class InfectionMonkey(object):
self._default_server_port = ''
def set_default_server(self):
"""
Sets the default server for the Monkey to communicate back to.
:raises PlannedShutdownException if couldn't find the server.
"""
if not ControlClient.find_server(default_tunnel=self._default_tunnel):
LOG.info("Monkey couldn't find server. Going down.")
return False
raise PlannedShutdownException("Monkey couldn't find server with {} default tunnel.".format(self._default_tunnel))
self._default_server = WormConfiguration.current_server
LOG.debug("default server set to: %s" % self._default_server)
return True

View File

@ -1,5 +1,6 @@
# -*- mode: python -*-
import os
import sys
import platform
@ -18,7 +19,9 @@ def main():
hookspath=['./pyinstaller_hooks'],
runtime_hooks=None,
binaries=None,
datas=None,
datas=[
("../common/BUILD", "/common")
],
excludes=None,
win_no_prefer_redirects=None,
win_private_assemblies=None,
@ -48,7 +51,7 @@ def is_windows():
def is_32_bit():
return platform.architecture()[0] == "32bit"
return sys.maxsize <= 2**32
def get_bin_folder():
@ -93,7 +96,18 @@ def get_traceroute_binaries():
def get_monkey_filename():
return 'monkey.exe' if is_windows() else 'monkey'
name = 'monkey-'
if is_windows():
name = name+"windows-"
else:
name = name+"linux-"
if is_32_bit():
name = name+"32"
else:
name = name+"64"
if is_windows():
name = name+".exe"
return name
def get_exe_strip():

View File

@ -20,7 +20,7 @@ class PostBreach(object):
self.os_is_linux = not is_windows_os()
self.pba_list = self.config_to_pba_list()
def execute(self):
def execute_all_configured(self):
"""
Executes all post breach actions.
"""

View File

@ -0,0 +1,6 @@
from PyInstaller.utils.hooks import collect_submodules, collect_data_files
# Import all actions as modules
hiddenimports = collect_submodules('infection_monkey.system_info.collectors')
# Add action files that we enumerate
datas = (collect_data_files('infection_monkey.system_info.collectors', include_py_files=True))

View File

@ -34,7 +34,7 @@ The monkey is composed of three separate parts.
6. To build the final exe:
- `cd monkey\infection_monkey`
- `build_windows.bat`
- `output is placed under dist\monkey.exe`
- output is placed under `dist\monkey32.exe` or `dist\monkey64.exe` depending on your version of Python
## Linux
@ -55,18 +55,18 @@ Tested on Ubuntu 16.04.
3. Build Sambacry binaries
- Build/Download according to sections at the end of this readme.
- Place the binaries under [code location]\infection_monkey\bin, under the names 'sc_monkey_runner32.so', 'sc_monkey_runner64.so'
- Place the binaries under [code location]/infection_monkey/bin, under the names 'sc_monkey_runner32.so', 'sc_monkey_runner64.so'
4. Build Traceroute binaries
- Build/Download according to sections at the end of this readme.
- Place the binaries under [code location]\infection_monkey\bin, under the names 'traceroute32', 'traceroute64'
- Place the binaries under [code location]/infection_monkey/bin, under the names 'traceroute32', 'traceroute64'
5. To build, run in terminal:
- `cd [code location]/infection_monkey`
- `chmod +x build_linux.sh`
- `./build_linux.sh`
output is placed under dist/monkey
output is placed under `dist/monkey32` or `dist/monkey64` depending on your version of python
### Sambacry

View File

@ -6,9 +6,9 @@ import psutil
from enum import IntEnum
from infection_monkey.network.info import get_host_subnets
from infection_monkey.system_info.aws_collector import AwsCollector
from infection_monkey.system_info.azure_cred_collector import AzureCollector
from infection_monkey.system_info.netstat_collector import NetstatCollector
from infection_monkey.system_info.system_info_collectors_handler import SystemInfoCollectorsHandler
LOG = logging.getLogger(__name__)
@ -61,50 +61,12 @@ class InfoCollector(object):
self.info = {}
def get_info(self):
self.get_hostname()
self.get_process_list()
# Collect all hardcoded
self.get_network_info()
self.get_azure_info()
self.get_aws_info()
def get_hostname(self):
"""
Adds the fully qualified computer hostname to the system information.
:return: None. Updates class information
"""
LOG.debug("Reading hostname")
self.info['hostname'] = socket.getfqdn()
def get_process_list(self):
"""
Adds process information from the host to the system information.
Currently lists process name, ID, parent ID, command line
and the full image path of each process.
:return: None. Updates class information
"""
LOG.debug("Reading process list")
processes = {}
for process in psutil.process_iter():
try:
processes[process.pid] = {"name": process.name(),
"pid": process.pid,
"ppid": process.ppid(),
"cmdline": " ".join(process.cmdline()),
"full_image_path": process.exe(),
}
except (psutil.AccessDenied, WindowsError):
# we may be running as non root
# and some processes are impossible to acquire in Windows/Linux
# in this case we'll just add what we can
processes[process.pid] = {"name": "null",
"pid": process.pid,
"ppid": process.ppid(),
"cmdline": "ACCESS DENIED",
"full_image_path": "null",
}
continue
self.info['process_list'] = processes
# Collect all plugins
SystemInfoCollectorsHandler().execute_all_configured()
def get_network_info(self):
"""
@ -150,11 +112,3 @@ class InfoCollector(object):
except Exception:
# If we failed to collect azure info, no reason to fail all the collection. Log and continue.
LOG.error("Failed collecting Azure info.", exc_info=True)
def get_aws_info(self):
# noinspection PyBroadException
try:
self.info['aws'] = AwsCollector().get_aws_info()
except Exception:
# If we failed to collect aws info, no reason to fail all the collection. Log and continue.
LOG.error("Failed collecting AWS info.", exc_info=True)

View File

@ -1,29 +0,0 @@
import logging
from common.cloud.aws_instance import AwsInstance
__author__ = 'itay.mizeretz'
LOG = logging.getLogger(__name__)
class AwsCollector(object):
"""
Extract info from AWS machines
"""
@staticmethod
def get_aws_info():
LOG.info("Collecting AWS info")
aws = AwsInstance()
info = {}
if aws.is_aws_instance():
LOG.info("Machine is an AWS instance")
info = \
{
'instance_id': aws.get_instance_id()
}
else:
LOG.info("Machine is NOT an AWS instance")
return info

View File

@ -0,0 +1,3 @@
"""
This package holds all the dynamic (plugin) collectors
"""

View File

@ -0,0 +1,31 @@
import logging
from common.cloud.aws.aws_instance import AwsInstance
from common.data.system_info_collectors_names import AWS_COLLECTOR
from infection_monkey.system_info.system_info_collector import SystemInfoCollector
logger = logging.getLogger(__name__)
class AwsCollector(SystemInfoCollector):
"""
Extract info from AWS machines.
"""
def __init__(self):
super().__init__(name=AWS_COLLECTOR)
def collect(self) -> dict:
logger.info("Collecting AWS info")
aws = AwsInstance()
info = {}
if aws.is_instance():
logger.info("Machine is an AWS instance")
info = \
{
'instance_id': aws.get_instance_id()
}
else:
logger.info("Machine is NOT an AWS instance")
return info

View File

@ -0,0 +1,24 @@
from common.cloud.all_instances import get_all_cloud_instances
from common.cloud.environment_names import Environment
from common.data.system_info_collectors_names import ENVIRONMENT_COLLECTOR
from infection_monkey.system_info.system_info_collector import SystemInfoCollector
def get_monkey_environment() -> str:
"""
Get the Monkey's running environment.
:return: One of the cloud providers if on cloud; otherwise, assumes "on premise".
"""
for instance in get_all_cloud_instances():
if instance.is_instance():
return instance.get_cloud_provider_name().value
return Environment.ON_PREMISE.value
class EnvironmentCollector(SystemInfoCollector):
def __init__(self):
super().__init__(name=ENVIRONMENT_COLLECTOR)
def collect(self) -> dict:
return {"environment": get_monkey_environment()}

View File

@ -0,0 +1,16 @@
import logging
import socket
from common.data.system_info_collectors_names import HOSTNAME_COLLECTOR
from infection_monkey.system_info.system_info_collector import SystemInfoCollector
logger = logging.getLogger(__name__)
class HostnameCollector(SystemInfoCollector):
def __init__(self):
super().__init__(name=HOSTNAME_COLLECTOR)
def collect(self) -> dict:
return {"hostname": socket.getfqdn()}

View File

@ -0,0 +1,50 @@
import logging
import psutil
from common.data.system_info_collectors_names import PROCESS_LIST_COLLECTOR
from infection_monkey.system_info.system_info_collector import SystemInfoCollector
logger = logging.getLogger(__name__)
# Linux doesn't have WindowsError
try:
WindowsError
except NameError:
# noinspection PyShadowingBuiltins
WindowsError = psutil.AccessDenied
class ProcessListCollector(SystemInfoCollector):
def __init__(self):
super().__init__(name=PROCESS_LIST_COLLECTOR)
def collect(self) -> dict:
"""
Adds process information from the host to the system information.
Currently lists process name, ID, parent ID, command line
and the full image path of each process.
"""
logger.debug("Reading process list")
processes = {}
for process in psutil.process_iter():
try:
processes[process.pid] = {
"name": process.name(),
"pid": process.pid,
"ppid": process.ppid(),
"cmdline": " ".join(process.cmdline()),
"full_image_path": process.exe(),
}
except (psutil.AccessDenied, WindowsError):
# we may be running as non root and some processes are impossible to acquire in Windows/Linux.
# In this case we'll just add what we know.
processes[process.pid] = {
"name": "null",
"pid": process.pid,
"ppid": process.ppid(),
"cmdline": "ACCESS DENIED",
"full_image_path": "null",
}
continue
return {'process_list': processes}

View File

@ -0,0 +1,38 @@
from infection_monkey.config import WormConfiguration
from infection_monkey.utils.plugins.plugin import Plugin
from abc import ABCMeta, abstractmethod
import infection_monkey.system_info.collectors
class SystemInfoCollector(Plugin, metaclass=ABCMeta):
"""
ABC for system info collection. See system_info_collector_handler for more info. Basically, to implement a new system info
collector, inherit from this class in an implementation in the infection_monkey.system_info.collectors class, and override
the 'collect' method. Don't forget to parse your results in the Monkey Island and to add the collector to the configuration
as well - see monkey_island.cc.services.processing.system_info_collectors for examples.
See the Wiki page "How to add a new System Info Collector to the Monkey?" for a detailed guide.
"""
def __init__(self, name="unknown"):
self.name = name
@staticmethod
def should_run(class_name) -> bool:
return class_name in WormConfiguration.system_info_collectors_classes
@staticmethod
def base_package_file():
return infection_monkey.system_info.collectors.__file__
@staticmethod
def base_package_name():
return infection_monkey.system_info.collectors.__package__
@abstractmethod
def collect(self) -> dict:
"""
Collect the relevant information and return it in a dictionary.
To be implemented by each collector.
"""
raise NotImplementedError()

View File

@ -0,0 +1,33 @@
import logging
from typing import Sequence
from infection_monkey.system_info.system_info_collector import SystemInfoCollector
from infection_monkey.telemetry.system_info_telem import SystemInfoTelem
LOG = logging.getLogger(__name__)
class SystemInfoCollectorsHandler(object):
def __init__(self):
self.collectors_list = self.config_to_collectors_list()
def execute_all_configured(self):
successful_collections = 0
system_info_telemetry = {}
for collector in self.collectors_list:
try:
LOG.debug("Executing system info collector: '{}'".format(collector.name))
collected_info = collector.collect()
system_info_telemetry[collector.name] = collected_info
successful_collections += 1
except Exception as e:
# If we failed one collector, no need to stop execution. Log and continue.
LOG.error("Collector {} failed. Error info: {}".format(collector.name, e))
LOG.info("All system info collectors executed. Total {} executed, out of which {} collected successfully.".
format(len(self.collectors_list), successful_collections))
SystemInfoTelem({"collectors": system_info_telemetry}).send()
@staticmethod
def config_to_collectors_list() -> Sequence[SystemInfoCollector]:
return SystemInfoCollector.get_instances()

View File

@ -5,15 +5,19 @@ __author__ = "itay.mizeretz"
class StateTelem(BaseTelem):
def __init__(self, is_done):
def __init__(self, is_done, version="Unknown"):
"""
Default state telemetry constructor
:param is_done: Whether the state of monkey is done.
"""
super(StateTelem, self).__init__()
self.is_done = is_done
self.version = version
telem_category = 'state'
def get_data(self):
return {'done': self.is_done}
return {
'done': self.is_done,
'version': self.version
}

View File

@ -26,8 +26,6 @@ class Environment(object, metaclass=ABCMeta):
def testing(self, value):
self._testing = value
_MONKEY_VERSION = "1.7.0"
def __init__(self):
self.config = None
self._testing = False # Assume env is not for unit testing.
@ -58,9 +56,6 @@ class Environment(object, metaclass=ABCMeta):
def is_develop(self):
return self.get_deployment() == 'develop'
def get_version(self):
return self._MONKEY_VERSION + ('-dev' if self.is_develop() else '')
def _get_from_config(self, key, default_value=None):
val = default_value
if self.config is not None:

View File

@ -1,6 +1,6 @@
import monkey_island.cc.auth
from monkey_island.cc.environment import Environment
from common.cloud.aws_instance import AwsInstance
from common.cloud.aws.aws_instance import AwsInstance
__author__ = 'itay.mizeretz'

View File

@ -26,6 +26,7 @@ from monkey_island.cc.utils import local_ip_addresses
from monkey_island.cc.environment.environment import env
from monkey_island.cc.database import is_db_server_up, get_db_version
from monkey_island.cc.resources.monkey_download import MonkeyDownload
from common.version import get_version
from monkey_island.cc.bootloader_server import BootloaderHttpServer
@ -66,8 +67,9 @@ def start_island_server():
def log_init_info():
logger.info(
'Monkey Island Server is running. Listening on the following URLs: {}'.format(
logger.info('Monkey Island Server is running!')
logger.info(f"version: {get_version()}")
logger.info('Listening on the following URLs: {}'.format(
", ".join(["https://{}:{}".format(x, env.get_island_port()) for x in local_ip_addresses()])
)
)

View File

@ -9,6 +9,7 @@ from monkey_island.cc.models.monkey_ttl import MonkeyTtl, create_monkey_ttl_docu
from monkey_island.cc.consts import DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS
from monkey_island.cc.models.command_control_channel import CommandControlChannel
from monkey_island.cc.utils import local_ip_addresses
from common.cloud import environment_names
MAX_MONKEYS_AMOUNT_TO_CACHE = 100
@ -42,6 +43,10 @@ class Monkey(Document):
ttl_ref = ReferenceField(MonkeyTtl)
tunnel = ReferenceField("self")
command_control_channel = EmbeddedDocumentField(CommandControlChannel)
# Environment related fields
environment = StringField(default=environment_names.Environment.UNKNOWN.value,
choices=environment_names.ALL_ENVIRONMENTS_NAMES)
aws_instance_id = StringField(required=False) # This field only exists when the monkey is running on an AWS
# instance. See https://github.com/guardicore/monkey/issues/426.
@ -55,7 +60,8 @@ class Monkey(Document):
raise MonkeyNotFoundError("info: {0} | id: {1}".format(ex, str(db_id)))
@staticmethod
def get_single_monkey_by_guid(monkey_guid):
# See https://www.python.org/dev/peps/pep-0484/#forward-references
def get_single_monkey_by_guid(monkey_guid) -> 'Monkey':
try:
return Monkey.objects.get(guid=monkey_guid)
except DoesNotExist as ex:

View File

@ -6,7 +6,7 @@ import flask_restful
from monkey_island.cc.auth import jwt_required
from monkey_island.cc.services.remote_run_aws import RemoteRunAwsService
from common.cloud.aws_service import AwsService
from common.cloud.aws.aws_service import AwsService
CLIENT_ERROR_FORMAT = "ClientError, error message: '{}'. Probably, the IAM role that has been associated with the " \
"instance doesn't permit SSM calls. "

View File

@ -1,7 +1,7 @@
import flask_restful
import logging
from monkey_island.cc.environment.environment import env
from common.version import get_version
from monkey_island.cc.services.version_update import VersionUpdateService
__author__ = 'itay.mizeretz'
@ -17,7 +17,7 @@ class VersionUpdate(flask_restful.Resource):
# even when not authenticated
def get(self):
return {
'current_version': env.get_version(),
'current_version': get_version(),
'newer_version': VersionUpdateService.get_newer_version(),
'download_link': VersionUpdateService.get_download_link()
}

View File

@ -153,9 +153,18 @@ class ConfigService:
def ssh_key_exists(keys, user, ip):
return [key for key in keys if key['user'] == user and key['ip'] == ip]
def _filter_none_values(data):
if isinstance(data, dict):
return {k: ConfigService._filter_none_values(v) for k, v in data.items() if k is not None and v is not None}
elif isinstance(data, list):
return [ConfigService._filter_none_values(item) for item in data if item is not None]
else:
return data
@staticmethod
def update_config(config_json, should_encrypt):
# PBA file upload happens on pba_file_upload endpoint and corresponding config options are set there
config_json = ConfigService._filter_none_values(config_json)
monkey_island.cc.services.post_breach_files.set_config_PBA_files(config_json)
if should_encrypt:
try:

View File

@ -1,3 +1,6 @@
from common.data.system_info_collectors_names \
import AWS_COLLECTOR, ENVIRONMENT_COLLECTOR, HOSTNAME_COLLECTOR, PROCESS_LIST_COLLECTOR
WARNING_SIGN = " \u26A0"
SCHEMA = {
@ -99,6 +102,44 @@ SCHEMA = {
}
]
},
"system_info_collectors_classes": {
"title": "System Information Collectors",
"type": "string",
"anyOf": [
{
"type": "string",
"enum": [
ENVIRONMENT_COLLECTOR
],
"title": "Collect which environment this machine is on (on prem/cloud)",
"attack_techniques": []
},
{
"type": "string",
"enum": [
AWS_COLLECTOR
],
"title": "If on AWS, collect more information about the instance",
"attack_techniques": []
},
{
"type": "string",
"enum": [
HOSTNAME_COLLECTOR
],
"title": "Collect the machine's hostname",
"attack_techniques": []
},
{
"type": "string",
"enum": [
PROCESS_LIST_COLLECTOR
],
"title": "Collect running processes on the machine",
"attack_techniques": []
},
],
},
"post_breach_acts": {
"title": "Post breach actions",
"type": "string",
@ -433,6 +474,21 @@ SCHEMA = {
"attack_techniques": ["T1003"],
"description": "Determines whether to use Mimikatz"
},
"system_info_collectors_classes": {
"title": "System info collectors",
"type": "array",
"uniqueItems": True,
"items": {
"$ref": "#/definitions/system_info_collectors_classes"
},
"default": [
ENVIRONMENT_COLLECTOR,
AWS_COLLECTOR,
HOSTNAME_COLLECTOR,
PROCESS_LIST_COLLECTOR
],
"description": "Determines which system information collectors will collect information."
},
}
},
"life_cycle": {

View File

@ -1,7 +1,7 @@
import logging
from common.cloud.aws_instance import AwsInstance
from common.cloud.aws_service import AwsService
from common.cloud.aws.aws_instance import AwsInstance
from common.cloud.aws.aws_service import AwsService
from common.cmd.aws.aws_cmd_runner import AwsCmdRunner
from common.cmd.cmd import Cmd
from common.cmd.cmd_runner import CmdRunner
@ -54,7 +54,7 @@ class RemoteRunAwsService:
@staticmethod
def is_running_on_aws():
return RemoteRunAwsService.aws_instance.is_aws_instance()
return RemoteRunAwsService.aws_instance.is_instance()
@staticmethod
def update_aws_region_authless():
@ -130,7 +130,8 @@ class RemoteRunAwsService:
return r"[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {" \
r"$true}; (New-Object System.Net.WebClient).DownloadFile('https://" + island_ip + \
r":5000/api/monkey/download/monkey-windows-" + bit_text + r".exe','.\\monkey.exe'); " \
r";Start-Process -FilePath '.\\monkey.exe' -ArgumentList 'm0nk3y -s " + island_ip + r":5000'; "
r";Start-Process -FilePath '.\\monkey.exe' " \
r"-ArgumentList 'm0nk3y -s " + island_ip + r":5000'; "
@staticmethod
def _get_run_monkey_cmd_line(is_linux, is_64bit, island_ip):

View File

@ -5,7 +5,7 @@ from datetime import datetime
import boto3
from botocore.exceptions import UnknownServiceError
from common.cloud.aws_instance import AwsInstance
from common.cloud.aws.aws_instance import AwsInstance
from monkey_island.cc.environment.environment import load_server_configuration_from_file
from monkey_island.cc.services.reporting.exporter import Exporter

View File

@ -386,10 +386,11 @@ class ReportService:
@staticmethod
def get_monkey_subnets(monkey_guid):
network_info = mongo.db.telemetry.find_one(
{'telem_category': 'system_info', 'monkey_guid': monkey_guid},
{'telem_category': 'system_info',
'monkey_guid': monkey_guid},
{'data.network_info.networks': 1}
)
if network_info is None:
if network_info is None or not network_info["data"]:
return []
return \

View File

@ -15,28 +15,34 @@ __regular_report_generating_lock = threading.Semaphore()
def safe_generate_reports():
# Entering the critical section; Wait until report generation is available.
__report_generating_lock.acquire()
report = safe_generate_regular_report()
attack_report = safe_generate_attack_report()
# Leaving the critical section.
__report_generating_lock.release()
try:
report = safe_generate_regular_report()
attack_report = safe_generate_attack_report()
finally:
# Leaving the critical section.
__report_generating_lock.release()
return report, attack_report
def safe_generate_regular_report():
# Local import to avoid circular imports
from monkey_island.cc.services.reporting.report import ReportService
__regular_report_generating_lock.acquire()
report = ReportService.generate_report()
__regular_report_generating_lock.release()
try:
__regular_report_generating_lock.acquire()
report = ReportService.generate_report()
finally:
__regular_report_generating_lock.release()
return report
def safe_generate_attack_report():
# Local import to avoid circular imports
from monkey_island.cc.services.attack.attack_report import AttackReportService
__attack_report_generating_lock.acquire()
attack_report = AttackReportService.generate_new_report()
__attack_report_generating_lock.release()
try:
__attack_report_generating_lock.acquire()
attack_report = AttackReportService.generate_new_report()
finally:
__attack_report_generating_lock.release()
return attack_report

View File

@ -1,9 +1,14 @@
import logging
from monkey_island.cc.models import Monkey
from monkey_island.cc.services.node import NodeService
from monkey_island.cc.services.telemetry.zero_trust_tests.segmentation import \
test_passed_findings_for_unreached_segments
logger = logging.getLogger(__name__)
def process_state_telemetry(telemetry_json):
monkey = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid'])
NodeService.add_communication_info(monkey, telemetry_json['command_control_channel'])
@ -15,3 +20,6 @@ def process_state_telemetry(telemetry_json):
if telemetry_json['data']['done']:
current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid'])
test_passed_findings_for_unreached_segments(current_monkey)
if telemetry_json['data']['version']:
logger.info(f"monkey {telemetry_json['monkey_guid']} has version {telemetry_json['data']['version']}")

View File

@ -1,25 +1,23 @@
import logging
from monkey_island.cc.database import mongo
from monkey_island.cc.models import Monkey
from monkey_island.cc.services import mimikatz_utils
from monkey_island.cc.services.node import NodeService
from monkey_island.cc.services.config import ConfigService
from monkey_island.cc.services.telemetry.zero_trust_tests.antivirus_existence import test_antivirus_existence
from monkey_island.cc.services.wmi_handler import WMIHandler
from monkey_island.cc.encryptor import encryptor
from monkey_island.cc.services import mimikatz_utils
from monkey_island.cc.services.config import ConfigService
from monkey_island.cc.services.node import NodeService
from monkey_island.cc.services.telemetry.processing.system_info_collectors.system_info_telemetry_dispatcher import \
SystemInfoTelemetryDispatcher
from monkey_island.cc.services.wmi_handler import WMIHandler
logger = logging.getLogger(__name__)
def process_system_info_telemetry(telemetry_json):
dispatcher = SystemInfoTelemetryDispatcher()
telemetry_processing_stages = [
process_ssh_info,
process_credential_info,
process_mimikatz_and_wmi_info,
process_aws_data,
update_db_with_new_hostname,
test_antivirus_existence,
dispatcher.dispatch_collector_results_to_relevant_processors
]
# Calling safe_process_telemetry so if one of the stages fail, we log and move on instead of failing the rest of
@ -34,7 +32,7 @@ def safe_process_telemetry(processing_function, telemetry_json):
processing_function(telemetry_json)
except Exception as err:
logger.error(
"Error {} while in {} stage of processing telemetry.".format(str(err), processing_function.func_name),
"Error {} while in {} stage of processing telemetry.".format(str(err), processing_function.__name__),
exc_info=True)
@ -104,14 +102,3 @@ def process_mimikatz_and_wmi_info(telemetry_json):
wmi_handler = WMIHandler(monkey_id, telemetry_json['data']['wmi'], users_secrets)
wmi_handler.process_and_handle_wmi_info()
def process_aws_data(telemetry_json):
if 'aws' in telemetry_json['data']:
if 'instance_id' in telemetry_json['data']['aws']:
monkey_id = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid']).get('_id')
mongo.db.monkey.update_one({'_id': monkey_id},
{'$set': {'aws_instance_id': telemetry_json['data']['aws']['instance_id']}})
def update_db_with_new_hostname(telemetry_json):
Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid']).set_hostname(telemetry_json['data']['hostname'])

View File

@ -0,0 +1,15 @@
import logging
from monkey_island.cc.models.monkey import Monkey
logger = logging.getLogger(__name__)
def process_aws_telemetry(collector_results, monkey_guid):
relevant_monkey = Monkey.get_single_monkey_by_guid(monkey_guid)
if "instance_id" in collector_results:
instance_id = collector_results["instance_id"]
relevant_monkey.aws_instance_id = instance_id
relevant_monkey.save()
logger.debug("Updated Monkey {} with aws instance id {}".format(str(relevant_monkey), instance_id))

View File

@ -0,0 +1,12 @@
import logging
from monkey_island.cc.models.monkey import Monkey
logger = logging.getLogger(__name__)
def process_environment_telemetry(collector_results, monkey_guid):
relevant_monkey = Monkey.get_single_monkey_by_guid(monkey_guid)
relevant_monkey.environment = collector_results["environment"]
relevant_monkey.save()
logger.debug("Updated Monkey {} with env {}".format(str(relevant_monkey), collector_results))

View File

@ -0,0 +1,9 @@
import logging
from monkey_island.cc.models.monkey import Monkey
logger = logging.getLogger(__name__)
def process_hostname_telemetry(collector_results, monkey_guid):
Monkey.get_single_monkey_by_guid(monkey_guid).set_hostname(collector_results["hostname"])

View File

@ -0,0 +1,64 @@
import logging
import typing
from common.data.system_info_collectors_names \
import AWS_COLLECTOR, ENVIRONMENT_COLLECTOR, HOSTNAME_COLLECTOR, PROCESS_LIST_COLLECTOR
from monkey_island.cc.services.telemetry.processing.system_info_collectors.aws import process_aws_telemetry
from monkey_island.cc.services.telemetry.processing.system_info_collectors.environment import process_environment_telemetry
from monkey_island.cc.services.telemetry.processing.system_info_collectors.hostname import process_hostname_telemetry
from monkey_island.cc.services.telemetry.zero_trust_tests.antivirus_existence import test_antivirus_existence
logger = logging.getLogger(__name__)
SYSTEM_INFO_COLLECTOR_TO_TELEMETRY_PROCESSORS = {
AWS_COLLECTOR: [process_aws_telemetry],
ENVIRONMENT_COLLECTOR: [process_environment_telemetry],
HOSTNAME_COLLECTOR: [process_hostname_telemetry],
PROCESS_LIST_COLLECTOR: [test_antivirus_existence]
}
class SystemInfoTelemetryDispatcher(object):
def __init__(self, collector_to_parsing_functions: typing.Mapping[str, typing.List[typing.Callable]] = None):
"""
:param collector_to_parsing_functions: Map between collector names and a list of functions that process the output of
that collector. If `None` is supplied, uses the default one; This should be the normal flow, overriding the
collector->functions mapping is useful mostly for testing.
"""
if collector_to_parsing_functions is None:
collector_to_parsing_functions = SYSTEM_INFO_COLLECTOR_TO_TELEMETRY_PROCESSORS
self.collector_to_processing_functions = collector_to_parsing_functions
def dispatch_collector_results_to_relevant_processors(self, telemetry_json):
"""
If the telemetry has collectors' results, dispatches the results to the relevant processing functions.
:param telemetry_json: Telemetry sent from the Monkey
"""
if "collectors" in telemetry_json["data"]:
self.dispatch_single_result_to_relevant_processor(telemetry_json)
def dispatch_single_result_to_relevant_processor(self, telemetry_json):
relevant_monkey_guid = telemetry_json['monkey_guid']
for collector_name, collector_results in telemetry_json["data"]["collectors"].items():
self.dispatch_result_of_single_collector_to_processing_functions(
collector_name,
collector_results,
relevant_monkey_guid)
def dispatch_result_of_single_collector_to_processing_functions(
self,
collector_name,
collector_results,
relevant_monkey_guid):
if collector_name in self.collector_to_processing_functions:
for processing_function in self.collector_to_processing_functions[collector_name]:
# noinspection PyBroadException
try:
processing_function(collector_results, relevant_monkey_guid)
except Exception as e:
logger.error(
"Error {} while processing {} system info telemetry".format(str(e), collector_name),
exc_info=True)
else:
logger.warning("Unknown system info collector name: {}".format(collector_name))

View File

@ -0,0 +1,31 @@
import uuid
from monkey_island.cc.models import Monkey
from monkey_island.cc.services.telemetry.processing.system_info_collectors.system_info_telemetry_dispatcher import \
SystemInfoTelemetryDispatcher
from monkey_island.cc.testing.IslandTestCase import IslandTestCase
class TestEnvironmentTelemetryProcessing(IslandTestCase):
def test_process_environment_telemetry(self):
self.fail_if_not_testing_env()
self.clean_monkey_db()
# Arrange
monkey_guid = str(uuid.uuid4())
a_monkey = Monkey(guid=monkey_guid)
a_monkey.save()
dispatcher = SystemInfoTelemetryDispatcher()
on_premise = "On Premise"
telem_json = {
"data": {
"collectors": {
"EnvironmentCollector": {"environment": on_premise},
}
},
"monkey_guid": monkey_guid
}
dispatcher.dispatch_collector_results_to_relevant_processors(telem_json)
self.assertEqual(Monkey.get_single_monkey_by_guid(monkey_guid).environment, on_premise)

View File

@ -0,0 +1,57 @@
import uuid
from monkey_island.cc.models import Monkey
from monkey_island.cc.services.telemetry.processing.system_info_collectors.system_info_telemetry_dispatcher import \
SystemInfoTelemetryDispatcher
from monkey_island.cc.services.telemetry.processing.system_info_collectors.system_info_telemetry_dispatcher import \
process_aws_telemetry
from monkey_island.cc.testing.IslandTestCase import IslandTestCase
TEST_SYS_INFO_TO_PROCESSING = {
"AwsCollector": [process_aws_telemetry],
}
class SystemInfoTelemetryDispatcherTest(IslandTestCase):
def test_dispatch_to_relevant_collector_bad_inputs(self):
self.fail_if_not_testing_env()
dispatcher = SystemInfoTelemetryDispatcher(TEST_SYS_INFO_TO_PROCESSING)
# Bad format telem JSONs - throws
bad_empty_telem_json = {}
self.assertRaises(KeyError, dispatcher.dispatch_collector_results_to_relevant_processors, bad_empty_telem_json)
bad_no_data_telem_json = {"monkey_guid": "bla"}
self.assertRaises(KeyError, dispatcher.dispatch_collector_results_to_relevant_processors, bad_no_data_telem_json)
bad_no_monkey_telem_json = {"data": {"collectors": {"AwsCollector": "Bla"}}}
self.assertRaises(KeyError, dispatcher.dispatch_collector_results_to_relevant_processors, bad_no_monkey_telem_json)
# Telem JSON with no collectors - nothing gets dispatched
good_telem_no_collectors = {"monkey_guid": "bla", "data": {"bla": "bla"}}
good_telem_empty_collectors = {"monkey_guid": "bla", "data": {"bla": "bla", "collectors": {}}}
dispatcher.dispatch_collector_results_to_relevant_processors(good_telem_no_collectors)
dispatcher.dispatch_collector_results_to_relevant_processors(good_telem_empty_collectors)
def test_dispatch_to_relevant_collector(self):
self.fail_if_not_testing_env()
self.clean_monkey_db()
a_monkey = Monkey(guid=str(uuid.uuid4()))
a_monkey.save()
dispatcher = SystemInfoTelemetryDispatcher()
# JSON with results - make sure functions are called
instance_id = "i-0bd2c14bd4c7d703f"
telem_json = {
"data": {
"collectors": {
"AwsCollector": {"instance_id": instance_id},
}
},
"monkey_guid": a_monkey.guid
}
dispatcher.dispatch_collector_results_to_relevant_processors(telem_json)
self.assertEquals(Monkey.get_single_monkey_by_guid(a_monkey.guid).aws_instance_id, instance_id)

View File

@ -7,36 +7,36 @@ from monkey_island.cc.models.zero_trust.event import Event
from monkey_island.cc.services.telemetry.zero_trust_tests.known_anti_viruses import ANTI_VIRUS_KNOWN_PROCESS_NAMES
def test_antivirus_existence(telemetry_json):
current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid'])
if 'process_list' in telemetry_json['data']:
process_list_event = Event.create_event(
title="Process list",
message="Monkey on {} scanned the process list".format(current_monkey.hostname),
event_type=zero_trust_consts.EVENT_TYPE_MONKEY_LOCAL)
events = [process_list_event]
def test_antivirus_existence(process_list_json, monkey_guid):
current_monkey = Monkey.get_single_monkey_by_guid(monkey_guid)
av_processes = filter_av_processes(telemetry_json)
process_list_event = Event.create_event(
title="Process list",
message="Monkey on {} scanned the process list".format(current_monkey.hostname),
event_type=zero_trust_consts.EVENT_TYPE_MONKEY_LOCAL)
events = [process_list_event]
for process in av_processes:
events.append(Event.create_event(
title="Found AV process",
message="The process '{}' was recognized as an Anti Virus process. Process "
"details: {}".format(process[1]['name'], json.dumps(process[1])),
event_type=zero_trust_consts.EVENT_TYPE_MONKEY_LOCAL
))
av_processes = filter_av_processes(process_list_json["process_list"])
if len(av_processes) > 0:
test_status = zero_trust_consts.STATUS_PASSED
else:
test_status = zero_trust_consts.STATUS_FAILED
AggregateFinding.create_or_add_to_existing(
test=zero_trust_consts.TEST_ENDPOINT_SECURITY_EXISTS, status=test_status, events=events
)
for process in av_processes:
events.append(Event.create_event(
title="Found AV process",
message="The process '{}' was recognized as an Anti Virus process. Process "
"details: {}".format(process[1]['name'], json.dumps(process[1])),
event_type=zero_trust_consts.EVENT_TYPE_MONKEY_LOCAL
))
if len(av_processes) > 0:
test_status = zero_trust_consts.STATUS_PASSED
else:
test_status = zero_trust_consts.STATUS_FAILED
AggregateFinding.create_or_add_to_existing(
test=zero_trust_consts.TEST_ENDPOINT_SECURITY_EXISTS, status=test_status, events=events
)
def filter_av_processes(telemetry_json):
all_processes = list(telemetry_json['data']['process_list'].items())
def filter_av_processes(process_list):
all_processes = list(process_list.items())
av_processes = []
for process in all_processes:
process_name = process[1]['name']

View File

@ -2,6 +2,7 @@ import logging
import requests
from common.version import get_version
from monkey_island.cc.environment.environment import env
__author__ = "itay.mizeretz"
@ -39,7 +40,7 @@ class VersionUpdateService:
Checks if newer monkey version is available
:return: False if not, version in string format ('1.6.2') otherwise
"""
url = VersionUpdateService.VERSION_SERVER_CHECK_NEW_URL % (env.get_deployment(), env.get_version())
url = VersionUpdateService.VERSION_SERVER_CHECK_NEW_URL % (env.get_deployment(), get_version())
reply = requests.get(url, timeout=15)
@ -53,4 +54,4 @@ class VersionUpdateService:
@staticmethod
def get_download_link():
return VersionUpdateService.VERSION_SERVER_DOWNLOAD_URL % (env.get_deployment(), env.get_version())
return VersionUpdateService.VERSION_SERVER_DOWNLOAD_URL % (env.get_deployment(), get_version())

View File

@ -30,7 +30,7 @@ let infectionMonkeyImage = require('../images/infection-monkey.svg');
let guardicoreLogoImage = require('../images/guardicore-logo.png');
let notificationIcon = require('../images/notification-logo-512x512.png');
const reportZeroTrustRoute = '/report/zero_trust';
const reportZeroTrustRoute = '/report/zeroTrust';
class AppComponent extends AuthComponent {
updateStatus = () => {

View File

@ -30,7 +30,7 @@ class ConfigurePageComponent extends AuthComponent {
lastAction: 'none',
sections: [],
selectedSection: 'attack',
allMonkeysAreDead: true,
monkeysRan: false,
PBAwinFile: [],
PBAlinuxFile: [],
showAttackAlert: false
@ -363,13 +363,7 @@ class ConfigurePageComponent extends AuthComponent {
this.authFetch('/api')
.then(res => res.json())
.then(res => {
// This check is used to prevent unnecessary re-rendering
let allMonkeysAreDead = (!res['completed_steps']['run_monkey']) || (res['completed_steps']['infection_done']);
if (allMonkeysAreDead !== this.state.allMonkeysAreDead) {
this.setState({
allMonkeysAreDead: allMonkeysAreDead
});
}
this.setState({monkeysRan: res['completed_steps']['run_monkey']});
});
};
@ -470,15 +464,15 @@ class ConfigurePageComponent extends AuthComponent {
</div>)
};
renderRunningMonkeysWarning = () => {
renderConfigWontChangeWarning = () => {
return (<div>
{this.state.allMonkeysAreDead ?
'' :
{this.state.monkeysRan ?
<div className="alert alert-warning">
<i className="glyphicon glyphicon-warning-sign" style={{'marginRight': '5px'}}/>
Some monkeys are currently running. Note that changing the configuration will only apply to new
infections.
Changed configuration will only apply to new infections.
"Start over" to run again with different configuration.
</div>
: ''
}
</div>)
};
@ -520,7 +514,7 @@ class ConfigurePageComponent extends AuthComponent {
{this.renderAttackAlertModal()}
<h1 className="page-title">Monkey Configuration</h1>
{this.renderNav()}
{this.renderRunningMonkeysWarning()}
{this.renderConfigWontChangeWarning()}
{content}
<div className="text-center">
<button type="submit" onClick={this.onSubmit} className="btn btn-success btn-lg" style={{margin: '5px'}}>

View File

@ -1,7 +1,9 @@
import React from 'react';
import {Col, Modal} from 'react-bootstrap';
import {Col} from 'react-bootstrap';
import {Link} from 'react-router-dom';
import AuthComponent from '../AuthComponent';
import StartOverModal from '../ui-components/StartOverModal';
import '../../styles/StartOverPage.scss';
class StartOverPageComponent extends AuthComponent {
constructor(props) {
@ -12,6 +14,9 @@ class StartOverPageComponent extends AuthComponent {
showCleanDialog: false,
allMonkeysAreDead: false
};
this.cleanup = this.cleanup.bind(this);
this.closeModal = this.closeModal.bind(this);
}
updateMonkeysRunning = () => {
@ -25,48 +30,14 @@ class StartOverPageComponent extends AuthComponent {
});
};
renderCleanDialogModal = () => {
return (
<Modal show={this.state.showCleanDialog} onHide={() => this.setState({showCleanDialog: false})}>
<Modal.Body>
<h2>
<div className="text-center">Reset environment</div>
</h2>
<p style={{'fontSize': '1.2em', 'marginBottom': '2em'}}>
Are you sure you want to reset the environment?
</p>
{
!this.state.allMonkeysAreDead ?
<div className="alert alert-warning">
<i className="glyphicon glyphicon-warning-sign" style={{'marginRight': '5px'}}/>
Some monkeys are still running. It's advised to kill all monkeys before resetting.
</div>
:
<div/>
}
<div className="text-center">
<button type="button" className="btn btn-danger btn-lg" style={{margin: '5px'}}
onClick={() => {
this.cleanup();
this.setState({showCleanDialog: false});
}}>
Reset environment
</button>
<button type="button" className="btn btn-success btn-lg" style={{margin: '5px'}}
onClick={() => this.setState({showCleanDialog: false})}>
Cancel
</button>
</div>
</Modal.Body>
</Modal>
)
};
render() {
return (
<Col xs={12} lg={8}>
{this.renderCleanDialogModal()}
<StartOverModal cleaned = {this.state.cleaned}
showCleanDialog = {this.state.showCleanDialog}
allMonkeysAreDead = {this.state.allMonkeysAreDead}
onVerify = {this.cleanup}
onClose = {this.closeModal}/>
<h1 className="page-title">Start Over</h1>
<div style={{'fontSize': '1.2em'}}>
<p>
@ -104,7 +75,7 @@ class StartOverPageComponent extends AuthComponent {
this.setState({
cleaned: false
});
this.authFetch('/api?action=reset')
return this.authFetch('/api?action=reset')
.then(res => res.json())
.then(res => {
if (res['status'] === 'OK') {
@ -112,8 +83,14 @@ class StartOverPageComponent extends AuthComponent {
cleaned: true
});
}
});
}
}).then(this.updateMonkeysRunning());
};
closeModal = () => {
this.setState({
showCleanDialog: false
})
};
}
export default StartOverPageComponent;

View File

@ -0,0 +1,74 @@
import {Modal} from 'react-bootstrap';
import React from 'react';
import {GridLoader} from 'react-spinners';
class StartOverModal extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
showCleanDialog: this.props.showCleanDialog,
allMonkeysAreDead: this.props.allMonkeysAreDead,
loading: false
};
}
componentDidUpdate(prevProps) {
if (this.props !== prevProps) {
this.setState({ showCleanDialog: this.props.showCleanDialog,
allMonkeysAreDead: this.props.allMonkeysAreDead})
}
}
render = () => {
return (
<Modal show={this.state.showCleanDialog} onHide={() => this.props.onClose()}>
<Modal.Body>
<h2>
<div className='text-center'>Reset environment</div>
</h2>
<p style={{'fontSize': '1.2em', 'marginBottom': '2em'}}>
Are you sure you want to reset the environment?
</p>
{
!this.state.allMonkeysAreDead ?
<div className='alert alert-warning'>
<i className='glyphicon glyphicon-warning-sign' style={{'marginRight': '5px'}}/>
Some monkeys are still running. It's advised to kill all monkeys before resetting.
</div>
:
<div/>
}
{
this.state.loading ? <div className={'modalLoader'}><GridLoader/></div> : this.showModalButtons()
}
</Modal.Body>
</Modal>
)
};
showModalButtons() {
return (<div className='text-center'>
<button type='button' className='btn btn-danger btn-lg' style={{margin: '5px'}}
onClick={this.modalVerificationOnClick}>
Reset environment
</button>
<button type='button' className='btn btn-success btn-lg' style={{margin: '5px'}}
onClick={() => this.setState({showCleanDialog: false})}>
Cancel
</button>
</div>)
}
modalVerificationOnClick = async () => {
this.setState({loading: true});
this.props.onVerify()
.then(() => {this.setState({loading: false});
this.props.onClose();})
}
}
export default StartOverModal;

View File

@ -0,0 +1,9 @@
$yellow: #ffcc00;
.modalLoader div{
margin-left: auto;
margin-right: auto;
}
.modalLoader div>div{
background-color: $yellow;
}

View File

@ -6,7 +6,7 @@ PYTHON_FOLDER=/var/monkey/monkey_island/bin/python
# Prepare python virtualenv
pip3 install virtualenv --no-index --find-links file://$INSTALLATION_FOLDER
virtualenv -p python3 ${PYTHON_FOLDER}
python3 -m virtualenv -p python3 ${PYTHON_FOLDER}
# install pip requirements
${PYTHON_FOLDER}/bin/python -m pip install -r $MONKEY_FOLDER/monkey_island/requirements.txt --no-index --find-links file://$INSTALLATION_FOLDER
@ -22,7 +22,7 @@ if [ -d "/etc/systemd/network" ]; then
systemctl enable monkey-island
fi
${MONKEY_FOLDER}/monkey_island/create_certificate.sh
${MONKEY_FOLDER}/monkey_island/create_certificate.sh ${MONKEY_FOLDER}/monkey_island/
service monkey-island start

View File

@ -1,7 +1,7 @@
Package: gc-monkey-island
Architecture: amd64
Maintainer: Guardicore
Homepage: http://www.guardicore.com
Maintainer: Guardicore <support@infectionmonkey.com>
Homepage: https://www.infectionmonkey.com
Priority: optional
Version: 1.0
Description: Guardicore Infection Monkey Island installation package

View File

@ -6,7 +6,7 @@ PYTHON_FOLDER=/var/monkey/monkey_island/bin/python
# Prepare python virtualenv
pip3 install virtualenv --no-index --find-links file://$INSTALLATION_FOLDER
virtualenv -p python3 ${PYTHON_FOLDER}
python3 -m virtualenv -p python3 ${PYTHON_FOLDER}
# install pip requirements
${PYTHON_FOLDER}/bin/python -m pip install -r $MONKEY_FOLDER/monkey_island/requirements.txt --no-index --find-links file://$INSTALLATION_FOLDER
@ -25,7 +25,7 @@ if [ -d "/etc/systemd/network" ]; then
systemctl enable monkey-island
fi
${MONKEY_FOLDER}/monkey_island/create_certificate.sh
${MONKEY_FOLDER}/monkey_island/create_certificate.sh ${MONKEY_FOLDER}/monkey_island/
service monkey-island start
service monkey-mongo start

View File

@ -1,6 +1,9 @@
#!/bin/bash
openssl genrsa -out ./cc/server.key 2048
openssl req -new -key ./cc/server.key -out ./cc/server.csr -subj "/OU=Monkey Department/CN=monkey.com"
openssl x509 -req -days 366 -in ./cc/server.csr -signkey ./cc/server.key -out ./cc/server.crt
server_root=${1:-"./cc"}
openssl genrsa -out "$server_root"/server.key 2048
openssl req -new -key "$server_root"/server.key -out "$server_root"/server.csr -subj "/C=GB/ST=London/L=London/O=Global Security/OU=Monkey Department/CN=monkey.com"
openssl x509 -req -days 366 -in "$server_root"/server.csr -signkey "$server_root"/server.key -out $server_root/server.crt

View File

@ -10,16 +10,25 @@ MONGODB_DIR=$1 # If using deb, this should be: /var/monkey/monkey_island/bin/mon
if [[ ${os_version_monkey} == "Ubuntu 16.04"* ]]; then
echo Detected Ubuntu 16.04
export tgz_url="https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1604-3.6.12.tgz"
export tgz_url="https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1604-4.2.3.tgz"
elif [[ ${os_version_monkey} == "Ubuntu 18.04"* ]]; then
echo Detected Ubuntu 18.04
export tgz_url="https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1804-4.2.0.tgz"
export tgz_url="https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1804-4.2.3.tgz"
elif [[ ${os_version_monkey} == "Ubuntu 19.10"* ]]; then
echo Detected Ubuntu 19.10
export tgz_url="https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1804-4.2.3.tgz"
elif [[ ${os_version_monkey} == "Debian GNU/Linux 8"* ]]; then
echo Detected Debian 8
export tgz_url="https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-debian81-3.6.12.tgz"
export tgz_url="https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-debian81-4.0.16.tgz"
elif [[ ${os_version_monkey} == "Debian GNU/Linux 9"* ]]; then
echo Detected Debian 9
export tgz_url="https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-debian92-3.6.12.tgz"
export tgz_url="https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-debian92-4.2.3.tgz"
elif [[ ${os_version_monkey} == "Debian GNU/Linux 10"* ]]; then
echo Detected Debian 10
export tgz_url="https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-debian10-4.2.3.tgz"
elif [[ ${os_version_monkey} == "Kali GNU/Linux"* ]]; then
echo Detected Kali Linux
export tgz_url="https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-debian10-4.2.3.tgz"
else
echo Unsupported OS
exit 1
@ -32,7 +41,7 @@ pushd "${TEMP_MONGO}" || {
}
if exists wget; then
wget ${tgz_url} -O mongodb.tgz
wget -q ${tgz_url} -O mongodb.tgz
else
if exists curl; then
curl --output mongodb.tgz ${tgz_url}

View File

@ -15,7 +15,9 @@ def main():
hookspath=None,
runtime_hooks=None,
binaries=None,
datas=None,
datas=[
("../common/BUILD", "/common")
],
excludes=None,
win_no_prefer_redirects=None,
win_private_assemblies=None,
@ -35,6 +37,7 @@ def main():
debug=False,
strip=get_exe_strip(),
upx=True,
upx_exclude=['vcruntime140.dll'],
console=True,
icon=get_exe_icon())
@ -44,7 +47,7 @@ def is_windows():
def is_32_bit():
return platform.architecture()[0] == "32bit"
return sys.maxsize <= 2**32
def process_datas(orig_datas):

View File

@ -103,4 +103,4 @@
#### How to run
1. When your current working directory is monkey, run ./monkey_island/linux/run.sh (located under /linux)
1. When your current working directory is monkey, run `chmod 755 ./monkey_island/linux/run.sh` followed by `./monkey_island/linux/run.sh` (located under /linux)

View File

@ -3,10 +3,6 @@ bson
python-dateutil
tornado
werkzeug
jinja2
markupsafe
itsdangerous
click
flask
Flask-Pymongo
Flask-Restful

View File

@ -4,3 +4,4 @@ log_cli_level = DEBUG
log_cli_format = %(asctime)s [%(levelname)s] %(module)s.%(funcName)s.%(lineno)d: %(message)s
log_cli_date_format=%H:%M:%S
addopts = -v --capture=sys
norecursedirs = node_modules dist